mirror of
https://github.com/junegunn/fzf.git
synced 2025-11-14 14:23:47 -05:00
Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bbe1721a18 | ||
|
|
c1470a51b8 | ||
|
|
6ee31d5dc5 | ||
|
|
65d74387e7 | ||
|
|
7d0ea599c4 | ||
|
|
b7795a3dea | ||
|
|
323f6f6202 | ||
|
|
0c61223884 | ||
|
|
32234be7a2 | ||
|
|
178b49832e | ||
|
|
18cbb4a84d | ||
|
|
e84afe196a | ||
|
|
e1e171a3c4 | ||
|
|
d075c00015 | ||
|
|
6c0ca4a64a | ||
|
|
6b5d461411 | ||
|
|
7419e0dde1 | ||
|
|
cf2bb5e40e | ||
|
|
f466e94d65 | ||
|
|
eb0257d48f | ||
|
|
b83dd6c6b4 | ||
|
|
51c207448d | ||
|
|
a6a558da30 | ||
|
|
2bf5fa27be | ||
|
|
af7940746f | ||
|
|
a2aa1a156c | ||
|
|
2f8a72a42a | ||
|
|
8179ca5eaa | ||
|
|
4b74f882c7 | ||
|
|
7cf45af502 | ||
|
|
46c21158d8 | ||
|
|
80da0776f8 | ||
|
|
e91f10ab16 | ||
|
|
2c15cd7923 | ||
|
|
d6584543e9 | ||
|
|
c13228f346 | ||
|
|
7220d8233e | ||
|
|
0237bf09bf | ||
|
|
04017c25bb | ||
|
|
02199cd609 | ||
|
|
26b9f5831a | ||
|
|
243a76002c | ||
|
|
c71e4ddee4 | ||
|
|
32eb8c1be9 | ||
|
|
c587017830 | ||
|
|
fb885652cc | ||
|
|
afc2f05e5e | ||
|
|
06547d0cbe | ||
|
|
578108280e |
10
.github/workflows/linux.yml
vendored
10
.github/workflows/linux.yml
vendored
@@ -16,7 +16,7 @@ env:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
@@ -30,19 +30,19 @@ jobs:
|
|||||||
- name: Setup Ruby
|
- name: Setup Ruby
|
||||||
uses: ruby/setup-ruby@v1
|
uses: ruby/setup-ruby@v1
|
||||||
with:
|
with:
|
||||||
ruby-version: 3.1.0
|
ruby-version: 3.4.1
|
||||||
|
|
||||||
- name: Install packages
|
- name: Install packages
|
||||||
run: sudo apt-get install --yes zsh fish tmux
|
run: sudo apt-get install --yes zsh fish tmux
|
||||||
|
|
||||||
- name: Install Ruby gems
|
- name: Install Ruby gems
|
||||||
run: sudo gem install --no-document minitest:5.25.1 rubocop:1.65.0 rubocop-minitest:0.35.1 rubocop-performance:1.21.1
|
run: bundle install
|
||||||
|
|
||||||
- name: Rubocop
|
- name: Rubocop
|
||||||
run: rubocop --require rubocop-minitest --require rubocop-performance
|
run: make lint
|
||||||
|
|
||||||
- name: Unit test
|
- name: Unit test
|
||||||
run: make test
|
run: make test
|
||||||
|
|
||||||
- name: Integration test
|
- name: Integration test
|
||||||
run: make install && ./install --all && tmux new-session -d && ruby test/test_go.rb --verbose
|
run: make install && ./install --all && tmux new-session -d && ruby test/runner.rb --verbose
|
||||||
|
|||||||
2
.github/workflows/typos.yml
vendored
2
.github/workflows/typos.yml
vendored
@@ -7,4 +7,4 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: crate-ci/typos@v1.28.4
|
- uses: crate-ci/typos@v1.29.4
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,7 +3,6 @@ bin/fzf.exe
|
|||||||
dist
|
dist
|
||||||
target
|
target
|
||||||
pkg
|
pkg
|
||||||
Gemfile.lock
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
doc/tags
|
doc/tags
|
||||||
vendor
|
vendor
|
||||||
|
|||||||
10
.rubocop.yml
10
.rubocop.yml
@@ -1,9 +1,13 @@
|
|||||||
|
AllCops:
|
||||||
|
NewCops: enable
|
||||||
Layout/LineLength:
|
Layout/LineLength:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
Metrics:
|
Metrics:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
Lint/ShadowingOuterLocalVariable:
|
Lint/ShadowingOuterLocalVariable:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
Lint/NestedMethodDefinition:
|
||||||
|
Enabled: false
|
||||||
Style/MethodCallWithArgsParentheses:
|
Style/MethodCallWithArgsParentheses:
|
||||||
Enabled: true
|
Enabled: true
|
||||||
AllowedMethods:
|
AllowedMethods:
|
||||||
@@ -28,5 +32,11 @@ Style/WordArray:
|
|||||||
MinSize: 1
|
MinSize: 1
|
||||||
Minitest/AssertEqual:
|
Minitest/AssertEqual:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
Minitest/EmptyLineBeforeAssertionMethods:
|
||||||
|
Enabled: false
|
||||||
Naming/VariableNumber:
|
Naming/VariableNumber:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
Lint/EmptyBlock:
|
||||||
|
Enabled: false
|
||||||
|
Style/SafeNavigationChainLength:
|
||||||
|
Enabled: false
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
golang 1.20.13
|
golang 1.20.13
|
||||||
|
ruby 3.4.1
|
||||||
|
|||||||
48
ADVANCED.md
48
ADVANCED.md
@@ -1,8 +1,8 @@
|
|||||||
Advanced fzf examples
|
Advanced fzf examples
|
||||||
======================
|
======================
|
||||||
|
|
||||||
* *Last update: 2024/06/24*
|
* *Last update: 2025/02/02*
|
||||||
* *Requires fzf 0.54.0 or later*
|
* *Requires fzf 0.59.0 or later*
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -22,6 +22,7 @@ Advanced fzf examples
|
|||||||
* [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)
|
* [Switching between Ripgrep mode and fzf mode](#switching-between-ripgrep-mode-and-fzf-mode)
|
||||||
* [Switching between Ripgrep mode and fzf mode using a single key binding](#switching-between-ripgrep-mode-and-fzf-mode-using-a-single-key-binding)
|
* [Switching between Ripgrep mode and fzf mode using a single key binding](#switching-between-ripgrep-mode-and-fzf-mode-using-a-single-key-binding)
|
||||||
|
* [Controlling Ripgrep search and fzf search simultaneously](#controlling-ripgrep-search-and-fzf-search-simultaneously)
|
||||||
* [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)
|
||||||
@@ -92,7 +93,7 @@ fzf --height=40% --layout=reverse --info=inline --border --margin=1 --padding=1
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
*(See `Layout` section of the man page to see the full list of options)*
|
*(See man page to see the full list of options)*
|
||||||
|
|
||||||
But you definitely don't want to repeat `--height=40% --layout=reverse
|
But you definitely don't want to repeat `--height=40% --layout=reverse
|
||||||
--info=inline --border --margin=1 --padding=1` every time you use fzf. You
|
--info=inline --border --margin=1 --padding=1` every time you use fzf. You
|
||||||
@@ -500,6 +501,44 @@ fzf --ansi --disabled --query "$INITIAL_QUERY" \
|
|||||||
--bind 'enter:become(vim {1} +{2})'
|
--bind 'enter:become(vim {1} +{2})'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Controlling Ripgrep search and fzf search simultaneously
|
||||||
|
|
||||||
|
`search` and `transform-search` action allow you to trigger an fzf search with
|
||||||
|
an arbitrary query string. This frees fzf from strictly following the prompt
|
||||||
|
input, enabling custom search syntax.
|
||||||
|
|
||||||
|
In the example below, `transform` action is used to conditionally trigger
|
||||||
|
`reload` for ripgrep, followed by `search` for fzf. The first word of the
|
||||||
|
query initiates the Ripgrep process to generate the initial results, while the
|
||||||
|
remainder of the query is passed to fzf for secondary filtering.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
export TEMP=$(mktemp -u)
|
||||||
|
trap 'rm -f "$TEMP"' EXIT
|
||||||
|
|
||||||
|
INITIAL_QUERY="${*:-}"
|
||||||
|
TRANSFORMER='
|
||||||
|
rg_pat={q:1} # The first word is passed to ripgrep
|
||||||
|
fzf_pat={q:2..} # The rest are passed to fzf
|
||||||
|
|
||||||
|
if ! [[ -r "$TEMP" ]] || [[ $rg_pat != $(cat "$TEMP") ]]; then
|
||||||
|
echo "$rg_pat" > "$TEMP"
|
||||||
|
printf "reload:sleep 0.1; rg --column --line-number --no-heading --color=always --smart-case %q || true" "$rg_pat"
|
||||||
|
fi
|
||||||
|
echo "+search:$fzf_pat"
|
||||||
|
'
|
||||||
|
fzf --ansi --disabled --query "$INITIAL_QUERY" \
|
||||||
|
--with-shell 'bash -c' \
|
||||||
|
--bind "start,change:transform:$TRANSFORMER" \
|
||||||
|
--color "hl:-1:underline,hl+:-1:underline:reverse" \
|
||||||
|
--delimiter : \
|
||||||
|
--preview 'bat --color=always {1} --highlight-line {2}' \
|
||||||
|
--preview-window 'up,60%,border-line,+{2}+3/3,~3' \
|
||||||
|
--bind 'enter:become(vim {1} +{2})'
|
||||||
|
```
|
||||||
|
|
||||||
Log tailing
|
Log tailing
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
@@ -529,8 +568,7 @@ pods() {
|
|||||||
--info=inline --layout=reverse --header-lines=1 \
|
--info=inline --layout=reverse --header-lines=1 \
|
||||||
--prompt "$(kubectl config current-context | sed 's/-context$//')> " \
|
--prompt "$(kubectl config current-context | sed 's/-context$//')> " \
|
||||||
--header $'╱ Enter (kubectl exec) ╱ CTRL-O (open log in editor) ╱ CTRL-R (reload) ╱\n\n' \
|
--header $'╱ Enter (kubectl exec) ╱ CTRL-O (open log in editor) ╱ CTRL-R (reload) ╱\n\n' \
|
||||||
--bind 'start:reload:$command' \
|
--bind 'start,ctrl-r:reload:$command' \
|
||||||
--bind 'ctrl-r:reload:$command' \
|
|
||||||
--bind 'ctrl-/:change-preview-window(80%,border-bottom|hidden|)' \
|
--bind 'ctrl-/:change-preview-window(80%,border-bottom|hidden|)' \
|
||||||
--bind 'enter:execute:kubectl exec -it --namespace {1} {2} -- bash' \
|
--bind 'enter:execute:kubectl exec -it --namespace {1} {2} -- bash' \
|
||||||
--bind 'ctrl-o:execute:${EDITOR:-vim} <(kubectl logs --all-containers --namespace {1} {2})' \
|
--bind 'ctrl-o:execute:${EDITOR:-vim} <(kubectl logs --all-containers --namespace {1} {2})' \
|
||||||
|
|||||||
96
CHANGELOG.md
96
CHANGELOG.md
@@ -1,6 +1,102 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
0.59.0
|
||||||
|
------
|
||||||
|
_Release highlights: https://junegunn.github.io/fzf/releases/0.59.0/_
|
||||||
|
|
||||||
|
- Prioritizing file name matches (#4192)
|
||||||
|
- Added a new tiebreak option `pathname` for prioritizing file name matches
|
||||||
|
- `--scheme=path` now sets `--tiebreak=pathname,length`
|
||||||
|
- fzf will automatically choose `path` scheme
|
||||||
|
* when the input is a TTY device, where fzf would start its built-in walker or run `$FZF_DEFAULT_COMMAND` which is usually a command for listing files,
|
||||||
|
* but not when `reload` or `transform` action is bound to `start` event, because in that case, fzf can't be sure of the input type.
|
||||||
|
- Added `--header-lines-border` to display header from `--header-lines` with a separate border
|
||||||
|
```sh
|
||||||
|
# Use --header-lines-border to separate two headers
|
||||||
|
ps -ef | fzf --style full --layout reverse --header-lines 1 \
|
||||||
|
--bind 'ctrl-r:reload(ps -ef)' --header 'Press CTRL-R to reload' \
|
||||||
|
--header-lines-border bottom --no-list-border
|
||||||
|
```
|
||||||
|
- `click-header` event now sets `$FZF_CLICK_HEADER_WORD` and `$FZF_CLICK_HEADER_NTH`. You can use them to implement a clickable header for changing the search scope using the new `transform-nth` action.
|
||||||
|
```sh
|
||||||
|
# Click on the header line to limit search scope
|
||||||
|
ps -ef | fzf --style full --layout reverse --header-lines 1 \
|
||||||
|
--header-lines-border bottom --no-list-border \
|
||||||
|
--color fg:dim,nth:regular \
|
||||||
|
--bind 'click-header:transform-nth(
|
||||||
|
echo $FZF_CLICK_HEADER_NTH
|
||||||
|
)+transform-prompt(
|
||||||
|
echo "$FZF_CLICK_HEADER_WORD> "
|
||||||
|
)'
|
||||||
|
```
|
||||||
|
- `$FZF_KEY` was updated to expose the type of the click. e.g. `click`, `ctrl-click`, etc. You can use it to implement a more sophisticated behavior.
|
||||||
|
- `kill` completion for bash and zsh were updated to use this feature
|
||||||
|
- Added `--no-input` option to completely disable and hide the input section
|
||||||
|
```sh
|
||||||
|
# Click header to trigger search
|
||||||
|
fzf --header '[src] [test]' --no-input --layout reverse \
|
||||||
|
--header-border bottom --input-border \
|
||||||
|
--bind 'click-header:transform-search:echo ${FZF_CLICK_HEADER_WORD:1:-1}'
|
||||||
|
|
||||||
|
# Vim-like mode switch
|
||||||
|
fzf --layout reverse-list --no-input \
|
||||||
|
--bind 'j:down,k:up,/:show-input+unbind(j,k,/)' \
|
||||||
|
--bind 'enter,esc,ctrl-c:transform:
|
||||||
|
if [[ $FZF_INPUT_STATE = enabled ]]; then
|
||||||
|
echo "rebind(j,k,/)+hide-input"
|
||||||
|
elif [[ $FZF_KEY = enter ]]; then
|
||||||
|
echo accept
|
||||||
|
else
|
||||||
|
echo abort
|
||||||
|
fi
|
||||||
|
'
|
||||||
|
```
|
||||||
|
- You can later show the input section using `show-input` or `toggle-input` action, and hide it again using `hide-input`, or `toggle-input`.
|
||||||
|
- Extended `{q}` placeholder to support ranges. e.g. `{q:1}`, `{q:2..}`, etc.
|
||||||
|
- Added `search(...)` and `transform-search(...)` action to trigger an fzf search with an arbitrary query string. This can be used to extend the search syntax of fzf. In the following example, fzf will use the first word of the query to trigger ripgrep search, and use the rest of the query to perform fzf search within the result.
|
||||||
|
```sh
|
||||||
|
export TEMP=$(mktemp -u)
|
||||||
|
trap 'rm -f "$TEMP"' EXIT
|
||||||
|
|
||||||
|
TRANSFORMER='
|
||||||
|
rg_pat={q:1} # The first word is passed to ripgrep
|
||||||
|
fzf_pat={q:2..} # The rest are passed to fzf
|
||||||
|
|
||||||
|
if ! [[ -r "$TEMP" ]] || [[ $rg_pat != $(cat "$TEMP") ]]; then
|
||||||
|
echo "$rg_pat" > "$TEMP"
|
||||||
|
printf "reload:sleep 0.1; rg --column --line-number --no-heading --color=always --smart-case %q || true" "$rg_pat"
|
||||||
|
fi
|
||||||
|
echo "+search:$fzf_pat"
|
||||||
|
'
|
||||||
|
fzf --ansi --disabled \
|
||||||
|
--with-shell 'bash -c' \
|
||||||
|
--bind "start,change:transform:$TRANSFORMER"
|
||||||
|
```
|
||||||
|
- You can now bind actions to multiple keys and events at once by writing a comma-separated list of keys and events before the colon
|
||||||
|
```sh
|
||||||
|
# Load 'ps -ef' output on start and reload it on CTRL-R
|
||||||
|
fzf --bind 'start,ctrl-r:reload:ps -ef'
|
||||||
|
```
|
||||||
|
- `--min-height` option now takes a number followed by `+`, which tells fzf to show at least that many items in the list section. The default value is now changed to `10+`.
|
||||||
|
```sh
|
||||||
|
# You will only see the input section which takes 3 lines
|
||||||
|
fzf --style=full --height 1% --min-height 3
|
||||||
|
|
||||||
|
# You will see 3 items in the list section
|
||||||
|
fzf --style full --height 1% --min-height 3+
|
||||||
|
```
|
||||||
|
- Shell integration scripts were updated to use `--min-height 20+` by default
|
||||||
|
- `--header-lines` will be displayed at the top in `reverse-list` layout
|
||||||
|
- Added `bell` action to ring the terminal bell
|
||||||
|
```sh
|
||||||
|
# Press CTRL-Y to copy the current line to the clipboard and ring the bell
|
||||||
|
fzf --bind 'ctrl-y:execute-silent(echo -n {} | pbcopy)+bell'
|
||||||
|
```
|
||||||
|
- Added `toggle-bind` action
|
||||||
|
- Bug fixes and improvements
|
||||||
|
- Fixed fish script to support fish 3.1.2 or later (@bitraid)
|
||||||
|
|
||||||
0.58.0
|
0.58.0
|
||||||
------
|
------
|
||||||
_Release highlights: https://junegunn.github.io/fzf/releases/0.58.0/_
|
_Release highlights: https://junegunn.github.io/fzf/releases/0.58.0/_
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
FROM ubuntu:24.04
|
FROM rubylang/ruby:3.4.1-noble
|
||||||
RUN apt-get update -y && apt install -y git make golang zsh fish ruby tmux
|
RUN apt-get update -y && apt install -y git make golang zsh fish tmux
|
||||||
RUN gem install --no-document -v 5.22.3 minitest
|
RUN gem install --no-document -v 5.22.3 minitest
|
||||||
RUN echo '. /usr/share/bash-completion/completions/git' >> ~/.bashrc
|
RUN echo '. /usr/share/bash-completion/completions/git' >> ~/.bashrc
|
||||||
RUN echo '. ~/.bashrc' >> ~/.bash_profile
|
RUN echo '. ~/.bashrc' >> ~/.bash_profile
|
||||||
@@ -9,4 +9,4 @@ RUN rm -f /etc/bash.bashrc
|
|||||||
COPY . /fzf
|
COPY . /fzf
|
||||||
RUN cd /fzf && make install && ./install --all
|
RUN cd /fzf && make install && ./install --all
|
||||||
ENV LANG=C.UTF-8
|
ENV LANG=C.UTF-8
|
||||||
CMD ["bash", "-ic", "tmux new 'set -o pipefail; ruby /fzf/test/test_go.rb | tee out && touch ok' && cat out && [ -e ok ]"]
|
CMD ["bash", "-ic", "tmux new 'set -o pipefail; ruby /fzf/test/runner.rb | tee out && touch ok' && cat out && [ -e ok ]"]
|
||||||
|
|||||||
8
Gemfile
Normal file
8
Gemfile
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
source 'https://rubygems.org'
|
||||||
|
|
||||||
|
gem 'minitest', '5.25.4'
|
||||||
|
gem 'rubocop', '1.71.0'
|
||||||
|
gem 'rubocop-minitest', '0.36.0'
|
||||||
|
gem 'rubocop-performance', '1.23.1'
|
||||||
47
Gemfile.lock
Normal file
47
Gemfile.lock
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
GEM
|
||||||
|
remote: https://rubygems.org/
|
||||||
|
specs:
|
||||||
|
ast (2.4.2)
|
||||||
|
json (2.9.1)
|
||||||
|
language_server-protocol (3.17.0.3)
|
||||||
|
minitest (5.25.4)
|
||||||
|
parallel (1.26.3)
|
||||||
|
parser (3.3.7.0)
|
||||||
|
ast (~> 2.4.1)
|
||||||
|
racc
|
||||||
|
racc (1.8.1)
|
||||||
|
rainbow (3.1.1)
|
||||||
|
regexp_parser (2.10.0)
|
||||||
|
rubocop (1.71.0)
|
||||||
|
json (~> 2.3)
|
||||||
|
language_server-protocol (>= 3.17.0)
|
||||||
|
parallel (~> 1.10)
|
||||||
|
parser (>= 3.3.0.2)
|
||||||
|
rainbow (>= 2.2.2, < 4.0)
|
||||||
|
regexp_parser (>= 2.9.3, < 3.0)
|
||||||
|
rubocop-ast (>= 1.36.2, < 2.0)
|
||||||
|
ruby-progressbar (~> 1.7)
|
||||||
|
unicode-display_width (>= 2.4.0, < 4.0)
|
||||||
|
rubocop-ast (1.37.0)
|
||||||
|
parser (>= 3.3.1.0)
|
||||||
|
rubocop-minitest (0.36.0)
|
||||||
|
rubocop (>= 1.61, < 2.0)
|
||||||
|
rubocop-ast (>= 1.31.1, < 2.0)
|
||||||
|
rubocop-performance (1.23.1)
|
||||||
|
rubocop (>= 1.48.1, < 2.0)
|
||||||
|
rubocop-ast (>= 1.31.1, < 2.0)
|
||||||
|
ruby-progressbar (1.13.0)
|
||||||
|
unicode-display_width (2.6.0)
|
||||||
|
|
||||||
|
PLATFORMS
|
||||||
|
arm64-darwin-23
|
||||||
|
ruby
|
||||||
|
|
||||||
|
DEPENDENCIES
|
||||||
|
minitest (= 5.25.4)
|
||||||
|
rubocop (= 1.71.0)
|
||||||
|
rubocop-minitest (= 0.36.0)
|
||||||
|
rubocop-performance (= 1.23.1)
|
||||||
|
|
||||||
|
BUNDLED WITH
|
||||||
|
2.6.2
|
||||||
9
Makefile
9
Makefile
@@ -82,12 +82,15 @@ test: $(SOURCES)
|
|||||||
github.com/junegunn/fzf/src/tui \
|
github.com/junegunn/fzf/src/tui \
|
||||||
github.com/junegunn/fzf/src/util
|
github.com/junegunn/fzf/src/util
|
||||||
|
|
||||||
|
itest:
|
||||||
|
ruby test/runner.rb
|
||||||
|
|
||||||
bench:
|
bench:
|
||||||
cd src && SHELL=/bin/sh GOOS= $(GO) test -v -tags "$(TAGS)" -run=Bench -bench=. -benchmem
|
cd src && SHELL=/bin/sh GOOS= $(GO) test -v -tags "$(TAGS)" -run=Bench -bench=. -benchmem
|
||||||
|
|
||||||
lint: $(SOURCES) test/test_go.rb
|
lint: $(SOURCES) test/*.rb test/lib/*.rb
|
||||||
[ -z "$$(gofmt -s -d src)" ] || (gofmt -s -d src; exit 1)
|
[ -z "$$(gofmt -s -d src)" ] || (gofmt -s -d src; exit 1)
|
||||||
rubocop --require rubocop-minitest --require rubocop-performance
|
bundle exec rubocop -a --require rubocop-minitest --require rubocop-performance
|
||||||
|
|
||||||
install: bin/fzf
|
install: bin/fzf
|
||||||
|
|
||||||
@@ -186,4 +189,4 @@ update:
|
|||||||
$(GO) get -u
|
$(GO) get -u
|
||||||
$(GO) mod tidy
|
$(GO) mod tidy
|
||||||
|
|
||||||
.PHONY: all generate build release test bench lint install clean docker docker-test update
|
.PHONY: all generate build release test itest bench lint install clean docker docker-test update
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -3,7 +3,7 @@ module github.com/junegunn/fzf
|
|||||||
require (
|
require (
|
||||||
github.com/charlievieth/fastwalk v1.0.9
|
github.com/charlievieth/fastwalk v1.0.9
|
||||||
github.com/gdamore/tcell/v2 v2.8.1
|
github.com/gdamore/tcell/v2 v2.8.1
|
||||||
github.com/junegunn/go-shellwords v0.0.0-20240813092932-a62c48c52e97
|
github.com/junegunn/go-shellwords v0.0.0-20250127100254-2aa3b3277741
|
||||||
github.com/mattn/go-isatty v0.0.20
|
github.com/mattn/go-isatty v0.0.20
|
||||||
github.com/rivo/uniseg v0.4.7
|
github.com/rivo/uniseg v0.4.7
|
||||||
golang.org/x/sys v0.29.0
|
golang.org/x/sys v0.29.0
|
||||||
|
|||||||
4
go.sum
4
go.sum
@@ -5,8 +5,8 @@ github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeekl
|
|||||||
github.com/gdamore/tcell/v2 v2.8.1 h1:KPNxyqclpWpWQlPLx6Xui1pMk8S+7+R37h3g07997NU=
|
github.com/gdamore/tcell/v2 v2.8.1 h1:KPNxyqclpWpWQlPLx6Xui1pMk8S+7+R37h3g07997NU=
|
||||||
github.com/gdamore/tcell/v2 v2.8.1/go.mod h1:bj8ori1BG3OYMjmb3IklZVWfZUJ1UBQt9JXrOCOhGWw=
|
github.com/gdamore/tcell/v2 v2.8.1/go.mod h1:bj8ori1BG3OYMjmb3IklZVWfZUJ1UBQt9JXrOCOhGWw=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/junegunn/go-shellwords v0.0.0-20240813092932-a62c48c52e97 h1:rqzLixVo1c/GQW6px9j1xQmlvQIn+lf/V6M1UQ7IFzw=
|
github.com/junegunn/go-shellwords v0.0.0-20250127100254-2aa3b3277741 h1:7dYDtfMDfKzjT+DVfIS4iqknSEKtZpEcXtu6vuaasHs=
|
||||||
github.com/junegunn/go-shellwords v0.0.0-20240813092932-a62c48c52e97/go.mod h1:6EILKtGpo5t+KLb85LNZLAF6P9LKp78hJI80PXMcn3c=
|
github.com/junegunn/go-shellwords v0.0.0-20250127100254-2aa3b3277741/go.mod h1:6EILKtGpo5t+KLb85LNZLAF6P9LKp78hJI80PXMcn3c=
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
|||||||
2
install
2
install
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
set -u
|
set -u
|
||||||
|
|
||||||
version=0.58.0
|
version=0.59.0
|
||||||
auto_completion=
|
auto_completion=
|
||||||
key_bindings=
|
key_bindings=
|
||||||
update_config=2
|
update_config=2
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
$version="0.58.0"
|
$version="0.59.0"
|
||||||
|
|
||||||
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||||
|
|
||||||
|
|||||||
2
main.go
2
main.go
@@ -11,7 +11,7 @@ import (
|
|||||||
"github.com/junegunn/fzf/src/protector"
|
"github.com/junegunn/fzf/src/protector"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version = "0.58"
|
var version = "0.59"
|
||||||
var revision = "devel"
|
var revision = "devel"
|
||||||
|
|
||||||
//go:embed shell/key-bindings.bash
|
//go:embed shell/key-bindings.bash
|
||||||
|
|||||||
@@ -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 "Jan 2025" "fzf 0.58.0" "fzf\-tmux - open fzf in tmux split pane"
|
.TH fzf\-tmux 1 "Feb 2025" "fzf 0.59.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
|
||||||
|
|||||||
@@ -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 "Jan 2025" "fzf 0.58.0" "fzf - a command-line fuzzy finder"
|
.TH fzf 1 "Feb 2025" "fzf 0.59.0" "fzf - a command-line fuzzy finder"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf - a command-line fuzzy finder
|
fzf - a command-line fuzzy finder
|
||||||
@@ -76,7 +76,8 @@ Generic scoring scheme designed to work well with any type of input.
|
|||||||
.RS
|
.RS
|
||||||
Additional bonus point is only given to the characters after path separator.
|
Additional bonus point is only given to the characters after path separator.
|
||||||
You might want to choose this scheme over \fBdefault\fR if you have many files
|
You might want to choose this scheme over \fBdefault\fR if you have many files
|
||||||
with spaces in their paths.
|
with spaces in their paths. This also sets \fB\-\-tiebreak=pathname,length\fR,
|
||||||
|
to prioritize matches occurring in the tail element of a file path.
|
||||||
.RE
|
.RE
|
||||||
.RE
|
.RE
|
||||||
|
|
||||||
@@ -90,6 +91,13 @@ more weight to the chronological ordering. This also sets
|
|||||||
.RE
|
.RE
|
||||||
.RE
|
.RE
|
||||||
|
|
||||||
|
.RS
|
||||||
|
fzf chooses \fBpath\fR scheme when the input is a TTY device, where fzf would
|
||||||
|
start its built-in walker or run \fB$FZF_DEFAULT_COMMAND\fR, and there is no
|
||||||
|
\fBreload\fR or \fBtransform\fR action bound to \fBstart\fR event. Otherwise,
|
||||||
|
it chooses \fBdefault\fR scheme.
|
||||||
|
.RE
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-algo=" TYPE
|
.BI "\-\-algo=" TYPE
|
||||||
Fuzzy matching algorithm (default: v2)
|
Fuzzy matching algorithm (default: v2)
|
||||||
@@ -144,6 +152,8 @@ Comma-separated list of sort criteria to apply when the scores are tied.
|
|||||||
.br
|
.br
|
||||||
.BR chunk " Prefers line with shorter matched chunk (delimited by whitespaces)"
|
.BR chunk " Prefers line with shorter matched chunk (delimited by whitespaces)"
|
||||||
.br
|
.br
|
||||||
|
.BR pathname " Prefers line with matched substring in the file name of the path"
|
||||||
|
.br
|
||||||
.BR begin " Prefers line with matched substring closer to the beginning"
|
.BR begin " Prefers line with matched substring closer to the beginning"
|
||||||
.br
|
.br
|
||||||
.BR end " Prefers line with matched substring closer to the end"
|
.BR end " Prefers line with matched substring closer to the end"
|
||||||
@@ -326,9 +336,12 @@ Adaptive height has the following limitations:
|
|||||||
* It will not find the right size when there are multi-line items
|
* It will not find the right size when there are multi-line items
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-min\-height=" "HEIGHT"
|
.BI "\-\-min\-height=" "HEIGHT[+]"
|
||||||
Minimum height when \fB\-\-height\fR is given in percent (default: 10).
|
Minimum height when \fB\-\-height\fR is given as a percentage.
|
||||||
Ignored when \fB\-\-height\fR is not specified.
|
Add \fB+\fR to automatically increase the value according to the other
|
||||||
|
layout options so that the specified number of items are visible in the list
|
||||||
|
section (default: \fB10+\fR).
|
||||||
|
Ignored when \fB\-\-height\fR is not specified or set as an absolute value.
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-tmux" "[=[center|top|bottom|left|right][,SIZE[%]][,SIZE[%]][,border-native]]"
|
.BI "\-\-tmux" "[=[center|top|bottom|left|right][,SIZE[%]][,SIZE[%]][,border-native]]"
|
||||||
Start fzf in a tmux popup (default \fBcenter,50%\fR). Requires tmux 3.3 or
|
Start fzf in a tmux popup (default \fBcenter,50%\fR). Requires tmux 3.3 or
|
||||||
@@ -607,6 +620,13 @@ Position of the list label
|
|||||||
|
|
||||||
.SS INPUT SECTION
|
.SS INPUT SECTION
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B "\-\-no\-input"
|
||||||
|
Disable and hide the input section. You can no longer type in queries. To
|
||||||
|
trigger a search, use \fBsearch\fR action. You can later show the input section
|
||||||
|
using \fBshow\-input\fR or \fBtoggle\-input\fR action, and hide it again using
|
||||||
|
\fBhide\-input\fR, or \fBtoggle\-input\fR.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-prompt=" "STR"
|
.BI "\-\-prompt=" "STR"
|
||||||
Input prompt (default: '> ')
|
Input prompt (default: '> ')
|
||||||
@@ -730,6 +750,8 @@ Also,
|
|||||||
|
|
||||||
* \fB{q}\fR is replaced to the current query string
|
* \fB{q}\fR is replaced to the current query string
|
||||||
.br
|
.br
|
||||||
|
* \fB{q}\fR can contain field index expressions. e.g. \fB{q:1}\fR, \fB{q:2..}\fR, etc.
|
||||||
|
.br
|
||||||
* \fB{n}\fR is replaced to the zero-based ordinal index of the current item.
|
* \fB{n}\fR is replaced to the zero-based ordinal index of the current item.
|
||||||
Use \fB{+n}\fR if you want all index numbers when multiple lines are selected.
|
Use \fB{+n}\fR if you want all index numbers when multiple lines are selected.
|
||||||
.br
|
.br
|
||||||
@@ -929,6 +951,12 @@ Label to print on the header border
|
|||||||
.BI "\-\-header\-label\-pos" [=N[:top|bottom]]
|
.BI "\-\-header\-label\-pos" [=N[:top|bottom]]
|
||||||
Position of the header label
|
Position of the header label
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BI "\-\-header\-lines\-border" [=STYLE]
|
||||||
|
Display header from \fB--header\-lines\fR with a separate border. Pass
|
||||||
|
\fBnone\fR to still separate the header lines but without a border. To combine
|
||||||
|
two headers, use \fB\-\-no\-header\-lines\-border\fR.
|
||||||
|
|
||||||
.SS SCRIPTING
|
.SS SCRIPTING
|
||||||
.TP
|
.TP
|
||||||
.BI "\-q, \-\-query=" "STR"
|
.BI "\-q, \-\-query=" "STR"
|
||||||
@@ -1122,9 +1150,9 @@ Show man page
|
|||||||
.SH ENVIRONMENT VARIABLES
|
.SH ENVIRONMENT VARIABLES
|
||||||
.TP
|
.TP
|
||||||
.B FZF_DEFAULT_COMMAND
|
.B FZF_DEFAULT_COMMAND
|
||||||
Default command to use when input is tty. On *nix systems, fzf runs the command
|
Default command to use when input is a TTY device. On *nix systems, fzf runs
|
||||||
with \fB$SHELL \-c\fR if \fBSHELL\fR is set, otherwise with \fBsh \-c\fR, so in
|
the command with \fB$SHELL \-c\fR if \fBSHELL\fR is set, otherwise with \fBsh
|
||||||
this case make sure that the command is POSIX-compliant.
|
\-c\fR, so in this case make sure that the command is POSIX-compliant.
|
||||||
.TP
|
.TP
|
||||||
.B FZF_DEFAULT_OPTS
|
.B FZF_DEFAULT_OPTS
|
||||||
Default options.
|
Default options.
|
||||||
@@ -1196,6 +1224,8 @@ fzf exports the following environment variables to its child processes.
|
|||||||
.br
|
.br
|
||||||
.BR FZF_QUERY " Current query string"
|
.BR FZF_QUERY " Current query string"
|
||||||
.br
|
.br
|
||||||
|
.BR FZF_INPUT_STATE " Current input state (enabled, disabled, hidden)"
|
||||||
|
.br
|
||||||
.BR FZF_NTH " Current \-\-nth option"
|
.BR FZF_NTH " Current \-\-nth option"
|
||||||
.br
|
.br
|
||||||
.BR FZF_PROMPT " Prompt string"
|
.BR FZF_PROMPT " Prompt string"
|
||||||
@@ -1275,10 +1305,15 @@ more \fBactions\fR. You can use it to customize key bindings or implement
|
|||||||
dynamic behaviors.
|
dynamic behaviors.
|
||||||
|
|
||||||
\fB\-\-bind\fR takes a comma-separated list of binding expressions. Each binding
|
\fB\-\-bind\fR takes a comma-separated list of binding expressions. Each binding
|
||||||
expression is \fBKEY:ACTION\fR or \fBEVENT:ACTION\fR.
|
expression is \fBKEY:ACTION\fR or \fBEVENT:ACTION\fR. You can bind actions to
|
||||||
|
multiple keys and events by writing comma-separated list of keys and events
|
||||||
|
before the colon. e.g. \fBKEY1,KEY2,EVENT1,EVENT2:ACTION\fR.
|
||||||
|
|
||||||
e.g.
|
e.g.
|
||||||
\fBfzf \-\-bind=ctrl\-j:accept,ctrl\-k:kill\-line\fR
|
\fBfzf \-\-bind=ctrl\-j:accept,ctrl\-k:kill\-line
|
||||||
|
|
||||||
|
# Load 'ps \-ef' output on start and reload it on CTRL\-R
|
||||||
|
fzf \-\-bind 'start,ctrl\-r:reload:ps \-ef'\fR
|
||||||
|
|
||||||
.SS AVAILABLE KEYS: (SYNONYMS)
|
.SS AVAILABLE KEYS: (SYNONYMS)
|
||||||
\fIctrl\-[a\-z]\fR
|
\fIctrl\-[a\-z]\fR
|
||||||
@@ -1503,11 +1538,21 @@ e.g.
|
|||||||
|
|
||||||
\fIclick\-header\fR
|
\fIclick\-header\fR
|
||||||
.RS
|
.RS
|
||||||
Triggered when a mouse click occurs within the header. Sets \fBFZF_CLICK_HEADER_LINE\fR and \fBFZF_CLICK_HEADER_COLUMN\fR environment variables starting from 1.
|
Triggered when a mouse click occurs within the header. Sets
|
||||||
|
\fBFZF_CLICK_HEADER_LINE\fR and \fBFZF_CLICK_HEADER_COLUMN\fR environment
|
||||||
|
variables starting from 1. It optionally sets \fBFZF_CLICK_HEADER_WORD\fR and
|
||||||
|
\fBFZF_CLICK_HEADER_NTH\fR if clicked on a word.
|
||||||
|
|
||||||
e.g.
|
e.g.
|
||||||
\fBprintf "head1\\nhead2" | fzf \-\-header\-lines=2 \-\-bind 'click\-header:transform\-prompt:printf ${FZF_CLICK_HEADER_LINE}x${FZF_CLICK_HEADER_COLUMN}'\fR
|
\fB# Click on the header line to limit search scope
|
||||||
|
ps \-ef | fzf \-\-style full \-\-layout reverse \-\-header\-lines 1 \\
|
||||||
|
\-\-header\-lines\-border bottom \-\-no\-list\-border \\
|
||||||
|
\-\-color fg:dim,nth:regular \\
|
||||||
|
\-\-bind 'click\-header:transform\-nth(
|
||||||
|
echo $FZF_CLICK_HEADER_NTH
|
||||||
|
)+transform\-prompt(
|
||||||
|
echo "$FZF_CLICK_HEADER_WORD> "
|
||||||
|
)'\fR
|
||||||
.RE
|
.RE
|
||||||
|
|
||||||
.SS AVAILABLE ACTIONS:
|
.SS AVAILABLE ACTIONS:
|
||||||
@@ -1525,6 +1570,7 @@ A key or an event can be bound to one or more of the following actions.
|
|||||||
\fBbackward\-word\fR \fIalt\-b shift\-left\fR
|
\fBbackward\-word\fR \fIalt\-b shift\-left\fR
|
||||||
\fBbecome(...)\fR (replace fzf process with the specified command; see below for the details)
|
\fBbecome(...)\fR (replace fzf process with the specified command; see below for the details)
|
||||||
\fBbeginning\-of\-line\fR \fIctrl\-a home\fR
|
\fBbeginning\-of\-line\fR \fIctrl\-a home\fR
|
||||||
|
\fBbell\fR (ring the terminal bell)
|
||||||
\fBcancel\fR (clear query string if not empty, abort fzf otherwise)
|
\fBcancel\fR (clear query string if not empty, abort fzf otherwise)
|
||||||
\fBchange\-border\-label(...)\fR (change \fB\-\-border\-label\fR to the given string)
|
\fBchange\-border\-label(...)\fR (change \fB\-\-border\-label\fR to the given string)
|
||||||
\fBchange\-header(...)\fR (change header to the given string; doesn't affect \fB\-\-header\-lines\fR)
|
\fBchange\-header(...)\fR (change header to the given string; doesn't affect \fB\-\-header\-lines\fR)
|
||||||
@@ -1568,6 +1614,7 @@ A key or an event can be bound to one or more of the following actions.
|
|||||||
\fBhalf\-page\-down\fR
|
\fBhalf\-page\-down\fR
|
||||||
\fBhalf\-page\-up\fR
|
\fBhalf\-page\-up\fR
|
||||||
\fBhide\-header\fR
|
\fBhide\-header\fR
|
||||||
|
\fBhide\-input\fR
|
||||||
\fBhide\-preview\fR
|
\fBhide\-preview\fR
|
||||||
\fBoffset\-down\fR (similar to CTRL\-E of Vim)
|
\fBoffset\-down\fR (similar to CTRL\-E of Vim)
|
||||||
\fBoffset\-up\fR (similar to CTRL\-Y of Vim)
|
\fBoffset\-up\fR (similar to CTRL\-Y of Vim)
|
||||||
@@ -1592,16 +1639,20 @@ A key or an event can be bound to one or more of the following actions.
|
|||||||
\fBreload(...)\fR (see below for the details)
|
\fBreload(...)\fR (see below for the details)
|
||||||
\fBreload\-sync(...)\fR (see below for the details)
|
\fBreload\-sync(...)\fR (see below for the details)
|
||||||
\fBreplace\-query\fR (replace query string with the current selection)
|
\fBreplace\-query\fR (replace query string with the current selection)
|
||||||
|
\fBsearch(...)\fR (trigger fzf search with the given string)
|
||||||
\fBselect\fR
|
\fBselect\fR
|
||||||
\fBselect\-all\fR (select all matches)
|
\fBselect\-all\fR (select all matches)
|
||||||
\fBshow\-header\fR
|
\fBshow\-header\fR
|
||||||
|
\fBshow\-input\fR
|
||||||
\fBshow\-preview\fR
|
\fBshow\-preview\fR
|
||||||
\fBtoggle\fR (\fIright\-click\fR)
|
\fBtoggle\fR (\fIright\-click\fR)
|
||||||
\fBtoggle\-all\fR (toggle all matches)
|
\fBtoggle\-all\fR (toggle all matches)
|
||||||
\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\-bind\fR
|
||||||
\fBtoggle\-header\fR
|
\fBtoggle\-header\fR
|
||||||
\fBtoggle\-hscroll\fR
|
\fBtoggle\-hscroll\fR
|
||||||
|
\fBtoggle\-input\fR
|
||||||
\fBtoggle\-multi\-line\fR
|
\fBtoggle\-multi\-line\fR
|
||||||
\fBtoggle\-preview\fR
|
\fBtoggle\-preview\fR
|
||||||
\fBtoggle\-preview\-wrap\fR
|
\fBtoggle\-preview\-wrap\fR
|
||||||
@@ -1619,9 +1670,11 @@ A key or an event can be bound to one or more of the following actions.
|
|||||||
\fBtransform\-header\-label(...)\fR (transform header label using an external command)
|
\fBtransform\-header\-label(...)\fR (transform header label using an external command)
|
||||||
\fBtransform\-input\-label(...)\fR (transform input label using an external command)
|
\fBtransform\-input\-label(...)\fR (transform input label using an external command)
|
||||||
\fBtransform\-list\-label(...)\fR (transform list label using an external command)
|
\fBtransform\-list\-label(...)\fR (transform list label using an external command)
|
||||||
|
\fBtransform\-nth(...)\fR (transform nth using an external command)
|
||||||
\fBtransform\-preview\-label(...)\fR (transform preview label using an external command)
|
\fBtransform\-preview\-label(...)\fR (transform preview label using an external command)
|
||||||
\fBtransform\-prompt(...)\fR (transform prompt string using an external command)
|
\fBtransform\-prompt(...)\fR (transform prompt string using an external command)
|
||||||
\fBtransform\-query(...)\fR (transform query string using an external command)
|
\fBtransform\-query(...)\fR (transform query string using an external command)
|
||||||
|
\fBtransform\-search(...)\fR (trigger fzf search with the output of an external command)
|
||||||
\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
|
||||||
|
|||||||
@@ -1081,7 +1081,7 @@ endfunction
|
|||||||
|
|
||||||
function! s:cmd(bang, ...) abort
|
function! s:cmd(bang, ...) abort
|
||||||
let args = copy(a:000)
|
let args = copy(a:000)
|
||||||
let opts = { 'options': ['--multi'] }
|
let opts = { 'options': ['--multi', '--scheme', 'path'] }
|
||||||
if len(args) && isdirectory(expand(args[-1]))
|
if len(args) && isdirectory(expand(args[-1]))
|
||||||
let opts.dir = substitute(substitute(remove(args, -1), '\\\(["'']\)', '\1', 'g'), '[/\\]*$', '/', '')
|
let opts.dir = substitute(substitute(remove(args, -1), '\\\(["'']\)', '\1', 'g'), '[/\\]*$', '/', '')
|
||||||
if s:is_win && !&shellslash
|
if s:is_win && !&shellslash
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ bind '"\e[0n": redraw-current-line' 2> /dev/null
|
|||||||
__fzf_defaults() {
|
__fzf_defaults() {
|
||||||
# $1: Prepend to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
# $1: Prepend to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
# $2: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
# $2: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
echo "--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore $1"
|
echo "--height ${FZF_TMUX_HEIGHT:-40%} --min-height 20+ --bind=ctrl-z:ignore $1"
|
||||||
command cat "${FZF_DEFAULT_OPTS_FILE-}" 2> /dev/null
|
command cat "${FZF_DEFAULT_OPTS_FILE-}" 2> /dev/null
|
||||||
echo "${FZF_DEFAULT_OPTS-} $2"
|
echo "${FZF_DEFAULT_OPTS-} $2"
|
||||||
}
|
}
|
||||||
@@ -409,7 +409,33 @@ _fzf_complete_kill() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_fzf_proc_completion() {
|
_fzf_proc_completion() {
|
||||||
_fzf_complete -m --header-lines=1 --no-preview --wrap -- "$@" < <(
|
local transformer
|
||||||
|
transformer='
|
||||||
|
if [[ $FZF_KEY =~ ctrl|alt|shift ]] && [[ -n $FZF_NTH ]]; then
|
||||||
|
nths=( ${FZF_NTH//,/ } )
|
||||||
|
new_nths=()
|
||||||
|
found=0
|
||||||
|
for nth in ${nths[@]}; do
|
||||||
|
if [[ $nth = $FZF_CLICK_HEADER_NTH ]]; then
|
||||||
|
found=1
|
||||||
|
else
|
||||||
|
new_nths+=($nth)
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
[[ $found = 0 ]] && new_nths+=($FZF_CLICK_HEADER_NTH)
|
||||||
|
new_nths=${new_nths[*]}
|
||||||
|
new_nths=${new_nths// /,}
|
||||||
|
echo "change-nth($new_nths)+change-prompt($new_nths> )"
|
||||||
|
else
|
||||||
|
if [[ $FZF_NTH = $FZF_CLICK_HEADER_NTH ]]; then
|
||||||
|
echo "change-nth()+change-prompt(> )"
|
||||||
|
else
|
||||||
|
echo "change-nth($FZF_CLICK_HEADER_NTH)+change-prompt($FZF_CLICK_HEADER_WORD> )"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
'
|
||||||
|
_fzf_complete -m --header-lines=1 --no-preview --wrap --color fg:dim,nth:regular \
|
||||||
|
--bind "click-header:transform:$transformer" -- "$@" < <(
|
||||||
command ps -eo user,pid,ppid,start,time,command 2> /dev/null ||
|
command ps -eo user,pid,ppid,start,time,command 2> /dev/null ||
|
||||||
command ps -eo user,pid,ppid,time,args 2> /dev/null || # For BusyBox
|
command ps -eo user,pid,ppid,time,args 2> /dev/null || # For BusyBox
|
||||||
command ps --everyone --full --windows # For cygwin
|
command ps --everyone --full --windows # For cygwin
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ if [[ -o interactive ]]; then
|
|||||||
__fzf_defaults() {
|
__fzf_defaults() {
|
||||||
# $1: Prepend to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
# $1: Prepend to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
# $2: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
# $2: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
echo "--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore $1"
|
echo "--height ${FZF_TMUX_HEIGHT:-40%} --min-height 20+ --bind=ctrl-z:ignore $1"
|
||||||
command cat "${FZF_DEFAULT_OPTS_FILE-}" 2> /dev/null
|
command cat "${FZF_DEFAULT_OPTS_FILE-}" 2> /dev/null
|
||||||
echo "${FZF_DEFAULT_OPTS-} $2"
|
echo "${FZF_DEFAULT_OPTS-} $2"
|
||||||
}
|
}
|
||||||
@@ -290,7 +290,33 @@ _fzf_complete_unalias() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_fzf_complete_kill() {
|
_fzf_complete_kill() {
|
||||||
_fzf_complete -m --header-lines=1 --no-preview --wrap -- "$@" < <(
|
local transformer
|
||||||
|
transformer='
|
||||||
|
if [[ $FZF_KEY =~ ctrl|alt|shift ]] && [[ -n $FZF_NTH ]]; then
|
||||||
|
nths=( ${FZF_NTH//,/ } )
|
||||||
|
new_nths=()
|
||||||
|
found=0
|
||||||
|
for nth in ${nths[@]}; do
|
||||||
|
if [[ $nth = $FZF_CLICK_HEADER_NTH ]]; then
|
||||||
|
found=1
|
||||||
|
else
|
||||||
|
new_nths+=($nth)
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
[[ $found = 0 ]] && new_nths+=($FZF_CLICK_HEADER_NTH)
|
||||||
|
new_nths=${new_nths[*]}
|
||||||
|
new_nths=${new_nths// /,}
|
||||||
|
echo "change-nth($new_nths)+change-prompt($new_nths> )"
|
||||||
|
else
|
||||||
|
if [[ $FZF_NTH = $FZF_CLICK_HEADER_NTH ]]; then
|
||||||
|
echo "change-nth()+change-prompt(> )"
|
||||||
|
else
|
||||||
|
echo "change-nth($FZF_CLICK_HEADER_NTH)+change-prompt($FZF_CLICK_HEADER_WORD> )"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
'
|
||||||
|
_fzf_complete -m --header-lines=1 --no-preview --wrap --color fg:dim,nth:regular \
|
||||||
|
--bind "click-header:transform:$transformer" -- "$@" < <(
|
||||||
command ps -eo user,pid,ppid,start,time,command 2> /dev/null ||
|
command ps -eo user,pid,ppid,start,time,command 2> /dev/null ||
|
||||||
command ps -eo user,pid,ppid,time,args 2> /dev/null || # For BusyBox
|
command ps -eo user,pid,ppid,time,args 2> /dev/null || # For BusyBox
|
||||||
command ps --everyone --full --windows # For cygwin
|
command ps --everyone --full --windows # For cygwin
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ if [[ $- =~ i ]]; then
|
|||||||
__fzf_defaults() {
|
__fzf_defaults() {
|
||||||
# $1: Prepend to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
# $1: Prepend to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
# $2: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
# $2: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
echo "--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore $1"
|
echo "--height ${FZF_TMUX_HEIGHT:-40%} --min-height 20+ --bind=ctrl-z:ignore $1"
|
||||||
command cat "${FZF_DEFAULT_OPTS_FILE-}" 2> /dev/null
|
command cat "${FZF_DEFAULT_OPTS_FILE-}" 2> /dev/null
|
||||||
echo "${FZF_DEFAULT_OPTS-} $2"
|
echo "${FZF_DEFAULT_OPTS-} $2"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ function fzf_key_bindings
|
|||||||
# $1: Prepend to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
# $1: Prepend to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
# $2: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
# $2: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
||||||
echo "--height $FZF_TMUX_HEIGHT --bind=ctrl-z:ignore" $argv[1]
|
echo "--height $FZF_TMUX_HEIGHT --min-height 20+ --bind=ctrl-z:ignore" $argv[1]
|
||||||
test -r "$FZF_DEFAULT_OPTS_FILE"; and string collect -N -- <$FZF_DEFAULT_OPTS_FILE
|
test -r "$FZF_DEFAULT_OPTS_FILE"; and string collect -N -- <$FZF_DEFAULT_OPTS_FILE
|
||||||
echo $FZF_DEFAULT_OPTS $argv[2]
|
echo $FZF_DEFAULT_OPTS $argv[2]
|
||||||
end
|
end
|
||||||
@@ -64,7 +64,8 @@ function fzf_key_bindings
|
|||||||
set -lx FZF_DEFAULT_OPTS (__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --wrap-sign '"\t"↳ ' --highlight-line +m $FZF_CTRL_R_OPTS")
|
set -lx FZF_DEFAULT_OPTS (__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --wrap-sign '"\t"↳ ' --highlight-line +m $FZF_CTRL_R_OPTS")
|
||||||
set -lx FZF_DEFAULT_OPTS_FILE ''
|
set -lx FZF_DEFAULT_OPTS_FILE ''
|
||||||
set -lx FZF_DEFAULT_COMMAND
|
set -lx FZF_DEFAULT_COMMAND
|
||||||
string match -q -r -- '/fish$' $SHELL; or set -lx SHELL (type -p fish)
|
set -a -- FZF_DEFAULT_OPTS --with-shell=(status fish-path)\\ -c
|
||||||
|
|
||||||
if type -q perl
|
if type -q perl
|
||||||
set -a FZF_DEFAULT_OPTS '--tac'
|
set -a FZF_DEFAULT_OPTS '--tac'
|
||||||
set FZF_DEFAULT_COMMAND 'builtin history -z --reverse | command perl -0 -pe \'s/^/$.\t/g; s/\n/\n\t/gm\''
|
set FZF_DEFAULT_COMMAND 'builtin history -z --reverse | command perl -0 -pe \'s/^/$.\t/g; s/\n/\n\t/gm\''
|
||||||
@@ -75,7 +76,7 @@ function fzf_key_bindings
|
|||||||
'string join0 -- $i\t(string replace -a -- \n \n\t $h[$i] | string collect);' \
|
'string join0 -- $i\t(string replace -a -- \n \n\t $h[$i] | string collect);' \
|
||||||
'end'
|
'end'
|
||||||
end
|
end
|
||||||
set -l result (eval "$FZF_DEFAULT_COMMAND | $(__fzfcmd) --read0 --print0 -q (commandline) --bind='enter:become:string replace -a -- \n\t \n {2..} | string collect'")
|
set -l result (eval $FZF_DEFAULT_COMMAND \| (__fzfcmd) --read0 --print0 -q (commandline | string escape) "--bind=enter:become:'string replace -a -- \n\t \n {2..} | string collect'")
|
||||||
and commandline -- $result
|
and commandline -- $result
|
||||||
end
|
end
|
||||||
commandline -f repaint
|
commandline -f repaint
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ if [[ -o interactive ]]; then
|
|||||||
__fzf_defaults() {
|
__fzf_defaults() {
|
||||||
# $1: Prepend to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
# $1: Prepend to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
# $2: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
# $2: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
echo "--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore $1"
|
echo "--height ${FZF_TMUX_HEIGHT:-40%} --min-height 20+ --bind=ctrl-z:ignore $1"
|
||||||
command cat "${FZF_DEFAULT_OPTS_FILE-}" 2> /dev/null
|
command cat "${FZF_DEFAULT_OPTS_FILE-}" 2> /dev/null
|
||||||
echo "${FZF_DEFAULT_OPTS-} $2"
|
echo "${FZF_DEFAULT_OPTS-} $2"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,75 +66,83 @@ func _() {
|
|||||||
_ = x[actToggleMultiLine-55]
|
_ = x[actToggleMultiLine-55]
|
||||||
_ = x[actToggleHscroll-56]
|
_ = x[actToggleHscroll-56]
|
||||||
_ = x[actTrackCurrent-57]
|
_ = x[actTrackCurrent-57]
|
||||||
_ = x[actUntrackCurrent-58]
|
_ = x[actToggleInput-58]
|
||||||
_ = x[actDown-59]
|
_ = x[actHideInput-59]
|
||||||
_ = x[actUp-60]
|
_ = x[actShowInput-60]
|
||||||
_ = x[actPageUp-61]
|
_ = x[actUntrackCurrent-61]
|
||||||
_ = x[actPageDown-62]
|
_ = x[actDown-62]
|
||||||
_ = x[actPosition-63]
|
_ = x[actUp-63]
|
||||||
_ = x[actHalfPageUp-64]
|
_ = x[actPageUp-64]
|
||||||
_ = x[actHalfPageDown-65]
|
_ = x[actPageDown-65]
|
||||||
_ = x[actOffsetUp-66]
|
_ = x[actPosition-66]
|
||||||
_ = x[actOffsetDown-67]
|
_ = x[actHalfPageUp-67]
|
||||||
_ = x[actOffsetMiddle-68]
|
_ = x[actHalfPageDown-68]
|
||||||
_ = x[actJump-69]
|
_ = x[actOffsetUp-69]
|
||||||
_ = x[actJumpAccept-70]
|
_ = x[actOffsetDown-70]
|
||||||
_ = x[actPrintQuery-71]
|
_ = x[actOffsetMiddle-71]
|
||||||
_ = x[actRefreshPreview-72]
|
_ = x[actJump-72]
|
||||||
_ = x[actReplaceQuery-73]
|
_ = x[actJumpAccept-73]
|
||||||
_ = x[actToggleSort-74]
|
_ = x[actPrintQuery-74]
|
||||||
_ = x[actShowPreview-75]
|
_ = x[actRefreshPreview-75]
|
||||||
_ = x[actHidePreview-76]
|
_ = x[actReplaceQuery-76]
|
||||||
_ = x[actTogglePreview-77]
|
_ = x[actToggleSort-77]
|
||||||
_ = x[actTogglePreviewWrap-78]
|
_ = x[actShowPreview-78]
|
||||||
_ = x[actTransform-79]
|
_ = x[actHidePreview-79]
|
||||||
_ = x[actTransformBorderLabel-80]
|
_ = x[actTogglePreview-80]
|
||||||
_ = x[actTransformListLabel-81]
|
_ = x[actTogglePreviewWrap-81]
|
||||||
_ = x[actTransformInputLabel-82]
|
_ = x[actTransform-82]
|
||||||
_ = x[actTransformHeader-83]
|
_ = x[actTransformBorderLabel-83]
|
||||||
_ = x[actTransformHeaderLabel-84]
|
_ = x[actTransformListLabel-84]
|
||||||
_ = x[actTransformPreviewLabel-85]
|
_ = x[actTransformInputLabel-85]
|
||||||
_ = x[actTransformPrompt-86]
|
_ = x[actTransformHeader-86]
|
||||||
_ = x[actTransformQuery-87]
|
_ = x[actTransformHeaderLabel-87]
|
||||||
_ = x[actPreview-88]
|
_ = x[actTransformNth-88]
|
||||||
_ = x[actChangePreview-89]
|
_ = x[actTransformPreviewLabel-89]
|
||||||
_ = x[actChangePreviewWindow-90]
|
_ = x[actTransformPrompt-90]
|
||||||
_ = x[actPreviewTop-91]
|
_ = x[actTransformQuery-91]
|
||||||
_ = x[actPreviewBottom-92]
|
_ = x[actTransformSearch-92]
|
||||||
_ = x[actPreviewUp-93]
|
_ = x[actSearch-93]
|
||||||
_ = x[actPreviewDown-94]
|
_ = x[actPreview-94]
|
||||||
_ = x[actPreviewPageUp-95]
|
_ = x[actChangePreview-95]
|
||||||
_ = x[actPreviewPageDown-96]
|
_ = x[actChangePreviewWindow-96]
|
||||||
_ = x[actPreviewHalfPageUp-97]
|
_ = x[actPreviewTop-97]
|
||||||
_ = x[actPreviewHalfPageDown-98]
|
_ = x[actPreviewBottom-98]
|
||||||
_ = x[actPrevHistory-99]
|
_ = x[actPreviewUp-99]
|
||||||
_ = x[actPrevSelected-100]
|
_ = x[actPreviewDown-100]
|
||||||
_ = x[actPrint-101]
|
_ = x[actPreviewPageUp-101]
|
||||||
_ = x[actPut-102]
|
_ = x[actPreviewPageDown-102]
|
||||||
_ = x[actNextHistory-103]
|
_ = x[actPreviewHalfPageUp-103]
|
||||||
_ = x[actNextSelected-104]
|
_ = x[actPreviewHalfPageDown-104]
|
||||||
_ = x[actExecute-105]
|
_ = x[actPrevHistory-105]
|
||||||
_ = x[actExecuteSilent-106]
|
_ = x[actPrevSelected-106]
|
||||||
_ = x[actExecuteMulti-107]
|
_ = x[actPrint-107]
|
||||||
_ = x[actSigStop-108]
|
_ = x[actPut-108]
|
||||||
_ = x[actFirst-109]
|
_ = x[actNextHistory-109]
|
||||||
_ = x[actLast-110]
|
_ = x[actNextSelected-110]
|
||||||
_ = x[actReload-111]
|
_ = x[actExecute-111]
|
||||||
_ = x[actReloadSync-112]
|
_ = x[actExecuteSilent-112]
|
||||||
_ = x[actDisableSearch-113]
|
_ = x[actExecuteMulti-113]
|
||||||
_ = x[actEnableSearch-114]
|
_ = x[actSigStop-114]
|
||||||
_ = x[actSelect-115]
|
_ = x[actFirst-115]
|
||||||
_ = x[actDeselect-116]
|
_ = x[actLast-116]
|
||||||
_ = x[actUnbind-117]
|
_ = x[actReload-117]
|
||||||
_ = x[actRebind-118]
|
_ = x[actReloadSync-118]
|
||||||
_ = x[actBecome-119]
|
_ = x[actDisableSearch-119]
|
||||||
_ = x[actShowHeader-120]
|
_ = x[actEnableSearch-120]
|
||||||
_ = x[actHideHeader-121]
|
_ = x[actSelect-121]
|
||||||
|
_ = x[actDeselect-122]
|
||||||
|
_ = x[actUnbind-123]
|
||||||
|
_ = x[actRebind-124]
|
||||||
|
_ = x[actToggleBind-125]
|
||||||
|
_ = x[actBecome-126]
|
||||||
|
_ = x[actShowHeader-127]
|
||||||
|
_ = x[actHideHeader-128]
|
||||||
|
_ = x[actBell-129]
|
||||||
}
|
}
|
||||||
|
|
||||||
const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeListLabelactChangeInputLabelactChangeHeaderactChangeHeaderLabelactChangeMultiactChangePreviewLabelactChangePromptactChangeQueryactChangeNthactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactToggleWrapactToggleMultiLineactToggleHscrollactTrackCurrentactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformListLabelactTransformInputLabelactTransformHeaderactTransformHeaderLabelactTransformPreviewLabelactTransformPromptactTransformQueryactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactBecomeactShowHeaderactHideHeader"
|
const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeListLabelactChangeInputLabelactChangeHeaderactChangeHeaderLabelactChangeMultiactChangePreviewLabelactChangePromptactChangeQueryactChangeNthactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactToggleWrapactToggleMultiLineactToggleHscrollactTrackCurrentactToggleInputactHideInputactShowInputactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformListLabelactTransformInputLabelactTransformHeaderactTransformHeaderLabelactTransformNthactTransformPreviewLabelactTransformPromptactTransformQueryactTransformSearchactSearchactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactToggleBindactBecomeactShowHeaderactHideHeaderactBell"
|
||||||
|
|
||||||
var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 42, 50, 68, 76, 85, 102, 123, 138, 159, 183, 198, 207, 227, 245, 264, 279, 299, 313, 334, 349, 363, 375, 389, 402, 419, 427, 440, 456, 468, 476, 490, 504, 515, 526, 544, 561, 568, 587, 599, 613, 622, 637, 649, 662, 673, 684, 696, 710, 731, 746, 759, 777, 793, 808, 825, 832, 837, 846, 857, 868, 881, 896, 907, 920, 935, 942, 955, 968, 985, 1000, 1013, 1027, 1041, 1057, 1077, 1089, 1112, 1133, 1155, 1173, 1196, 1220, 1238, 1255, 1265, 1281, 1303, 1316, 1332, 1344, 1358, 1374, 1392, 1412, 1434, 1448, 1463, 1471, 1477, 1491, 1506, 1516, 1532, 1547, 1557, 1565, 1572, 1581, 1594, 1610, 1625, 1634, 1645, 1654, 1663, 1672, 1685, 1698}
|
var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 42, 50, 68, 76, 85, 102, 123, 138, 159, 183, 198, 207, 227, 245, 264, 279, 299, 313, 334, 349, 363, 375, 389, 402, 419, 427, 440, 456, 468, 476, 490, 504, 515, 526, 544, 561, 568, 587, 599, 613, 622, 637, 649, 662, 673, 684, 696, 710, 731, 746, 759, 777, 793, 808, 822, 834, 846, 863, 870, 875, 884, 895, 906, 919, 934, 945, 958, 973, 980, 993, 1006, 1023, 1038, 1051, 1065, 1079, 1095, 1115, 1127, 1150, 1171, 1193, 1211, 1234, 1249, 1273, 1291, 1308, 1326, 1335, 1345, 1361, 1383, 1396, 1412, 1424, 1438, 1454, 1472, 1492, 1514, 1528, 1543, 1551, 1557, 1571, 1586, 1596, 1612, 1627, 1637, 1645, 1652, 1661, 1674, 1690, 1705, 1714, 1725, 1734, 1743, 1756, 1765, 1778, 1791, 1798}
|
||||||
|
|
||||||
func (i actionType) String() string {
|
func (i actionType) String() string {
|
||||||
if i < 0 || i >= actionType(len(_actionType_index)-1) {
|
if i < 0 || i >= actionType(len(_actionType_index)-1) {
|
||||||
|
|||||||
13
src/ansi.go
13
src/ansi.go
@@ -358,12 +358,17 @@ func interpretCode(ansiCode string, prevState *ansiState) ansiState {
|
|||||||
if ansiCode[0] != '\x1b' || ansiCode[1] != '[' || ansiCode[len(ansiCode)-1] != 'm' {
|
if ansiCode[0] != '\x1b' || ansiCode[1] != '[' || ansiCode[len(ansiCode)-1] != 'm' {
|
||||||
if prevState != nil && strings.HasSuffix(ansiCode, "0K") {
|
if prevState != nil && strings.HasSuffix(ansiCode, "0K") {
|
||||||
state.lbg = prevState.bg
|
state.lbg = prevState.bg
|
||||||
} else if ansiCode == "\x1b]8;;\x1b\\" { // End of a hyperlink
|
} else if strings.HasPrefix(ansiCode, "\x1b]8;") && (strings.HasSuffix(ansiCode, "\x1b\\") || strings.HasSuffix(ansiCode, "\a")) {
|
||||||
|
stLen := 2
|
||||||
|
if strings.HasSuffix(ansiCode, "\a") {
|
||||||
|
stLen = 1
|
||||||
|
}
|
||||||
|
// "\x1b]8;;\x1b\\" or "\x1b]8;;\a"
|
||||||
|
if len(ansiCode) == 5+stLen && ansiCode[4] == ';' {
|
||||||
state.url = nil
|
state.url = nil
|
||||||
} else if strings.HasPrefix(ansiCode, "\x1b]8;") && strings.HasSuffix(ansiCode, "\x1b\\") {
|
} else if paramsEnd := strings.IndexRune(ansiCode[4:], ';'); paramsEnd >= 0 {
|
||||||
if paramsEnd := strings.IndexRune(ansiCode[4:], ';'); paramsEnd >= 0 {
|
|
||||||
params := ansiCode[4 : 4+paramsEnd]
|
params := ansiCode[4 : 4+paramsEnd]
|
||||||
uri := ansiCode[5+paramsEnd : len(ansiCode)-2]
|
uri := ansiCode[5+paramsEnd : len(ansiCode)-stLen]
|
||||||
state.url = &url{uri: uri, params: params}
|
state.url = &url{uri: uri, params: params}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -188,6 +188,9 @@ func Run(opts *Options) (int, error) {
|
|||||||
forward = false
|
forward = false
|
||||||
case byBegin:
|
case byBegin:
|
||||||
forward = true
|
forward = true
|
||||||
|
case byPathname:
|
||||||
|
withPos = true
|
||||||
|
forward = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
231
src/options.go
231
src/options.go
@@ -11,6 +11,7 @@ import (
|
|||||||
|
|
||||||
"github.com/junegunn/fzf/src/algo"
|
"github.com/junegunn/fzf/src/algo"
|
||||||
"github.com/junegunn/fzf/src/tui"
|
"github.com/junegunn/fzf/src/tui"
|
||||||
|
"github.com/junegunn/fzf/src/util"
|
||||||
|
|
||||||
"github.com/junegunn/go-shellwords"
|
"github.com/junegunn/go-shellwords"
|
||||||
"github.com/rivo/uniseg"
|
"github.com/rivo/uniseg"
|
||||||
@@ -46,8 +47,8 @@ Usage: fzf [options]
|
|||||||
--tail=NUM Maximum number of items to keep in memory
|
--tail=NUM Maximum number of items to keep in memory
|
||||||
--disabled Do not perform search
|
--disabled Do not perform search
|
||||||
--tiebreak=CRI[,..] Comma-separated list of sort criteria to apply
|
--tiebreak=CRI[,..] Comma-separated list of sort criteria to apply
|
||||||
when the scores are tied [length|chunk|begin|end|index]
|
when the scores are tied
|
||||||
(default: length)
|
[length|chunk|pathname|begin|end|index] (default: length)
|
||||||
|
|
||||||
INPUT/OUTPUT
|
INPUT/OUTPUT
|
||||||
--read0 Read input delimited by ASCII NUL characters
|
--read0 Read input delimited by ASCII NUL characters
|
||||||
@@ -68,8 +69,9 @@ Usage: fzf [options]
|
|||||||
minus the given value.
|
minus the given value.
|
||||||
If prefixed with '~', fzf will determine the height
|
If prefixed with '~', fzf will determine the height
|
||||||
according to the input size.
|
according to the input size.
|
||||||
--min-height=HEIGHT Minimum height for percent --height is given in percent
|
--min-height=HEIGHT[+] Minimum height when --height is given as a percentage.
|
||||||
(default: 10)
|
Add '+' to automatically increase the value
|
||||||
|
according to the other layout options (default: 10+).
|
||||||
--tmux[=OPTS] Start fzf in a tmux popup (requires tmux 3.3+)
|
--tmux[=OPTS] Start fzf in a tmux popup (requires tmux 3.3+)
|
||||||
[center|top|bottom|left|right][,SIZE[%]][,SIZE[%]]
|
[center|top|bottom|left|right][,SIZE[%]][,SIZE[%]]
|
||||||
[,border-native] (default: center,50%)
|
[,border-native] (default: center,50%)
|
||||||
@@ -125,6 +127,7 @@ Usage: fzf [options]
|
|||||||
(default: 0 or center)
|
(default: 0 or center)
|
||||||
|
|
||||||
INPUT SECTION
|
INPUT SECTION
|
||||||
|
--no-input Disable and hide the input section
|
||||||
--prompt=STR Input prompt (default: '> ')
|
--prompt=STR Input prompt (default: '> ')
|
||||||
--info=STYLE Finder info style
|
--info=STYLE Finder info style
|
||||||
[default|right|hidden|inline[-right][:PREFIX]]
|
[default|right|hidden|inline[-right][:PREFIX]]
|
||||||
@@ -164,6 +167,9 @@ Usage: fzf [options]
|
|||||||
--header-border[=STYLE] Draw border around the header section
|
--header-border[=STYLE] Draw border around the header section
|
||||||
[rounded|sharp|bold|block|thinblock|double|horizontal|vertical|
|
[rounded|sharp|bold|block|thinblock|double|horizontal|vertical|
|
||||||
top|bottom|left|right|none] (default: rounded)
|
top|bottom|left|right|none] (default: rounded)
|
||||||
|
--header-lines-border[=STYLE]
|
||||||
|
Display header from --header-lines with a separate border.
|
||||||
|
Pass 'none' to still separate it but without a border.
|
||||||
--header-label=LABEL Label to print on the header border
|
--header-label=LABEL Label to print on the header border
|
||||||
--header-label-pos=COL Position of the header label
|
--header-label-pos=COL Position of the header label
|
||||||
[POSITIVE_INTEGER: columns from left|
|
[POSITIVE_INTEGER: columns from left|
|
||||||
@@ -238,6 +244,7 @@ const (
|
|||||||
byLength
|
byLength
|
||||||
byBegin
|
byBegin
|
||||||
byEnd
|
byEnd
|
||||||
|
byPathname
|
||||||
)
|
)
|
||||||
|
|
||||||
type heightSpec struct {
|
type heightSpec struct {
|
||||||
@@ -532,6 +539,7 @@ type Options struct {
|
|||||||
Scheme string
|
Scheme string
|
||||||
Extended bool
|
Extended bool
|
||||||
Phony bool
|
Phony bool
|
||||||
|
Inputless bool
|
||||||
Case Case
|
Case Case
|
||||||
Normalize bool
|
Normalize bool
|
||||||
Nth []Range
|
Nth []Range
|
||||||
@@ -597,6 +605,7 @@ type Options struct {
|
|||||||
ListBorderShape tui.BorderShape
|
ListBorderShape tui.BorderShape
|
||||||
InputBorderShape tui.BorderShape
|
InputBorderShape tui.BorderShape
|
||||||
HeaderBorderShape tui.BorderShape
|
HeaderBorderShape tui.BorderShape
|
||||||
|
HeaderLinesShape tui.BorderShape
|
||||||
InputLabel labelOpts
|
InputLabel labelOpts
|
||||||
HeaderLabel labelOpts
|
HeaderLabel labelOpts
|
||||||
BorderLabel labelOpts
|
BorderLabel labelOpts
|
||||||
@@ -649,9 +658,10 @@ func defaultOptions() *Options {
|
|||||||
Man: false,
|
Man: false,
|
||||||
Fuzzy: true,
|
Fuzzy: true,
|
||||||
FuzzyAlgo: algo.FuzzyMatchV2,
|
FuzzyAlgo: algo.FuzzyMatchV2,
|
||||||
Scheme: "default",
|
Scheme: "", // Unknown
|
||||||
Extended: true,
|
Extended: true,
|
||||||
Phony: false,
|
Phony: false,
|
||||||
|
Inputless: false,
|
||||||
Case: CaseSmart,
|
Case: CaseSmart,
|
||||||
Normalize: true,
|
Normalize: true,
|
||||||
Nth: make([]Range, 0),
|
Nth: make([]Range, 0),
|
||||||
@@ -660,14 +670,14 @@ func defaultOptions() *Options {
|
|||||||
Sort: 1000,
|
Sort: 1000,
|
||||||
Track: trackDisabled,
|
Track: trackDisabled,
|
||||||
Tac: false,
|
Tac: false,
|
||||||
Criteria: []criterion{byScore, byLength},
|
Criteria: []criterion{}, // Unknown
|
||||||
Multi: 0,
|
Multi: 0,
|
||||||
Ansi: false,
|
Ansi: false,
|
||||||
Mouse: true,
|
Mouse: true,
|
||||||
Theme: theme,
|
Theme: theme,
|
||||||
Black: false,
|
Black: false,
|
||||||
Bold: true,
|
Bold: true,
|
||||||
MinHeight: 10,
|
MinHeight: -10,
|
||||||
Layout: layoutDefault,
|
Layout: layoutDefault,
|
||||||
Cycle: false,
|
Cycle: false,
|
||||||
Wrap: false,
|
Wrap: false,
|
||||||
@@ -800,16 +810,6 @@ func parseAlgo(str string) (algo.Algo, error) {
|
|||||||
return nil, errors.New("invalid algorithm (expected: v1 or v2)")
|
return nil, errors.New("invalid algorithm (expected: v1 or v2)")
|
||||||
}
|
}
|
||||||
|
|
||||||
func processScheme(opts *Options) error {
|
|
||||||
if !algo.Init(opts.Scheme) {
|
|
||||||
return errors.New("invalid scoring scheme (expected: default|path|history)")
|
|
||||||
}
|
|
||||||
if opts.Scheme == "history" {
|
|
||||||
opts.Criteria = []criterion{byScore}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseBorder(str string, optional bool, allowLine bool) (tui.BorderShape, error) {
|
func parseBorder(str string, optional bool, allowLine bool) (tui.BorderShape, error) {
|
||||||
switch str {
|
switch str {
|
||||||
case "line":
|
case "line":
|
||||||
@@ -885,7 +885,7 @@ func parseKeyChordsImpl(str string, message string) (map[tui.Event]string, error
|
|||||||
case "right":
|
case "right":
|
||||||
add(tui.Right)
|
add(tui.Right)
|
||||||
case "enter", "return":
|
case "enter", "return":
|
||||||
add(tui.CtrlM)
|
add(tui.Enter)
|
||||||
case "space":
|
case "space":
|
||||||
chords[tui.Key(' ')] = key
|
chords[tui.Key(' ')] = key
|
||||||
case "backspace", "bspace", "bs":
|
case "backspace", "bspace", "bs":
|
||||||
@@ -1033,6 +1033,19 @@ func parseKeyChordsImpl(str string, message string) (map[tui.Event]string, error
|
|||||||
return chords, nil
|
return chords, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseScheme(str string) (string, []criterion, error) {
|
||||||
|
str = strings.ToLower(str)
|
||||||
|
switch str {
|
||||||
|
case "history":
|
||||||
|
return str, []criterion{byScore}, nil
|
||||||
|
case "path":
|
||||||
|
return str, []criterion{byScore, byPathname, byLength}, nil
|
||||||
|
case "default":
|
||||||
|
return str, []criterion{byScore, byLength}, nil
|
||||||
|
}
|
||||||
|
return str, nil, errors.New("invalid scoring scheme: " + str + " (expected: default|path|history)")
|
||||||
|
}
|
||||||
|
|
||||||
func parseTiebreak(str string) ([]criterion, error) {
|
func parseTiebreak(str string) ([]criterion, error) {
|
||||||
criteria := []criterion{byScore}
|
criteria := []criterion{byScore}
|
||||||
hasIndex := false
|
hasIndex := false
|
||||||
@@ -1040,6 +1053,7 @@ func parseTiebreak(str string) ([]criterion, error) {
|
|||||||
hasLength := false
|
hasLength := false
|
||||||
hasBegin := false
|
hasBegin := false
|
||||||
hasEnd := false
|
hasEnd := false
|
||||||
|
hasPathname := false
|
||||||
check := func(notExpected *bool, name string) error {
|
check := func(notExpected *bool, name string) error {
|
||||||
if *notExpected {
|
if *notExpected {
|
||||||
return errors.New("duplicate sort criteria: " + name)
|
return errors.New("duplicate sort criteria: " + name)
|
||||||
@@ -1061,6 +1075,11 @@ func parseTiebreak(str string) ([]criterion, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
criteria = append(criteria, byChunk)
|
criteria = append(criteria, byChunk)
|
||||||
|
case "pathname":
|
||||||
|
if err := check(&hasPathname, "pathname"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
criteria = append(criteria, byPathname)
|
||||||
case "length":
|
case "length":
|
||||||
if err := check(&hasLength, "length"); err != nil {
|
if err := check(&hasLength, "length"); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -1317,7 +1336,7 @@ const (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
executeRegexp = regexp.MustCompile(
|
executeRegexp = regexp.MustCompile(
|
||||||
`(?si)[:+](become|execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|transform)-(?:query|prompt|(?:border|list|preview|input|header)-label|header)|transform|change-(?:preview-window|preview|multi|nth)|(?:re|un)bind|pos|put|print)`)
|
`(?si)[:+](become|execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|transform)-(?:query|prompt|(?:border|list|preview|input|header)-label|header|search|nth)|transform|change-(?:preview-window|preview|multi)|(?:re|un|toggle-)bind|pos|put|print|search)`)
|
||||||
splitRegexp = regexp.MustCompile("[,:]+")
|
splitRegexp = regexp.MustCompile("[,:]+")
|
||||||
actionNameRegexp = regexp.MustCompile("(?i)^[a-z-]+")
|
actionNameRegexp = regexp.MustCompile("(?i)^[a-z-]+")
|
||||||
}
|
}
|
||||||
@@ -1372,6 +1391,8 @@ Loop:
|
|||||||
masked += strings.Repeat(" ", loc[1])
|
masked += strings.Repeat(" ", loc[1])
|
||||||
action = action[loc[1]:]
|
action = action[loc[1]:]
|
||||||
}
|
}
|
||||||
|
masked = strings.ReplaceAll(masked, ",,,", string([]rune{',', escapedComma, ','}))
|
||||||
|
masked = strings.ReplaceAll(masked, ",:,", string([]rune{',', escapedColon, ','}))
|
||||||
masked = strings.ReplaceAll(masked, "::", string([]rune{escapedColon, ':'}))
|
masked = strings.ReplaceAll(masked, "::", string([]rune{escapedColon, ':'}))
|
||||||
masked = strings.ReplaceAll(masked, ",:", string([]rune{escapedComma, ':'}))
|
masked = strings.ReplaceAll(masked, ",:", string([]rune{escapedComma, ':'}))
|
||||||
masked = strings.ReplaceAll(masked, "+:", string([]rune{escapedPlus, ':'}))
|
masked = strings.ReplaceAll(masked, "+:", string([]rune{escapedPlus, ':'}))
|
||||||
@@ -1479,6 +1500,12 @@ func parseActionList(masked string, original string, prevActions []*action, putA
|
|||||||
appendAction(actToggleTrack)
|
appendAction(actToggleTrack)
|
||||||
case "toggle-track-current":
|
case "toggle-track-current":
|
||||||
appendAction(actToggleTrackCurrent)
|
appendAction(actToggleTrackCurrent)
|
||||||
|
case "toggle-input":
|
||||||
|
appendAction(actToggleInput)
|
||||||
|
case "hide-input":
|
||||||
|
appendAction(actHideInput)
|
||||||
|
case "show-input":
|
||||||
|
appendAction(actShowInput)
|
||||||
case "toggle-header":
|
case "toggle-header":
|
||||||
appendAction(actToggleHeader)
|
appendAction(actToggleHeader)
|
||||||
case "toggle-wrap":
|
case "toggle-wrap":
|
||||||
@@ -1571,6 +1598,8 @@ func parseActionList(masked string, original string, prevActions []*action, putA
|
|||||||
} else {
|
} else {
|
||||||
return nil, errors.New("unable to put non-printable character")
|
return nil, errors.New("unable to put non-printable character")
|
||||||
}
|
}
|
||||||
|
case "bell":
|
||||||
|
appendAction(actBell)
|
||||||
default:
|
default:
|
||||||
t := isExecuteAction(specLower)
|
t := isExecuteAction(specLower)
|
||||||
if t == actIgnore {
|
if t == actIgnore {
|
||||||
@@ -1597,7 +1626,7 @@ func parseActionList(masked string, original string, prevActions []*action, putA
|
|||||||
actions = append(actions, &action{t: t, a: actionArg})
|
actions = append(actions, &action{t: t, a: actionArg})
|
||||||
}
|
}
|
||||||
switch t {
|
switch t {
|
||||||
case actUnbind, actRebind:
|
case actUnbind, actRebind, actToggleBind:
|
||||||
if _, err := parseKeyChordsImpl(actionArg, spec[0:offset]+" target required"); err != nil {
|
if _, err := parseKeyChordsImpl(actionArg, spec[0:offset]+" target required"); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -1621,23 +1650,29 @@ func parseKeymap(keymap map[tui.Event][]*action, str string) error {
|
|||||||
var err error
|
var err error
|
||||||
masked := maskActionContents(str)
|
masked := maskActionContents(str)
|
||||||
idx := 0
|
idx := 0
|
||||||
|
keys := []string{}
|
||||||
for _, pairStr := range strings.Split(masked, ",") {
|
for _, pairStr := range strings.Split(masked, ",") {
|
||||||
origPairStr := str[idx : idx+len(pairStr)]
|
origPairStr := str[idx : idx+len(pairStr)]
|
||||||
idx += len(pairStr) + 1
|
idx += len(pairStr) + 1
|
||||||
|
|
||||||
pair := strings.SplitN(pairStr, ":", 2)
|
pair := strings.SplitN(pairStr, ":", 2)
|
||||||
if len(pair) < 2 {
|
if len(pair[0]) == 0 {
|
||||||
return errors.New("bind action not specified: " + origPairStr)
|
return errors.New("key name required")
|
||||||
}
|
}
|
||||||
|
keys = append(keys, pair[0])
|
||||||
|
if len(pair) < 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, keyName := range keys {
|
||||||
var key tui.Event
|
var key tui.Event
|
||||||
if len(pair[0]) == 1 && pair[0][0] == escapedColon {
|
if len(keyName) == 1 && keyName[0] == escapedColon {
|
||||||
key = tui.Key(':')
|
key = tui.Key(':')
|
||||||
} else if len(pair[0]) == 1 && pair[0][0] == escapedComma {
|
} else if len(keyName) == 1 && keyName[0] == escapedComma {
|
||||||
key = tui.Key(',')
|
key = tui.Key(',')
|
||||||
} else if len(pair[0]) == 1 && pair[0][0] == escapedPlus {
|
} else if len(keyName) == 1 && keyName[0] == escapedPlus {
|
||||||
key = tui.Key('+')
|
key = tui.Key('+')
|
||||||
} else {
|
} else {
|
||||||
keys, err := parseKeyChordsImpl(pair[0], "key name required")
|
keys, err := parseKeyChordsImpl(keyName, "key name required")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1649,6 +1684,11 @@ func parseKeymap(keymap map[tui.Event][]*action, str string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
keys = keys[:0]
|
||||||
|
}
|
||||||
|
if len(keys) > 0 {
|
||||||
|
return errors.New("bind action not specified: " + strings.Join(keys, ", "))
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1671,6 +1711,8 @@ func isExecuteAction(str string) actionType {
|
|||||||
return actUnbind
|
return actUnbind
|
||||||
case "rebind":
|
case "rebind":
|
||||||
return actRebind
|
return actRebind
|
||||||
|
case "toggle-bind":
|
||||||
|
return actToggleBind
|
||||||
case "preview":
|
case "preview":
|
||||||
return actPreview
|
return actPreview
|
||||||
case "change-header":
|
case "change-header":
|
||||||
@@ -1723,10 +1765,16 @@ func isExecuteAction(str string) actionType {
|
|||||||
return actTransformHeaderLabel
|
return actTransformHeaderLabel
|
||||||
case "transform-header":
|
case "transform-header":
|
||||||
return actTransformHeader
|
return actTransformHeader
|
||||||
|
case "transform-nth":
|
||||||
|
return actTransformNth
|
||||||
case "transform-prompt":
|
case "transform-prompt":
|
||||||
return actTransformPrompt
|
return actTransformPrompt
|
||||||
case "transform-query":
|
case "transform-query":
|
||||||
return actTransformQuery
|
return actTransformQuery
|
||||||
|
case "transform-search":
|
||||||
|
return actTransformSearch
|
||||||
|
case "search":
|
||||||
|
return actSearch
|
||||||
}
|
}
|
||||||
return actIgnore
|
return actIgnore
|
||||||
}
|
}
|
||||||
@@ -2257,7 +2305,9 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
opts.Scheme = strings.ToLower(str)
|
if opts.Scheme, opts.Criteria, err = parseScheme(str); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
case "--expect":
|
case "--expect":
|
||||||
str, err := nextString("key names required")
|
str, err := nextString("key names required")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -2276,6 +2326,8 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
|
|||||||
opts.Phony = false
|
opts.Phony = false
|
||||||
case "--disabled", "--phony":
|
case "--disabled", "--phony":
|
||||||
opts.Phony = true
|
opts.Phony = true
|
||||||
|
case "--no-input":
|
||||||
|
opts.Inputless = true
|
||||||
case "--tiebreak":
|
case "--tiebreak":
|
||||||
str, err := nextString("sort criterion required")
|
str, err := nextString("sort criterion required")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -2624,9 +2676,23 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case "--min-height":
|
case "--min-height":
|
||||||
if opts.MinHeight, err = nextInt("height required: HEIGHT"); err != nil {
|
expr, err := nextString("minimum height required: HEIGHT[+]")
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
auto := false
|
||||||
|
if strings.HasSuffix(expr, "+") {
|
||||||
|
expr = expr[:len(expr)-1]
|
||||||
|
auto = true
|
||||||
|
}
|
||||||
|
num, err := atoi(expr)
|
||||||
|
if err != nil || num < 0 {
|
||||||
|
return errors.New("minimum height must be a non-negative integer")
|
||||||
|
}
|
||||||
|
if auto {
|
||||||
|
num *= -1
|
||||||
|
}
|
||||||
|
opts.MinHeight = num
|
||||||
case "--no-height":
|
case "--no-height":
|
||||||
opts.Height = heightSpec{}
|
opts.Height = heightSpec{}
|
||||||
case "--no-margin":
|
case "--no-margin":
|
||||||
@@ -2669,6 +2735,13 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
|
|||||||
if opts.HeaderBorderShape, err = parseBorder(arg, !hasArg, false); err != nil {
|
if opts.HeaderBorderShape, err = parseBorder(arg, !hasArg, false); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
case "--no-header-lines-border":
|
||||||
|
opts.HeaderLinesShape = tui.BorderNone
|
||||||
|
case "--header-lines-border":
|
||||||
|
hasArg, arg := optionalNextString()
|
||||||
|
if opts.HeaderLinesShape, err = parseBorder(arg, !hasArg, false); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
case "--no-header-label":
|
case "--no-header-label":
|
||||||
opts.HeaderLabel.label = ""
|
opts.HeaderLabel.label = ""
|
||||||
case "--header-label":
|
case "--header-label":
|
||||||
@@ -2993,6 +3066,24 @@ func validateOptions(opts *Options) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func noSeparatorLine(style infoStyle, separator bool) bool {
|
||||||
|
switch style {
|
||||||
|
case infoInline:
|
||||||
|
return true
|
||||||
|
case infoHidden, infoInlineRight:
|
||||||
|
return !separator
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (opts *Options) noSeparatorLine() bool {
|
||||||
|
if opts.Inputless {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
sep := opts.Separator == nil && !opts.InputBorderShape.Visible() || opts.Separator != nil && len(*opts.Separator) > 0
|
||||||
|
return noSeparatorLine(opts.InfoStyle, sep)
|
||||||
|
}
|
||||||
|
|
||||||
// This function can have side-effects and alter some global states.
|
// This function can have side-effects and alter some global states.
|
||||||
// So we run it on fzf.Run and not on ParseOptions.
|
// So we run it on fzf.Run and not on ParseOptions.
|
||||||
func postProcessOptions(opts *Options) error {
|
func postProcessOptions(opts *Options) error {
|
||||||
@@ -3016,6 +3107,19 @@ func postProcessOptions(opts *Options) error {
|
|||||||
opts.HeaderBorderShape = tui.BorderNone
|
opts.HeaderBorderShape = tui.BorderNone
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if opts.HeaderLinesShape == tui.BorderNone {
|
||||||
|
opts.HeaderLinesShape = tui.BorderPhantom
|
||||||
|
} else if opts.HeaderLinesShape == tui.BorderUndefined {
|
||||||
|
// In reverse-list layout, header lines should be at the top, while
|
||||||
|
// ordinary header should be at the bottom. So let's use a separate
|
||||||
|
// window for the header lines.
|
||||||
|
if opts.Layout == layoutReverseList {
|
||||||
|
opts.HeaderLinesShape = tui.BorderPhantom
|
||||||
|
} else {
|
||||||
|
opts.HeaderLinesShape = tui.BorderNone
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if opts.Pointer == nil {
|
if opts.Pointer == nil {
|
||||||
defaultPointer := "▌"
|
defaultPointer := "▌"
|
||||||
if !opts.Unicode {
|
if !opts.Unicode {
|
||||||
@@ -3116,7 +3220,7 @@ func postProcessOptions(opts *Options) error {
|
|||||||
|
|
||||||
// If 'double-click' is left unbound, bind it to the action bound to 'enter'
|
// If 'double-click' is left unbound, bind it to the action bound to 'enter'
|
||||||
if _, prs := opts.Keymap[tui.DoubleClick.AsEvent()]; !prs {
|
if _, prs := opts.Keymap[tui.DoubleClick.AsEvent()]; !prs {
|
||||||
opts.Keymap[tui.DoubleClick.AsEvent()] = opts.Keymap[tui.CtrlM.AsEvent()]
|
opts.Keymap[tui.DoubleClick.AsEvent()] = opts.Keymap[tui.Enter.AsEvent()]
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we're not using extended search mode, --nth option becomes irrelevant
|
// If we're not using extended search mode, --nth option becomes irrelevant
|
||||||
@@ -3152,11 +3256,46 @@ func postProcessOptions(opts *Options) error {
|
|||||||
opts.Height = heightSpec{}
|
opts.Height = heightSpec{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sets --min-height automatically
|
||||||
|
if opts.Height.size > 0 && opts.Height.percent && opts.MinHeight < 0 {
|
||||||
|
opts.MinHeight = -opts.MinHeight + borderLines(opts.BorderShape) + borderLines(opts.ListBorderShape)
|
||||||
|
if !opts.Inputless {
|
||||||
|
opts.MinHeight += 1 + borderLines(opts.InputBorderShape)
|
||||||
|
if !opts.noSeparatorLine() {
|
||||||
|
opts.MinHeight++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(opts.Header) > 0 {
|
||||||
|
opts.MinHeight += borderLines(opts.HeaderBorderShape) + len(opts.Header)
|
||||||
|
}
|
||||||
|
if opts.HeaderLines > 0 {
|
||||||
|
borderShape := opts.HeaderBorderShape
|
||||||
|
if opts.HeaderLinesShape.Visible() {
|
||||||
|
borderShape = opts.HeaderLinesShape
|
||||||
|
}
|
||||||
|
opts.MinHeight += borderLines(borderShape) + opts.HeaderLines
|
||||||
|
}
|
||||||
|
if len(opts.Preview.command) > 0 && (opts.Preview.position == posUp || opts.Preview.position == posDown) && opts.Preview.Visible() && opts.Preview.position == posUp {
|
||||||
|
borderShape := opts.Preview.border
|
||||||
|
if opts.Preview.border == tui.BorderLine {
|
||||||
|
borderShape = tui.BorderTop
|
||||||
|
}
|
||||||
|
opts.MinHeight += borderLines(borderShape) + 10
|
||||||
|
}
|
||||||
|
for _, s := range []sizeSpec{opts.Margin[0], opts.Margin[2], opts.Padding[0], opts.Padding[2]} {
|
||||||
|
if !s.percent {
|
||||||
|
opts.MinHeight += int(s.size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err := opts.initProfiling(); err != nil {
|
if err := opts.initProfiling(); err != nil {
|
||||||
return errors.New("failed to start pprof profiles: " + err.Error())
|
return errors.New("failed to start pprof profiles: " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
return processScheme(opts)
|
algo.Init(opts.Scheme)
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseShellWords(str string) ([]string, error) {
|
func parseShellWords(str string) ([]string, error) {
|
||||||
@@ -3206,7 +3345,26 @@ func ParseOptions(useDefaults bool, args []string) (*Options, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Final validation of merged options
|
// 4. Change default scheme when built-in walker is used
|
||||||
|
if len(opts.Scheme) == 0 {
|
||||||
|
opts.Scheme = "default"
|
||||||
|
if len(opts.Criteria) == 0 {
|
||||||
|
// NOTE: Let's assume $FZF_DEFAULT_COMMAND generates a list of file paths.
|
||||||
|
// But it is possible that it is set to a command that doesn't generate
|
||||||
|
// file paths.
|
||||||
|
//
|
||||||
|
// In that case, you can either
|
||||||
|
// 1. explicitly set --scheme=default,
|
||||||
|
// 2. or replace $FZF_DEFAULT_COMMAND with an equivalent 'start:reload'
|
||||||
|
// binding, which is the new preferred way.
|
||||||
|
if !opts.hasReloadOrTransformOnStart() && util.IsTty(os.Stdin) {
|
||||||
|
opts.Scheme = "path"
|
||||||
|
}
|
||||||
|
_, opts.Criteria, _ = parseScheme(opts.Scheme)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Final validation of merged options
|
||||||
if err := validateOptions(opts); err != nil {
|
if err := validateOptions(opts); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -3214,6 +3372,17 @@ func ParseOptions(useDefaults bool, args []string) (*Options, error) {
|
|||||||
return opts, nil
|
return opts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (opts *Options) hasReloadOrTransformOnStart() bool {
|
||||||
|
if actions, prs := opts.Keymap[tui.Start.AsEvent()]; prs {
|
||||||
|
for _, action := range actions {
|
||||||
|
if action.t == actReload || action.t == actReloadSync || action.t == actTransform {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (opts *Options) extractReloadOnStart() string {
|
func (opts *Options) extractReloadOnStart() string {
|
||||||
cmd := ""
|
cmd := ""
|
||||||
if actions, prs := opts.Keymap[tui.Start.AsEvent()]; prs {
|
if actions, prs := opts.Keymap[tui.Start.AsEvent()]; prs {
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ func TestParseKeys(t *testing.T) {
|
|||||||
if len(pairs) != 9 {
|
if len(pairs) != 9 {
|
||||||
t.Error(9)
|
t.Error(9)
|
||||||
}
|
}
|
||||||
check(tui.CtrlM, "Return")
|
check(tui.Enter, "Return")
|
||||||
checkEvent(tui.Key(' '), "space")
|
checkEvent(tui.Key(' '), "space")
|
||||||
check(tui.Tab, "tab")
|
check(tui.Tab, "tab")
|
||||||
check(tui.ShiftTab, "btab")
|
check(tui.ShiftTab, "btab")
|
||||||
@@ -195,7 +195,7 @@ func TestParseKeys(t *testing.T) {
|
|||||||
check(tui.ShiftLeft, "shift-left")
|
check(tui.ShiftLeft, "shift-left")
|
||||||
check(tui.ShiftRight, "shift-right")
|
check(tui.ShiftRight, "shift-right")
|
||||||
check(tui.ShiftTab, "shift-tab")
|
check(tui.ShiftTab, "shift-tab")
|
||||||
check(tui.CtrlM, "Enter")
|
check(tui.Enter, "Enter")
|
||||||
check(tui.Backspace, "bspace")
|
check(tui.Backspace, "bspace")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -69,6 +69,21 @@ func buildResult(item *Item, offsets []Offset, score int) Result {
|
|||||||
}
|
}
|
||||||
case byLength:
|
case byLength:
|
||||||
val = item.TrimLength()
|
val = item.TrimLength()
|
||||||
|
case byPathname:
|
||||||
|
if validOffsetFound {
|
||||||
|
// lastDelim := strings.LastIndexByte(item.text.ToString(), '/')
|
||||||
|
lastDelim := -1
|
||||||
|
s := item.text.ToString()
|
||||||
|
for i := len(s) - 1; i >= 0; i-- {
|
||||||
|
if s[i] == '/' || s[i] == '\\' {
|
||||||
|
lastDelim = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if lastDelim <= minBegin {
|
||||||
|
val = util.AsUint16(minBegin - lastDelim)
|
||||||
|
}
|
||||||
|
}
|
||||||
case byBegin, byEnd:
|
case byBegin, byEnd:
|
||||||
if validOffsetFound {
|
if validOffsetFound {
|
||||||
whitePrefixLen := 0
|
whitePrefixLen := 0
|
||||||
|
|||||||
607
src/terminal.go
607
src/terminal.go
File diff suppressed because it is too large
Load Diff
@@ -485,6 +485,11 @@ func TestParsePlaceholder(t *testing.T) {
|
|||||||
// query flag is not removed after parsing, so it gets doubled
|
// query flag is not removed after parsing, so it gets doubled
|
||||||
// while the double q is invalid, it is useful here for testing purposes
|
// while the double q is invalid, it is useful here for testing purposes
|
||||||
`{q}`: `{qq}`,
|
`{q}`: `{qq}`,
|
||||||
|
`{q:1}`: `{qq:1}`,
|
||||||
|
`{q:2..}`: `{qq:2..}`,
|
||||||
|
`{q:..}`: `{qq:..}`,
|
||||||
|
`{q:2..-1}`: `{qq:2..-1}`,
|
||||||
|
`{q:s2..-1}`: `{sqq:2..-1}`, // FIXME
|
||||||
|
|
||||||
// IV. escaping placeholder
|
// IV. escaping placeholder
|
||||||
`\{}`: `{}`,
|
`\{}`: `{}`,
|
||||||
|
|||||||
@@ -22,6 +22,18 @@ func (r Range) IsFull() bool {
|
|||||||
return r.begin == rangeEllipsis && r.end == rangeEllipsis
|
return r.begin == rangeEllipsis && r.end == rangeEllipsis
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func compareRanges(r1 []Range, r2 []Range) bool {
|
||||||
|
if len(r1) != len(r2) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for idx := range r1 {
|
||||||
|
if r1[idx] != r2[idx] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func RangesToString(ranges []Range) string {
|
func RangesToString(ranges []Range) string {
|
||||||
strs := []string{}
|
strs := []string{}
|
||||||
for _, r := range ranges {
|
for _, r := range ranges {
|
||||||
|
|||||||
@@ -44,6 +44,9 @@ func (r *FullscreenRenderer) PassThrough(string) {}
|
|||||||
func (r *FullscreenRenderer) Clear() {}
|
func (r *FullscreenRenderer) Clear() {}
|
||||||
func (r *FullscreenRenderer) NeedScrollbarRedraw() bool { return false }
|
func (r *FullscreenRenderer) NeedScrollbarRedraw() bool { return false }
|
||||||
func (r *FullscreenRenderer) ShouldEmitResizeEvent() bool { return false }
|
func (r *FullscreenRenderer) ShouldEmitResizeEvent() bool { return false }
|
||||||
|
func (r *FullscreenRenderer) Bell() {}
|
||||||
|
func (r *FullscreenRenderer) HideCursor() {}
|
||||||
|
func (r *FullscreenRenderer) ShowCursor() {}
|
||||||
func (r *FullscreenRenderer) Refresh() {}
|
func (r *FullscreenRenderer) Refresh() {}
|
||||||
func (r *FullscreenRenderer) Close() {}
|
func (r *FullscreenRenderer) Close() {}
|
||||||
func (r *FullscreenRenderer) Size() TermSize { return TermSize{} }
|
func (r *FullscreenRenderer) Size() TermSize { return TermSize{} }
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ func _() {
|
|||||||
_ = x[CtrlJ-10]
|
_ = x[CtrlJ-10]
|
||||||
_ = x[CtrlK-11]
|
_ = x[CtrlK-11]
|
||||||
_ = x[CtrlL-12]
|
_ = x[CtrlL-12]
|
||||||
_ = x[CtrlM-13]
|
_ = x[Enter-13]
|
||||||
_ = x[CtrlN-14]
|
_ = x[CtrlN-14]
|
||||||
_ = x[CtrlO-15]
|
_ = x[CtrlO-15]
|
||||||
_ = x[CtrlP-16]
|
_ = x[CtrlP-16]
|
||||||
@@ -110,7 +110,7 @@ func _() {
|
|||||||
_ = x[ClickHeader-99]
|
_ = x[ClickHeader-99]
|
||||||
}
|
}
|
||||||
|
|
||||||
const _EventType_name = "RuneCtrlACtrlBCtrlCCtrlDCtrlECtrlFCtrlGCtrlHTabCtrlJCtrlKCtrlLCtrlMCtrlNCtrlOCtrlPCtrlQCtrlRCtrlSCtrlTCtrlUCtrlVCtrlWCtrlXCtrlYCtrlZEscCtrlSpaceCtrlDeleteCtrlBackSlashCtrlRightBracketCtrlCaretCtrlSlashShiftTabBackspaceDeletePageUpPageDownUpDownLeftRightHomeEndInsertShiftUpShiftDownShiftLeftShiftRightShiftDeleteF1F2F3F4F5F6F7F8F9F10F11F12AltBackspaceAltUpAltDownAltLeftAltRightAltShiftUpAltShiftDownAltShiftLeftAltShiftRightAltCtrlAltInvalidFatalMouseDoubleClickLeftClickRightClickSLeftClickSRightClickScrollUpScrollDownSScrollUpSScrollDownPreviewScrollUpPreviewScrollDownResizeChangeBackwardEOFStartLoadFocusOneZeroResultJumpJumpCancelClickHeader"
|
const _EventType_name = "RuneCtrlACtrlBCtrlCCtrlDCtrlECtrlFCtrlGCtrlHTabCtrlJCtrlKCtrlLEnterCtrlNCtrlOCtrlPCtrlQCtrlRCtrlSCtrlTCtrlUCtrlVCtrlWCtrlXCtrlYCtrlZEscCtrlSpaceCtrlDeleteCtrlBackSlashCtrlRightBracketCtrlCaretCtrlSlashShiftTabBackspaceDeletePageUpPageDownUpDownLeftRightHomeEndInsertShiftUpShiftDownShiftLeftShiftRightShiftDeleteF1F2F3F4F5F6F7F8F9F10F11F12AltBackspaceAltUpAltDownAltLeftAltRightAltShiftUpAltShiftDownAltShiftLeftAltShiftRightAltCtrlAltInvalidFatalMouseDoubleClickLeftClickRightClickSLeftClickSRightClickScrollUpScrollDownSScrollUpSScrollDownPreviewScrollUpPreviewScrollDownResizeChangeBackwardEOFStartLoadFocusOneZeroResultJumpJumpCancelClickHeader"
|
||||||
|
|
||||||
var _EventType_index = [...]uint16{0, 4, 9, 14, 19, 24, 29, 34, 39, 44, 47, 52, 57, 62, 67, 72, 77, 82, 87, 92, 97, 102, 107, 112, 117, 122, 127, 132, 135, 144, 154, 167, 183, 192, 201, 209, 218, 224, 230, 238, 240, 244, 248, 253, 257, 260, 266, 273, 282, 291, 301, 312, 314, 316, 318, 320, 322, 324, 326, 328, 330, 333, 336, 339, 351, 356, 363, 370, 378, 388, 400, 412, 425, 428, 435, 442, 447, 452, 463, 472, 482, 492, 503, 511, 521, 530, 541, 556, 573, 579, 585, 596, 601, 605, 610, 613, 617, 623, 627, 637, 648}
|
var _EventType_index = [...]uint16{0, 4, 9, 14, 19, 24, 29, 34, 39, 44, 47, 52, 57, 62, 67, 72, 77, 82, 87, 92, 97, 102, 107, 112, 117, 122, 127, 132, 135, 144, 154, 167, 183, 192, 201, 209, 218, 224, 230, 238, 240, 244, 248, 253, 257, 260, 266, 273, 282, 291, 301, 312, 314, 316, 318, 320, 322, 324, 326, 328, 330, 333, 336, 339, 351, 356, 363, 370, 378, 388, 400, 412, 425, 428, 435, 442, 447, 452, 463, 472, 482, 492, 503, 511, 521, 530, 541, 556, 573, 579, 585, 596, 601, 605, 610, 613, 617, 623, 627, 637, 648}
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,10 @@ const consoleDevice string = "/dev/tty"
|
|||||||
var offsetRegexp = regexp.MustCompile("(.*)\x1b\\[([0-9]+);([0-9]+)R")
|
var offsetRegexp = regexp.MustCompile("(.*)\x1b\\[([0-9]+);([0-9]+)R")
|
||||||
var offsetRegexpBegin = regexp.MustCompile("^\x1b\\[[0-9]+;[0-9]+R")
|
var offsetRegexpBegin = regexp.MustCompile("^\x1b\\[[0-9]+;[0-9]+R")
|
||||||
|
|
||||||
|
func (r *LightRenderer) Bell() {
|
||||||
|
r.flushRaw("\a")
|
||||||
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) PassThrough(str string) {
|
func (r *LightRenderer) PassThrough(str string) {
|
||||||
r.queued.WriteString("\x1b7" + str + "\x1b8")
|
r.queued.WriteString("\x1b7" + str + "\x1b8")
|
||||||
}
|
}
|
||||||
@@ -73,7 +77,13 @@ func (r *LightRenderer) csi(code string) string {
|
|||||||
|
|
||||||
func (r *LightRenderer) flush() {
|
func (r *LightRenderer) flush() {
|
||||||
if r.queued.Len() > 0 {
|
if r.queued.Len() > 0 {
|
||||||
r.flushRaw("\x1b[?7l\x1b[?25l" + r.queued.String() + "\x1b[?25h\x1b[?7h")
|
raw := "\x1b[?7l\x1b[?25l" + r.queued.String()
|
||||||
|
if r.showCursor {
|
||||||
|
raw += "\x1b[?25h\x1b[?7h"
|
||||||
|
} else {
|
||||||
|
raw += "\x1b[?7h"
|
||||||
|
}
|
||||||
|
r.flushRaw(raw)
|
||||||
r.queued.Reset()
|
r.queued.Reset()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -106,6 +116,7 @@ type LightRenderer struct {
|
|||||||
y int
|
y int
|
||||||
x int
|
x int
|
||||||
maxHeightFunc func(int) int
|
maxHeightFunc func(int) int
|
||||||
|
showCursor bool
|
||||||
|
|
||||||
// Windows only
|
// Windows only
|
||||||
ttyinChannel chan byte
|
ttyinChannel chan byte
|
||||||
@@ -148,7 +159,8 @@ func NewLightRenderer(ttyin *os.File, theme *ColorTheme, forceBlack bool, mouse
|
|||||||
tabstop: tabstop,
|
tabstop: tabstop,
|
||||||
fullscreen: fullscreen,
|
fullscreen: fullscreen,
|
||||||
upOneLine: false,
|
upOneLine: false,
|
||||||
maxHeightFunc: maxHeightFunc}
|
maxHeightFunc: maxHeightFunc,
|
||||||
|
showCursor: true}
|
||||||
return &r, nil
|
return &r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -622,15 +634,13 @@ func (r *LightRenderer) mouseSequence(sz *int) Event {
|
|||||||
|
|
||||||
// middle := t & 0b1
|
// middle := t & 0b1
|
||||||
left := t&0b11 == 0
|
left := t&0b11 == 0
|
||||||
|
ctrl := t&0b10000 > 0
|
||||||
// shift := t & 0b100
|
alt := t&0b01000 > 0
|
||||||
// ctrl := t & 0b1000
|
shift := t&0b00100 > 0
|
||||||
mod := t&0b1100 > 0
|
drag := t&0b100000 > 0 // 32
|
||||||
|
|
||||||
drag := t&0b100000 > 0
|
|
||||||
|
|
||||||
if scroll != 0 {
|
if scroll != 0 {
|
||||||
return Event{Mouse, 0, &MouseEvent{y, x, scroll, false, false, false, mod}}
|
return Event{Mouse, 0, &MouseEvent{y, x, scroll, false, false, false, ctrl, alt, shift}}
|
||||||
}
|
}
|
||||||
|
|
||||||
double := false
|
double := false
|
||||||
@@ -654,7 +664,7 @@ func (r *LightRenderer) mouseSequence(sz *int) Event {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Event{Mouse, 0, &MouseEvent{y, x, 0, left, down, double, mod}}
|
return Event{Mouse, 0, &MouseEvent{y, x, 0, left, down, double, ctrl, alt, shift}}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) smcup() {
|
func (r *LightRenderer) smcup() {
|
||||||
@@ -757,6 +767,9 @@ func (r *LightRenderer) Close() {
|
|||||||
} else if !r.fullscreen {
|
} else if !r.fullscreen {
|
||||||
r.csi("u")
|
r.csi("u")
|
||||||
}
|
}
|
||||||
|
if !r.showCursor {
|
||||||
|
r.csi("?25h")
|
||||||
|
}
|
||||||
r.disableMouse()
|
r.disableMouse()
|
||||||
r.flush()
|
r.flush()
|
||||||
r.closePlatform()
|
r.closePlatform()
|
||||||
@@ -1212,3 +1225,13 @@ func (w *LightWindow) Erase() {
|
|||||||
func (w *LightWindow) EraseMaybe() bool {
|
func (w *LightWindow) EraseMaybe() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) HideCursor() {
|
||||||
|
r.showCursor = false
|
||||||
|
r.csi("?25l")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) ShowCursor() {
|
||||||
|
r.showCursor = true
|
||||||
|
r.csi("?25h")
|
||||||
|
}
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ type TcellWindow struct {
|
|||||||
borderStyle BorderStyle
|
borderStyle BorderStyle
|
||||||
uri *string
|
uri *string
|
||||||
params *string
|
params *string
|
||||||
|
showCursor bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) Top() int {
|
func (w *TcellWindow) Top() int {
|
||||||
@@ -72,7 +73,9 @@ func (w *TcellWindow) Height() int {
|
|||||||
|
|
||||||
func (w *TcellWindow) Refresh() {
|
func (w *TcellWindow) Refresh() {
|
||||||
if w.moveCursor {
|
if w.moveCursor {
|
||||||
|
if w.showCursor {
|
||||||
_screen.ShowCursor(w.left+w.lastX, w.top+w.lastY)
|
_screen.ShowCursor(w.left+w.lastX, w.top+w.lastY)
|
||||||
|
}
|
||||||
w.moveCursor = false
|
w.moveCursor = false
|
||||||
}
|
}
|
||||||
w.lastX = 0
|
w.lastX = 0
|
||||||
@@ -100,6 +103,18 @@ const (
|
|||||||
BoldForce = Attr(1 << 10)
|
BoldForce = Attr(1 << 10)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (r *FullscreenRenderer) Bell() {
|
||||||
|
_screen.Beep()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FullscreenRenderer) HideCursor() {
|
||||||
|
r.showCursor = false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *FullscreenRenderer) ShowCursor() {
|
||||||
|
r.showCursor = true
|
||||||
|
}
|
||||||
|
|
||||||
func (r *FullscreenRenderer) PassThrough(str string) {
|
func (r *FullscreenRenderer) PassThrough(str string) {
|
||||||
// No-op
|
// No-op
|
||||||
// https://github.com/gdamore/tcell/pull/650#issuecomment-1806442846
|
// https://github.com/gdamore/tcell/pull/650#issuecomment-1806442846
|
||||||
@@ -164,6 +179,9 @@ func (r *FullscreenRenderer) getScreen() (tcell.Screen, error) {
|
|||||||
if e != nil {
|
if e != nil {
|
||||||
return nil, e
|
return nil, e
|
||||||
}
|
}
|
||||||
|
if !r.showCursor {
|
||||||
|
s.HideCursor()
|
||||||
|
}
|
||||||
_screen = s
|
_screen = s
|
||||||
}
|
}
|
||||||
return _screen, nil
|
return _screen, nil
|
||||||
@@ -262,7 +280,11 @@ func (r *FullscreenRenderer) GetChar() Event {
|
|||||||
// so mouse click is three consecutive events, but the first and last are indistinguishable from movement events (with released buttons)
|
// so mouse click is three consecutive events, but the first and last are indistinguishable from movement events (with released buttons)
|
||||||
// dragging has same structure, it only repeats the middle (main) event appropriately
|
// dragging has same structure, it only repeats the middle (main) event appropriately
|
||||||
x, y := ev.Position()
|
x, y := ev.Position()
|
||||||
mod := ev.Modifiers() != 0
|
|
||||||
|
mod := ev.Modifiers()
|
||||||
|
ctrl := (mod & tcell.ModCtrl) > 0
|
||||||
|
alt := (mod & tcell.ModAlt) > 0
|
||||||
|
shift := (mod & tcell.ModShift) > 0
|
||||||
|
|
||||||
// since we dont have mouse down events (unlike LightRenderer), we need to track state in prevButton
|
// since we dont have mouse down events (unlike LightRenderer), we need to track state in prevButton
|
||||||
prevButton, button := _prevMouseButton, ev.Buttons()
|
prevButton, button := _prevMouseButton, ev.Buttons()
|
||||||
@@ -271,9 +293,9 @@ func (r *FullscreenRenderer) GetChar() Event {
|
|||||||
|
|
||||||
switch {
|
switch {
|
||||||
case button&tcell.WheelDown != 0:
|
case button&tcell.WheelDown != 0:
|
||||||
return Event{Mouse, 0, &MouseEvent{y, x, -1, false, false, false, mod}}
|
return Event{Mouse, 0, &MouseEvent{y, x, -1, false, false, false, ctrl, alt, shift}}
|
||||||
case button&tcell.WheelUp != 0:
|
case button&tcell.WheelUp != 0:
|
||||||
return Event{Mouse, 0, &MouseEvent{y, x, +1, false, false, false, mod}}
|
return Event{Mouse, 0, &MouseEvent{y, x, +1, false, false, false, ctrl, alt, shift}}
|
||||||
case button&tcell.Button1 != 0:
|
case button&tcell.Button1 != 0:
|
||||||
double := false
|
double := false
|
||||||
if !drag {
|
if !drag {
|
||||||
@@ -296,9 +318,9 @@ func (r *FullscreenRenderer) GetChar() Event {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// fire single or double click event
|
// fire single or double click event
|
||||||
return Event{Mouse, 0, &MouseEvent{y, x, 0, true, !double, double, mod}}
|
return Event{Mouse, 0, &MouseEvent{y, x, 0, true, !double, double, ctrl, alt, shift}}
|
||||||
case button&tcell.Button2 != 0:
|
case button&tcell.Button2 != 0:
|
||||||
return Event{Mouse, 0, &MouseEvent{y, x, 0, false, true, false, mod}}
|
return Event{Mouse, 0, &MouseEvent{y, x, 0, false, true, false, ctrl, alt, shift}}
|
||||||
default:
|
default:
|
||||||
// double and single taps on Windows don't quite work due to
|
// double and single taps on Windows don't quite work due to
|
||||||
// the console acting on the events and not allowing us
|
// the console acting on the events and not allowing us
|
||||||
@@ -307,7 +329,11 @@ func (r *FullscreenRenderer) GetChar() Event {
|
|||||||
down := left || button&tcell.Button3 != 0
|
down := left || button&tcell.Button3 != 0
|
||||||
double := false
|
double := false
|
||||||
|
|
||||||
return Event{Mouse, 0, &MouseEvent{y, x, 0, left, down, double, mod}}
|
// No need to report mouse movement events when no button is pressed
|
||||||
|
if drag {
|
||||||
|
return Event{Invalid, 0, nil}
|
||||||
|
}
|
||||||
|
return Event{Mouse, 0, &MouseEvent{y, x, 0, left, down, double, ctrl, alt, shift}}
|
||||||
}
|
}
|
||||||
|
|
||||||
// process keyboard:
|
// process keyboard:
|
||||||
@@ -534,7 +560,7 @@ func (r *FullscreenRenderer) GetChar() Event {
|
|||||||
|
|
||||||
func (r *FullscreenRenderer) Pause(clear bool) {
|
func (r *FullscreenRenderer) Pause(clear bool) {
|
||||||
if clear {
|
if clear {
|
||||||
_screen.Fini()
|
r.Close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -546,6 +572,7 @@ func (r *FullscreenRenderer) Resume(clear bool, sigcont bool) {
|
|||||||
|
|
||||||
func (r *FullscreenRenderer) Close() {
|
func (r *FullscreenRenderer) Close() {
|
||||||
_screen.Fini()
|
_screen.Fini()
|
||||||
|
_screen = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *FullscreenRenderer) RefreshWindows(windows []Window) {
|
func (r *FullscreenRenderer) RefreshWindows(windows []Window) {
|
||||||
@@ -578,7 +605,8 @@ func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int,
|
|||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
normal: normal,
|
normal: normal,
|
||||||
borderStyle: borderStyle}
|
borderStyle: borderStyle,
|
||||||
|
showCursor: r.showCursor}
|
||||||
w.Erase()
|
w.Erase()
|
||||||
return w
|
return w
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,9 +82,9 @@ func TestGetCharEventKey(t *testing.T) {
|
|||||||
{giveKey{tcell.KeyTab, rune(tcell.KeyTab), tcell.ModNone}, wantKey{Tab, 0, nil}}, // unhandled, actual "Tab" keystroke
|
{giveKey{tcell.KeyTab, rune(tcell.KeyTab), tcell.ModNone}, wantKey{Tab, 0, nil}}, // unhandled, actual "Tab" keystroke
|
||||||
{giveKey{tcell.KeyTAB, rune(tcell.KeyTAB), tcell.ModNone}, wantKey{Tab, 0, nil}}, // fabricated, unhandled
|
{giveKey{tcell.KeyTAB, rune(tcell.KeyTAB), tcell.ModNone}, wantKey{Tab, 0, nil}}, // fabricated, unhandled
|
||||||
// KeyEnter is alias for KeyCR
|
// KeyEnter is alias for KeyCR
|
||||||
{giveKey{tcell.KeyCtrlM, rune(tcell.KeyCtrlM), tcell.ModNone}, wantKey{CtrlM, 0, nil}}, // actual "Enter" keystroke
|
{giveKey{tcell.KeyCtrlM, rune(tcell.KeyCtrlM), tcell.ModNone}, wantKey{Enter, 0, nil}}, // actual "Enter" keystroke
|
||||||
{giveKey{tcell.KeyCR, rune(tcell.KeyCR), tcell.ModNone}, wantKey{CtrlM, 0, nil}}, // fabricated, unhandled
|
{giveKey{tcell.KeyCR, rune(tcell.KeyCR), tcell.ModNone}, wantKey{Enter, 0, nil}}, // fabricated, unhandled
|
||||||
{giveKey{tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone}, wantKey{CtrlM, 0, nil}}, // fabricated, unhandled
|
{giveKey{tcell.KeyEnter, rune(tcell.KeyEnter), tcell.ModNone}, wantKey{Enter, 0, nil}}, // fabricated, unhandled
|
||||||
// Ctrl+Alt keys
|
// Ctrl+Alt keys
|
||||||
{giveKey{tcell.KeyCtrlA, rune(tcell.KeyCtrlA), tcell.ModCtrl | tcell.ModAlt}, wantKey{CtrlAlt, 'a', nil}}, // fabricated
|
{giveKey{tcell.KeyCtrlA, rune(tcell.KeyCtrlA), tcell.ModCtrl | tcell.ModAlt}, wantKey{CtrlAlt, 'a', nil}}, // fabricated
|
||||||
{giveKey{tcell.KeyCtrlA, rune(tcell.KeyCtrlA), tcell.ModCtrl | tcell.ModAlt | tcell.ModShift}, wantKey{CtrlAlt, 'a', nil}}, // fabricated
|
{giveKey{tcell.KeyCtrlA, rune(tcell.KeyCtrlA), tcell.ModCtrl | tcell.ModAlt | tcell.ModShift}, wantKey{CtrlAlt, 'a', nil}}, // fabricated
|
||||||
@@ -233,7 +233,7 @@ Quick reference
|
|||||||
10 1 KeyCtrlJ KeyLF = ^J CtrlJ
|
10 1 KeyCtrlJ KeyLF = ^J CtrlJ
|
||||||
11 1 KeyCtrlK KeyVT = ^K CtrlK
|
11 1 KeyCtrlK KeyVT = ^K CtrlK
|
||||||
12 1 KeyCtrlL KeyFF = ^L CtrlL
|
12 1 KeyCtrlL KeyFF = ^L CtrlL
|
||||||
13 1 KeyCtrlM KeyCR = ^M KeyEnter CtrlM
|
13 1 KeyCtrlM KeyCR = ^M KeyEnter Enter
|
||||||
14 1 KeyCtrlN KeySO = ^N CtrlN
|
14 1 KeyCtrlN KeySO = ^N CtrlN
|
||||||
15 1 KeyCtrlO KeySI = ^O CtrlO
|
15 1 KeyCtrlO KeySI = ^O CtrlO
|
||||||
16 1 KeyCtrlP KeyDLE = ^P CtrlP
|
16 1 KeyCtrlP KeyDLE = ^P CtrlP
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ const (
|
|||||||
CtrlJ
|
CtrlJ
|
||||||
CtrlK
|
CtrlK
|
||||||
CtrlL
|
CtrlL
|
||||||
CtrlM
|
Enter
|
||||||
CtrlN
|
CtrlN
|
||||||
CtrlO
|
CtrlO
|
||||||
CtrlP
|
CtrlP
|
||||||
@@ -150,12 +150,19 @@ func (e Event) Comparable() Event {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (e Event) KeyName() string {
|
func (e Event) KeyName() string {
|
||||||
|
if me := e.MouseEvent; me != nil {
|
||||||
|
return me.Name()
|
||||||
|
}
|
||||||
|
|
||||||
if e.Type >= Invalid {
|
if e.Type >= Invalid {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
switch e.Type {
|
switch e.Type {
|
||||||
case Rune:
|
case Rune:
|
||||||
|
if e.Char == ' ' {
|
||||||
|
return "space"
|
||||||
|
}
|
||||||
return string(e.Char)
|
return string(e.Char)
|
||||||
case Alt:
|
case Alt:
|
||||||
return "alt-" + string(e.Char)
|
return "alt-" + string(e.Char)
|
||||||
@@ -367,7 +374,37 @@ type MouseEvent struct {
|
|||||||
Left bool
|
Left bool
|
||||||
Down bool
|
Down bool
|
||||||
Double bool
|
Double bool
|
||||||
Mod bool
|
Ctrl bool
|
||||||
|
Alt bool
|
||||||
|
Shift bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e MouseEvent) Mod() bool {
|
||||||
|
return e.Ctrl || e.Alt || e.Shift
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e MouseEvent) Name() string {
|
||||||
|
name := ""
|
||||||
|
if e.Down {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.Ctrl {
|
||||||
|
name += "ctrl-"
|
||||||
|
}
|
||||||
|
if e.Alt {
|
||||||
|
name += "alt-"
|
||||||
|
}
|
||||||
|
if e.Shift {
|
||||||
|
name += "shift-"
|
||||||
|
}
|
||||||
|
if e.Double {
|
||||||
|
name += "double-"
|
||||||
|
}
|
||||||
|
if !e.Left {
|
||||||
|
name += "right-"
|
||||||
|
}
|
||||||
|
return name + "click"
|
||||||
}
|
}
|
||||||
|
|
||||||
type BorderShape int
|
type BorderShape int
|
||||||
@@ -376,6 +413,7 @@ const (
|
|||||||
BorderUndefined BorderShape = iota
|
BorderUndefined BorderShape = iota
|
||||||
BorderLine
|
BorderLine
|
||||||
BorderNone
|
BorderNone
|
||||||
|
BorderPhantom
|
||||||
BorderRounded
|
BorderRounded
|
||||||
BorderSharp
|
BorderSharp
|
||||||
BorderBold
|
BorderBold
|
||||||
@@ -392,7 +430,7 @@ const (
|
|||||||
|
|
||||||
func (s BorderShape) HasLeft() bool {
|
func (s BorderShape) HasLeft() bool {
|
||||||
switch s {
|
switch s {
|
||||||
case BorderNone, BorderLine, BorderRight, BorderTop, BorderBottom, BorderHorizontal: // No Left
|
case BorderNone, BorderPhantom, BorderLine, BorderRight, BorderTop, BorderBottom, BorderHorizontal: // No Left
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@@ -400,7 +438,7 @@ func (s BorderShape) HasLeft() bool {
|
|||||||
|
|
||||||
func (s BorderShape) HasRight() bool {
|
func (s BorderShape) HasRight() bool {
|
||||||
switch s {
|
switch s {
|
||||||
case BorderNone, BorderLine, BorderLeft, BorderTop, BorderBottom, BorderHorizontal: // No right
|
case BorderNone, BorderPhantom, BorderLine, BorderLeft, BorderTop, BorderBottom, BorderHorizontal: // No right
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@@ -408,7 +446,7 @@ func (s BorderShape) HasRight() bool {
|
|||||||
|
|
||||||
func (s BorderShape) HasTop() bool {
|
func (s BorderShape) HasTop() bool {
|
||||||
switch s {
|
switch s {
|
||||||
case BorderNone, BorderLine, BorderLeft, BorderRight, BorderBottom, BorderVertical: // No top
|
case BorderNone, BorderPhantom, BorderLine, BorderLeft, BorderRight, BorderBottom, BorderVertical: // No top
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@@ -416,7 +454,7 @@ func (s BorderShape) HasTop() bool {
|
|||||||
|
|
||||||
func (s BorderShape) HasBottom() bool {
|
func (s BorderShape) HasBottom() bool {
|
||||||
switch s {
|
switch s {
|
||||||
case BorderNone, BorderLine, BorderLeft, BorderRight, BorderTop, BorderVertical: // No bottom
|
case BorderNone, BorderPhantom, BorderLine, BorderLeft, BorderRight, BorderTop, BorderVertical: // No bottom
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@@ -441,7 +479,7 @@ type BorderStyle struct {
|
|||||||
type BorderCharacter int
|
type BorderCharacter int
|
||||||
|
|
||||||
func MakeBorderStyle(shape BorderShape, unicode bool) BorderStyle {
|
func MakeBorderStyle(shape BorderShape, unicode bool) BorderStyle {
|
||||||
if shape == BorderNone {
|
if shape == BorderNone || shape == BorderPhantom {
|
||||||
return BorderStyle{
|
return BorderStyle{
|
||||||
shape: shape,
|
shape: shape,
|
||||||
top: ' ',
|
top: ' ',
|
||||||
@@ -579,6 +617,9 @@ type Renderer interface {
|
|||||||
PassThrough(string)
|
PassThrough(string)
|
||||||
NeedScrollbarRedraw() bool
|
NeedScrollbarRedraw() bool
|
||||||
ShouldEmitResizeEvent() bool
|
ShouldEmitResizeEvent() bool
|
||||||
|
Bell()
|
||||||
|
HideCursor()
|
||||||
|
ShowCursor()
|
||||||
|
|
||||||
GetChar() Event
|
GetChar() Event
|
||||||
|
|
||||||
@@ -626,6 +667,7 @@ type FullscreenRenderer struct {
|
|||||||
forceBlack bool
|
forceBlack bool
|
||||||
prevDownTime time.Time
|
prevDownTime time.Time
|
||||||
clicks [][2]int
|
clicks [][2]int
|
||||||
|
showCursor bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFullscreenRenderer(theme *ColorTheme, forceBlack bool, mouse bool) Renderer {
|
func NewFullscreenRenderer(theme *ColorTheme, forceBlack bool, mouse bool) Renderer {
|
||||||
@@ -634,7 +676,8 @@ func NewFullscreenRenderer(theme *ColorTheme, forceBlack bool, mouse bool) Rende
|
|||||||
mouse: mouse,
|
mouse: mouse,
|
||||||
forceBlack: forceBlack,
|
forceBlack: forceBlack,
|
||||||
prevDownTime: time.Unix(0, 0),
|
prevDownTime: time.Unix(0, 0),
|
||||||
clicks: [][2]int{}}
|
clicks: [][2]int{},
|
||||||
|
showCursor: true}
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
240
test/lib/common.rb
Normal file
240
test/lib/common.rb
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'bundler/setup'
|
||||||
|
require 'minitest/autorun'
|
||||||
|
require 'fileutils'
|
||||||
|
require 'English'
|
||||||
|
require 'shellwords'
|
||||||
|
require 'erb'
|
||||||
|
require 'tempfile'
|
||||||
|
require 'net/http'
|
||||||
|
require 'json'
|
||||||
|
|
||||||
|
TEMPLATE = File.read(File.expand_path('common.sh', __dir__))
|
||||||
|
UNSETS = %w[
|
||||||
|
FZF_DEFAULT_COMMAND FZF_DEFAULT_OPTS
|
||||||
|
FZF_TMUX FZF_TMUX_OPTS
|
||||||
|
FZF_CTRL_T_COMMAND FZF_CTRL_T_OPTS
|
||||||
|
FZF_ALT_C_COMMAND
|
||||||
|
FZF_ALT_C_OPTS FZF_CTRL_R_OPTS
|
||||||
|
FZF_API_KEY
|
||||||
|
].freeze
|
||||||
|
DEFAULT_TIMEOUT = 10
|
||||||
|
|
||||||
|
FILE = File.expand_path(__FILE__)
|
||||||
|
BASE = File.expand_path('../..', __dir__)
|
||||||
|
Dir.chdir(BASE)
|
||||||
|
FZF = "FZF_DEFAULT_OPTS=\"--no-scrollbar --pointer \\> --marker \\>\" FZF_DEFAULT_COMMAND= #{BASE}/bin/fzf".freeze
|
||||||
|
|
||||||
|
def wait(timeout = DEFAULT_TIMEOUT)
|
||||||
|
since = Time.now
|
||||||
|
begin
|
||||||
|
yield or raise Minitest::Assertion, 'Assertion failure'
|
||||||
|
rescue Minitest::Assertion
|
||||||
|
raise if Time.now - since > timeout
|
||||||
|
|
||||||
|
sleep(0.05)
|
||||||
|
retry
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Shell
|
||||||
|
class << self
|
||||||
|
def bash
|
||||||
|
@bash ||=
|
||||||
|
begin
|
||||||
|
bashrc = '/tmp/fzf.bash'
|
||||||
|
File.open(bashrc, 'w') do |f|
|
||||||
|
f.puts ERB.new(TEMPLATE).result(binding)
|
||||||
|
end
|
||||||
|
|
||||||
|
"bash --rcfile #{bashrc}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def zsh
|
||||||
|
@zsh ||=
|
||||||
|
begin
|
||||||
|
zdotdir = '/tmp/fzf-zsh'
|
||||||
|
FileUtils.rm_rf(zdotdir)
|
||||||
|
FileUtils.mkdir_p(zdotdir)
|
||||||
|
File.open("#{zdotdir}/.zshrc", 'w') do |f|
|
||||||
|
f.puts ERB.new(TEMPLATE).result(binding)
|
||||||
|
end
|
||||||
|
"ZDOTDIR=#{zdotdir} zsh"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def fish
|
||||||
|
"unset #{UNSETS.join(' ')}; rm -f ~/.local/share/fish/fzf_test_history; FZF_DEFAULT_OPTS=\"--no-scrollbar --pointer '>' --marker '>'\" fish_history=fzf_test fish"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Tmux
|
||||||
|
attr_reader :win
|
||||||
|
|
||||||
|
def initialize(shell = :bash)
|
||||||
|
@win = go(%W[new-window -d -P -F #I #{Shell.send(shell)}]).first
|
||||||
|
go(%W[set-window-option -t #{@win} pane-base-index 0])
|
||||||
|
return unless shell == :fish
|
||||||
|
|
||||||
|
send_keys 'function fish_prompt; end; clear', :Enter
|
||||||
|
self.until(&:empty?)
|
||||||
|
end
|
||||||
|
|
||||||
|
def kill
|
||||||
|
go(%W[kill-window -t #{win}])
|
||||||
|
end
|
||||||
|
|
||||||
|
def focus
|
||||||
|
go(%W[select-window -t #{win}])
|
||||||
|
end
|
||||||
|
|
||||||
|
def send_keys(*args)
|
||||||
|
go(%W[send-keys -t #{win}] + args.map(&:to_s))
|
||||||
|
end
|
||||||
|
|
||||||
|
def paste(str)
|
||||||
|
system('tmux', 'setb', str, ';', 'pasteb', '-t', win, ';', 'send-keys', '-t', win, 'Enter')
|
||||||
|
end
|
||||||
|
|
||||||
|
def capture
|
||||||
|
go(%W[capture-pane -p -J -t #{win}]).map(&:rstrip).reverse.drop_while(&:empty?).reverse
|
||||||
|
end
|
||||||
|
|
||||||
|
def until(refresh = false, timeout: DEFAULT_TIMEOUT)
|
||||||
|
lines = nil
|
||||||
|
begin
|
||||||
|
wait(timeout) do
|
||||||
|
lines = capture
|
||||||
|
class << lines
|
||||||
|
def counts
|
||||||
|
lazy
|
||||||
|
.map { |l| l.scan(%r{^. ([0-9]+)/([0-9]+)( \(([0-9]+)\))?}) }
|
||||||
|
.reject(&:empty?)
|
||||||
|
.first&.first&.map(&:to_i)&.values_at(0, 1, 3) || [0, 0, 0]
|
||||||
|
end
|
||||||
|
|
||||||
|
def match_count
|
||||||
|
counts[0]
|
||||||
|
end
|
||||||
|
|
||||||
|
def item_count
|
||||||
|
counts[1]
|
||||||
|
end
|
||||||
|
|
||||||
|
def select_count
|
||||||
|
counts[2]
|
||||||
|
end
|
||||||
|
|
||||||
|
def any_include?(val)
|
||||||
|
method = val.is_a?(Regexp) ? :match : :include?
|
||||||
|
find { |line| line.send(method, val) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
yield(lines).tap do |ok|
|
||||||
|
send_keys 'C-l' if refresh && !ok
|
||||||
|
end
|
||||||
|
end
|
||||||
|
rescue Minitest::Assertion
|
||||||
|
puts $ERROR_INFO.backtrace
|
||||||
|
puts '>' * 80
|
||||||
|
puts lines
|
||||||
|
puts '<' * 80
|
||||||
|
raise
|
||||||
|
end
|
||||||
|
lines
|
||||||
|
end
|
||||||
|
|
||||||
|
def prepare
|
||||||
|
tries = 0
|
||||||
|
begin
|
||||||
|
self.until(true) do |lines|
|
||||||
|
message = "Prepare[#{tries}]"
|
||||||
|
send_keys ' ', 'C-u', :Enter, message, :Left, :Right
|
||||||
|
lines[-1] == message
|
||||||
|
end
|
||||||
|
rescue Minitest::Assertion
|
||||||
|
(tries += 1) < 5 ? retry : raise
|
||||||
|
end
|
||||||
|
send_keys 'C-u', 'C-l'
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def go(args)
|
||||||
|
IO.popen(%w[tmux] + args) { |io| io.readlines(chomp: true) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class TestBase < Minitest::Test
|
||||||
|
TEMPNAME = Dir::Tmpname.create(%w[fzf]) {}
|
||||||
|
FIFONAME = Dir::Tmpname.create(%w[fzf-fifo]) {}
|
||||||
|
|
||||||
|
def writelines(lines)
|
||||||
|
File.write(TEMPNAME, lines.join("\n"))
|
||||||
|
end
|
||||||
|
|
||||||
|
def tempname
|
||||||
|
TEMPNAME
|
||||||
|
end
|
||||||
|
|
||||||
|
def fzf_output
|
||||||
|
@thread.join.value.chomp.tap { @thread = nil }
|
||||||
|
end
|
||||||
|
|
||||||
|
def fzf_output_lines
|
||||||
|
fzf_output.lines(chomp: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
def setup
|
||||||
|
File.mkfifo(FIFONAME)
|
||||||
|
end
|
||||||
|
|
||||||
|
def teardown
|
||||||
|
FileUtils.rm_f([TEMPNAME, FIFONAME])
|
||||||
|
end
|
||||||
|
|
||||||
|
alias assert_equal_org assert_equal
|
||||||
|
def assert_equal(expected, actual)
|
||||||
|
# Ignore info separator
|
||||||
|
actual = actual&.sub(/\s*─+$/, '') if actual.is_a?(String) && actual&.match?(%r{\d+/\d+})
|
||||||
|
assert_equal_org(expected, actual)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Run fzf with its output piped to a fifo
|
||||||
|
def fzf(*opts)
|
||||||
|
raise 'fzf_output not taken' if @thread
|
||||||
|
|
||||||
|
@thread = Thread.new { File.read(FIFONAME) }
|
||||||
|
fzf!(*opts) + " > #{FIFONAME.shellescape}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def fzf!(*opts)
|
||||||
|
opts = opts.filter_map do |o|
|
||||||
|
case o
|
||||||
|
when Symbol
|
||||||
|
o = o.to_s
|
||||||
|
o.length > 1 ? "--#{o.tr('_', '-')}" : "-#{o}"
|
||||||
|
when String, Numeric
|
||||||
|
o.to_s
|
||||||
|
end
|
||||||
|
end
|
||||||
|
"#{FZF} #{opts.join(' ')}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class TestInteractive < TestBase
|
||||||
|
attr_reader :tmux
|
||||||
|
|
||||||
|
def setup
|
||||||
|
super
|
||||||
|
@tmux = Tmux.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def teardown
|
||||||
|
super
|
||||||
|
@tmux.kill
|
||||||
|
end
|
||||||
|
end
|
||||||
59
test/lib/common.sh
Normal file
59
test/lib/common.sh
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
set -u
|
||||||
|
PS1= PROMPT_COMMAND= HISTFILE= HISTSIZE=100
|
||||||
|
unset <%= UNSETS.join(' ') %>
|
||||||
|
unset $(env | sed -n /^_fzf_orig/s/=.*//p)
|
||||||
|
unset $(declare -F | sed -n "/_fzf/s/.*-f //p")
|
||||||
|
|
||||||
|
export FZF_DEFAULT_OPTS="--no-scrollbar --pointer '>' --marker '>'"
|
||||||
|
|
||||||
|
# Setup fzf
|
||||||
|
# ---------
|
||||||
|
if [[ ! "$PATH" == *<%= BASE %>/bin* ]]; then
|
||||||
|
export PATH="${PATH:+${PATH}:}<%= BASE %>/bin"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Auto-completion
|
||||||
|
# ---------------
|
||||||
|
[[ $- == *i* ]] && source "<%= BASE %>/shell/completion.<%= __method__ %>" 2> /dev/null
|
||||||
|
|
||||||
|
# Key bindings
|
||||||
|
# ------------
|
||||||
|
source "<%= BASE %>/shell/key-bindings.<%= __method__ %>"
|
||||||
|
|
||||||
|
# Old API
|
||||||
|
_fzf_complete_f() {
|
||||||
|
_fzf_complete "+m --multi --prompt \"prompt-f> \"" "$@" < <(
|
||||||
|
echo foo
|
||||||
|
echo bar
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
# New API
|
||||||
|
_fzf_complete_g() {
|
||||||
|
_fzf_complete +m --multi --prompt "prompt-g> " -- "$@" < <(
|
||||||
|
echo foo
|
||||||
|
echo bar
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
_fzf_complete_f_post() {
|
||||||
|
awk '{print "f" $0 $0}'
|
||||||
|
}
|
||||||
|
|
||||||
|
_fzf_complete_g_post() {
|
||||||
|
awk '{print "g" $0 $0}'
|
||||||
|
}
|
||||||
|
|
||||||
|
[ -n "${BASH-}" ] && complete -F _fzf_complete_f -o default -o bashdefault f
|
||||||
|
[ -n "${BASH-}" ] && complete -F _fzf_complete_g -o default -o bashdefault g
|
||||||
|
|
||||||
|
_comprun() {
|
||||||
|
local command=$1
|
||||||
|
shift
|
||||||
|
|
||||||
|
case "$command" in
|
||||||
|
f) fzf "$@" --preview 'echo preview-f-{}' ;;
|
||||||
|
g) fzf "$@" --preview 'echo preview-g-{}' ;;
|
||||||
|
*) fzf "$@" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
5
test/runner.rb
Normal file
5
test/runner.rb
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
Dir[File.join(__dir__, 'test_*.rb')].each { require it }
|
||||||
|
|
||||||
|
require 'minitest/autorun'
|
||||||
1668
test/test_core.rb
Normal file
1668
test/test_core.rb
Normal file
File diff suppressed because it is too large
Load Diff
417
test/test_exec.rb
Normal file
417
test/test_exec.rb
Normal file
@@ -0,0 +1,417 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require_relative 'lib/common'
|
||||||
|
|
||||||
|
# Process execution: execute, become, reload
|
||||||
|
class TestExec < TestInteractive
|
||||||
|
def test_execute
|
||||||
|
output = '/tmp/fzf-test-execute'
|
||||||
|
opts = %[--bind "alt-a:execute(echo /{}/ >> #{output})+change-header(alt-a),alt-b:execute[echo /{}{}/ >> #{output}]+change-header(alt-b),C:execute(echo /{}{}{}/ >> #{output})+change-header(C)"]
|
||||||
|
writelines(%w[foo'bar foo"bar foo$bar])
|
||||||
|
tmux.send_keys "cat #{tempname} | #{FZF} #{opts}", :Enter
|
||||||
|
tmux.until { |lines| assert_equal 3, lines.match_count }
|
||||||
|
|
||||||
|
ready = ->(s) { tmux.until { |lines| assert_includes lines[-3], s } }
|
||||||
|
tmux.send_keys :Escape, :a
|
||||||
|
ready.call('alt-a')
|
||||||
|
tmux.send_keys :Escape, :b
|
||||||
|
ready.call('alt-b')
|
||||||
|
|
||||||
|
tmux.send_keys :Up
|
||||||
|
tmux.send_keys :Escape, :a
|
||||||
|
ready.call('alt-a')
|
||||||
|
tmux.send_keys :Escape, :b
|
||||||
|
ready.call('alt-b')
|
||||||
|
|
||||||
|
tmux.send_keys :Up
|
||||||
|
tmux.send_keys :C
|
||||||
|
ready.call('C')
|
||||||
|
|
||||||
|
tmux.send_keys 'barfoo'
|
||||||
|
tmux.until { |lines| assert_equal ' 0/3', lines[-2] }
|
||||||
|
|
||||||
|
tmux.send_keys :Escape, :a
|
||||||
|
ready.call('alt-a')
|
||||||
|
tmux.send_keys :Escape, :b
|
||||||
|
ready.call('alt-b')
|
||||||
|
|
||||||
|
wait do
|
||||||
|
assert_path_exists output
|
||||||
|
assert_equal %w[
|
||||||
|
/foo'bar/ /foo'barfoo'bar/
|
||||||
|
/foo"bar/ /foo"barfoo"bar/
|
||||||
|
/foo$barfoo$barfoo$bar/
|
||||||
|
], File.readlines(output, chomp: true)
|
||||||
|
end
|
||||||
|
ensure
|
||||||
|
FileUtils.rm_f(output)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_execute_multi
|
||||||
|
output = '/tmp/fzf-test-execute-multi'
|
||||||
|
opts = %[--multi --bind "alt-a:execute-multi(echo {}/{+} >> #{output})+change-header(alt-a),alt-b:change-header(alt-b)"]
|
||||||
|
writelines(%w[foo'bar foo"bar foo$bar foobar])
|
||||||
|
tmux.send_keys "cat #{tempname} | #{FZF} #{opts}", :Enter
|
||||||
|
ready = ->(s) { tmux.until { |lines| assert_includes lines[-3], s } }
|
||||||
|
|
||||||
|
tmux.until { |lines| assert_equal ' 4/4 (0)', lines[-2] }
|
||||||
|
tmux.send_keys :Escape, :a
|
||||||
|
ready.call('alt-a')
|
||||||
|
tmux.send_keys :Escape, :b
|
||||||
|
ready.call('alt-b')
|
||||||
|
|
||||||
|
tmux.send_keys :BTab, :BTab, :BTab
|
||||||
|
tmux.until { |lines| assert_equal ' 4/4 (3)', lines[-2] }
|
||||||
|
tmux.send_keys :Escape, :a
|
||||||
|
ready.call('alt-a')
|
||||||
|
tmux.send_keys :Escape, :b
|
||||||
|
ready.call('alt-b')
|
||||||
|
|
||||||
|
tmux.send_keys :Tab, :Tab
|
||||||
|
tmux.until { |lines| assert_equal ' 4/4 (3)', lines[-2] }
|
||||||
|
tmux.send_keys :Escape, :a
|
||||||
|
ready.call('alt-a')
|
||||||
|
wait do
|
||||||
|
assert_path_exists output
|
||||||
|
assert_equal [
|
||||||
|
%(foo'bar/foo'bar),
|
||||||
|
%(foo'bar foo"bar foo$bar/foo'bar foo"bar foo$bar),
|
||||||
|
%(foo'bar foo"bar foobar/foo'bar foo"bar foobar)
|
||||||
|
], File.readlines(output, chomp: true)
|
||||||
|
end
|
||||||
|
ensure
|
||||||
|
FileUtils.rm_f(output)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_execute_plus_flag
|
||||||
|
output = tempname + '.tmp'
|
||||||
|
FileUtils.rm_f(output)
|
||||||
|
writelines(['foo bar', '123 456'])
|
||||||
|
|
||||||
|
tmux.send_keys "cat #{tempname} | #{FZF} --multi --bind 'x:execute-silent(echo {+}/{}/{+2}/{2} >> #{output})'", :Enter
|
||||||
|
|
||||||
|
tmux.until { |lines| assert_equal ' 2/2 (0)', lines[-2] }
|
||||||
|
tmux.send_keys 'xy'
|
||||||
|
tmux.until { |lines| assert_equal ' 0/2 (0)', lines[-2] }
|
||||||
|
tmux.send_keys :BSpace
|
||||||
|
tmux.until { |lines| assert_equal ' 2/2 (0)', lines[-2] }
|
||||||
|
|
||||||
|
tmux.send_keys :Up
|
||||||
|
tmux.send_keys :Tab
|
||||||
|
tmux.send_keys 'xy'
|
||||||
|
tmux.until { |lines| assert_equal ' 0/2 (1)', lines[-2] }
|
||||||
|
tmux.send_keys :BSpace
|
||||||
|
tmux.until { |lines| assert_equal ' 2/2 (1)', lines[-2] }
|
||||||
|
|
||||||
|
tmux.send_keys :Tab
|
||||||
|
tmux.send_keys 'xy'
|
||||||
|
tmux.until { |lines| assert_equal ' 0/2 (2)', lines[-2] }
|
||||||
|
tmux.send_keys :BSpace
|
||||||
|
tmux.until { |lines| assert_equal ' 2/2 (2)', lines[-2] }
|
||||||
|
|
||||||
|
wait do
|
||||||
|
assert_path_exists output
|
||||||
|
assert_equal [
|
||||||
|
%(foo bar/foo bar/bar/bar),
|
||||||
|
%(123 456/foo bar/456/bar),
|
||||||
|
%(123 456 foo bar/foo bar/456 bar/bar)
|
||||||
|
], File.readlines(output, chomp: true)
|
||||||
|
end
|
||||||
|
rescue StandardError
|
||||||
|
FileUtils.rm_f(output)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_execute_shell
|
||||||
|
# Custom script to use as $SHELL
|
||||||
|
output = tempname + '.out'
|
||||||
|
FileUtils.rm_f(output)
|
||||||
|
writelines(['#!/usr/bin/env bash', "echo $1 / $2 > #{output}"])
|
||||||
|
system("chmod +x #{tempname}")
|
||||||
|
|
||||||
|
tmux.send_keys "echo foo | SHELL=#{tempname} fzf --bind 'enter:execute:{}bar'", :Enter
|
||||||
|
tmux.until { |lines| assert_equal ' 1/1', lines[-2] }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { |lines| assert_equal ' 1/1', lines[-2] }
|
||||||
|
wait do
|
||||||
|
assert_path_exists output
|
||||||
|
assert_equal ["-c / 'foo'bar"], File.readlines(output, chomp: true)
|
||||||
|
end
|
||||||
|
ensure
|
||||||
|
FileUtils.rm_f(output)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_interrupt_execute
|
||||||
|
tmux.send_keys "seq 100 | #{FZF} --bind 'ctrl-l:execute:echo executing {}; sleep 100'", :Enter
|
||||||
|
tmux.until { |lines| assert_equal 100, lines.match_count }
|
||||||
|
tmux.send_keys 'C-l'
|
||||||
|
tmux.until { |lines| assert lines.any_include?('executing 1') }
|
||||||
|
tmux.send_keys 'C-c'
|
||||||
|
tmux.until { |lines| assert_equal 100, lines.match_count }
|
||||||
|
tmux.send_keys 99
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_kill_default_command_on_abort
|
||||||
|
writelines(['#!/usr/bin/env bash',
|
||||||
|
"echo 'Started'",
|
||||||
|
'while :; do sleep 1; done'])
|
||||||
|
system("chmod +x #{tempname}")
|
||||||
|
|
||||||
|
tmux.send_keys FZF.sub('FZF_DEFAULT_COMMAND=', "FZF_DEFAULT_COMMAND=#{tempname}"), :Enter
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||||
|
tmux.send_keys 'C-c'
|
||||||
|
tmux.send_keys 'C-l', 'closed'
|
||||||
|
tmux.until { |lines| assert_includes lines[0], 'closed' }
|
||||||
|
wait { refute system("pgrep -f #{tempname}") }
|
||||||
|
ensure
|
||||||
|
system("pkill -9 -f #{tempname}")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_kill_default_command_on_accept
|
||||||
|
writelines(['#!/usr/bin/env bash',
|
||||||
|
"echo 'Started'",
|
||||||
|
'while :; do sleep 1; done'])
|
||||||
|
system("chmod +x #{tempname}")
|
||||||
|
|
||||||
|
tmux.send_keys fzf.sub('FZF_DEFAULT_COMMAND=', "FZF_DEFAULT_COMMAND=#{tempname}"), :Enter
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
assert_equal 'Started', fzf_output
|
||||||
|
wait { refute system("pgrep -f #{tempname}") }
|
||||||
|
ensure
|
||||||
|
system("pkill -9 -f #{tempname}")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_kill_reload_command_on_abort
|
||||||
|
writelines(['#!/usr/bin/env bash',
|
||||||
|
"echo 'Started'",
|
||||||
|
'while :; do sleep 1; done'])
|
||||||
|
system("chmod +x #{tempname}")
|
||||||
|
|
||||||
|
tmux.send_keys "seq 1 3 | #{FZF} --bind 'ctrl-r:reload(#{tempname})'", :Enter
|
||||||
|
tmux.until { |lines| assert_equal 3, lines.match_count }
|
||||||
|
tmux.send_keys 'C-r'
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||||
|
tmux.send_keys 'C-c'
|
||||||
|
tmux.send_keys 'C-l', 'closed'
|
||||||
|
tmux.until { |lines| assert_includes lines[0], 'closed' }
|
||||||
|
wait { refute system("pgrep -f #{tempname}") }
|
||||||
|
ensure
|
||||||
|
system("pkill -9 -f #{tempname}")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_kill_reload_command_on_accept
|
||||||
|
writelines(['#!/usr/bin/env bash',
|
||||||
|
"echo 'Started'",
|
||||||
|
'while :; do sleep 1; done'])
|
||||||
|
system("chmod +x #{tempname}")
|
||||||
|
|
||||||
|
tmux.send_keys "seq 1 3 | #{fzf("--bind 'ctrl-r:reload(#{tempname})'")}", :Enter
|
||||||
|
tmux.until { |lines| assert_equal 3, lines.match_count }
|
||||||
|
tmux.send_keys 'C-r'
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
assert_equal 'Started', fzf_output
|
||||||
|
wait { refute system("pgrep -f #{tempname}") }
|
||||||
|
ensure
|
||||||
|
system("pkill -9 -f #{tempname}")
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_reload
|
||||||
|
tmux.send_keys %(seq 1000 | #{FZF} --bind 'change:reload(seq $FZF_QUERY),a:reload(seq 100),b:reload:seq 200' --header-lines 2 --multi 2), :Enter
|
||||||
|
tmux.until { |lines| assert_equal 998, lines.match_count }
|
||||||
|
tmux.send_keys 'a'
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 98, lines.item_count
|
||||||
|
assert_equal 98, lines.match_count
|
||||||
|
end
|
||||||
|
tmux.send_keys 'b'
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 198, lines.item_count
|
||||||
|
assert_equal 198, lines.match_count
|
||||||
|
end
|
||||||
|
tmux.send_keys :Tab
|
||||||
|
tmux.until { |lines| assert_equal ' 198/198 (1/2)', lines[-2] }
|
||||||
|
tmux.send_keys '555'
|
||||||
|
tmux.until { |lines| assert_equal ' 1/553 (0/2)', lines[-2] }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_reload_even_when_theres_no_match
|
||||||
|
tmux.send_keys %(: | #{FZF} --bind 'space:reload(seq 10)'), :Enter
|
||||||
|
tmux.until { |lines| assert_equal 0, lines.item_count }
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until { |lines| assert_equal 10, lines.item_count }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_reload_should_terminate_standard_input_stream
|
||||||
|
tmux.send_keys %(ruby -e "STDOUT.sync = true; loop { puts 1; sleep 0.1 }" | fzf --bind 'start:reload(seq 100)'), :Enter
|
||||||
|
tmux.until { |lines| assert_equal 100, lines.match_count }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_clear_list_when_header_lines_changed_due_to_reload
|
||||||
|
tmux.send_keys %(seq 10 | #{FZF} --header 0 --header-lines 3 --bind 'space:reload(seq 1)'), :Enter
|
||||||
|
tmux.until { |lines| assert_includes lines, ' 9' }
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until { |lines| refute_includes lines, ' 9' }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_item_index_reset_on_reload
|
||||||
|
tmux.send_keys "seq 10 | #{FZF} --preview 'echo [[{n}]]' --bind 'up:last,down:first,space:reload:seq 100'", :Enter
|
||||||
|
tmux.until { |lines| assert_includes lines[1], '[[0]]' }
|
||||||
|
tmux.send_keys :Up
|
||||||
|
tmux.until { |lines| assert_includes lines[1], '[[9]]' }
|
||||||
|
tmux.send_keys :Down
|
||||||
|
tmux.until { |lines| assert_includes lines[1], '[[0]]' }
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 100, lines.match_count
|
||||||
|
assert_includes lines[1], '[[0]]'
|
||||||
|
end
|
||||||
|
tmux.send_keys :Up
|
||||||
|
tmux.until { |lines| assert_includes lines[1], '[[99]]' }
|
||||||
|
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.match_count }
|
||||||
|
tmux.until { |lines| refute_includes lines[1], '1' }
|
||||||
|
tmux.send_keys 'C-t'
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||||
|
tmux.until { |lines| assert_includes lines[1], '4' }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_reload_sync
|
||||||
|
tmux.send_keys "seq 100 | #{FZF} --bind 'load:reload-sync(sleep 1; seq 1000)+unbind(load)'", :Enter
|
||||||
|
tmux.until { |lines| assert_equal 100, lines.match_count }
|
||||||
|
tmux.send_keys '00'
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||||
|
# After 1 second
|
||||||
|
tmux.until { |lines| assert_equal 10, lines.match_count }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_reload_disabled_case1
|
||||||
|
tmux.send_keys "seq 100 | #{FZF} --query 99 --bind 'space:disable-search+reload(sleep 2; seq 1000)'", :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 100, lines.item_count
|
||||||
|
assert_equal 1, lines.match_count
|
||||||
|
end
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||||
|
tmux.send_keys :BSpace
|
||||||
|
tmux.until { |lines| assert_equal 0, lines.match_count }
|
||||||
|
tmux.until { |lines| assert_equal 1000, lines.match_count }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_reload_disabled_case2
|
||||||
|
tmux.send_keys "seq 100 | #{FZF} --query 99 --bind 'space:disable-search+reload-sync(sleep 2; seq 1000)'", :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 100, lines.item_count
|
||||||
|
assert_equal 1, lines.match_count
|
||||||
|
end
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||||
|
tmux.send_keys :BSpace
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||||
|
tmux.until { |lines| assert_equal 1000, lines.match_count }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_reload_disabled_case3
|
||||||
|
tmux.send_keys "seq 100 | #{FZF} --query 99 --bind 'space:disable-search+reload(sleep 2; seq 1000)+backward-delete-char'", :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 100, lines.item_count
|
||||||
|
assert_equal 1, lines.match_count
|
||||||
|
end
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||||
|
tmux.send_keys :BSpace
|
||||||
|
tmux.until { |lines| assert_equal 0, lines.match_count }
|
||||||
|
tmux.until { |lines| assert_equal 1000, lines.match_count }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_reload_disabled_case4
|
||||||
|
tmux.send_keys "seq 100 | #{FZF} --query 99 --bind 'space:disable-search+reload-sync(sleep 2; seq 1000)+backward-delete-char'", :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 100, lines.item_count
|
||||||
|
assert_equal 1, lines.match_count
|
||||||
|
end
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||||
|
tmux.send_keys :BSpace
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||||
|
tmux.until { |lines| assert_equal 1000, lines.match_count }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_reload_disabled_case5
|
||||||
|
tmux.send_keys "seq 100 | #{FZF} --query 99 --bind 'space:disable-search+reload(echo xx; sleep 2; seq 1000)'", :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 100, lines.item_count
|
||||||
|
assert_equal 1, lines.match_count
|
||||||
|
end
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 1, lines.item_count
|
||||||
|
assert_equal 1, lines.match_count
|
||||||
|
end
|
||||||
|
tmux.send_keys :BSpace
|
||||||
|
tmux.until { |lines| assert_equal 1001, lines.match_count }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_reload_disabled_case6
|
||||||
|
tmux.send_keys "seq 1000 | #{FZF} --disabled --bind 'change:reload:sleep 0.5; seq {q}'", :Enter
|
||||||
|
tmux.until { |lines| assert_equal 1000, lines.match_count }
|
||||||
|
tmux.send_keys '9'
|
||||||
|
tmux.until { |lines| assert_equal 9, lines.match_count }
|
||||||
|
tmux.send_keys '9'
|
||||||
|
tmux.until { |lines| assert_equal 99, lines.match_count }
|
||||||
|
|
||||||
|
# TODO: How do we verify if an intermediate empty list is not shown?
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_reload_and_change
|
||||||
|
tmux.send_keys "(echo foo; echo bar) | #{FZF} --bind 'load:reload-sync(sleep 60)+change-query(bar)'", :Enter
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_become_tty
|
||||||
|
tmux.send_keys "sleep 0.5 | #{FZF} --bind 'start:reload:ls' --bind 'load:become:tty'", :Enter
|
||||||
|
tmux.until { |lines| assert_includes lines, '/dev/tty' }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_disabled_preview_update
|
||||||
|
tmux.send_keys "echo bar | #{FZF} --disabled --bind 'change:reload:echo foo' --preview 'echo [{q}-{}]'", :Enter
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||||
|
tmux.until { |lines| assert(lines.any? { |line| line.include?('[-bar]') }) }
|
||||||
|
tmux.send_keys :x
|
||||||
|
tmux.until { |lines| assert(lines.any? { |line| line.include?('[x-foo]') }) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_start_on_reload
|
||||||
|
tmux.send_keys %(echo foo | #{FZF} --header Loading --header-lines 1 --bind 'start:reload:sleep 2; echo bar' --bind 'load:change-header:Loaded' --bind space:change-header:), :Enter
|
||||||
|
tmux.until(timeout: 1) { |lines| assert_includes lines[-3], 'Loading' }
|
||||||
|
tmux.until(timeout: 1) { |lines| refute_includes lines[-4], 'foo' }
|
||||||
|
tmux.until { |lines| assert_includes lines[-3], 'Loaded' }
|
||||||
|
tmux.until { |lines| assert_includes lines[-4], 'bar' }
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until { |lines| assert_includes lines[-3], 'bar' }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_become
|
||||||
|
tmux.send_keys "seq 100 | #{FZF} --bind 'enter:become:seq {} | #{FZF}'", :Enter
|
||||||
|
tmux.until { |lines| assert_equal 100, lines.match_count }
|
||||||
|
tmux.send_keys 999
|
||||||
|
tmux.until { |lines| assert_equal 0, lines.match_count }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { |lines| assert_equal 0, lines.match_count }
|
||||||
|
tmux.send_keys :BSpace
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { |lines| assert_equal 99, lines.item_count }
|
||||||
|
end
|
||||||
|
end
|
||||||
302
test/test_filter.rb
Normal file
302
test/test_filter.rb
Normal file
@@ -0,0 +1,302 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require_relative 'lib/common'
|
||||||
|
|
||||||
|
# Non-interactive tests
|
||||||
|
class TestFilter < TestBase
|
||||||
|
def test_default_extended
|
||||||
|
assert_equal '100', `seq 100 | #{FZF} -f "1 00$"`.chomp
|
||||||
|
assert_equal '', `seq 100 | #{FZF} -f "1 00$" +x`.chomp
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_exact
|
||||||
|
assert_equal 4, `seq 123 | #{FZF} -f 13`.lines.length
|
||||||
|
assert_equal 2, `seq 123 | #{FZF} -f 13 -e`.lines.length
|
||||||
|
assert_equal 4, `seq 123 | #{FZF} -f 13 +e`.lines.length
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_or_operator
|
||||||
|
assert_equal %w[1 5 10], `seq 10 | #{FZF} -f "1 | 5"`.lines(chomp: true)
|
||||||
|
assert_equal %w[1 10 2 3 4 5 6 7 8 9],
|
||||||
|
`seq 10 | #{FZF} -f '1 | !1'`.lines(chomp: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_smart_case_for_each_term
|
||||||
|
assert_equal 1, `echo Foo bar | #{FZF} -x -f "foo Fbar" | wc -l`.to_i
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_filter_exitstatus
|
||||||
|
# filter / streaming filter
|
||||||
|
['', '--no-sort'].each do |opts|
|
||||||
|
assert_includes `echo foo | #{FZF} -f foo #{opts}`, 'foo'
|
||||||
|
assert_equal 0, $CHILD_STATUS.exitstatus
|
||||||
|
|
||||||
|
assert_empty `echo foo | #{FZF} -f bar #{opts}`
|
||||||
|
assert_equal 1, $CHILD_STATUS.exitstatus
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_long_line
|
||||||
|
data = '.' * 256 * 1024
|
||||||
|
File.open(tempname, 'w') do |f|
|
||||||
|
f << data
|
||||||
|
end
|
||||||
|
assert_equal data, `#{FZF} -f . < #{tempname}`.chomp
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_read0
|
||||||
|
lines = `find .`.lines(chomp: true)
|
||||||
|
assert_equal lines.last, `find . | #{FZF} -e -f "^#{lines.last}$"`.chomp
|
||||||
|
assert_equal \
|
||||||
|
lines.last,
|
||||||
|
`find . -print0 | #{FZF} --read0 -e -f "^#{lines.last}$"`.chomp
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_with_nth_basic
|
||||||
|
writelines(['hello world ', 'byebye'])
|
||||||
|
assert_equal \
|
||||||
|
'hello world ',
|
||||||
|
`#{FZF} -f"^he hehe" -x -n 2.. --with-nth 2,1,1 < #{tempname}`.chomp
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_with_nth_ansi
|
||||||
|
writelines(["\x1b[33mhello \x1b[34;1mworld\x1b[m ", 'byebye'])
|
||||||
|
assert_equal \
|
||||||
|
'hello world ',
|
||||||
|
`#{FZF} -f"^he hehe" -x -n 2.. --with-nth 2,1,1 --ansi < #{tempname}`.chomp
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_with_nth_no_ansi
|
||||||
|
src = "\x1b[33mhello \x1b[34;1mworld\x1b[m "
|
||||||
|
writelines([src, 'byebye'])
|
||||||
|
assert_equal \
|
||||||
|
src,
|
||||||
|
`#{FZF} -fhehe -x -n 2.. --with-nth 2,1,1 --no-ansi < #{tempname}`.chomp
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_escaped_meta_characters
|
||||||
|
input = [
|
||||||
|
'foo^bar',
|
||||||
|
'foo$bar',
|
||||||
|
'foo!bar',
|
||||||
|
"foo'bar",
|
||||||
|
'foo bar',
|
||||||
|
'bar foo'
|
||||||
|
]
|
||||||
|
writelines(input)
|
||||||
|
|
||||||
|
assert_equal input.length, `#{FZF} -f'foo bar' < #{tempname}`.lines.length
|
||||||
|
assert_equal input.length - 1, `#{FZF} -f'^foo bar$' < #{tempname}`.lines.length
|
||||||
|
assert_equal ['foo bar'], `#{FZF} -f'foo\\ bar' < #{tempname}`.lines(chomp: true)
|
||||||
|
assert_equal ['foo bar'], `#{FZF} -f'^foo\\ bar$' < #{tempname}`.lines(chomp: true)
|
||||||
|
assert_equal input.length - 1, `#{FZF} -f'!^foo\\ bar$' < #{tempname}`.lines.length
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_normalized_match
|
||||||
|
echoes = '(echo a; echo á; echo A; echo Á;)'
|
||||||
|
assert_equal %w[a á A Á], `#{echoes} | #{FZF} -f a`.lines.map(&:chomp)
|
||||||
|
assert_equal %w[á Á], `#{echoes} | #{FZF} -f á`.lines.map(&:chomp)
|
||||||
|
assert_equal %w[A Á], `#{echoes} | #{FZF} -f A`.lines.map(&:chomp)
|
||||||
|
assert_equal %w[Á], `#{echoes} | #{FZF} -f Á`.lines.map(&:chomp)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_unicode_case
|
||||||
|
writelines(%w[строКА1 СТРОКА2 строка3 Строка4])
|
||||||
|
assert_equal %w[СТРОКА2 Строка4], `#{FZF} -fС < #{tempname}`.lines(chomp: true)
|
||||||
|
assert_equal %w[строКА1 СТРОКА2 строка3 Строка4], `#{FZF} -fс < #{tempname}`.lines(chomp: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_tiebreak
|
||||||
|
input = %w[
|
||||||
|
--foobar--------
|
||||||
|
-----foobar---
|
||||||
|
----foobar--
|
||||||
|
-------foobar-
|
||||||
|
]
|
||||||
|
writelines(input)
|
||||||
|
|
||||||
|
assert_equal input, `#{FZF} -ffoobar --tiebreak=index < #{tempname}`.lines(chomp: true)
|
||||||
|
|
||||||
|
by_length = %w[
|
||||||
|
----foobar--
|
||||||
|
-----foobar---
|
||||||
|
-------foobar-
|
||||||
|
--foobar--------
|
||||||
|
]
|
||||||
|
assert_equal by_length, `#{FZF} -ffoobar < #{tempname}`.lines(chomp: true)
|
||||||
|
assert_equal by_length, `#{FZF} -ffoobar --tiebreak=length < #{tempname}`.lines(chomp: true)
|
||||||
|
|
||||||
|
by_begin = %w[
|
||||||
|
--foobar--------
|
||||||
|
----foobar--
|
||||||
|
-----foobar---
|
||||||
|
-------foobar-
|
||||||
|
]
|
||||||
|
assert_equal by_begin, `#{FZF} -ffoobar --tiebreak=begin < #{tempname}`.lines(chomp: true)
|
||||||
|
assert_equal by_begin, `#{FZF} -f"!z foobar" -x --tiebreak begin < #{tempname}`.lines(chomp: true)
|
||||||
|
|
||||||
|
assert_equal %w[
|
||||||
|
-------foobar-
|
||||||
|
----foobar--
|
||||||
|
-----foobar---
|
||||||
|
--foobar--------
|
||||||
|
], `#{FZF} -ffoobar --tiebreak end < #{tempname}`.lines(chomp: true)
|
||||||
|
|
||||||
|
assert_equal input, `#{FZF} -f"!z" -x --tiebreak end < #{tempname}`.lines(chomp: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_tiebreak_index_begin
|
||||||
|
writelines([
|
||||||
|
'xoxxxxxoxx',
|
||||||
|
'xoxxxxxox',
|
||||||
|
'xxoxxxoxx',
|
||||||
|
'xxxoxoxxx',
|
||||||
|
'xxxxoxox',
|
||||||
|
' xxoxoxxx'
|
||||||
|
])
|
||||||
|
|
||||||
|
assert_equal [
|
||||||
|
'xxxxoxox',
|
||||||
|
' xxoxoxxx',
|
||||||
|
'xxxoxoxxx',
|
||||||
|
'xxoxxxoxx',
|
||||||
|
'xoxxxxxox',
|
||||||
|
'xoxxxxxoxx'
|
||||||
|
], `#{FZF} -foo < #{tempname}`.lines(chomp: true)
|
||||||
|
|
||||||
|
assert_equal [
|
||||||
|
'xxxoxoxxx',
|
||||||
|
'xxxxoxox',
|
||||||
|
' xxoxoxxx',
|
||||||
|
'xxoxxxoxx',
|
||||||
|
'xoxxxxxoxx',
|
||||||
|
'xoxxxxxox'
|
||||||
|
], `#{FZF} -foo --tiebreak=index < #{tempname}`.lines(chomp: true)
|
||||||
|
|
||||||
|
# Note that --tiebreak=begin is now based on the first occurrence of the
|
||||||
|
# first character on the pattern
|
||||||
|
assert_equal [
|
||||||
|
' xxoxoxxx',
|
||||||
|
'xxxoxoxxx',
|
||||||
|
'xxxxoxox',
|
||||||
|
'xxoxxxoxx',
|
||||||
|
'xoxxxxxoxx',
|
||||||
|
'xoxxxxxox'
|
||||||
|
], `#{FZF} -foo --tiebreak=begin < #{tempname}`.lines(chomp: true)
|
||||||
|
|
||||||
|
assert_equal [
|
||||||
|
' xxoxoxxx',
|
||||||
|
'xxxoxoxxx',
|
||||||
|
'xxxxoxox',
|
||||||
|
'xxoxxxoxx',
|
||||||
|
'xoxxxxxox',
|
||||||
|
'xoxxxxxoxx'
|
||||||
|
], `#{FZF} -foo --tiebreak=begin,length < #{tempname}`.lines(chomp: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_tiebreak_begin_algo_v2
|
||||||
|
writelines(['baz foo bar',
|
||||||
|
'foo bar baz'])
|
||||||
|
assert_equal [
|
||||||
|
'foo bar baz',
|
||||||
|
'baz foo bar'
|
||||||
|
], `#{FZF} -fbar --tiebreak=begin --algo=v2 < #{tempname}`.lines(chomp: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_tiebreak_end
|
||||||
|
writelines(['xoxxxxxxxx',
|
||||||
|
'xxoxxxxxxx',
|
||||||
|
'xxxoxxxxxx',
|
||||||
|
'xxxxoxxxx',
|
||||||
|
'xxxxxoxxx',
|
||||||
|
' xxxxoxxx'])
|
||||||
|
|
||||||
|
assert_equal [
|
||||||
|
' xxxxoxxx',
|
||||||
|
'xxxxoxxxx',
|
||||||
|
'xxxxxoxxx',
|
||||||
|
'xoxxxxxxxx',
|
||||||
|
'xxoxxxxxxx',
|
||||||
|
'xxxoxxxxxx'
|
||||||
|
], `#{FZF} -fo < #{tempname}`.lines(chomp: true)
|
||||||
|
|
||||||
|
assert_equal [
|
||||||
|
'xxxxxoxxx',
|
||||||
|
' xxxxoxxx',
|
||||||
|
'xxxxoxxxx',
|
||||||
|
'xxxoxxxxxx',
|
||||||
|
'xxoxxxxxxx',
|
||||||
|
'xoxxxxxxxx'
|
||||||
|
], `#{FZF} -fo --tiebreak=end < #{tempname}`.lines(chomp: true)
|
||||||
|
|
||||||
|
assert_equal [
|
||||||
|
'xxxxxoxxx',
|
||||||
|
' xxxxoxxx',
|
||||||
|
'xxxxoxxxx',
|
||||||
|
'xxxoxxxxxx',
|
||||||
|
'xxoxxxxxxx',
|
||||||
|
'xoxxxxxxxx'
|
||||||
|
], `#{FZF} -fo --tiebreak=end,length,begin < #{tempname}`.lines(chomp: true)
|
||||||
|
|
||||||
|
writelines(['/bar/baz', '/foo/bar/baz'])
|
||||||
|
assert_equal [
|
||||||
|
'/foo/bar/baz',
|
||||||
|
'/bar/baz'
|
||||||
|
], `#{FZF} -fbaz --tiebreak=end < #{tempname}`.lines(chomp: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_tiebreak_length_with_nth
|
||||||
|
input = %w[
|
||||||
|
1:hell
|
||||||
|
123:hello
|
||||||
|
12345:he
|
||||||
|
1234567:h
|
||||||
|
]
|
||||||
|
writelines(input)
|
||||||
|
|
||||||
|
output = %w[
|
||||||
|
1:hell
|
||||||
|
12345:he
|
||||||
|
123:hello
|
||||||
|
1234567:h
|
||||||
|
]
|
||||||
|
assert_equal output, `#{FZF} -fh < #{tempname}`.lines(chomp: true)
|
||||||
|
|
||||||
|
# Since 0.16.8, --nth doesn't affect --tiebreak
|
||||||
|
assert_equal output, `#{FZF} -fh -n2 -d: < #{tempname}`.lines(chomp: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_tiebreak_chunk
|
||||||
|
writelines(['1 foobarbaz ba',
|
||||||
|
'2 foobar baz',
|
||||||
|
'3 foo barbaz'])
|
||||||
|
|
||||||
|
assert_equal [
|
||||||
|
'3 foo barbaz',
|
||||||
|
'2 foobar baz',
|
||||||
|
'1 foobarbaz ba'
|
||||||
|
], `#{FZF} -fo --tiebreak=chunk < #{tempname}`.lines(chomp: true)
|
||||||
|
|
||||||
|
assert_equal [
|
||||||
|
'1 foobarbaz ba',
|
||||||
|
'2 foobar baz',
|
||||||
|
'3 foo barbaz'
|
||||||
|
], `#{FZF} -fba --tiebreak=chunk < #{tempname}`.lines(chomp: true)
|
||||||
|
|
||||||
|
assert_equal [
|
||||||
|
'3 foo barbaz'
|
||||||
|
], `#{FZF} -f'!foobar' --tiebreak=chunk < #{tempname}`.lines(chomp: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_boundary_match
|
||||||
|
# Underscore boundaries should be ranked lower
|
||||||
|
{
|
||||||
|
default: [' x '] + %w[/x/ [x] -x- -x_ _x- _x_],
|
||||||
|
path: ['/x/', ' x '] + %w[[x] -x- -x_ _x- _x_],
|
||||||
|
history: ['[x]', '-x-', ' x '] + %w[/x/ -x_ _x- _x_]
|
||||||
|
}.each do |scheme, expected|
|
||||||
|
result = `printf -- 'xxx\n-xx\nxx-\n_x_\n_x-\n-x_\n[x]\n-x-\n x \n/x/\n' | #{FZF} -f"'x'" --scheme=#{scheme}`.lines(chomp: true)
|
||||||
|
assert_equal expected, result
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
4323
test/test_go.rb
4323
test/test_go.rb
File diff suppressed because it is too large
Load Diff
981
test/test_layout.rb
Normal file
981
test/test_layout.rb
Normal file
@@ -0,0 +1,981 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require_relative 'lib/common'
|
||||||
|
|
||||||
|
# Test cases that mainly use assert_block to verify the layout of fzf
|
||||||
|
class TestLayout < TestInteractive
|
||||||
|
def assert_block(expected, lines)
|
||||||
|
cols = expected.lines.map { it.chomp.length }.max
|
||||||
|
top = lines.take(expected.lines.length).map { it[0, cols].rstrip + "\n" }.join.chomp
|
||||||
|
bottom = lines.reverse.take(expected.lines.length).reverse.map { it[0, cols].rstrip + "\n" }.join.chomp
|
||||||
|
assert_includes [top, bottom], expected.chomp
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_vanilla
|
||||||
|
tmux.send_keys "seq 1 100000 | #{fzf}", :Enter
|
||||||
|
block = <<~BLOCK
|
||||||
|
2
|
||||||
|
> 1
|
||||||
|
100000/100000
|
||||||
|
>
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, it) }
|
||||||
|
|
||||||
|
# Testing basic key bindings
|
||||||
|
tmux.send_keys '99', 'C-a', '1', 'C-f', '3', 'C-b', 'C-h', 'C-u', 'C-e', 'C-y', 'C-k', 'Tab', 'BTab'
|
||||||
|
block = <<~BLOCK
|
||||||
|
> 3910
|
||||||
|
391
|
||||||
|
856/100000
|
||||||
|
> 391
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, it) }
|
||||||
|
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
assert_equal '3910', fzf_output
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_header_first
|
||||||
|
tmux.send_keys "seq 1000 | #{FZF} --header foobar --header-lines 3 --header-first", :Enter
|
||||||
|
block = <<~OUTPUT
|
||||||
|
> 4
|
||||||
|
997/997
|
||||||
|
>
|
||||||
|
3
|
||||||
|
2
|
||||||
|
1
|
||||||
|
foobar
|
||||||
|
OUTPUT
|
||||||
|
tmux.until { assert_block(block, it) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_header_first_reverse
|
||||||
|
tmux.send_keys "seq 1000 | #{FZF} --header foobar --header-lines 3 --header-first --reverse --inline-info", :Enter
|
||||||
|
block = <<~OUTPUT
|
||||||
|
foobar
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
> < 997/997
|
||||||
|
> 4
|
||||||
|
OUTPUT
|
||||||
|
tmux.until { assert_block(block, it) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_change_and_transform_header
|
||||||
|
[
|
||||||
|
'space:change-header:$(seq 4)',
|
||||||
|
'space:transform-header:seq 4'
|
||||||
|
].each_with_index do |binding, i|
|
||||||
|
tmux.send_keys %(seq 3 | #{FZF} --header-lines 2 --header bar --bind "#{binding}"), :Enter
|
||||||
|
expected = <<~OUTPUT
|
||||||
|
> 3
|
||||||
|
2
|
||||||
|
1
|
||||||
|
bar
|
||||||
|
1/1
|
||||||
|
>
|
||||||
|
OUTPUT
|
||||||
|
tmux.until { assert_block(expected, it) }
|
||||||
|
tmux.send_keys :Space
|
||||||
|
expected = <<~OUTPUT
|
||||||
|
> 3
|
||||||
|
2
|
||||||
|
1
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
1/1
|
||||||
|
>
|
||||||
|
OUTPUT
|
||||||
|
tmux.until { assert_block(expected, it) }
|
||||||
|
next unless i.zero?
|
||||||
|
|
||||||
|
teardown
|
||||||
|
setup
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_change_header
|
||||||
|
tmux.send_keys %(seq 3 | #{FZF} --header-lines 2 --header bar --bind "space:change-header:$(seq 4)"), :Enter
|
||||||
|
expected = <<~OUTPUT
|
||||||
|
> 3
|
||||||
|
2
|
||||||
|
1
|
||||||
|
bar
|
||||||
|
1/1
|
||||||
|
>
|
||||||
|
OUTPUT
|
||||||
|
tmux.until { assert_block(expected, it) }
|
||||||
|
tmux.send_keys :Space
|
||||||
|
expected = <<~OUTPUT
|
||||||
|
> 3
|
||||||
|
2
|
||||||
|
1
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
1/1
|
||||||
|
>
|
||||||
|
OUTPUT
|
||||||
|
tmux.until { assert_block(expected, it) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_reload_and_change_cache
|
||||||
|
tmux.send_keys "echo bar | #{FZF} --bind 'zero:change-header(foo)+reload(echo foo)+clear-query'", :Enter
|
||||||
|
expected = <<~OUTPUT
|
||||||
|
> bar
|
||||||
|
1/1
|
||||||
|
>
|
||||||
|
OUTPUT
|
||||||
|
tmux.until { assert_block(expected, it) }
|
||||||
|
tmux.send_keys :z
|
||||||
|
expected = <<~OUTPUT
|
||||||
|
> foo
|
||||||
|
foo
|
||||||
|
1/1
|
||||||
|
>
|
||||||
|
OUTPUT
|
||||||
|
tmux.until { assert_block(expected, it) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_toggle_header
|
||||||
|
tmux.send_keys "seq 4 | #{FZF} --header-lines 2 --header foo --bind space:toggle-header --header-first --height 10 --border rounded", :Enter
|
||||||
|
before = <<~OUTPUT
|
||||||
|
╭───────
|
||||||
|
│
|
||||||
|
│ 4
|
||||||
|
│ > 3
|
||||||
|
│ 2/2
|
||||||
|
│ >
|
||||||
|
│ 2
|
||||||
|
│ 1
|
||||||
|
│ foo
|
||||||
|
╰───────
|
||||||
|
OUTPUT
|
||||||
|
tmux.until { assert_block(before, it) }
|
||||||
|
tmux.send_keys :Space
|
||||||
|
after = <<~OUTPUT
|
||||||
|
╭───────
|
||||||
|
│
|
||||||
|
│
|
||||||
|
│
|
||||||
|
│
|
||||||
|
│ 4
|
||||||
|
│ > 3
|
||||||
|
│ 2/2
|
||||||
|
│ >
|
||||||
|
╰───────
|
||||||
|
OUTPUT
|
||||||
|
tmux.until { assert_block(after, it) }
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until { assert_block(before, it) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_height_range_fit
|
||||||
|
tmux.send_keys 'seq 3 | fzf --height ~100% --info=inline --border rounded', :Enter
|
||||||
|
expected = <<~OUTPUT
|
||||||
|
╭──────────
|
||||||
|
│ 3
|
||||||
|
│ 2
|
||||||
|
│ > 1
|
||||||
|
│ > < 3/3
|
||||||
|
╰──────────
|
||||||
|
OUTPUT
|
||||||
|
tmux.until { assert_block(expected, it) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_height_range_fit_preview_above
|
||||||
|
tmux.send_keys 'seq 3 | fzf --height ~100% --info=inline --border rounded --preview-window border-rounded --preview "seq {}" --preview-window up,60%', :Enter
|
||||||
|
expected = <<~OUTPUT
|
||||||
|
╭──────────
|
||||||
|
│ ╭────────
|
||||||
|
│ │ 1
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ ╰────────
|
||||||
|
│ 3
|
||||||
|
│ 2
|
||||||
|
│ > 1
|
||||||
|
│ > < 3/3
|
||||||
|
╰──────────
|
||||||
|
OUTPUT
|
||||||
|
tmux.until { assert_block(expected, it) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_height_range_fit_preview_above_alternative
|
||||||
|
tmux.send_keys 'seq 3 | fzf --height ~100% --border=sharp --preview "seq {}" --preview-window up,40%,border-bottom --padding 1 --exit-0 --header hello --header-lines=2', :Enter
|
||||||
|
expected = <<~OUTPUT
|
||||||
|
┌─────────
|
||||||
|
│
|
||||||
|
│ 1
|
||||||
|
│ 2
|
||||||
|
│ 3
|
||||||
|
│ ───────
|
||||||
|
│ > 3
|
||||||
|
│ 2
|
||||||
|
│ 1
|
||||||
|
│ hello
|
||||||
|
│ 1/1 ─
|
||||||
|
│ >
|
||||||
|
│
|
||||||
|
└─────────
|
||||||
|
OUTPUT
|
||||||
|
tmux.until { assert_block(expected, it) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_height_range_fit_preview_left
|
||||||
|
tmux.send_keys "seq 3 | fzf --height ~100% --border=vertical --preview 'seq {}' --preview-window left,5,border-right --padding 1 --exit-0 --header $'hello\\nworld' --header-lines=2", :Enter
|
||||||
|
expected = <<~OUTPUT
|
||||||
|
│
|
||||||
|
│ 1 │ > 3
|
||||||
|
│ 2 │ 2
|
||||||
|
│ 3 │ 1
|
||||||
|
│ │ hello
|
||||||
|
│ │ world
|
||||||
|
│ │ 1/1 ─
|
||||||
|
│ │ >
|
||||||
|
│
|
||||||
|
OUTPUT
|
||||||
|
tmux.until { assert_block(expected, it) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_height_range_overflow
|
||||||
|
tmux.send_keys 'seq 100 | fzf --height ~5 --info=inline --border rounded', :Enter
|
||||||
|
expected = <<~OUTPUT
|
||||||
|
╭──────────────
|
||||||
|
│ 2
|
||||||
|
│ > 1
|
||||||
|
│ > < 100/100
|
||||||
|
╰──────────────
|
||||||
|
OUTPUT
|
||||||
|
tmux.until { assert_block(expected, it) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_no_extra_newline_issue_3209
|
||||||
|
tmux.send_keys(%(seq 100 | #{FZF} --height 10 --preview-window up,wrap,border-rounded --preview 'printf "─%.0s" $(seq 1 "$((FZF_PREVIEW_COLUMNS - 5))"); printf $"\\e[7m%s\\e[0m" title; echo; echo something'), :Enter)
|
||||||
|
expected = <<~OUTPUT
|
||||||
|
╭──────────
|
||||||
|
│ ─────────
|
||||||
|
│ something
|
||||||
|
│
|
||||||
|
╰──────────
|
||||||
|
3
|
||||||
|
2
|
||||||
|
> 1
|
||||||
|
100/100 ─
|
||||||
|
>
|
||||||
|
OUTPUT
|
||||||
|
tmux.until { assert_block(expected, it) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_fzf_multi_line
|
||||||
|
tmux.send_keys %[(echo -en '0\\0'; echo -en '1\\n2\\0'; seq 1000) | fzf --read0 --multi --bind load:select-all --border rounded], :Enter
|
||||||
|
block = <<~BLOCK
|
||||||
|
│ ┃998
|
||||||
|
│ ┃999
|
||||||
|
│ ┃1000
|
||||||
|
│ ╹
|
||||||
|
│ ╻1
|
||||||
|
│ ╹2
|
||||||
|
│ >>0
|
||||||
|
│ 3/3 (3)
|
||||||
|
│ >
|
||||||
|
╰───────────
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, it) }
|
||||||
|
tmux.send_keys :Up, :Up
|
||||||
|
block = <<~BLOCK
|
||||||
|
╭───────
|
||||||
|
│ >╻1
|
||||||
|
│ >┃2
|
||||||
|
│ >┃3
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, it) }
|
||||||
|
|
||||||
|
block = <<~BLOCK
|
||||||
|
│ >┃
|
||||||
|
│
|
||||||
|
│ >
|
||||||
|
╰───
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, it) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_fzf_multi_line_reverse
|
||||||
|
tmux.send_keys %[(echo -en '0\\0'; echo -en '1\\n2\\0'; seq 1000) | fzf --read0 --multi --bind load:select-all --border rounded --reverse], :Enter
|
||||||
|
block = <<~BLOCK
|
||||||
|
╭───────────
|
||||||
|
│ >
|
||||||
|
│ 3/3 (3)
|
||||||
|
│ >>0
|
||||||
|
│ ╻1
|
||||||
|
│ ╹2
|
||||||
|
│ ╻1
|
||||||
|
│ ┃2
|
||||||
|
│ ┃3
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, it) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_fzf_multi_line_no_pointer_and_marker
|
||||||
|
tmux.send_keys %[(echo -en '0\\0'; echo -en '1\\n2\\0'; seq 1000) | fzf --read0 --multi --bind load:select-all --border rounded --reverse --pointer '' --marker '' --marker-multi-line ''], :Enter
|
||||||
|
block = <<~BLOCK
|
||||||
|
╭───────────
|
||||||
|
│ >
|
||||||
|
│ 3/3 (3)
|
||||||
|
│ 0
|
||||||
|
│ 1
|
||||||
|
│ 2
|
||||||
|
│ 1
|
||||||
|
│ 2
|
||||||
|
│ 3
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, it) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_gap
|
||||||
|
tmux.send_keys %(seq 100 | #{FZF} --gap --border rounded --reverse), :Enter
|
||||||
|
block = <<~BLOCK
|
||||||
|
╭─────────────────
|
||||||
|
│ >
|
||||||
|
│ 100/100 ──────
|
||||||
|
│ > 1
|
||||||
|
│ ┈┈┈┈┈┈┈┈┈┈┈┈┈┈
|
||||||
|
│ 2
|
||||||
|
│ ┈┈┈┈┈┈┈┈┈┈┈┈┈┈
|
||||||
|
│ 3
|
||||||
|
│ ┈┈┈┈┈┈┈┈┈┈┈┈┈┈
|
||||||
|
│ 4
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, it) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_gap_2
|
||||||
|
tmux.send_keys %(seq 100 | #{FZF} --gap=2 --gap-line xyz --border rounded --reverse), :Enter
|
||||||
|
block = <<~BLOCK
|
||||||
|
╭─────────────────
|
||||||
|
│ >
|
||||||
|
│ 100/100 ──────
|
||||||
|
│ > 1
|
||||||
|
│
|
||||||
|
│ xyzxyzxyzxyzxy
|
||||||
|
│ 2
|
||||||
|
│
|
||||||
|
│ xyzxyzxyzxyzxy
|
||||||
|
│ 3
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, it) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_list_border_and_label
|
||||||
|
tmux.send_keys %(seq 100 | #{FZF} --border rounded --list-border double --list-label list --list-label-pos 2:bottom --header-lines 3 --query 1 --padding 1,2), :Enter
|
||||||
|
block = <<~BLOCK
|
||||||
|
│ ║ 11
|
||||||
|
│ ║ > 10
|
||||||
|
│ ║ 3
|
||||||
|
│ ║ 2
|
||||||
|
│ ║ 1
|
||||||
|
│ ║ 19/97 ─
|
||||||
|
│ ║ > 1
|
||||||
|
│ ╚list══════
|
||||||
|
│
|
||||||
|
╰──────────────
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, it) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_input_border_and_label
|
||||||
|
tmux.send_keys %(seq 100 | #{FZF} --border rounded --input-border bold --input-label input --input-label-pos 2 --header-lines 3 --query 1 --padding 1,2), :Enter
|
||||||
|
block = <<~BLOCK
|
||||||
|
│ 11
|
||||||
|
│ > 10
|
||||||
|
│ 3
|
||||||
|
│ 2
|
||||||
|
│ 1
|
||||||
|
│ ┏input━━━━━
|
||||||
|
│ ┃ 19/97
|
||||||
|
│ ┃ > 1
|
||||||
|
│ ┗━━━━━━━━━━
|
||||||
|
│
|
||||||
|
╰──────────────
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, it) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_input_border_and_label_header_first
|
||||||
|
tmux.send_keys %(seq 100 | #{FZF} --border rounded --input-border bold --input-label input --input-label-pos 2 --header-lines 3 --query 1 --padding 1,2 --header-first), :Enter
|
||||||
|
block = <<~BLOCK
|
||||||
|
│ 11
|
||||||
|
│ > 10
|
||||||
|
│ ┏input━━━━━
|
||||||
|
│ ┃ 19/97
|
||||||
|
│ ┃ > 1
|
||||||
|
│ ┗━━━━━━━━━━
|
||||||
|
│ 3
|
||||||
|
│ 2
|
||||||
|
│ 1
|
||||||
|
│
|
||||||
|
╰──────────────
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, it) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_list_input_border_and_label
|
||||||
|
tmux.send_keys %(
|
||||||
|
seq 100 | #{FZF} --border rounded --list-border double --input-border bold --list-label-pos 2:bottom --input-label-pos 2 --header-lines 3 --query 1 --padding 1,2 \
|
||||||
|
--bind 'start:transform-input-label(echo INPUT)+transform-list-label(echo LIST)' \
|
||||||
|
--bind 'space:change-input-label( input )+change-list-label( list )'
|
||||||
|
).strip, :Enter
|
||||||
|
block = <<~BLOCK
|
||||||
|
│ ║ 11
|
||||||
|
│ ║ > 10
|
||||||
|
│ ╚LIST══════
|
||||||
|
│ 3
|
||||||
|
│ 2
|
||||||
|
│ 1
|
||||||
|
│ ┏INPUT━━━━━
|
||||||
|
│ ┃ 19/97
|
||||||
|
│ ┃ > 1
|
||||||
|
│ ┗━━━━━━━━━━
|
||||||
|
│
|
||||||
|
╰──────────────
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, it) }
|
||||||
|
tmux.send_keys :Space
|
||||||
|
block = <<~BLOCK
|
||||||
|
│ ║ 11
|
||||||
|
│ ║ > 10
|
||||||
|
│ ╚ list ════
|
||||||
|
│ 3
|
||||||
|
│ 2
|
||||||
|
│ 1
|
||||||
|
│ ┏ input ━━━
|
||||||
|
│ ┃ 19/97
|
||||||
|
│ ┃ > 1
|
||||||
|
│ ┗━━━━━━━━━━
|
||||||
|
│
|
||||||
|
╰──────────────
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, it) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_list_input_border_and_label_header_first
|
||||||
|
tmux.send_keys %(
|
||||||
|
seq 100 | #{FZF} --border rounded --list-border double --input-border bold --list-label-pos 2:bottom --input-label-pos 2 --header-lines 3 --query 1 --padding 1,2 \
|
||||||
|
--bind 'start:transform-input-label(echo INPUT)+transform-list-label(echo LIST)' \
|
||||||
|
--bind 'space:change-input-label( input )+change-list-label( list )' --header-first
|
||||||
|
).strip, :Enter
|
||||||
|
block = <<~BLOCK
|
||||||
|
│ ║ 11
|
||||||
|
│ ║ > 10
|
||||||
|
│ ╚LIST══════
|
||||||
|
│ ┏INPUT━━━━━
|
||||||
|
│ ┃ 19/97
|
||||||
|
│ ┃ > 1
|
||||||
|
│ ┗━━━━━━━━━━
|
||||||
|
│ 3
|
||||||
|
│ 2
|
||||||
|
│ 1
|
||||||
|
│
|
||||||
|
╰──────────────
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, it) }
|
||||||
|
tmux.send_keys :Space
|
||||||
|
block = <<~BLOCK
|
||||||
|
│ ║ 11
|
||||||
|
│ ║ > 10
|
||||||
|
│ ╚ list ════
|
||||||
|
│ ┏ input ━━━
|
||||||
|
│ ┃ 19/97
|
||||||
|
│ ┃ > 1
|
||||||
|
│ ┗━━━━━━━━━━
|
||||||
|
│ 3
|
||||||
|
│ 2
|
||||||
|
│ 1
|
||||||
|
│
|
||||||
|
╰──────────────
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, it) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_header_border_and_label
|
||||||
|
tmux.send_keys %(seq 100 | #{FZF} --border rounded --header-lines 3 --header-border sharp --header-label header --header-label-pos 2:bottom --query 1 --padding 1,2), :Enter
|
||||||
|
block = <<~BLOCK
|
||||||
|
│ 12
|
||||||
|
│ 11
|
||||||
|
│ > 10
|
||||||
|
│ ┌────────
|
||||||
|
│ │ 3
|
||||||
|
│ │ 2
|
||||||
|
│ │ 1
|
||||||
|
│ └header──
|
||||||
|
│ 19/97 ─
|
||||||
|
│ > 1
|
||||||
|
│
|
||||||
|
╰────────────
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, it) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_header_border_toggle
|
||||||
|
tmux.send_keys %(seq 100 | #{FZF} --list-border rounded --header-border rounded --bind 'space:change-header(hello),enter:change-header()'), :Enter
|
||||||
|
block1 = <<~BLOCK
|
||||||
|
│ 5
|
||||||
|
│ 4
|
||||||
|
│ 3
|
||||||
|
│ 2
|
||||||
|
│ > 1
|
||||||
|
│ 100/100 ─
|
||||||
|
│ >
|
||||||
|
╰────────────
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block1, it) }
|
||||||
|
|
||||||
|
tmux.send_keys :Space
|
||||||
|
block2 = <<~BLOCK
|
||||||
|
│ 3
|
||||||
|
│ 2
|
||||||
|
│ > 1
|
||||||
|
╰────────────
|
||||||
|
╭────────────
|
||||||
|
│ hello
|
||||||
|
╰────────────
|
||||||
|
100/100 ─
|
||||||
|
>
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block2, it) }
|
||||||
|
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { assert_block(block1, it) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_header_border_toggle_with_header_lines
|
||||||
|
tmux.send_keys %(seq 100 | #{FZF} --list-border rounded --header-border rounded --bind 'space:change-header(hello),enter:change-header()' --header-lines 2), :Enter
|
||||||
|
block1 = <<~BLOCK
|
||||||
|
│ 5
|
||||||
|
│ 4
|
||||||
|
│ > 3
|
||||||
|
╰──────────
|
||||||
|
╭──────────
|
||||||
|
│ 2
|
||||||
|
│ 1
|
||||||
|
╰──────────
|
||||||
|
98/98 ─
|
||||||
|
>
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block1, it) }
|
||||||
|
|
||||||
|
tmux.send_keys :Space
|
||||||
|
block2 = <<~BLOCK
|
||||||
|
│ 4
|
||||||
|
│ > 3
|
||||||
|
╰──────────
|
||||||
|
╭──────────
|
||||||
|
│ 2
|
||||||
|
│ 1
|
||||||
|
│ hello
|
||||||
|
╰──────────
|
||||||
|
98/98 ─
|
||||||
|
>
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block2, it) }
|
||||||
|
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { assert_block(block1, it) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_header_border_toggle_with_header_lines_header_first
|
||||||
|
tmux.send_keys %(seq 100 | #{FZF} --list-border rounded --header-border rounded --bind 'space:change-header(hello),enter:change-header()' --header-lines 2 --header-first), :Enter
|
||||||
|
block1 = <<~BLOCK
|
||||||
|
│ 5
|
||||||
|
│ 4
|
||||||
|
│ > 3
|
||||||
|
╰──────────
|
||||||
|
98/98 ─
|
||||||
|
>
|
||||||
|
╭──────────
|
||||||
|
│ 2
|
||||||
|
│ 1
|
||||||
|
╰──────────
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block1, it) }
|
||||||
|
|
||||||
|
tmux.send_keys :Space
|
||||||
|
block2 = <<~BLOCK
|
||||||
|
│ 4
|
||||||
|
│ > 3
|
||||||
|
╰──────────
|
||||||
|
98/98 ─
|
||||||
|
>
|
||||||
|
╭──────────
|
||||||
|
│ 2
|
||||||
|
│ 1
|
||||||
|
│ hello
|
||||||
|
╰──────────
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block2, it) }
|
||||||
|
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { assert_block(block1, it) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_header_border_toggle_with_header_lines_header_lines_border
|
||||||
|
tmux.send_keys %(seq 100 | #{FZF} --list-border rounded --header-border rounded --bind 'space:change-header(hello),enter:change-header()' --header-lines 2 --header-lines-border double), :Enter
|
||||||
|
block1 = <<~BLOCK
|
||||||
|
│ 5
|
||||||
|
│ 4
|
||||||
|
│ > 3
|
||||||
|
╰──────────
|
||||||
|
╔══════════
|
||||||
|
║ 2
|
||||||
|
║ 1
|
||||||
|
╚══════════
|
||||||
|
98/98 ─
|
||||||
|
>
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block1, it) }
|
||||||
|
|
||||||
|
tmux.send_keys :Space
|
||||||
|
block2 = <<~BLOCK
|
||||||
|
│ > 3
|
||||||
|
╰──────────
|
||||||
|
╔══════════
|
||||||
|
║ 2
|
||||||
|
║ 1
|
||||||
|
╚══════════
|
||||||
|
╭──────────
|
||||||
|
│ hello
|
||||||
|
╰──────────
|
||||||
|
98/98 ─
|
||||||
|
>
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block2, it) }
|
||||||
|
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { assert_block(block1, it) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_header_border_toggle_with_header_lines_header_first_header_lines_border
|
||||||
|
tmux.send_keys %(seq 100 | #{FZF} --list-border rounded --header-border rounded --bind 'space:change-header(hello),enter:change-header()' --header-lines 2 --header-first --header-lines-border double), :Enter
|
||||||
|
block1 = <<~BLOCK
|
||||||
|
│ 5
|
||||||
|
│ 4
|
||||||
|
│ > 3
|
||||||
|
╰──────────
|
||||||
|
╔══════════
|
||||||
|
║ 2
|
||||||
|
║ 1
|
||||||
|
╚══════════
|
||||||
|
98/98 ─
|
||||||
|
>
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block1, it) }
|
||||||
|
|
||||||
|
tmux.send_keys :Space
|
||||||
|
block2 = <<~BLOCK
|
||||||
|
│ > 3
|
||||||
|
╰──────────
|
||||||
|
╔══════════
|
||||||
|
║ 2
|
||||||
|
║ 1
|
||||||
|
╚══════════
|
||||||
|
98/98 ─
|
||||||
|
>
|
||||||
|
╭──────────
|
||||||
|
│ hello
|
||||||
|
╰──────────
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block2, it) }
|
||||||
|
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { assert_block(block1, it) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_header_border_and_label_header_first
|
||||||
|
tmux.send_keys %(seq 100 | #{FZF} --border rounded --header-lines 3 --header-border sharp --header-label header --header-label-pos 2:bottom --query 1 --padding 1,2 --header-first), :Enter
|
||||||
|
block = <<~BLOCK
|
||||||
|
│ 12
|
||||||
|
│ 11
|
||||||
|
│ > 10
|
||||||
|
│ 19/97 ─
|
||||||
|
│ > 1
|
||||||
|
│ ┌────────
|
||||||
|
│ │ 3
|
||||||
|
│ │ 2
|
||||||
|
│ │ 1
|
||||||
|
│ └header──
|
||||||
|
│
|
||||||
|
╰────────────
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, it) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_header_border_and_label_with_list_border
|
||||||
|
tmux.send_keys %(seq 100 | #{FZF} --border rounded --list-border double --list-label list --list-label-pos 2:bottom --header-lines 3 --header-border sharp --header-label header --header-label-pos 2:bottom --query 1 --padding 1,2), :Enter
|
||||||
|
block = <<~BLOCK
|
||||||
|
│ ║ 12
|
||||||
|
│ ║ 11
|
||||||
|
│ ║ > 10
|
||||||
|
│ ╚list══════
|
||||||
|
│ ┌──────────
|
||||||
|
│ │ 3
|
||||||
|
│ │ 2
|
||||||
|
│ │ 1
|
||||||
|
│ └header────
|
||||||
|
│ 19/97 ─
|
||||||
|
│ > 1
|
||||||
|
│
|
||||||
|
╰──────────────
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, it) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_header_border_and_label_with_list_border_header_first
|
||||||
|
tmux.send_keys %(seq 100 | #{FZF} --border rounded --list-border double --list-label list --list-label-pos 2:bottom --header-lines 3 --header-border sharp --header-label header --header-label-pos 2:bottom --query 1 --padding 1,2 --header-first), :Enter
|
||||||
|
block = <<~BLOCK
|
||||||
|
│ ║ 12
|
||||||
|
│ ║ 11
|
||||||
|
│ ║ > 10
|
||||||
|
│ ╚list══════
|
||||||
|
│ 19/97 ─
|
||||||
|
│ > 1
|
||||||
|
│ ┌──────────
|
||||||
|
│ │ 3
|
||||||
|
│ │ 2
|
||||||
|
│ │ 1
|
||||||
|
│ └header────
|
||||||
|
│
|
||||||
|
╰──────────────
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, it) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_all_borders
|
||||||
|
tmux.send_keys %(seq 100 | #{FZF} --border rounded --list-border double --list-label list --list-label-pos 2:bottom --header-lines 3 --header-border sharp --header-label header --header-label-pos 2:bottom --query 1 --padding 1,2 --input-border bold --input-label input --input-label-pos 2:bottom), :Enter
|
||||||
|
block = <<~BLOCK
|
||||||
|
│ ║ 12
|
||||||
|
│ ║ 11
|
||||||
|
│ ║ > 10
|
||||||
|
│ ╚list══════
|
||||||
|
│ ┌──────────
|
||||||
|
│ │ 3
|
||||||
|
│ │ 2
|
||||||
|
│ │ 1
|
||||||
|
│ └header────
|
||||||
|
│ ┏━━━━━━━━━━
|
||||||
|
│ ┃ 19/97
|
||||||
|
│ ┃ > 1
|
||||||
|
│ ┗input━━━━━
|
||||||
|
│
|
||||||
|
╰──────────────
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, it) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_all_borders_header_first
|
||||||
|
tmux.send_keys %(seq 100 | #{FZF} --border rounded --list-border double --list-label list --list-label-pos 2:bottom --header-lines 3 --header-border sharp --header-label header --header-label-pos 2:bottom --query 1 --padding 1,2 --input-border bold --input-label input --input-label-pos 2:bottom --header-first), :Enter
|
||||||
|
block = <<~BLOCK
|
||||||
|
│ ║ 12
|
||||||
|
│ ║ 11
|
||||||
|
│ ║ > 10
|
||||||
|
│ ╚list══════
|
||||||
|
│ ┏━━━━━━━━━━
|
||||||
|
│ ┃ 19/97
|
||||||
|
│ ┃ > 1
|
||||||
|
│ ┗input━━━━━
|
||||||
|
│ ┌──────────
|
||||||
|
│ │ 3
|
||||||
|
│ │ 2
|
||||||
|
│ │ 1
|
||||||
|
│ └header────
|
||||||
|
│
|
||||||
|
╰──────────────
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, it) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_style_full_adaptive_height
|
||||||
|
tmux.send_keys %(seq 1| #{FZF} --style=full:rounded --height=~100% --header-lines=1 --info=default), :Enter
|
||||||
|
block = <<~BLOCK
|
||||||
|
╭────────
|
||||||
|
╰────────
|
||||||
|
╭────────
|
||||||
|
│ 1
|
||||||
|
╰────────
|
||||||
|
╭────────
|
||||||
|
│ 0/0
|
||||||
|
│ >
|
||||||
|
╰────────
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, it) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_style_full_adaptive_height_double
|
||||||
|
tmux.send_keys %(seq 1| #{FZF} --style=full:double --border --height=~100% --header-lines=1 --info=default), :Enter
|
||||||
|
block = <<~BLOCK
|
||||||
|
╔══════════
|
||||||
|
║ ╔════════
|
||||||
|
║ ╚════════
|
||||||
|
║ ╔════════
|
||||||
|
║ ║ 1
|
||||||
|
║ ╚════════
|
||||||
|
║ ╔════════
|
||||||
|
║ ║ 0/0
|
||||||
|
║ ║ >
|
||||||
|
║ ╚════════
|
||||||
|
╚══════════
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, it) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_preview_window_noinfo
|
||||||
|
# │ 1 ││
|
||||||
|
tmux.send_keys %(#{FZF} --preview 'seq 1000' --preview-window top,noinfo --scrollbar --bind space:change-preview-window:info), :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert lines[1]&.start_with?('│ 1')
|
||||||
|
assert lines[1]&.end_with?(' ││')
|
||||||
|
end
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert lines[1]&.start_with?('│ 1')
|
||||||
|
assert lines[1]&.end_with?('1000││')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_min_height_no_auto
|
||||||
|
tmux.send_keys %(seq 100 | #{FZF} --border sharp --style full:sharp --height 1% --min-height 5), :Enter
|
||||||
|
|
||||||
|
block = <<~BLOCK
|
||||||
|
┌───────
|
||||||
|
│ ┌─────
|
||||||
|
│ │ >
|
||||||
|
│ └─────
|
||||||
|
└───────
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, it) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_min_height_auto
|
||||||
|
tmux.send_keys %(seq 100 | #{FZF} --style full:sharp --height 1% --min-height 5+), :Enter
|
||||||
|
|
||||||
|
block = <<~BLOCK
|
||||||
|
┌─────────
|
||||||
|
│ 5
|
||||||
|
│ 4
|
||||||
|
│ 3
|
||||||
|
│ 2
|
||||||
|
│ > 1
|
||||||
|
└─────────
|
||||||
|
┌─────────
|
||||||
|
│ >
|
||||||
|
└─────────
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, it) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_min_height_auto_no_input
|
||||||
|
tmux.send_keys %(seq 100 | #{FZF} --style full:sharp --no-input --height 1% --min-height 5+), :Enter
|
||||||
|
|
||||||
|
block = <<~BLOCK
|
||||||
|
┌─────────
|
||||||
|
│ 5
|
||||||
|
│ 4
|
||||||
|
│ 3
|
||||||
|
│ 2
|
||||||
|
│ > 1
|
||||||
|
└─────────
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, it) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_min_height_auto_no_input_reverse_list
|
||||||
|
tmux.send_keys %(seq 100 | #{FZF} --style full:sharp --layout reverse-list --no-input --height 1% --min-height 5+ --bind a:show-input,b:hide-input,c:toggle-input), :Enter
|
||||||
|
|
||||||
|
block = <<~BLOCK
|
||||||
|
┌─────────
|
||||||
|
│ > 1
|
||||||
|
│ 2
|
||||||
|
│ 3
|
||||||
|
│ 4
|
||||||
|
│ 5
|
||||||
|
└─────────
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, it) }
|
||||||
|
tmux.send_keys :a
|
||||||
|
block2 = <<~BLOCK
|
||||||
|
┌─────
|
||||||
|
│ > 1
|
||||||
|
│ 2
|
||||||
|
└─────
|
||||||
|
┌─────
|
||||||
|
│ >
|
||||||
|
└─────
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block2, it) }
|
||||||
|
tmux.send_keys :b
|
||||||
|
tmux.until { assert_block(block, it) }
|
||||||
|
tmux.send_keys :c
|
||||||
|
tmux.until { assert_block(block2, it) }
|
||||||
|
tmux.send_keys :c
|
||||||
|
tmux.until { assert_block(block, it) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_layout_reverse_list
|
||||||
|
prefix = "seq 5 | #{FZF} --layout reverse-list --no-list-border --height ~100% --border sharp "
|
||||||
|
suffixes = [
|
||||||
|
%(),
|
||||||
|
%[--header "$(seq 101 103)"],
|
||||||
|
%[--header "$(seq 101 103)" --header-first],
|
||||||
|
%[--header "$(seq 101 103)" --header-lines 3],
|
||||||
|
%[--header "$(seq 101 103)" --header-lines 3 --header-first],
|
||||||
|
%[--header "$(seq 101 103)" --header-border sharp],
|
||||||
|
%[--header "$(seq 101 103)" --header-border sharp --header-first],
|
||||||
|
%[--header "$(seq 101 103)" --header-border sharp --header-lines 3],
|
||||||
|
%[--header "$(seq 101 103)" --header-border sharp --header-lines 3 --header-lines-border sharp],
|
||||||
|
%[--header "$(seq 101 103)" --header-border sharp --header-lines 3 --header-lines-border sharp --header-first],
|
||||||
|
%[--header "$(seq 101 103)" --header-border sharp --header-lines 3 --header-lines-border sharp --header-first --input-border sharp],
|
||||||
|
%[--header "$(seq 101 103)" --header-border sharp --header-lines 3 --header-lines-border sharp --header-first --no-input],
|
||||||
|
%[--header "$(seq 101 103)" --input-border sharp],
|
||||||
|
%[--header "$(seq 101 103)" --style full:sharp],
|
||||||
|
%[--header "$(seq 101 103)" --style full:sharp --header-first]
|
||||||
|
]
|
||||||
|
output = <<~BLOCK
|
||||||
|
┌──────── ┌──────── ┌──────── ┌──────── ┌──────── ┌──────── ┌──────── ┌──────── ┌──────── ┌──────── ┌───────── ┌─────── ┌───────── ┌───────── ┌─────────
|
||||||
|
│ > 1 │ > 1 │ > 1 │ 1 │ 1 │ > 1 │ > 1 │ 1 │ ┌────── │ ┌────── │ ┌─────── │ ┌───── │ > 1 │ ┌─────── │ ┌───────
|
||||||
|
│ 2 │ 2 │ 2 │ 2 │ 2 │ 2 │ 2 │ 2 │ │ 1 │ │ 1 │ │ 1 │ │ 1 │ 2 │ │ > 1 │ │ > 1
|
||||||
|
│ 3 │ 3 │ 3 │ 3 │ 3 │ 3 │ 3 │ 3 │ │ 2 │ │ 2 │ │ 2 │ │ 2 │ 3 │ │ 2 │ │ 2
|
||||||
|
│ 4 │ 4 │ 4 │ > 4 │ > 4 │ 4 │ 4 │ > 4 │ │ 3 │ │ 3 │ │ 3 │ │ 3 │ 4 │ │ 3 │ │ 3
|
||||||
|
│ 5 │ 5 │ 5 │ 5 │ 5 │ 5 │ 5 │ 5 │ └────── │ └────── │ └─────── │ └───── │ 5 │ │ 4 │ │ 4
|
||||||
|
│ 5/5 ─ │ 101 │ 5/5 ─ │ 101 │ 2/2 ─ │ ┌────── │ 5/5 ─ │ ┌────── │ > 4 │ > 4 │ > 4 │ > 4 │ 101 │ │ 5 │ │ 5
|
||||||
|
│ > │ 102 │ > │ 102 │ > │ │ 101 │ > │ │ 101 │ 5 │ 5 │ 5 │ 5 │ 102 │ └─────── │ └───────
|
||||||
|
└──────── │ 103 │ 101 │ 103 │ 101 │ │ 102 │ ┌────── │ │ 102 │ ┌────── │ 2/2 ─ │ ┌─────── │ ┌───── │ 103 │ ┌─────── │ ┌───────
|
||||||
|
│ 5/5 ─ │ 102 │ 2/2 ─ │ 102 │ │ 103 │ │ 101 │ │ 103 │ │ 101 │ > │ │ 2/2 │ │ 101 │ ┌─────── │ │ 101 │ │ >
|
||||||
|
│ > │ 103 │ > │ 103 │ └────── │ │ 102 │ └────── │ │ 102 │ ┌────── │ │ > │ │ 102 │ │ 5/5 │ │ 102 │ └───────
|
||||||
|
└──────── └──────── └──────── └──────── │ 5/5 ─ │ │ 103 │ 2/2 ─ │ │ 103 │ │ 101 │ └─────── │ │ 103 │ │ > │ │ 103 │ ┌───────
|
||||||
|
│ > │ └────── │ > │ └────── │ │ 102 │ ┌─────── │ └───── │ └─────── │ └─────── │ │ 101
|
||||||
|
└──────── └──────── └──────── │ 2/2 ─ │ │ 103 │ │ 101 └─────── └───────── │ ┌─────── │ │ 102
|
||||||
|
│ > │ └────── │ │ 102 │ │ > │ │ 103
|
||||||
|
└──────── └──────── │ │ 103 │ └─────── │ └───────
|
||||||
|
│ └─────── └───────── └─────────
|
||||||
|
└─────────
|
||||||
|
BLOCK
|
||||||
|
|
||||||
|
expects = []
|
||||||
|
output.each_line.first.scan(/\S+/) do
|
||||||
|
offset = Regexp.last_match.offset(0)
|
||||||
|
expects << output.lines.filter_map { it[offset[0]...offset[1]]&.strip }.take_while { !it.empty? }.join("\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
suffixes.zip(expects).each do |suffix, block|
|
||||||
|
tmux.send_keys(prefix + suffix, :Enter)
|
||||||
|
tmux.until { assert_block(block, it) }
|
||||||
|
|
||||||
|
teardown
|
||||||
|
setup
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
547
test/test_preview.rb
Normal file
547
test/test_preview.rb
Normal file
@@ -0,0 +1,547 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require_relative 'lib/common'
|
||||||
|
|
||||||
|
# Test cases for preview
|
||||||
|
class TestPreview < TestInteractive
|
||||||
|
def test_preview
|
||||||
|
tmux.send_keys %(seq 1000 | sed s/^2$// | #{FZF} -m --preview 'sleep 0.2; echo {{}-{+}}' --bind ?:toggle-preview), :Enter
|
||||||
|
tmux.until { |lines| assert_includes lines[1], ' {1-1} ' }
|
||||||
|
tmux.send_keys :Up
|
||||||
|
tmux.until { |lines| assert_includes lines[1], ' {-} ' }
|
||||||
|
tmux.send_keys '555'
|
||||||
|
tmux.until { |lines| assert_includes lines[1], ' {555-555} ' }
|
||||||
|
tmux.send_keys '?'
|
||||||
|
tmux.until { |lines| refute_includes lines[1], ' {555-555} ' }
|
||||||
|
tmux.send_keys '?'
|
||||||
|
tmux.until { |lines| assert_includes lines[1], ' {555-555} ' }
|
||||||
|
tmux.send_keys :BSpace
|
||||||
|
tmux.until { |lines| assert lines[-2]&.start_with?(' 28/1000 ') }
|
||||||
|
tmux.send_keys 'foobar'
|
||||||
|
tmux.until { |lines| refute_includes lines[1], ' {55-55} ' }
|
||||||
|
tmux.send_keys 'C-u'
|
||||||
|
tmux.until { |lines| assert_equal 1000, lines.match_count }
|
||||||
|
tmux.until { |lines| assert_includes lines[1], ' {1-1} ' }
|
||||||
|
tmux.send_keys :BTab
|
||||||
|
tmux.until { |lines| assert_includes lines[1], ' {-1} ' }
|
||||||
|
tmux.send_keys :BTab
|
||||||
|
tmux.until { |lines| assert_includes lines[1], ' {3-1 } ' }
|
||||||
|
tmux.send_keys :BTab
|
||||||
|
tmux.until { |lines| assert_includes lines[1], ' {4-1 3} ' }
|
||||||
|
tmux.send_keys :BTab
|
||||||
|
tmux.until { |lines| assert_includes lines[1], ' {5-1 3 4} ' }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_toggle_preview_without_default_preview_command
|
||||||
|
tmux.send_keys %(seq 100 | #{FZF} --bind 'space:preview(echo [{}]),enter:toggle-preview' --preview-window up,border-double), :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 100, lines.match_count
|
||||||
|
refute_includes lines[1], '║ [1]'
|
||||||
|
end
|
||||||
|
|
||||||
|
# toggle-preview should do nothing
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { |lines| refute_includes lines[1], '║ [1]' }
|
||||||
|
tmux.send_keys :Up
|
||||||
|
tmux.until do |lines|
|
||||||
|
refute_includes lines[1], '║ [1]'
|
||||||
|
refute_includes lines[1], '║ [2]'
|
||||||
|
end
|
||||||
|
|
||||||
|
tmux.send_keys :Up
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_includes lines, '> 3'
|
||||||
|
refute_includes lines[1], '║ [3]'
|
||||||
|
end
|
||||||
|
|
||||||
|
# One-off preview action
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until { |lines| assert_includes lines[1], '║ [3]' }
|
||||||
|
|
||||||
|
# toggle-preview to hide it
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { |lines| refute_includes lines[1], '║ [3]' }
|
||||||
|
|
||||||
|
# toggle-preview again does nothing
|
||||||
|
tmux.send_keys :Enter, :Up
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_includes lines, '> 4'
|
||||||
|
refute_includes lines[1], '║ [4]'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_show_and_hide_preview
|
||||||
|
tmux.send_keys %(seq 100 | #{FZF} --preview-window hidden,border-bold --preview 'echo [{}]' --bind 'a:show-preview,b:hide-preview'), :Enter
|
||||||
|
|
||||||
|
# Hidden by default
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 100, lines.match_count
|
||||||
|
refute_includes lines[1], '┃ [1]'
|
||||||
|
end
|
||||||
|
|
||||||
|
# Show
|
||||||
|
tmux.send_keys :a
|
||||||
|
tmux.until { |lines| assert_includes lines[1], '┃ [1]' }
|
||||||
|
|
||||||
|
# Already shown
|
||||||
|
tmux.send_keys :a
|
||||||
|
tmux.send_keys :Up
|
||||||
|
tmux.until { |lines| assert_includes lines[1], '┃ [2]' }
|
||||||
|
|
||||||
|
# Hide
|
||||||
|
tmux.send_keys :b
|
||||||
|
tmux.send_keys :Up
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_includes lines, '> 3'
|
||||||
|
refute_includes lines[1], '┃ [3]'
|
||||||
|
end
|
||||||
|
|
||||||
|
# Already hidden
|
||||||
|
tmux.send_keys :b
|
||||||
|
tmux.send_keys :Up
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_includes lines, '> 4'
|
||||||
|
refute_includes lines[1], '┃ [4]'
|
||||||
|
end
|
||||||
|
|
||||||
|
# Show it again
|
||||||
|
tmux.send_keys :a
|
||||||
|
tmux.until { |lines| assert_includes lines[1], '┃ [4]' }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_preview_hidden
|
||||||
|
tmux.send_keys %(seq 1000 | #{FZF} --preview 'echo {{}-{}-$FZF_PREVIEW_LINES-$FZF_PREVIEW_COLUMNS}' --preview-window down:1:hidden --bind ?:toggle-preview), :Enter
|
||||||
|
tmux.until { |lines| assert_equal '>', lines[-1] }
|
||||||
|
tmux.send_keys '?'
|
||||||
|
tmux.until { |lines| assert_match(/ {1-1-1-[0-9]+}/, lines[-2]) }
|
||||||
|
tmux.send_keys '555'
|
||||||
|
tmux.until { |lines| assert_match(/ {555-555-1-[0-9]+}/, lines[-2]) }
|
||||||
|
tmux.send_keys '?'
|
||||||
|
tmux.until { |lines| assert_equal '> 555', lines[-1] }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_preview_size_0
|
||||||
|
tmux.send_keys %(seq 100 | #{FZF} --reverse --preview 'echo {} >> #{tempname}; echo ' --preview-window 0 --bind space:toggle-preview), :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 100, lines.match_count
|
||||||
|
assert_equal ' 100/100', lines[1]
|
||||||
|
assert_equal '> 1', lines[2]
|
||||||
|
end
|
||||||
|
wait do
|
||||||
|
assert_path_exists tempname
|
||||||
|
assert_equal %w[1], File.readlines(tempname, chomp: true)
|
||||||
|
end
|
||||||
|
tmux.send_keys :Space, :Down, :Down
|
||||||
|
tmux.until { |lines| assert_equal '> 3', lines[4] }
|
||||||
|
wait do
|
||||||
|
assert_path_exists tempname
|
||||||
|
assert_equal %w[1], File.readlines(tempname, chomp: true)
|
||||||
|
end
|
||||||
|
tmux.send_keys :Space, :Down
|
||||||
|
tmux.until { |lines| assert_equal '> 4', lines[5] }
|
||||||
|
wait do
|
||||||
|
assert_path_exists tempname
|
||||||
|
assert_equal %w[1 3 4], File.readlines(tempname, chomp: true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_preview_size_0_hidden
|
||||||
|
tmux.send_keys %(seq 100 | #{FZF} --reverse --preview 'echo {} >> #{tempname}; echo ' --preview-window 0,hidden --bind space:toggle-preview), :Enter
|
||||||
|
tmux.until { |lines| assert_equal 100, lines.match_count }
|
||||||
|
tmux.send_keys :Down, :Down
|
||||||
|
tmux.until { |lines| assert_includes lines, '> 3' }
|
||||||
|
wait { refute_path_exists tempname }
|
||||||
|
tmux.send_keys :Space
|
||||||
|
wait do
|
||||||
|
assert_path_exists tempname
|
||||||
|
assert_equal %w[3], File.readlines(tempname, chomp: true)
|
||||||
|
end
|
||||||
|
tmux.send_keys :Down
|
||||||
|
wait do
|
||||||
|
assert_equal %w[3 4], File.readlines(tempname, chomp: true)
|
||||||
|
end
|
||||||
|
tmux.send_keys :Space, :Down
|
||||||
|
tmux.until { |lines| assert_includes lines, '> 5' }
|
||||||
|
tmux.send_keys :Down
|
||||||
|
tmux.until { |lines| assert_includes lines, '> 6' }
|
||||||
|
tmux.send_keys :Space
|
||||||
|
wait do
|
||||||
|
assert_equal %w[3 4 6], File.readlines(tempname, chomp: true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_preview_flags
|
||||||
|
tmux.send_keys %(seq 10 | sed 's/^/:: /; s/$/ /' |
|
||||||
|
#{FZF} --multi --preview 'echo {{2}/{s2}/{+2}/{+s2}/{q}/{n}/{+n}}'), :Enter
|
||||||
|
tmux.until { |lines| assert_includes lines[1], ' {1/1 /1/1 //0/0} ' }
|
||||||
|
tmux.send_keys '123'
|
||||||
|
tmux.until { |lines| assert_includes lines[1], ' {////123//} ' }
|
||||||
|
tmux.send_keys 'C-u', '1'
|
||||||
|
tmux.until { |lines| assert_equal 2, lines.match_count }
|
||||||
|
tmux.until { |lines| assert_includes lines[1], ' {1/1 /1/1 /1/0/0} ' }
|
||||||
|
tmux.send_keys :BTab
|
||||||
|
tmux.until { |lines| assert_includes lines[1], ' {10/10 /1/1 /1/9/0} ' }
|
||||||
|
tmux.send_keys :BTab
|
||||||
|
tmux.until { |lines| assert_includes lines[1], ' {10/10 /1 10/1 10 /1/9/0 9} ' }
|
||||||
|
tmux.send_keys '2'
|
||||||
|
tmux.until { |lines| assert_includes lines[1], ' {//1 10/1 10 /12//0 9} ' }
|
||||||
|
tmux.send_keys '3'
|
||||||
|
tmux.until { |lines| assert_includes lines[1], ' {//1 10/1 10 /123//0 9} ' }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_preview_file
|
||||||
|
tmux.send_keys %[(echo foo bar; echo bar foo) | #{FZF} --multi --preview 'cat {+f} {+f2} {+nf} {+fn}' --print0], :Enter
|
||||||
|
tmux.until { |lines| assert_includes lines[1], ' foo barbar00 ' }
|
||||||
|
tmux.send_keys :BTab
|
||||||
|
tmux.until { |lines| assert_includes lines[1], ' foo barbar00 ' }
|
||||||
|
tmux.send_keys :BTab
|
||||||
|
tmux.until { |lines| assert_includes lines[1], ' foo barbar foobarfoo0101 ' }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_preview_q_no_match
|
||||||
|
tmux.send_keys %(: | #{FZF} --preview 'echo foo {q} foo'), :Enter
|
||||||
|
tmux.until { |lines| assert_equal 0, lines.match_count }
|
||||||
|
tmux.until { |lines| assert_includes lines[1], ' foo foo' }
|
||||||
|
tmux.send_keys 'bar'
|
||||||
|
tmux.until { |lines| assert_includes lines[1], ' foo bar foo' }
|
||||||
|
tmux.send_keys 'C-u'
|
||||||
|
tmux.until { |lines| assert_includes lines[1], ' foo foo' }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_preview_q_no_match_with_initial_query
|
||||||
|
tmux.send_keys %(: | #{FZF} --preview 'echo 1. /{q}/{q:1}/; echo 2. /{q:..}/{q:2}/{q:-1}/; echo 3. /{q:s-2}/{q:-2}/{q:x}/' --query 'foo bar'), :Enter
|
||||||
|
tmux.until { |lines| assert_equal 0, lines.match_count }
|
||||||
|
tmux.until { |lines| assert_includes lines[1], '1. /foo bar/foo/' }
|
||||||
|
tmux.until { |lines| assert_includes lines[2], '2. /foo bar/bar/bar/' }
|
||||||
|
tmux.until { |lines| assert_includes lines[3], '3. /foo /foo/{q:x}/' }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_preview_update_on_select
|
||||||
|
tmux.send_keys %(seq 10 | fzf -m --preview 'echo {+}' --bind a:toggle-all),
|
||||||
|
:Enter
|
||||||
|
tmux.until { |lines| assert_equal 10, lines.match_count }
|
||||||
|
tmux.send_keys 'a'
|
||||||
|
tmux.until { |lines| assert(lines.any? { |line| line.include?(' 1 2 3 4 5 ') }) }
|
||||||
|
tmux.send_keys 'a'
|
||||||
|
tmux.until { |lines| lines.each { |line| refute_includes line, ' 1 2 3 4 5 ' } }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_preview_correct_tab_width_after_ansi_reset_code
|
||||||
|
writelines(["\x1b[31m+\x1b[m\t\x1b[32mgreen"])
|
||||||
|
tmux.send_keys "#{FZF} --preview 'cat #{tempname}'", :Enter
|
||||||
|
tmux.until { |lines| assert_includes lines[1], ' + green ' }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_preview_bindings_with_default_preview
|
||||||
|
tmux.send_keys "seq 10 | #{FZF} --preview 'echo [{}]' --bind 'a:preview(echo [{}{}]),b:preview(echo [{}{}{}]),c:refresh-preview'", :Enter
|
||||||
|
tmux.until { |lines| lines.match_count == 10 }
|
||||||
|
tmux.until { |lines| assert_includes lines[1], '[1]' }
|
||||||
|
tmux.send_keys 'a'
|
||||||
|
tmux.until { |lines| assert_includes lines[1], '[11]' }
|
||||||
|
tmux.send_keys 'c'
|
||||||
|
tmux.until { |lines| assert_includes lines[1], '[1]' }
|
||||||
|
tmux.send_keys 'b'
|
||||||
|
tmux.until { |lines| assert_includes lines[1], '[111]' }
|
||||||
|
tmux.send_keys :Up
|
||||||
|
tmux.until { |lines| assert_includes lines[1], '[2]' }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_preview_bindings_without_default_preview
|
||||||
|
tmux.send_keys "seq 10 | #{FZF} --bind 'a:preview(echo [{}{}]),b:preview(echo [{}{}{}]),c:refresh-preview'", :Enter
|
||||||
|
tmux.until { |lines| lines.match_count == 10 }
|
||||||
|
tmux.until { |lines| refute_includes lines[1], '1' }
|
||||||
|
tmux.send_keys 'a'
|
||||||
|
tmux.until { |lines| assert_includes lines[1], '[11]' }
|
||||||
|
tmux.send_keys 'c' # does nothing
|
||||||
|
tmux.until { |lines| assert_includes lines[1], '[11]' }
|
||||||
|
tmux.send_keys 'b'
|
||||||
|
tmux.until { |lines| assert_includes lines[1], '[111]' }
|
||||||
|
tmux.send_keys 9
|
||||||
|
tmux.until { |lines| lines.match_count == 1 }
|
||||||
|
tmux.until { |lines| refute_includes lines[1], '2' }
|
||||||
|
tmux.until { |lines| assert_includes lines[1], '[111]' }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_preview_scroll_begin_constant
|
||||||
|
tmux.send_keys "echo foo 123 321 | #{FZF} --preview 'seq 1000' --preview-window left:+123", :Enter
|
||||||
|
tmux.until { |lines| assert_match %r{1/1}, lines[-2] }
|
||||||
|
tmux.until { |lines| assert_match %r{123.*123/1000}, lines[1] }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_preview_scroll_begin_expr
|
||||||
|
tmux.send_keys "echo foo 123 321 | #{FZF} --preview 'seq 1000' --preview-window left:+{3}", :Enter
|
||||||
|
tmux.until { |lines| assert_match %r{1/1}, lines[-2] }
|
||||||
|
tmux.until { |lines| assert_match %r{321.*321/1000}, lines[1] }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_preview_scroll_begin_and_offset
|
||||||
|
['echo foo 123 321', 'echo foo :123: 321'].each do |input|
|
||||||
|
tmux.send_keys "#{input} | #{FZF} --preview 'seq 1000' --preview-window left:+{2}-2", :Enter
|
||||||
|
tmux.until { |lines| assert_match %r{1/1}, lines[-2] }
|
||||||
|
tmux.until { |lines| assert_match %r{121.*121/1000}, lines[1] }
|
||||||
|
tmux.send_keys 'C-c'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_preview_clear_screen
|
||||||
|
tmux.send_keys %{seq 100 | #{FZF} --preview 'for i in $(seq 300); do (( i % 200 == 0 )) && printf "\\033[2J"; echo "[$i]"; sleep 0.001; done'}, :Enter
|
||||||
|
tmux.until { |lines| lines.match_count == 100 }
|
||||||
|
tmux.until { |lines| lines[1]&.include?('[200]') }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_preview_window_follow
|
||||||
|
file = Tempfile.new('fzf-follow')
|
||||||
|
file.sync = true
|
||||||
|
|
||||||
|
tmux.send_keys %(seq 100 | #{FZF} --preview 'echo start; tail -f "#{file.path}"' --preview-window follow --bind 'up:preview-up,down:preview-down,space:change-preview-window:follow|nofollow' --preview-window '~4'), :Enter
|
||||||
|
tmux.until { |lines| lines.match_count == 100 }
|
||||||
|
|
||||||
|
# Write to the temporary file, and check if the preview window is showing
|
||||||
|
# the last line of the file
|
||||||
|
tmux.until { |lines| assert_includes lines[1], 'start' }
|
||||||
|
3.times { file.puts _1 } # header lines
|
||||||
|
1000.times { file.puts _1 }
|
||||||
|
tmux.until { |lines| assert_includes lines[1], '/1004' }
|
||||||
|
tmux.until { |lines| assert_includes lines[-2], '999' }
|
||||||
|
|
||||||
|
# Scroll the preview window and fzf should stop following the file content
|
||||||
|
tmux.send_keys :Up
|
||||||
|
tmux.until { |lines| assert_includes lines[-2], '998' }
|
||||||
|
file.puts 'foo', 'bar'
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_includes lines[1], '/1006'
|
||||||
|
assert_includes lines[-2], '998'
|
||||||
|
end
|
||||||
|
|
||||||
|
# Scroll back to the bottom and fzf should start following the file again
|
||||||
|
%w[999 foo bar].each do |item|
|
||||||
|
wait do
|
||||||
|
tmux.send_keys :Down
|
||||||
|
tmux.until { |lines| assert_includes lines[-2], item }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
file.puts 'baz'
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_includes lines[1], '/1007'
|
||||||
|
assert_includes lines[-2], 'baz'
|
||||||
|
end
|
||||||
|
|
||||||
|
# Scroll upwards to stop following
|
||||||
|
tmux.send_keys :Up
|
||||||
|
wait { assert_includes lines[-2], 'bar' }
|
||||||
|
file.puts 'aaa'
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_includes lines[1], '/1008'
|
||||||
|
assert_includes lines[-2], 'bar'
|
||||||
|
end
|
||||||
|
|
||||||
|
# Manually enable following
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until { |lines| assert_includes lines[-2], 'aaa' }
|
||||||
|
file.puts 'bbb'
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_includes lines[1], '/1009'
|
||||||
|
assert_includes lines[-2], 'bbb'
|
||||||
|
end
|
||||||
|
|
||||||
|
# Disable following
|
||||||
|
tmux.send_keys :Space
|
||||||
|
file.puts 'ccc', 'ddd'
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_includes lines[1], '/1011'
|
||||||
|
assert_includes lines[-2], 'bbb'
|
||||||
|
end
|
||||||
|
rescue StandardError
|
||||||
|
file.close
|
||||||
|
file.unlink
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_toggle_preview_wrap
|
||||||
|
tmux.send_keys "#{FZF} --preview 'for i in $(seq $FZF_PREVIEW_COLUMNS); do echo -n .; done; echo wrapped; echo 2nd line' --bind ctrl-w:toggle-preview-wrap", :Enter
|
||||||
|
2.times do
|
||||||
|
tmux.until { |lines| assert_includes lines[2], '2nd line' }
|
||||||
|
tmux.send_keys 'C-w'
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_includes lines[2], 'wrapped'
|
||||||
|
assert_includes lines[3], '2nd line'
|
||||||
|
end
|
||||||
|
tmux.send_keys 'C-w'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_close
|
||||||
|
tmux.send_keys "seq 100 | #{FZF} --preview 'echo foo' --bind ctrl-c:close", :Enter
|
||||||
|
tmux.until { |lines| assert_equal 100, lines.match_count }
|
||||||
|
tmux.until { |lines| assert_includes lines[1], 'foo' }
|
||||||
|
tmux.send_keys 'C-c'
|
||||||
|
tmux.until { |lines| refute_includes lines[1], 'foo' }
|
||||||
|
tmux.send_keys '10'
|
||||||
|
tmux.until { |lines| assert_equal 2, lines.match_count }
|
||||||
|
tmux.send_keys 'C-c'
|
||||||
|
tmux.send_keys 'C-l', 'closed'
|
||||||
|
tmux.until { |lines| assert_includes lines[0], 'closed' }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_preview_header
|
||||||
|
tmux.send_keys "seq 100 | #{FZF} --bind ctrl-k:preview-up+preview-up,ctrl-j:preview-down+preview-down+preview-down --preview 'seq 1000' --preview-window 'top:+{1}:~3'", :Enter
|
||||||
|
tmux.until { |lines| assert_equal 100, lines.match_count }
|
||||||
|
top5 = ->(lines) { lines.drop(1).take(5).map { |s| s[/[0-9]+/] } }
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_includes lines[1], '4/1000'
|
||||||
|
assert_equal(%w[1 2 3 4 5], top5[lines])
|
||||||
|
end
|
||||||
|
tmux.send_keys '55'
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 1, lines.match_count
|
||||||
|
assert_equal(%w[1 2 3 55 56], top5[lines])
|
||||||
|
end
|
||||||
|
tmux.send_keys 'C-J'
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal(%w[1 2 3 58 59], top5[lines])
|
||||||
|
end
|
||||||
|
tmux.send_keys :BSpace
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 19, lines.match_count
|
||||||
|
assert_equal(%w[1 2 3 5 6], top5[lines])
|
||||||
|
end
|
||||||
|
tmux.send_keys 'C-K'
|
||||||
|
tmux.until { |lines| assert_equal(%w[1 2 3 4 5], top5[lines]) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_change_preview_window
|
||||||
|
tmux.send_keys "seq 1000 | #{FZF} --preview 'echo [[{}]]' --no-preview-border --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.match_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_should_not_reset_change_preview
|
||||||
|
tmux.send_keys "#{FZF} --preview-window up,border-none --bind 'start:change-preview(echo hello)' --bind 'enter:change-preview-window(border-left)'", :Enter
|
||||||
|
tmux.until { |lines| assert_includes lines, 'hello' }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { |lines| assert_includes lines, '│ hello' }
|
||||||
|
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
|
||||||
|
tmux.until { |lines| assert(lines.any? { _1.include?('100/100') }) }
|
||||||
|
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_change_preview_window_rotate_hidden
|
||||||
|
tmux.send_keys "seq 100 | #{FZF} --preview-window hidden --preview 'echo =={}==' --bind '" \
|
||||||
|
"a:change-preview-window(nohidden||down,1|)'", :Enter
|
||||||
|
tmux.until { |lines| assert_equal 100, lines.match_count }
|
||||||
|
tmux.until { |lines| refute_includes lines[1], '==1==' }
|
||||||
|
tmux.send_keys 'a'
|
||||||
|
tmux.until { |lines| assert_includes lines[1], '==1==' }
|
||||||
|
tmux.send_keys 'a'
|
||||||
|
tmux.until { |lines| refute_includes lines[1], '==1==' }
|
||||||
|
tmux.send_keys 'a'
|
||||||
|
tmux.until { |lines| assert_includes lines[-2], '==1==' }
|
||||||
|
tmux.send_keys 'a'
|
||||||
|
tmux.until { |lines| refute_includes lines[-2], '==1==' }
|
||||||
|
tmux.send_keys 'a'
|
||||||
|
tmux.until { |lines| assert_includes lines[1], '==1==' }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_change_preview_window_rotate_hidden_down
|
||||||
|
tmux.send_keys "seq 100 | #{FZF} --bind '?:change-preview-window:up||down|' --preview 'echo =={}==' --preview-window hidden,down,1", :Enter
|
||||||
|
tmux.until { |lines| assert_equal 100, lines.match_count }
|
||||||
|
tmux.until { |lines| refute_includes lines[1], '==1==' }
|
||||||
|
tmux.send_keys '?'
|
||||||
|
tmux.until { |lines| assert_includes lines[1], '==1==' }
|
||||||
|
tmux.send_keys '?'
|
||||||
|
tmux.until { |lines| refute_includes lines[1], '==1==' }
|
||||||
|
tmux.send_keys '?'
|
||||||
|
tmux.until { |lines| assert_includes lines[-2], '==1==' }
|
||||||
|
tmux.send_keys '?'
|
||||||
|
tmux.until { |lines| refute_includes lines[-2], '==1==' }
|
||||||
|
tmux.send_keys '?'
|
||||||
|
tmux.until { |lines| assert_includes lines[1], '==1==' }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_toggle_alternative_preview_window
|
||||||
|
tmux.send_keys "seq 10 | #{FZF} --bind space:toggle-preview --preview-window '<100000(hidden,up,border-none)' --preview 'echo /{}/{}/'", :Enter
|
||||||
|
tmux.until { |lines| assert_equal 10, lines.match_count }
|
||||||
|
tmux.until { |lines| refute_includes lines, '/1/1/' }
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until { |lines| assert_includes lines, '/1/1/' }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_alternative_preview_window_opts
|
||||||
|
tmux.send_keys "seq 10 | #{FZF} --preview-border rounded --preview-window '~5,2,+0,<100000(~0,+100,wrap,noinfo)' --preview 'seq 1000'", :Enter
|
||||||
|
tmux.until { |lines| assert_equal 10, lines.match_count }
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal ['╭────╮', '│ 10 │', '│ 0 │', '│ 10 │', '│ 1 │'], lines.take(5).map(&:strip)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_preview_window_width_exception
|
||||||
|
tmux.send_keys "seq 10 | #{FZF} --scrollbar --preview-window border-left --border --preview 'seq 1000'", :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert lines[1]&.end_with?(' 1/1000││')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_preview_window_hidden_on_focus
|
||||||
|
tmux.send_keys "seq 3 | #{FZF} --preview 'echo {}' --bind focus:hide-preview", :Enter
|
||||||
|
tmux.until { |lines| assert_includes lines, '> 1' }
|
||||||
|
tmux.send_keys :Up
|
||||||
|
tmux.until { |lines| assert_includes lines, '> 2' }
|
||||||
|
end
|
||||||
|
end
|
||||||
52
test/test_server.rb
Normal file
52
test/test_server.rb
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require_relative 'lib/common'
|
||||||
|
|
||||||
|
# Test cases for API server
|
||||||
|
class TestServer < TestInteractive
|
||||||
|
def test_listen
|
||||||
|
{ '--listen 6266' => -> { URI('http://localhost:6266') },
|
||||||
|
"--listen --sync --bind 'start:execute-silent:echo $FZF_PORT > /tmp/fzf-port'" =>
|
||||||
|
-> { URI("http://localhost:#{File.read('/tmp/fzf-port').chomp}") } }.each do |opts, fn|
|
||||||
|
tmux.send_keys "seq 10 | fzf #{opts}", :Enter
|
||||||
|
tmux.until { |lines| assert_equal 10, lines.match_count }
|
||||||
|
state = JSON.parse(Net::HTTP.get(fn.call), symbolize_names: true)
|
||||||
|
assert_equal 10, state[:totalCount]
|
||||||
|
assert_equal 10, state[:matchCount]
|
||||||
|
assert_empty state[:query]
|
||||||
|
assert_equal({ index: 0, text: '1' }, state[:current])
|
||||||
|
|
||||||
|
Net::HTTP.post(fn.call, 'change-query(yo)+reload(seq 100)+change-prompt:hundred> ')
|
||||||
|
tmux.until { |lines| assert_equal 100, lines.item_count }
|
||||||
|
tmux.until { |lines| assert_equal 'hundred> yo', lines[-1] }
|
||||||
|
state = JSON.parse(Net::HTTP.get(fn.call), symbolize_names: true)
|
||||||
|
assert_equal 100, state[:totalCount]
|
||||||
|
assert_equal 0, state[:matchCount]
|
||||||
|
assert_equal 'yo', state[:query]
|
||||||
|
assert_nil state[:current]
|
||||||
|
|
||||||
|
teardown
|
||||||
|
setup
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_listen_with_api_key
|
||||||
|
post_uri = URI('http://localhost:6266')
|
||||||
|
tmux.send_keys 'seq 10 | FZF_API_KEY=123abc fzf --listen 6266', :Enter
|
||||||
|
tmux.until { |lines| assert_equal 10, lines.match_count }
|
||||||
|
# Incorrect API Key
|
||||||
|
[nil, { 'x-api-key' => '' }, { 'x-api-key' => '124abc' }].each do |headers|
|
||||||
|
res = Net::HTTP.post(post_uri, 'change-query(yo)+reload(seq 100)+change-prompt:hundred> ', headers)
|
||||||
|
assert_equal '401', res.code
|
||||||
|
assert_equal 'Unauthorized', res.message
|
||||||
|
assert_equal "invalid api key\n", res.body
|
||||||
|
end
|
||||||
|
# Valid API Key
|
||||||
|
[{ 'x-api-key' => '123abc' }, { 'X-API-Key' => '123abc' }].each do |headers|
|
||||||
|
res = Net::HTTP.post(post_uri, 'change-query(yo)+reload(seq 100)+change-prompt:hundred> ', headers)
|
||||||
|
assert_equal '200', res.code
|
||||||
|
tmux.until { |lines| assert_equal 100, lines.item_count }
|
||||||
|
tmux.until { |lines| assert_equal 'hundred> yo', lines[-1] }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
485
test/test_shell_integration.rb
Normal file
485
test/test_shell_integration.rb
Normal file
@@ -0,0 +1,485 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require_relative 'lib/common'
|
||||||
|
|
||||||
|
# Testing shell integration
|
||||||
|
module TestShell
|
||||||
|
attr_reader :tmux
|
||||||
|
|
||||||
|
def setup
|
||||||
|
@tmux = Tmux.new(shell)
|
||||||
|
tmux.prepare
|
||||||
|
end
|
||||||
|
|
||||||
|
def teardown
|
||||||
|
@tmux.kill
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_var(name, val)
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys "export #{name}='#{val}'", :Enter
|
||||||
|
tmux.prepare
|
||||||
|
end
|
||||||
|
|
||||||
|
def unset_var(name)
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys "unset #{name}", :Enter
|
||||||
|
tmux.prepare
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_ctrl_t
|
||||||
|
set_var('FZF_CTRL_T_COMMAND', 'seq 100')
|
||||||
|
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys 'C-t'
|
||||||
|
tmux.until { |lines| assert_equal 100, lines.match_count }
|
||||||
|
tmux.send_keys :Tab, :Tab, :Tab
|
||||||
|
tmux.until { |lines| assert lines.any_include?(' (3)') }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { |lines| assert lines.any_include?('1 2 3') }
|
||||||
|
tmux.send_keys 'C-c'
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_ctrl_t_unicode
|
||||||
|
writelines(['fzf-unicode 테스트1', 'fzf-unicode 테스트2'])
|
||||||
|
set_var('FZF_CTRL_T_COMMAND', "cat #{tempname}")
|
||||||
|
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys 'echo ', 'C-t'
|
||||||
|
tmux.until { |lines| assert_equal 2, lines.match_count }
|
||||||
|
tmux.send_keys 'fzf-unicode'
|
||||||
|
tmux.until { |lines| assert_equal 2, lines.match_count }
|
||||||
|
|
||||||
|
tmux.send_keys '1'
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||||
|
tmux.send_keys :Tab
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.select_count }
|
||||||
|
|
||||||
|
tmux.send_keys :BSpace
|
||||||
|
tmux.until { |lines| assert_equal 2, lines.match_count }
|
||||||
|
|
||||||
|
tmux.send_keys '2'
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||||
|
tmux.send_keys :Tab
|
||||||
|
tmux.until { |lines| assert_equal 2, lines.select_count }
|
||||||
|
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { |lines| assert_match(/echo .*fzf-unicode.*1.* .*fzf-unicode.*2/, lines.join) }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { |lines| assert_equal 'fzf-unicode 테스트1 fzf-unicode 테스트2', lines[-1] }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_alt_c
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys :Escape, :c
|
||||||
|
lines = tmux.until { |lines| assert_operator lines.match_count, :>, 0 }
|
||||||
|
expected = lines.reverse.find { |l| l.start_with?('> ') }[2..]
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys :pwd, :Enter
|
||||||
|
tmux.until { |lines| assert lines[-1]&.end_with?(expected) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_alt_c_command
|
||||||
|
set_var('FZF_ALT_C_COMMAND', 'echo /tmp')
|
||||||
|
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys 'cd /', :Enter
|
||||||
|
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys :Escape, :c
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys :pwd, :Enter
|
||||||
|
tmux.until { |lines| assert_equal '/tmp', lines[-1] }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_ctrl_r
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys 'echo 1st', :Enter
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys 'echo 2nd', :Enter
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys 'echo 3d', :Enter
|
||||||
|
tmux.prepare
|
||||||
|
3.times do
|
||||||
|
tmux.send_keys 'echo 3rd', :Enter
|
||||||
|
tmux.prepare
|
||||||
|
end
|
||||||
|
tmux.send_keys 'echo 4th', :Enter
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys 'C-r'
|
||||||
|
tmux.until { |lines| assert_operator lines.match_count, :>, 0 }
|
||||||
|
tmux.send_keys 'e3d'
|
||||||
|
# Duplicates removed: 3d (1) + 3rd (1) => 2 matches
|
||||||
|
tmux.until { |lines| assert_equal 2, lines.match_count }
|
||||||
|
tmux.until { |lines| assert lines[-3]&.end_with?(' echo 3d') }
|
||||||
|
tmux.send_keys 'C-r'
|
||||||
|
tmux.until { |lines| assert lines[-3]&.end_with?(' echo 3rd') }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { |lines| assert_equal 'echo 3rd', lines[-1] }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { |lines| assert_equal '3rd', lines[-1] }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_ctrl_r_multiline
|
||||||
|
# NOTE: Current bash implementation shows an extra new line if there's
|
||||||
|
# only entry in the history
|
||||||
|
tmux.send_keys ':', :Enter
|
||||||
|
tmux.send_keys 'echo "foo', :Enter, 'bar"', :Enter
|
||||||
|
tmux.until { |lines| assert_equal %w[foo bar], lines[-2..] }
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys 'C-r'
|
||||||
|
tmux.until { |lines| assert_equal '>', lines[-1] }
|
||||||
|
tmux.send_keys 'foo bar'
|
||||||
|
tmux.until { |lines| assert_includes lines[-4], '"foo' } unless shell == :zsh
|
||||||
|
tmux.until { |lines| assert lines[-3]&.match?(/bar"␊?/) }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { |lines| assert lines[-1]&.match?(/bar"␊?/) }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { |lines| assert_equal %w[foo bar], lines[-2..] }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_ctrl_r_abort
|
||||||
|
skip("doesn't restore the original line when search is aborted pre Bash 4") if shell == :bash && `#{Shell.bash} --version`[/(?<= version )\d+/].to_i < 4
|
||||||
|
%w[foo ' "].each do |query|
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys :Enter, query
|
||||||
|
tmux.until { |lines| assert lines[-1]&.start_with?(query) }
|
||||||
|
tmux.send_keys 'C-r'
|
||||||
|
tmux.until { |lines| assert_equal "> #{query}", lines[-1] }
|
||||||
|
tmux.send_keys 'C-g'
|
||||||
|
tmux.until { |lines| assert lines[-1]&.start_with?(query) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
module CompletionTest
|
||||||
|
def test_file_completion
|
||||||
|
FileUtils.mkdir_p('/tmp/fzf-test')
|
||||||
|
FileUtils.mkdir_p('/tmp/fzf test')
|
||||||
|
(1..100).each { |i| FileUtils.touch("/tmp/fzf-test/#{i}") }
|
||||||
|
['no~such~user', '/tmp/fzf test/foobar'].each do |f|
|
||||||
|
FileUtils.touch(File.expand_path(f))
|
||||||
|
end
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys 'cat /tmp/fzf-test/10**', :Tab
|
||||||
|
tmux.until { |lines| assert_operator lines.match_count, :>, 0 }
|
||||||
|
tmux.send_keys ' !d'
|
||||||
|
tmux.until { |lines| assert_equal 2, lines.match_count }
|
||||||
|
tmux.send_keys :Tab, :Tab
|
||||||
|
tmux.until { |lines| assert_equal 2, lines.select_count }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until(true) do |lines|
|
||||||
|
assert_equal 'cat /tmp/fzf-test/10 /tmp/fzf-test/100', lines[-1]
|
||||||
|
end
|
||||||
|
|
||||||
|
# ~USERNAME**<TAB>
|
||||||
|
user = `whoami`.chomp
|
||||||
|
tmux.send_keys 'C-u'
|
||||||
|
tmux.send_keys "cat ~#{user}**", :Tab
|
||||||
|
tmux.until { |lines| assert_operator lines.match_count, :>, 0 }
|
||||||
|
tmux.send_keys "/#{user}"
|
||||||
|
tmux.until { |lines| assert(lines.any? { |l| l.end_with?("/#{user}") }) }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until(true) do |lines|
|
||||||
|
assert_match %r{cat .*/#{user}}, lines[-1]
|
||||||
|
end
|
||||||
|
|
||||||
|
# ~INVALID_USERNAME**<TAB>
|
||||||
|
tmux.send_keys 'C-u'
|
||||||
|
tmux.send_keys 'cat ~such**', :Tab
|
||||||
|
tmux.until(true) { |lines| assert lines.any_include?('no~such~user') }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until(true) { |lines| assert_equal 'cat no~such~user', lines[-1] }
|
||||||
|
|
||||||
|
# /tmp/fzf\ test**<TAB>
|
||||||
|
tmux.send_keys 'C-u'
|
||||||
|
tmux.send_keys 'cat /tmp/fzf\ test/**', :Tab
|
||||||
|
tmux.until { |lines| assert_operator lines.match_count, :>, 0 }
|
||||||
|
tmux.send_keys 'foobar$'
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 1, lines.match_count
|
||||||
|
assert lines.any_include?('> /tmp/fzf test/foobar')
|
||||||
|
end
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until(true) { |lines| assert_equal 'cat /tmp/fzf\ test/foobar', lines[-1] }
|
||||||
|
|
||||||
|
# Should include hidden files
|
||||||
|
(1..100).each { |i| FileUtils.touch("/tmp/fzf-test/.hidden-#{i}") }
|
||||||
|
tmux.send_keys 'C-u'
|
||||||
|
tmux.send_keys 'cat /tmp/fzf-test/hidden**', :Tab
|
||||||
|
tmux.until(true) do |lines|
|
||||||
|
assert_equal 100, lines.match_count
|
||||||
|
assert lines.any_include?('/tmp/fzf-test/.hidden-')
|
||||||
|
end
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
ensure
|
||||||
|
['/tmp/fzf-test', '/tmp/fzf test', '~/.fzf-home', 'no~such~user'].each do |f|
|
||||||
|
FileUtils.rm_rf(File.expand_path(f))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_file_completion_root
|
||||||
|
tmux.send_keys 'ls /**', :Tab
|
||||||
|
tmux.until { |lines| assert_operator lines.match_count, :>, 0 }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_dir_completion
|
||||||
|
(1..100).each do |idx|
|
||||||
|
FileUtils.mkdir_p("/tmp/fzf-test/d#{idx}")
|
||||||
|
end
|
||||||
|
FileUtils.touch('/tmp/fzf-test/d55/xxx')
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys 'cd /tmp/fzf-test/**', :Tab
|
||||||
|
tmux.until { |lines| assert_operator lines.match_count, :>, 0 }
|
||||||
|
tmux.send_keys :Tab, :Tab # Tab does not work here
|
||||||
|
tmux.send_keys 55
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 1, lines.match_count
|
||||||
|
assert_includes lines, '> 55'
|
||||||
|
assert_includes lines, '> /tmp/fzf-test/d55'
|
||||||
|
end
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until(true) { |lines| assert_equal 'cd /tmp/fzf-test/d55/', lines[-1] }
|
||||||
|
tmux.send_keys :xx
|
||||||
|
tmux.until { |lines| assert_equal 'cd /tmp/fzf-test/d55/xx', lines[-1] }
|
||||||
|
|
||||||
|
# Should not match regular files (bash-only)
|
||||||
|
if instance_of?(TestBash)
|
||||||
|
tmux.send_keys :Tab
|
||||||
|
tmux.until { |lines| assert_equal 'cd /tmp/fzf-test/d55/xx', lines[-1] }
|
||||||
|
end
|
||||||
|
|
||||||
|
# Fail back to plusdirs
|
||||||
|
tmux.send_keys :BSpace, :BSpace, :BSpace
|
||||||
|
tmux.until { |lines| assert_equal 'cd /tmp/fzf-test/d55', lines[-1] }
|
||||||
|
tmux.send_keys :Tab
|
||||||
|
tmux.until { |lines| assert_equal 'cd /tmp/fzf-test/d55/', lines[-1] }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_process_completion
|
||||||
|
tmux.send_keys 'sleep 12345 &', :Enter
|
||||||
|
lines = tmux.until { |lines| assert lines[-1]&.start_with?('[1] ') }
|
||||||
|
pid = lines[-1]&.split&.last
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys 'C-L'
|
||||||
|
tmux.send_keys 'kill **', :Tab
|
||||||
|
tmux.until { |lines| assert_operator lines.match_count, :>, 0 }
|
||||||
|
tmux.send_keys 'sleep12345'
|
||||||
|
tmux.until { |lines| assert lines.any_include?('sleep 12345') }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until(true) { |lines| assert_equal "kill #{pid}", lines[-1] }
|
||||||
|
ensure
|
||||||
|
if pid
|
||||||
|
begin
|
||||||
|
Process.kill('KILL', pid.to_i)
|
||||||
|
rescue StandardError
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_custom_completion
|
||||||
|
tmux.send_keys '_fzf_compgen_path() { echo "$1"; seq 10; }', :Enter
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys 'ls /tmp/**', :Tab
|
||||||
|
tmux.until { |lines| assert_equal 11, lines.match_count }
|
||||||
|
tmux.send_keys :Tab, :Tab, :Tab
|
||||||
|
tmux.until { |lines| assert_equal 3, lines.select_count }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until(true) { |lines| assert_equal 'ls /tmp 1 2', lines[-1] }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_unset_completion
|
||||||
|
tmux.send_keys 'export FZFFOOBAR=BAZ', :Enter
|
||||||
|
tmux.prepare
|
||||||
|
|
||||||
|
# Using tmux
|
||||||
|
tmux.send_keys 'unset FZFFOOBR**', :Tab
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { |lines| assert_equal 'unset FZFFOOBAR', lines[-1] }
|
||||||
|
tmux.send_keys 'C-c'
|
||||||
|
|
||||||
|
# FZF_TMUX=1
|
||||||
|
new_shell
|
||||||
|
tmux.focus
|
||||||
|
tmux.send_keys 'unset FZFFOOBR**', :Tab
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { |lines| assert_equal 'unset FZFFOOBAR', lines[-1] }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_completion_in_command_sequence
|
||||||
|
tmux.send_keys 'export FZFFOOBAR=BAZ', :Enter
|
||||||
|
tmux.prepare
|
||||||
|
|
||||||
|
triggers = ['**', '~~', '++', 'ff', '/']
|
||||||
|
triggers.push('&', '[', ';', '`') if instance_of?(TestZsh)
|
||||||
|
|
||||||
|
triggers.each do |trigger|
|
||||||
|
set_var('FZF_COMPLETION_TRIGGER', trigger)
|
||||||
|
command = "echo foo; QUX=THUD unset FZFFOOBR#{trigger}"
|
||||||
|
tmux.send_keys command.sub(/(;|`)$/, '\\\\\1'), :Tab
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { |lines| assert_equal 'echo foo; QUX=THUD unset FZFFOOBAR', lines[-1] }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_file_completion_unicode
|
||||||
|
FileUtils.mkdir_p('/tmp/fzf-test')
|
||||||
|
tmux.paste "cd /tmp/fzf-test; echo test3 > $'fzf-unicode \\355\\205\\214\\354\\212\\244\\355\\212\\2701'; echo test4 > $'fzf-unicode \\355\\205\\214\\354\\212\\244\\355\\212\\2702'"
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys 'cat fzf-unicode**', :Tab
|
||||||
|
tmux.until { |lines| assert_equal 2, lines.match_count }
|
||||||
|
|
||||||
|
tmux.send_keys '1'
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||||
|
tmux.send_keys :Tab
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.select_count }
|
||||||
|
|
||||||
|
tmux.send_keys :BSpace
|
||||||
|
tmux.until { |lines| assert_equal 2, lines.match_count }
|
||||||
|
|
||||||
|
tmux.send_keys '2'
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.select_count }
|
||||||
|
tmux.send_keys :Tab
|
||||||
|
tmux.until { |lines| assert_equal 2, lines.select_count }
|
||||||
|
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until(true) { |lines| assert_match(/cat .*fzf-unicode.*1.* .*fzf-unicode.*2/, lines[-1]) }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { |lines| assert_equal %w[test3 test4], lines[-2..] }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_custom_completion_api
|
||||||
|
tmux.send_keys 'eval "_fzf$(declare -f _comprun)"', :Enter
|
||||||
|
%w[f g].each do |command|
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys "#{command} b**", :Tab
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 2, lines.item_count
|
||||||
|
assert_equal 1, lines.match_count
|
||||||
|
assert lines.any_include?("prompt-#{command}")
|
||||||
|
assert lines.any_include?("preview-#{command}-bar")
|
||||||
|
end
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { |lines| assert_equal "#{command} #{command}barbar", lines[-1] }
|
||||||
|
tmux.send_keys 'C-u'
|
||||||
|
end
|
||||||
|
ensure
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys 'unset -f _fzf_comprun', :Enter
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_ssh_completion
|
||||||
|
(1..5).each { |i| FileUtils.touch("/tmp/fzf-test-ssh-#{i}") }
|
||||||
|
|
||||||
|
tmux.send_keys 'ssh jg@localhost**', :Tab
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_operator lines.match_count, :>=, 1
|
||||||
|
end
|
||||||
|
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { |lines| assert lines.any_include?('ssh jg@localhost') }
|
||||||
|
tmux.send_keys ' -i /tmp/fzf-test-ssh**', :Tab
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_operator lines.match_count, :>=, 5
|
||||||
|
assert_equal 0, lines.select_count
|
||||||
|
end
|
||||||
|
tmux.send_keys :Tab, :Tab, :Tab
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 3, lines.select_count
|
||||||
|
end
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { |lines| assert lines.any_include?('ssh jg@localhost -i /tmp/fzf-test-ssh-') }
|
||||||
|
|
||||||
|
tmux.send_keys 'localhost**', :Tab
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_operator lines.match_count, :>=, 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class TestBash < TestBase
|
||||||
|
include TestShell
|
||||||
|
include CompletionTest
|
||||||
|
|
||||||
|
def shell
|
||||||
|
:bash
|
||||||
|
end
|
||||||
|
|
||||||
|
def new_shell
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys "FZF_TMUX=1 #{Shell.bash}", :Enter
|
||||||
|
tmux.prepare
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_dynamic_completion_loader
|
||||||
|
tmux.paste 'touch /tmp/foo; _fzf_completion_loader=1'
|
||||||
|
tmux.paste '_completion_loader() { complete -o default fake; }'
|
||||||
|
tmux.paste 'complete -F _fzf_path_completion -o default -o bashdefault fake'
|
||||||
|
tmux.send_keys 'fake /tmp/foo**', :Tab
|
||||||
|
tmux.until { |lines| assert_operator lines.match_count, :>, 0 }
|
||||||
|
tmux.send_keys 'C-c'
|
||||||
|
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys 'fake /tmp/foo'
|
||||||
|
tmux.send_keys :Tab, 'C-u'
|
||||||
|
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys 'fake /tmp/foo**', :Tab
|
||||||
|
tmux.until { |lines| assert_operator lines.match_count, :>, 0 }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class TestZsh < TestBase
|
||||||
|
include TestShell
|
||||||
|
include CompletionTest
|
||||||
|
|
||||||
|
def shell
|
||||||
|
:zsh
|
||||||
|
end
|
||||||
|
|
||||||
|
def new_shell
|
||||||
|
tmux.send_keys "FZF_TMUX=1 #{Shell.zsh}", :Enter
|
||||||
|
tmux.prepare
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_complete_quoted_command
|
||||||
|
tmux.send_keys 'export FZFFOOBAR=BAZ', :Enter
|
||||||
|
['unset', '\unset', "'unset'"].each do |command|
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys "#{command} FZFFOOBR**", :Tab
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { |lines| assert_equal "#{command} FZFFOOBAR", lines[-1] }
|
||||||
|
tmux.send_keys 'C-c'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class TestFish < TestBase
|
||||||
|
include TestShell
|
||||||
|
|
||||||
|
def shell
|
||||||
|
:fish
|
||||||
|
end
|
||||||
|
|
||||||
|
def new_shell
|
||||||
|
tmux.send_keys 'env FZF_TMUX=1 FZF_DEFAULT_OPTS=--no-scrollbar fish', :Enter
|
||||||
|
tmux.send_keys 'function fish_prompt; end; clear', :Enter
|
||||||
|
tmux.until { |lines| assert_empty lines }
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_var(name, val)
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys "set -g #{name} '#{val}'", :Enter
|
||||||
|
tmux.prepare
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -13,7 +13,7 @@ Execute (fzf#run with dir option):
|
|||||||
|
|
||||||
execute 'lcd' fnameescape(cwd)
|
execute 'lcd' fnameescape(cwd)
|
||||||
let result = sort(fzf#run({ 'source': 'git ls-files', 'options': '--filter e', 'dir': g:dir }))
|
let result = sort(fzf#run({ 'source': 'git ls-files', 'options': '--filter e', 'dir': g:dir }))
|
||||||
AssertEqual ['fzf.vader', 'test_go.rb'], result
|
AssertEqual ['fzf.vader'], result
|
||||||
AssertEqual 1, haslocaldir()
|
AssertEqual 1, haslocaldir()
|
||||||
AssertEqual getcwd(), cwd
|
AssertEqual getcwd(), cwd
|
||||||
|
|
||||||
@@ -23,8 +23,8 @@ Execute (fzf#run with Funcref command):
|
|||||||
call add(g:ret, a:e)
|
call add(g:ret, a:e)
|
||||||
endfunction
|
endfunction
|
||||||
let result = sort(fzf#run({ 'source': 'git ls-files', 'sink': function('g:FzfTest'), 'options': '--filter e', 'dir': g:dir }))
|
let result = sort(fzf#run({ 'source': 'git ls-files', 'sink': function('g:FzfTest'), 'options': '--filter e', 'dir': g:dir }))
|
||||||
AssertEqual ['fzf.vader', 'test_go.rb'], result
|
AssertEqual ['fzf.vader'], result
|
||||||
AssertEqual ['fzf.vader', 'test_go.rb'], sort(g:ret)
|
AssertEqual ['fzf.vader'], sort(g:ret)
|
||||||
|
|
||||||
Execute (fzf#run with string source):
|
Execute (fzf#run with string source):
|
||||||
let result = sort(fzf#run({ 'source': 'echo hi', 'options': '-f i' }))
|
let result = sort(fzf#run({ 'source': 'echo hi', 'options': '-f i' }))
|
||||||
@@ -78,18 +78,18 @@ Execute (fzf#wrap):
|
|||||||
|
|
||||||
let opts = fzf#wrap('foobar')
|
let opts = fzf#wrap('foobar')
|
||||||
Log opts
|
Log opts
|
||||||
AssertEqual '~40%', opts.down
|
AssertEqual 0.9, opts.window.width
|
||||||
Assert opts.options =~ '--expect='
|
Assert opts.options =~ '--expect='
|
||||||
Assert !has_key(opts, 'sink')
|
Assert !has_key(opts, 'sink')
|
||||||
Assert has_key(opts, 'sink*')
|
Assert has_key(opts, 'sink*')
|
||||||
|
|
||||||
let opts = fzf#wrap('foobar', {}, 0)
|
let opts = fzf#wrap('foobar', {}, 0)
|
||||||
Log opts
|
Log opts
|
||||||
AssertEqual '~40%', opts.down
|
AssertEqual 0.9, opts.window.width
|
||||||
|
|
||||||
let opts = fzf#wrap('foobar', {}, 1)
|
let opts = fzf#wrap('foobar', {}, 1)
|
||||||
Log opts
|
Log opts
|
||||||
Assert !has_key(opts, 'down')
|
Assert !has_key(opts, 'window')
|
||||||
|
|
||||||
let opts = fzf#wrap('foobar', {'down': '50%'})
|
let opts = fzf#wrap('foobar', {'down': '50%'})
|
||||||
Log opts
|
Log opts
|
||||||
@@ -148,7 +148,7 @@ Execute (fzf#wrap):
|
|||||||
|
|
||||||
let g:fzf_colors = { 'fg': ['fg', 'Error'] }
|
let g:fzf_colors = { 'fg': ['fg', 'Error'] }
|
||||||
let opts = fzf#wrap({})
|
let opts = fzf#wrap({})
|
||||||
Assert opts.options =~ '^--color=fg:'
|
Assert opts.options =~ '--color=fg:'
|
||||||
|
|
||||||
Execute (fzf#shellescape with sh):
|
Execute (fzf#shellescape with sh):
|
||||||
AssertEqual '''''', fzf#shellescape('', 'sh')
|
AssertEqual '''''', fzf#shellescape('', 'sh')
|
||||||
Reference in New Issue
Block a user