mirror of
https://github.com/junegunn/fzf.git
synced 2025-09-02 05:13:49 -07:00
Compare commits
35 Commits
history-hi
...
v0.55.0
Author | SHA1 | Date | |
---|---|---|---|
|
fc69308057 | ||
|
c6d620c99e | ||
|
f510a4def6 | ||
|
4ae3069c6f | ||
|
c0f27751d3 | ||
|
efbcd5a683 | ||
|
6a67712944 | ||
|
e8a690928d | ||
|
0eee95af57 | ||
|
a09c6e991a | ||
|
d06c5ab990 | ||
|
e0924d27b8 | ||
|
2775b771f2 | ||
|
cf7a3c6a0e | ||
|
230cc6acc3 | ||
|
626a23a585 | ||
|
74f196eebb | ||
|
cf2242aea3 | ||
|
8cb59e6fca | ||
|
5cce17e80a | ||
|
ee5302fb2d | ||
|
387c6ef664 | ||
|
581734c369 | ||
|
d90a969c00 | ||
|
2c8a96bb27 | ||
|
5da168db30 | ||
|
e215e2daf3 | ||
|
e28f5aa45b | ||
|
a2d0e8f233 | ||
|
303d04106a | ||
|
c423c496a1 | ||
|
4e85f72f0e | ||
|
dd0737aac0 | ||
|
f90985845d | ||
|
af4917dbb6 |
2
.github/workflows/linux.yml
vendored
2
.github/workflows/linux.yml
vendored
@@ -36,7 +36,7 @@ jobs:
|
|||||||
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.17.0 rubocop:1.43.0 rubocop-minitest:0.25.1 rubocop-performance:1.15.2
|
run: sudo gem install --no-document minitest:5.25.1 rubocop:1.65.0 rubocop-minitest:0.35.1 rubocop-performance:1.21.1
|
||||||
|
|
||||||
- name: Rubocop
|
- name: Rubocop
|
||||||
run: rubocop --require rubocop-minitest --require rubocop-performance
|
run: rubocop --require rubocop-minitest --require rubocop-performance
|
||||||
|
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.23.1
|
- uses: crate-ci/typos@v1.24.1
|
||||||
|
@@ -6,7 +6,7 @@ Lint/ShadowingOuterLocalVariable:
|
|||||||
Enabled: false
|
Enabled: false
|
||||||
Style/MethodCallWithArgsParentheses:
|
Style/MethodCallWithArgsParentheses:
|
||||||
Enabled: true
|
Enabled: true
|
||||||
IgnoredMethods:
|
AllowedMethods:
|
||||||
- assert
|
- assert
|
||||||
- exit
|
- exit
|
||||||
- paste
|
- paste
|
||||||
@@ -15,7 +15,7 @@ Style/MethodCallWithArgsParentheses:
|
|||||||
- refute
|
- refute
|
||||||
- require
|
- require
|
||||||
- send_keys
|
- send_keys
|
||||||
IgnoredPatterns:
|
AllowedPatterns:
|
||||||
- ^assert_
|
- ^assert_
|
||||||
- ^refute_
|
- ^refute_
|
||||||
Style/NumericPredicate:
|
Style/NumericPredicate:
|
||||||
|
44
CHANGELOG.md
44
CHANGELOG.md
@@ -1,9 +1,51 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
0.55.0
|
||||||
|
------
|
||||||
|
_Release highlights: https://junegunn.github.io/fzf/releases/0.55.0/_
|
||||||
|
|
||||||
|
- Added `exact-boundary-match` type to the search syntax. When a search term is single-quoted, fzf will search for the exact occurrences of the string with both ends at word boundaries.
|
||||||
|
```sh
|
||||||
|
fzf --query "'here'" << EOF
|
||||||
|
come here
|
||||||
|
not there
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
- [bash] Fuzzy path completion is enabled for all commands
|
||||||
|
- 1. If the default completion is not already set
|
||||||
|
- 2. And if the current bash supports `complete -D` option
|
||||||
|
- However, fuzzy completion for some commands can be "dynamically" disabled by the dynamic completion loader
|
||||||
|
- See the comment in `__fzf_default_completion` function for more information
|
||||||
|
- Comments are now allowed in `$FZF_DEFAULT_OPTS` and `$FZF_DEFAULT_OPTS_FILE`
|
||||||
|
```sh
|
||||||
|
export FZF_DEFAULT_OPTS='
|
||||||
|
# Layout options
|
||||||
|
--layout=reverse
|
||||||
|
--info=inline-right # Show info on the right side of the prompt line
|
||||||
|
# ...
|
||||||
|
'
|
||||||
|
```
|
||||||
|
- Hyperlinks (OSC 8) are now supported in the preview window and in the main window
|
||||||
|
```sh
|
||||||
|
printf '<< \e]8;;http://github.com/junegunn/fzf\e\\Link to \e[32mfz\e[0mf\e]8;;\e\\ >>' | fzf --ansi
|
||||||
|
|
||||||
|
fzf --preview "printf '<< \e]8;;http://github.com/junegunn/fzf\e\\Link to \e[32mfz\e[0mf\e]8;;\e\\ >>'"
|
||||||
|
```
|
||||||
|
- The default `--ellipsis` is now `··` instead of `..`.
|
||||||
|
- [vim] A spec can have `exit` callback that is called with the exit status of fzf
|
||||||
|
- This can be used to clean up temporary resources or restore the original state when fzf is closed without a selection
|
||||||
|
- Fixed `--tmux bottom` when the status line is not at the bottom
|
||||||
|
- Fixed extra scroll offset in multi-line mode (`--read0` or `--wrap`)
|
||||||
|
- Added fallback `ps` command for `kill` completion on Cygwin
|
||||||
|
|
||||||
0.54.3
|
0.54.3
|
||||||
------
|
------
|
||||||
- Fixed incompatibility of adaptive height and 'start:reload'
|
- Fixed incompatibility of adaptive height specification and 'start:reload'
|
||||||
|
```sh
|
||||||
|
# A regression in 0.54.0 would cause this to fail
|
||||||
|
fzf --height '~100%' --bind 'start:reload:seq 10'
|
||||||
|
```
|
||||||
- Environment variables are now available to `$FZF_DEFAULT_COMMAND`
|
- Environment variables are now available to `$FZF_DEFAULT_COMMAND`
|
||||||
```sh
|
```sh
|
||||||
FZF_DEFAULT_COMMAND='echo $FZF_QUERY' fzf --query foo
|
FZF_DEFAULT_COMMAND='echo $FZF_QUERY' fzf --query foo
|
||||||
|
7
Makefile
7
Makefile
@@ -77,7 +77,6 @@ endif
|
|||||||
all: target/$(BINARY)
|
all: target/$(BINARY)
|
||||||
|
|
||||||
test: $(SOURCES)
|
test: $(SOURCES)
|
||||||
[ -z "$$(gofmt -s -d src)" ] || (gofmt -s -d src; exit 1)
|
|
||||||
SHELL=/bin/sh GOOS= $(GO) test -v -tags "$(TAGS)" \
|
SHELL=/bin/sh GOOS= $(GO) test -v -tags "$(TAGS)" \
|
||||||
github.com/junegunn/fzf/src \
|
github.com/junegunn/fzf/src \
|
||||||
github.com/junegunn/fzf/src/algo \
|
github.com/junegunn/fzf/src/algo \
|
||||||
@@ -87,6 +86,10 @@ test: $(SOURCES)
|
|||||||
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
|
||||||
|
[ -z "$$(gofmt -s -d src)" ] || (gofmt -s -d src; exit 1)
|
||||||
|
rubocop --require rubocop-minitest --require rubocop-performance
|
||||||
|
|
||||||
install: bin/fzf
|
install: bin/fzf
|
||||||
|
|
||||||
generate:
|
generate:
|
||||||
@@ -184,4 +187,4 @@ update:
|
|||||||
$(GO) get -u
|
$(GO) get -u
|
||||||
$(GO) mod tidy
|
$(GO) mod tidy
|
||||||
|
|
||||||
.PHONY: all generate build release test bench install clean docker docker-test update
|
.PHONY: all generate build release test bench lint install clean docker docker-test update
|
||||||
|
@@ -289,8 +289,9 @@ The following table summarizes the available options.
|
|||||||
| `source` | string | External command to generate input to fzf (e.g. `find .`) |
|
| `source` | string | External command to generate input to fzf (e.g. `find .`) |
|
||||||
| `source` | list | Vim list as input to fzf |
|
| `source` | list | Vim list as input to fzf |
|
||||||
| `sink` | string | Vim command to handle the selected item (e.g. `e`, `tabe`) |
|
| `sink` | string | Vim command to handle the selected item (e.g. `e`, `tabe`) |
|
||||||
| `sink` | funcref | Reference to function to process each selected item |
|
| `sink` | funcref | Function to be called with each selected item |
|
||||||
| `sinklist` (or `sink*`) | funcref | Similar to `sink`, but takes the list of output lines at once |
|
| `sinklist` (or `sink*`) | funcref | Similar to `sink`, but takes the list of output lines at once |
|
||||||
|
| `exit` | funcref | Function to be called with the exit status of fzf (e.g. 0, 1, 2, 130) |
|
||||||
| `options` | string/list | Options to fzf |
|
| `options` | string/list | Options to fzf |
|
||||||
| `dir` | string | Working directory |
|
| `dir` | string | Working directory |
|
||||||
| `up`/`down`/`left`/`right` | number/string | (Layout) Window position and size (e.g. `20`, `50%`) |
|
| `up`/`down`/`left`/`right` | number/string | (Layout) Window position and size (e.g. `20`, `50%`) |
|
||||||
|
24
bin/fzf-tmux
24
bin/fzf-tmux
@@ -19,6 +19,9 @@ term=""
|
|||||||
[[ -n "$LINES" ]] && lines=$LINES || lines=$(tput lines) || lines=$(tmux display-message -p "#{pane_height}")
|
[[ -n "$LINES" ]] && lines=$LINES || lines=$(tput lines) || lines=$(tmux display-message -p "#{pane_height}")
|
||||||
[[ -n "$COLUMNS" ]] && columns=$COLUMNS || columns=$(tput cols) || columns=$(tmux display-message -p "#{pane_width}")
|
[[ -n "$COLUMNS" ]] && columns=$COLUMNS || columns=$(tput cols) || columns=$(tmux display-message -p "#{pane_width}")
|
||||||
|
|
||||||
|
tmux_version=$(tmux -V | sed 's/[^0-9.]//g')
|
||||||
|
tmux_32=$(awk '{print ($1 >= 3.2)}' <<< "$tmux_version" 2> /dev/null || bc -l <<< "$tmux_version >= 3.2")
|
||||||
|
|
||||||
help() {
|
help() {
|
||||||
>&2 echo 'usage: fzf-tmux [LAYOUT OPTIONS] [--] [FZF OPTIONS]
|
>&2 echo 'usage: fzf-tmux [LAYOUT OPTIONS] [--] [FZF OPTIONS]
|
||||||
|
|
||||||
@@ -94,10 +97,18 @@ while [[ $# -gt 0 ]]; do
|
|||||||
opt="$opt ${arg:0:2}$size"
|
opt="$opt ${arg:0:2}$size"
|
||||||
elif [[ "$size" =~ %$ ]]; then
|
elif [[ "$size" =~ %$ ]]; then
|
||||||
size=${size:0:((${#size}-1))}
|
size=${size:0:((${#size}-1))}
|
||||||
if [[ -n "$swap" ]]; then
|
if [[ $tmux_32 = 1 ]]; then
|
||||||
opt="$opt -l $(( 100 - size ))%"
|
if [[ -n "$swap" ]]; then
|
||||||
|
opt="$opt -l $(( 100 - size ))%"
|
||||||
|
else
|
||||||
|
opt="$opt -l $size%"
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
opt="$opt -l $size%"
|
if [[ -n "$swap" ]]; then
|
||||||
|
opt="$opt -p $(( 100 - size ))"
|
||||||
|
else
|
||||||
|
opt="$opt -p $size"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
if [[ -n "$swap" ]]; then
|
if [[ -n "$swap" ]]; then
|
||||||
@@ -187,12 +198,11 @@ trap 'cleanup' EXIT
|
|||||||
|
|
||||||
envs="export TERM=$TERM "
|
envs="export TERM=$TERM "
|
||||||
if [[ "$opt" =~ "-E" ]]; then
|
if [[ "$opt" =~ "-E" ]]; then
|
||||||
tmux_version=$(tmux -V | sed 's/[^0-9.]//g')
|
if [[ $tmux_version = 3.2 ]]; then
|
||||||
if [[ $(awk '{print ($1 > 3.2)}' <<< "$tmux_version" 2> /dev/null || bc -l <<< "$tmux_version > 3.2") = 1 ]]; then
|
FZF_DEFAULT_OPTS="--margin 0,1 $FZF_DEFAULT_OPTS"
|
||||||
|
elif [[ $tmux_32 = 1 ]]; then
|
||||||
FZF_DEFAULT_OPTS="--border $FZF_DEFAULT_OPTS"
|
FZF_DEFAULT_OPTS="--border $FZF_DEFAULT_OPTS"
|
||||||
opt="-B $opt"
|
opt="-B $opt"
|
||||||
elif [[ $tmux_version = 3.2 ]]; then
|
|
||||||
FZF_DEFAULT_OPTS="--margin 0,1 $FZF_DEFAULT_OPTS"
|
|
||||||
else
|
else
|
||||||
echo "fzf-tmux: tmux 3.2 or above is required for popup mode" >&2
|
echo "fzf-tmux: tmux 3.2 or above is required for popup mode" >&2
|
||||||
exit 2
|
exit 2
|
||||||
|
@@ -306,8 +306,9 @@ The following table summarizes the available options.
|
|||||||
`source` | string | External command to generate input to fzf (e.g. `find .` )
|
`source` | string | External command to generate input to fzf (e.g. `find .` )
|
||||||
`source` | list | Vim list as input to fzf
|
`source` | list | Vim list as input to fzf
|
||||||
`sink` | string | Vim command to handle the selected item (e.g. `e` , `tabe` )
|
`sink` | string | Vim command to handle the selected item (e.g. `e` , `tabe` )
|
||||||
`sink` | funcref | Reference to function to process each selected item
|
`sink` | funcref | Function to be called with each selected item
|
||||||
`sinklist` (or `sink*` ) | funcref | Similar to `sink` , but takes the list of output lines at once
|
`sinklist` (or `sink*` ) | funcref | Similar to `sink` , but takes the list of output lines at once
|
||||||
|
`exit` | funcref | Function to be called with the exit status of fzf (e.g. 0, 1, 2, 130)
|
||||||
`options` | string/list | Options to fzf
|
`options` | string/list | Options to fzf
|
||||||
`dir` | string | Working directory
|
`dir` | string | Working directory
|
||||||
`up` / `down` / `left` / `right` | number/string | (Layout) Window position and size (e.g. `20` , `50%` )
|
`up` / `down` / `left` / `right` | number/string | (Layout) Window position and size (e.g. `20` , `50%` )
|
||||||
|
6
go.mod
6
go.mod
@@ -3,11 +3,11 @@ module github.com/junegunn/fzf
|
|||||||
require (
|
require (
|
||||||
github.com/charlievieth/fastwalk v1.0.8
|
github.com/charlievieth/fastwalk v1.0.8
|
||||||
github.com/gdamore/tcell/v2 v2.7.4
|
github.com/gdamore/tcell/v2 v2.7.4
|
||||||
|
github.com/junegunn/go-shellwords v0.0.0-20240813092932-a62c48c52e97
|
||||||
github.com/mattn/go-isatty v0.0.20
|
github.com/mattn/go-isatty v0.0.20
|
||||||
github.com/mattn/go-shellwords v1.0.12
|
|
||||||
github.com/rivo/uniseg v0.4.7
|
github.com/rivo/uniseg v0.4.7
|
||||||
golang.org/x/sys v0.22.0
|
golang.org/x/sys v0.24.0
|
||||||
golang.org/x/term v0.22.0
|
golang.org/x/term v0.23.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
12
go.sum
12
go.sum
@@ -4,14 +4,14 @@ github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdk
|
|||||||
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||||
github.com/gdamore/tcell/v2 v2.7.4 h1:sg6/UnTM9jGpZU+oFYAsDahfchWAFW8Xx2yFinNSAYU=
|
github.com/gdamore/tcell/v2 v2.7.4 h1:sg6/UnTM9jGpZU+oFYAsDahfchWAFW8Xx2yFinNSAYU=
|
||||||
github.com/gdamore/tcell/v2 v2.7.4/go.mod h1:dSXtXTSK0VsW1biw65DZLZ2NKr7j0qP/0J7ONmsraWg=
|
github.com/gdamore/tcell/v2 v2.7.4/go.mod h1:dSXtXTSK0VsW1biw65DZLZ2NKr7j0qP/0J7ONmsraWg=
|
||||||
|
github.com/junegunn/go-shellwords v0.0.0-20240813092932-a62c48c52e97 h1:rqzLixVo1c/GQW6px9j1xQmlvQIn+lf/V6M1UQ7IFzw=
|
||||||
|
github.com/junegunn/go-shellwords v0.0.0-20240813092932-a62c48c52e97/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=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
|
|
||||||
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
@@ -36,14 +36,14 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
||||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||||
golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
|
golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
|
||||||
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
|
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
2
install
2
install
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
set -u
|
set -u
|
||||||
|
|
||||||
version=0.54.2
|
version=0.55.0
|
||||||
auto_completion=
|
auto_completion=
|
||||||
key_bindings=
|
key_bindings=
|
||||||
update_config=2
|
update_config=2
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
$version="0.54.2"
|
$version="0.55.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.54"
|
var version = "0.55"
|
||||||
var revision = "devel"
|
var revision = "devel"
|
||||||
|
|
||||||
//go:embed shell/key-bindings.bash
|
//go:embed shell/key-bindings.bash
|
||||||
|
@@ -21,13 +21,13 @@ 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 "Jul 2024" "fzf 0.54.2" "fzf\-tmux - open fzf in tmux split pane"
|
.TH fzf\-tmux 1 "Aug 2024" "fzf 0.55.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
|
||||||
|
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
.B fzf\-tmux [LAYOUT OPTIONS] [\-\-] [FZF OPTIONS]
|
.B fzf\-tmux [\fILAYOUT OPTIONS\fR] [\-\-] [\fIFZF OPTIONS\fR]
|
||||||
|
|
||||||
.SH DESCRIPTION
|
.SH DESCRIPTION
|
||||||
fzf\-tmux is a wrapper script for fzf that opens fzf in a tmux split pane or in
|
fzf\-tmux is a wrapper script for fzf that opens fzf in a tmux split pane or in
|
||||||
|
@@ -21,13 +21,13 @@ 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 "Jul 2024" "fzf 0.54.2" "fzf - a command-line fuzzy finder"
|
.TH fzf 1 "Aug 2024" "fzf 0.55.0" "fzf - a command-line fuzzy finder"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf - a command-line fuzzy finder
|
fzf - a command-line fuzzy finder
|
||||||
|
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
fzf [options]
|
fzf [\fIoptions\fR]
|
||||||
|
|
||||||
.SH DESCRIPTION
|
.SH DESCRIPTION
|
||||||
fzf is an interactive filter program for any kind of list.
|
fzf is an interactive filter program for any kind of list.
|
||||||
@@ -526,7 +526,7 @@ lines that follow.
|
|||||||
Print header before the prompt line
|
Print header before the prompt line
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-ellipsis=" "STR"
|
.BI "\-\-ellipsis=" "STR"
|
||||||
Ellipsis to show when line is truncated (default: '..')
|
Ellipsis to show when line is truncated (default: '··')
|
||||||
.SS Display
|
.SS Display
|
||||||
.TP
|
.TP
|
||||||
.B "\-\-ansi"
|
.B "\-\-ansi"
|
||||||
@@ -1146,6 +1146,22 @@ A term can be prefixed by \fB^\fR, or suffixed by \fB$\fR to become an
|
|||||||
anchored-match term. Then fzf will search for the lines that start with or end
|
anchored-match term. Then fzf will search for the lines that start with or end
|
||||||
with the given string. An anchored-match term is also an exact-match term.
|
with the given string. An anchored-match term is also an exact-match term.
|
||||||
|
|
||||||
|
.SS Exact\-boundary\-match (quoted both ends)
|
||||||
|
A single-quoted term is interpreted as an "exact\-boundary\-match". fzf will
|
||||||
|
search for the exact occurrences of the string with both ends at the word
|
||||||
|
boundaries. Unlike in regular expressions, this also sees an underscore as
|
||||||
|
a word boundary. But the words around underscores are ranked lower and appear
|
||||||
|
later in the result than the other words around the other types of word
|
||||||
|
boundaries.
|
||||||
|
|
||||||
|
1. xxx foo xxx (highest score)
|
||||||
|
.br
|
||||||
|
2. xxx foo_xxx
|
||||||
|
.br
|
||||||
|
3. xxx_foo xxx
|
||||||
|
.br
|
||||||
|
4. xxx_foo_xxx (lowest score)
|
||||||
|
|
||||||
.SS Negation
|
.SS Negation
|
||||||
If a term is prefixed by \fB!\fR, fzf will exclude the lines that satisfy the
|
If a term is prefixed by \fB!\fR, fzf will exclude the lines that satisfy the
|
||||||
term from the result. In this case, fzf performs exact match by default.
|
term from the result. In this case, fzf performs exact match by default.
|
||||||
|
@@ -198,6 +198,7 @@ function! s:compare_binary_versions(a, b)
|
|||||||
return s:compare_versions(s:get_version(a:a), s:get_version(a:b))
|
return s:compare_versions(s:get_version(a:a), s:get_version(a:b))
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
|
let s:min_version = '0.53.0'
|
||||||
let s:checked = {}
|
let s:checked = {}
|
||||||
function! fzf#exec(...)
|
function! fzf#exec(...)
|
||||||
if !exists('s:exec')
|
if !exists('s:exec')
|
||||||
@@ -225,7 +226,11 @@ function! fzf#exec(...)
|
|||||||
let s:exec = binaries[-1]
|
let s:exec = binaries[-1]
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if a:0 && !has_key(s:checked, a:1)
|
let min_version = s:min_version
|
||||||
|
if a:0 && s:compare_versions(a:1, min_version) > 0
|
||||||
|
let min_version = a:1
|
||||||
|
endif
|
||||||
|
if !has_key(s:checked, min_version)
|
||||||
let fzf_version = s:get_version(s:exec)
|
let fzf_version = s:get_version(s:exec)
|
||||||
if empty(fzf_version)
|
if empty(fzf_version)
|
||||||
let message = printf('Failed to run "%s --version"', s:exec)
|
let message = printf('Failed to run "%s --version"', s:exec)
|
||||||
@@ -233,17 +238,17 @@ function! fzf#exec(...)
|
|||||||
throw message
|
throw message
|
||||||
end
|
end
|
||||||
|
|
||||||
if s:compare_versions(fzf_version, a:1) >= 0
|
if s:compare_versions(fzf_version, min_version) >= 0
|
||||||
let s:checked[a:1] = 1
|
let s:checked[min_version] = 1
|
||||||
return s:exec
|
return s:exec
|
||||||
elseif a:0 < 2 && input(printf('You need fzf %s or above. Found: %s. Download binary? (y/n) ', a:1, fzf_version)) =~? '^y'
|
elseif a:0 < 2 && input(printf('You need fzf %s or above. Found: %s. Download binary? (y/n) ', min_version, fzf_version)) =~? '^y'
|
||||||
let s:versions = {}
|
let s:versions = {}
|
||||||
unlet s:exec
|
unlet s:exec
|
||||||
redraw
|
redraw
|
||||||
call fzf#install()
|
call fzf#install()
|
||||||
return fzf#exec(a:1, 1)
|
return fzf#exec(min_version, 1)
|
||||||
else
|
else
|
||||||
throw printf('You need to upgrade fzf (required: %s or above)', a:1)
|
throw printf('You need to upgrade fzf (required: %s or above)', min_version)
|
||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
@@ -665,21 +670,17 @@ else
|
|||||||
let s:launcher = function('s:xterm_launcher')
|
let s:launcher = function('s:xterm_launcher')
|
||||||
endif
|
endif
|
||||||
|
|
||||||
function! s:exit_handler(code, command, ...)
|
function! s:exit_handler(dict, code, command, ...)
|
||||||
if a:code == 130
|
if has_key(a:dict, 'exit')
|
||||||
return 0
|
call a:dict.exit(a:code)
|
||||||
elseif has('nvim') && a:code == 129
|
endif
|
||||||
" When deleting the terminal buffer while fzf is still running,
|
if a:code == 2
|
||||||
" Nvim sends SIGHUP.
|
|
||||||
return 0
|
|
||||||
elseif a:code > 1
|
|
||||||
call s:error('Error running ' . a:command)
|
call s:error('Error running ' . a:command)
|
||||||
if !empty(a:000)
|
if !empty(a:000)
|
||||||
sleep
|
sleep
|
||||||
endif
|
endif
|
||||||
return 0
|
|
||||||
endif
|
endif
|
||||||
return 1
|
return a:code
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:execute(dict, command, use_height, temps) abort
|
function! s:execute(dict, command, use_height, temps) abort
|
||||||
@@ -731,7 +732,7 @@ function! s:execute(dict, command, use_height, temps) abort
|
|||||||
let exit_status = v:shell_error
|
let exit_status = v:shell_error
|
||||||
redraw!
|
redraw!
|
||||||
let lines = s:collect(a:temps)
|
let lines = s:collect(a:temps)
|
||||||
return s:exit_handler(exit_status, command) ? lines : []
|
return s:exit_handler(a:dict, exit_status, command) < 2 ? lines : []
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:execute_tmux(dict, command, temps) abort
|
function! s:execute_tmux(dict, command, temps) abort
|
||||||
@@ -746,7 +747,7 @@ function! s:execute_tmux(dict, command, temps) abort
|
|||||||
let exit_status = v:shell_error
|
let exit_status = v:shell_error
|
||||||
redraw!
|
redraw!
|
||||||
let lines = s:collect(a:temps)
|
let lines = s:collect(a:temps)
|
||||||
return s:exit_handler(exit_status, command) ? lines : []
|
return s:exit_handler(a:dict, exit_status, command) < 2 ? lines : []
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:calc_size(max, val, dict)
|
function! s:calc_size(max, val, dict)
|
||||||
@@ -912,7 +913,7 @@ function! s:execute_term(dict, command, temps) abort
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
let lines = s:collect(self.temps)
|
let lines = s:collect(self.temps)
|
||||||
if !s:exit_handler(a:code, self.command, 1)
|
if s:exit_handler(self.dict, a:code, self.command, 1) >= 2
|
||||||
return
|
return
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
@@ -289,7 +289,7 @@ __fzf_generic_path_completion() {
|
|||||||
fi
|
fi
|
||||||
COMPREPLY=()
|
COMPREPLY=()
|
||||||
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
||||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
[[ $COMP_CWORD -ge 0 ]] && cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
if [[ "$cur" == *"$trigger" ]] && [[ $cur != *'$('* ]] && [[ $cur != *':='* ]] && [[ $cur != *'`'* ]]; then
|
if [[ "$cur" == *"$trigger" ]] && [[ $cur != *'$('* ]] && [[ $cur != *':='* ]] && [[ $cur != *'`'* ]]; then
|
||||||
base=${cur:0:${#cur}-${#trigger}}
|
base=${cur:0:${#cur}-${#trigger}}
|
||||||
eval "base=$base" 2> /dev/null || return
|
eval "base=$base" 2> /dev/null || return
|
||||||
@@ -410,7 +410,8 @@ _fzf_complete_kill() {
|
|||||||
_fzf_proc_completion() {
|
_fzf_proc_completion() {
|
||||||
_fzf_complete -m --header-lines=1 --no-preview --wrap -- "$@" < <(
|
_fzf_complete -m --header-lines=1 --no-preview --wrap -- "$@" < <(
|
||||||
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 # For BusyBox
|
command ps -eo user,pid,ppid,time,args 2> /dev/null || # For BusyBox
|
||||||
|
command ps --everyone --full --windows # For cygwin
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -480,10 +481,36 @@ complete -o default -F _fzf_opts_completion fzf
|
|||||||
# fzf-tmux specific options (like `-w WIDTH`) are left as a future patch.
|
# fzf-tmux specific options (like `-w WIDTH`) are left as a future patch.
|
||||||
complete -o default -F _fzf_opts_completion fzf-tmux
|
complete -o default -F _fzf_opts_completion fzf-tmux
|
||||||
|
|
||||||
|
# Default path completion
|
||||||
|
__fzf_default_completion() {
|
||||||
|
__fzf_generic_path_completion _fzf_compgen_path "-m" "" "$@"
|
||||||
|
|
||||||
|
# Dynamic completion loader has updated the completion for the command
|
||||||
|
if [[ $? -eq 124 ]]; then
|
||||||
|
# We trigger _fzf_setup_completion so that fuzzy completion for the command
|
||||||
|
# still works. However, loader can update the completion for multiple
|
||||||
|
# commands at once, and fuzzy completion will no longer work for those
|
||||||
|
# other commands. e.g. pytest -> py.test, pytest-2, pytest-3, etc
|
||||||
|
_fzf_setup_completion path "$1"
|
||||||
|
return 124
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Set fuzzy path completion as the default completion for all commands.
|
||||||
|
# We can't set up default completion,
|
||||||
|
# 1. if it's already set up by another script
|
||||||
|
# 2. or if the current version of bash doesn't support -D option
|
||||||
|
complete | command grep -q __fzf_default_completion ||
|
||||||
|
complete | command grep -- '-D$' | command grep -qv _comp_complete_load ||
|
||||||
|
complete -D -F __fzf_default_completion -o default -o bashdefault 2> /dev/null
|
||||||
|
|
||||||
d_cmds="${FZF_COMPLETION_DIR_COMMANDS-cd pushd rmdir}"
|
d_cmds="${FZF_COMPLETION_DIR_COMMANDS-cd pushd rmdir}"
|
||||||
|
|
||||||
# NOTE: $FZF_COMPLETION_PATH_COMMANDS and $FZF_COMPLETION_VAR_COMMANDS are
|
# NOTE: $FZF_COMPLETION_PATH_COMMANDS and $FZF_COMPLETION_VAR_COMMANDS are
|
||||||
# undocumented and subject to change in the future.
|
# undocumented and subject to change in the future.
|
||||||
|
#
|
||||||
|
# NOTE: Although we have default completion, we still need to set up completion
|
||||||
|
# for each command in case they already have completion set up by another script.
|
||||||
a_cmds="${FZF_COMPLETION_PATH_COMMANDS-"
|
a_cmds="${FZF_COMPLETION_PATH_COMMANDS-"
|
||||||
awk bat cat code diff diff3
|
awk bat cat code diff diff3
|
||||||
emacs emacsclient ex file ftp g++ gcc gvim head hg hx java
|
emacs emacsclient ex file ftp g++ gcc gvim head hg hx java
|
||||||
|
@@ -300,7 +300,8 @@ _fzf_complete_unalias() {
|
|||||||
_fzf_complete_kill() {
|
_fzf_complete_kill() {
|
||||||
_fzf_complete -m --header-lines=1 --no-preview --wrap -- "$@" < <(
|
_fzf_complete -m --header-lines=1 --no-preview --wrap -- "$@" < <(
|
||||||
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 # For BusyBox
|
command ps -eo user,pid,ppid,time,args 2> /dev/null || # For BusyBox
|
||||||
|
command ps --everyone --full --windows # For cygwin
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -798,6 +798,14 @@ func FuzzyMatchV1(caseSensitive bool, normalize bool, forward bool, text *util.C
|
|||||||
// The solution is much cheaper since there is only one possible alignment of
|
// The solution is much cheaper since there is only one possible alignment of
|
||||||
// the pattern.
|
// the pattern.
|
||||||
func ExactMatchNaive(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
func ExactMatchNaive(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
||||||
|
return exactMatchNaive(caseSensitive, normalize, forward, false, text, pattern, withPos, slab)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExactMatchBoundary(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
||||||
|
return exactMatchNaive(caseSensitive, normalize, forward, true, text, pattern, withPos, slab)
|
||||||
|
}
|
||||||
|
|
||||||
|
func exactMatchNaive(caseSensitive bool, normalize bool, forward bool, boundaryCheck bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
||||||
if len(pattern) == 0 {
|
if len(pattern) == 0 {
|
||||||
return Result{0, 0, 0}, nil
|
return Result{0, 0, 0}, nil
|
||||||
}
|
}
|
||||||
@@ -832,10 +840,22 @@ func ExactMatchNaive(caseSensitive bool, normalize bool, forward bool, text *uti
|
|||||||
}
|
}
|
||||||
pidx_ := indexAt(pidx, lenPattern, forward)
|
pidx_ := indexAt(pidx, lenPattern, forward)
|
||||||
pchar := pattern[pidx_]
|
pchar := pattern[pidx_]
|
||||||
if pchar == char {
|
ok := pchar == char
|
||||||
|
if ok {
|
||||||
if pidx_ == 0 {
|
if pidx_ == 0 {
|
||||||
bonus = bonusAt(text, index_)
|
bonus = bonusAt(text, index_)
|
||||||
}
|
}
|
||||||
|
if boundaryCheck {
|
||||||
|
ok = bonus >= bonusBoundary
|
||||||
|
if ok && pidx_ == 0 {
|
||||||
|
ok = index_ == 0 || charClassOf(text.Get(index_-1)) <= charDelimiter
|
||||||
|
}
|
||||||
|
if ok && pidx_ == len(pattern)-1 {
|
||||||
|
ok = index_ == lenRunes-1 || charClassOf(text.Get(index_+1)) <= charDelimiter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
pidx++
|
pidx++
|
||||||
if pidx == lenPattern {
|
if pidx == lenPattern {
|
||||||
if bonus > bestBonus {
|
if bonus > bestBonus {
|
||||||
@@ -861,7 +881,23 @@ func ExactMatchNaive(caseSensitive bool, normalize bool, forward bool, text *uti
|
|||||||
sidx = lenRunes - (bestPos + 1)
|
sidx = lenRunes - (bestPos + 1)
|
||||||
eidx = lenRunes - (bestPos - lenPattern + 1)
|
eidx = lenRunes - (bestPos - lenPattern + 1)
|
||||||
}
|
}
|
||||||
score, _ := calculateScore(caseSensitive, normalize, text, pattern, sidx, eidx, false)
|
var score int
|
||||||
|
if boundaryCheck {
|
||||||
|
// Underscore boundaries should be ranked lower than the other types of boundaries
|
||||||
|
score = int(bonus)
|
||||||
|
deduct := int(bonus-bonusBoundary) + 1
|
||||||
|
if sidx > 0 && text.Get(sidx-1) == '_' {
|
||||||
|
score -= deduct + 1
|
||||||
|
deduct = 1
|
||||||
|
}
|
||||||
|
if eidx < lenRunes && text.Get(eidx) == '_' {
|
||||||
|
score -= deduct
|
||||||
|
}
|
||||||
|
// Add base score so that this can compete with other match types e.g. 'foo' | bar
|
||||||
|
score += scoreMatch*lenPattern + int(bonusBoundaryWhite)*(lenPattern+1)
|
||||||
|
} else {
|
||||||
|
score, _ = calculateScore(caseSensitive, normalize, text, pattern, sidx, eidx, false)
|
||||||
|
}
|
||||||
return Result{sidx, eidx, score}, nil
|
return Result{sidx, eidx, score}, nil
|
||||||
}
|
}
|
||||||
return Result{-1, -1, 0}, nil
|
return Result{-1, -1, 0}, nil
|
||||||
|
38
src/ansi.go
38
src/ansi.go
@@ -1,6 +1,7 @@
|
|||||||
package fzf
|
package fzf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
@@ -13,22 +14,28 @@ type ansiOffset struct {
|
|||||||
color ansiState
|
color ansiState
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type url struct {
|
||||||
|
uri string
|
||||||
|
params string
|
||||||
|
}
|
||||||
|
|
||||||
type ansiState struct {
|
type ansiState struct {
|
||||||
fg tui.Color
|
fg tui.Color
|
||||||
bg tui.Color
|
bg tui.Color
|
||||||
attr tui.Attr
|
attr tui.Attr
|
||||||
lbg tui.Color
|
lbg tui.Color
|
||||||
|
url *url
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ansiState) colored() bool {
|
func (s *ansiState) colored() bool {
|
||||||
return s.fg != -1 || s.bg != -1 || s.attr > 0 || s.lbg >= 0
|
return s.fg != -1 || s.bg != -1 || s.attr > 0 || s.lbg >= 0 || s.url != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ansiState) equals(t *ansiState) bool {
|
func (s *ansiState) equals(t *ansiState) bool {
|
||||||
if t == nil {
|
if t == nil {
|
||||||
return !s.colored()
|
return !s.colored()
|
||||||
}
|
}
|
||||||
return s.fg == t.fg && s.bg == t.bg && s.attr == t.attr && s.lbg == t.lbg
|
return s.fg == t.fg && s.bg == t.bg && s.attr == t.attr && s.lbg == t.lbg && s.url == t.url
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ansiState) ToString() string {
|
func (s *ansiState) ToString() string {
|
||||||
@@ -60,7 +67,11 @@ func (s *ansiState) ToString() string {
|
|||||||
}
|
}
|
||||||
ret += toAnsiString(s.fg, 30) + toAnsiString(s.bg, 40)
|
ret += toAnsiString(s.fg, 30) + toAnsiString(s.bg, 40)
|
||||||
|
|
||||||
return "\x1b[" + strings.TrimSuffix(ret, ";") + "m"
|
ret = "\x1b[" + strings.TrimSuffix(ret, ";") + "m"
|
||||||
|
if s.url != nil {
|
||||||
|
ret = fmt.Sprintf("\x1b]8;%s;%s\x1b\\%s\x1b]8;;\x1b", s.url.params, s.url.uri, ret)
|
||||||
|
}
|
||||||
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func toAnsiString(color tui.Color, offset int) string {
|
func toAnsiString(color tui.Color, offset int) string {
|
||||||
@@ -98,10 +109,19 @@ func matchOperatingSystemCommand(s string) int {
|
|||||||
if s[i] == '\x07' {
|
if s[i] == '\x07' {
|
||||||
return i + 1
|
return i + 1
|
||||||
}
|
}
|
||||||
|
// `\x1b]8;PARAMS;URI\x1b\\TITLE\x1b]8;;\x1b`
|
||||||
|
// ------
|
||||||
if s[i] == '\x1b' && i < len(s)-1 && s[i+1] == '\\' {
|
if s[i] == '\x1b' && i < len(s)-1 && s[i+1] == '\\' {
|
||||||
return i + 2
|
return i + 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// `\x1b]8;PARAMS;URI\x1b\\TITLE\x1b]8;;\x1b`
|
||||||
|
// ------------
|
||||||
|
if i < len(s) && s[:i+1] == "\x1b]8;;\x1b" {
|
||||||
|
return i + 1
|
||||||
|
}
|
||||||
|
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -328,13 +348,21 @@ func parseAnsiCode(s string, delimiter byte) (int, byte, string) {
|
|||||||
func interpretCode(ansiCode string, prevState *ansiState) ansiState {
|
func interpretCode(ansiCode string, prevState *ansiState) ansiState {
|
||||||
var state ansiState
|
var state ansiState
|
||||||
if prevState == nil {
|
if prevState == nil {
|
||||||
state = ansiState{-1, -1, 0, -1}
|
state = ansiState{-1, -1, 0, -1, nil}
|
||||||
} else {
|
} else {
|
||||||
state = ansiState{prevState.fg, prevState.bg, prevState.attr, prevState.lbg}
|
state = ansiState{prevState.fg, prevState.bg, prevState.attr, prevState.lbg, prevState.url}
|
||||||
}
|
}
|
||||||
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
|
||||||
|
state.url = nil
|
||||||
|
} else if strings.HasPrefix(ansiCode, "\x1b]8;") && strings.HasSuffix(ansiCode, "\x1b\\") {
|
||||||
|
if paramsEnd := strings.IndexRune(ansiCode[4:], ';'); paramsEnd >= 0 {
|
||||||
|
params := ansiCode[4 : 4+paramsEnd]
|
||||||
|
uri := ansiCode[5+paramsEnd : len(ansiCode)-2]
|
||||||
|
state.url = &url{uri: uri, params: params}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
@@ -12,7 +12,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/mattn/go-shellwords"
|
"github.com/junegunn/go-shellwords"
|
||||||
"github.com/rivo/uniseg"
|
"github.com/rivo/uniseg"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -103,7 +103,7 @@ Usage: fzf [options]
|
|||||||
--header=STR String to print as header
|
--header=STR String to print as header
|
||||||
--header-lines=N The first N lines of the input are treated as header
|
--header-lines=N The first N lines of the input are treated as header
|
||||||
--header-first Print header before the prompt line
|
--header-first Print header before the prompt line
|
||||||
--ellipsis=STR Ellipsis to show when line is truncated (default: '..')
|
--ellipsis=STR Ellipsis to show when line is truncated (default: '··')
|
||||||
|
|
||||||
Display
|
Display
|
||||||
--ansi Enable processing of ANSI color codes
|
--ansi Enable processing of ANSI color codes
|
||||||
@@ -472,7 +472,7 @@ type Options struct {
|
|||||||
Header []string
|
Header []string
|
||||||
HeaderLines int
|
HeaderLines int
|
||||||
HeaderFirst bool
|
HeaderFirst bool
|
||||||
Ellipsis string
|
Ellipsis *string
|
||||||
Scrollbar *string
|
Scrollbar *string
|
||||||
Margin [4]sizeSpec
|
Margin [4]sizeSpec
|
||||||
Padding [4]sizeSpec
|
Padding [4]sizeSpec
|
||||||
@@ -578,7 +578,7 @@ func defaultOptions() *Options {
|
|||||||
Header: make([]string, 0),
|
Header: make([]string, 0),
|
||||||
HeaderLines: 0,
|
HeaderLines: 0,
|
||||||
HeaderFirst: false,
|
HeaderFirst: false,
|
||||||
Ellipsis: "..",
|
Ellipsis: nil,
|
||||||
Scrollbar: nil,
|
Scrollbar: nil,
|
||||||
Margin: defaultMargin(),
|
Margin: defaultMargin(),
|
||||||
Padding: defaultMargin(),
|
Padding: defaultMargin(),
|
||||||
@@ -2339,9 +2339,12 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
|
|||||||
case "--no-header-first":
|
case "--no-header-first":
|
||||||
opts.HeaderFirst = false
|
opts.HeaderFirst = false
|
||||||
case "--ellipsis":
|
case "--ellipsis":
|
||||||
if opts.Ellipsis, err = nextString(allArgs, &i, "ellipsis string required"); err != nil {
|
str, err := nextString(allArgs, &i, "ellipsis string required")
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
str = firstLine(str)
|
||||||
|
opts.Ellipsis = &str
|
||||||
case "--preview":
|
case "--preview":
|
||||||
if opts.Preview.command, err = nextString(allArgs, &i, "preview command required"); err != nil {
|
if opts.Preview.command, err = nextString(allArgs, &i, "preview command required"); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -2623,7 +2626,8 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else if match, value := optString(arg, "--ellipsis="); match {
|
} else if match, value := optString(arg, "--ellipsis="); match {
|
||||||
opts.Ellipsis = value
|
str := firstLine(value)
|
||||||
|
opts.Ellipsis = &str
|
||||||
} else if match, value := optString(arg, "--preview="); match {
|
} else if match, value := optString(arg, "--preview="); match {
|
||||||
opts.Preview.command = value
|
opts.Preview.command = value
|
||||||
} else if match, value := optString(arg, "--preview-window="); match {
|
} else if match, value := optString(arg, "--preview-window="); match {
|
||||||
@@ -2915,6 +2919,12 @@ func postProcessOptions(opts *Options) error {
|
|||||||
return processScheme(opts)
|
return processScheme(opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseShellWords(str string) ([]string, error) {
|
||||||
|
parser := shellwords.NewParser()
|
||||||
|
parser.ParseComment = true
|
||||||
|
return parser.Parse(str)
|
||||||
|
}
|
||||||
|
|
||||||
// ParseOptions parses command-line options
|
// ParseOptions parses command-line options
|
||||||
func ParseOptions(useDefaults bool, args []string) (*Options, error) {
|
func ParseOptions(useDefaults bool, args []string) (*Options, error) {
|
||||||
opts := defaultOptions()
|
opts := defaultOptions()
|
||||||
@@ -2928,7 +2938,7 @@ func ParseOptions(useDefaults bool, args []string) (*Options, error) {
|
|||||||
return nil, errors.New("$FZF_DEFAULT_OPTS_FILE: " + err.Error())
|
return nil, errors.New("$FZF_DEFAULT_OPTS_FILE: " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
words, parseErr := shellwords.Parse(string(bytes))
|
words, parseErr := parseShellWords(string(bytes))
|
||||||
if parseErr != nil {
|
if parseErr != nil {
|
||||||
return nil, errors.New(path + ": " + parseErr.Error())
|
return nil, errors.New(path + ": " + parseErr.Error())
|
||||||
}
|
}
|
||||||
@@ -2940,7 +2950,7 @@ func ParseOptions(useDefaults bool, args []string) (*Options, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2. Options from $FZF_DEFAULT_OPTS string
|
// 2. Options from $FZF_DEFAULT_OPTS string
|
||||||
words, parseErr := shellwords.Parse(os.Getenv("FZF_DEFAULT_OPTS"))
|
words, parseErr := parseShellWords(os.Getenv("FZF_DEFAULT_OPTS"))
|
||||||
if parseErr != nil {
|
if parseErr != nil {
|
||||||
return nil, errors.New("$FZF_DEFAULT_OPTS: " + parseErr.Error())
|
return nil, errors.New("$FZF_DEFAULT_OPTS: " + parseErr.Error())
|
||||||
}
|
}
|
||||||
|
@@ -23,6 +23,7 @@ type termType int
|
|||||||
const (
|
const (
|
||||||
termFuzzy termType = iota
|
termFuzzy termType = iota
|
||||||
termExact
|
termExact
|
||||||
|
termExactBoundary
|
||||||
termPrefix
|
termPrefix
|
||||||
termSuffix
|
termSuffix
|
||||||
termEqual
|
termEqual
|
||||||
@@ -147,6 +148,7 @@ func BuildPattern(cache *ChunkCache, patternCache map[string]*Pattern, fuzzy boo
|
|||||||
ptr.procFun[termFuzzy] = fuzzyAlgo
|
ptr.procFun[termFuzzy] = fuzzyAlgo
|
||||||
ptr.procFun[termEqual] = algo.EqualMatch
|
ptr.procFun[termEqual] = algo.EqualMatch
|
||||||
ptr.procFun[termExact] = algo.ExactMatchNaive
|
ptr.procFun[termExact] = algo.ExactMatchNaive
|
||||||
|
ptr.procFun[termExactBoundary] = algo.ExactMatchBoundary
|
||||||
ptr.procFun[termPrefix] = algo.PrefixMatch
|
ptr.procFun[termPrefix] = algo.PrefixMatch
|
||||||
ptr.procFun[termSuffix] = algo.SuffixMatch
|
ptr.procFun[termSuffix] = algo.SuffixMatch
|
||||||
|
|
||||||
@@ -193,7 +195,10 @@ func parseTerms(fuzzy bool, caseMode Case, normalize bool, str string) []termSet
|
|||||||
text = text[:len(text)-1]
|
text = text[:len(text)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(text, "'") {
|
if len(text) > 2 && strings.HasPrefix(text, "'") && strings.HasSuffix(text, "'") {
|
||||||
|
typ = termExactBoundary
|
||||||
|
text = text[1 : len(text)-1]
|
||||||
|
} else if strings.HasPrefix(text, "'") {
|
||||||
// Flip exactness
|
// Flip exactness
|
||||||
if fuzzy && !inv {
|
if fuzzy && !inv {
|
||||||
typ = termExact
|
typ = termExact
|
||||||
|
@@ -16,6 +16,7 @@ type colorOffset struct {
|
|||||||
offset [2]int32
|
offset [2]int32
|
||||||
color tui.ColorPair
|
color tui.ColorPair
|
||||||
match bool
|
match bool
|
||||||
|
url *url
|
||||||
}
|
}
|
||||||
|
|
||||||
type Result struct {
|
type Result struct {
|
||||||
@@ -177,8 +178,11 @@ func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme,
|
|||||||
if curr != 0 && idx > start {
|
if curr != 0 && idx > start {
|
||||||
if curr < 0 {
|
if curr < 0 {
|
||||||
color := colMatch
|
color := colMatch
|
||||||
|
var url *url
|
||||||
if curr < -1 && theme.Colored {
|
if curr < -1 && theme.Colored {
|
||||||
origColor := ansiToColorPair(itemColors[-curr-2], colMatch)
|
ansi := itemColors[-curr-2]
|
||||||
|
url = ansi.color.url
|
||||||
|
origColor := ansiToColorPair(ansi, colMatch)
|
||||||
// hl or hl+ only sets the foreground color, so colMatch is the
|
// hl or hl+ only sets the foreground color, so colMatch is the
|
||||||
// combination of either [hl and bg] or [hl+ and bg+].
|
// combination of either [hl and bg] or [hl+ and bg+].
|
||||||
//
|
//
|
||||||
@@ -194,13 +198,14 @@ func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
colors = append(colors, colorOffset{
|
colors = append(colors, colorOffset{
|
||||||
offset: [2]int32{int32(start), int32(idx)}, color: color, match: true})
|
offset: [2]int32{int32(start), int32(idx)}, color: color, match: true, url: url})
|
||||||
} else {
|
} else {
|
||||||
ansi := itemColors[curr-1]
|
ansi := itemColors[curr-1]
|
||||||
colors = append(colors, colorOffset{
|
colors = append(colors, colorOffset{
|
||||||
offset: [2]int32{int32(start), int32(idx)},
|
offset: [2]int32{int32(start), int32(idx)},
|
||||||
color: ansiToColorPair(ansi, colBase),
|
color: ansiToColorPair(ansi, colBase),
|
||||||
match: false})
|
match: false,
|
||||||
|
url: ansi.color.url})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -124,10 +124,10 @@ func TestColorOffset(t *testing.T) {
|
|||||||
item := Result{
|
item := Result{
|
||||||
item: &Item{
|
item: &Item{
|
||||||
colors: &[]ansiOffset{
|
colors: &[]ansiOffset{
|
||||||
{[2]int32{0, 20}, ansiState{1, 5, 0, -1}},
|
{[2]int32{0, 20}, ansiState{1, 5, 0, -1, nil}},
|
||||||
{[2]int32{22, 27}, ansiState{2, 6, tui.Bold, -1}},
|
{[2]int32{22, 27}, ansiState{2, 6, tui.Bold, -1, nil}},
|
||||||
{[2]int32{30, 32}, ansiState{3, 7, 0, -1}},
|
{[2]int32{30, 32}, ansiState{3, 7, 0, -1, nil}},
|
||||||
{[2]int32{33, 40}, ansiState{4, 8, tui.Bold, -1}}}}}
|
{[2]int32{33, 40}, ansiState{4, 8, tui.Bold, -1, nil}}}}}
|
||||||
|
|
||||||
colBase := tui.NewColorPair(89, 189, tui.AttrUndefined)
|
colBase := tui.NewColorPair(89, 189, tui.AttrUndefined)
|
||||||
colMatch := tui.NewColorPair(99, 199, tui.AttrUndefined)
|
colMatch := tui.NewColorPair(99, 199, tui.AttrUndefined)
|
||||||
|
@@ -382,6 +382,7 @@ const (
|
|||||||
reqRedrawPreviewLabel
|
reqRedrawPreviewLabel
|
||||||
reqClose
|
reqClose
|
||||||
reqPrintQuery
|
reqPrintQuery
|
||||||
|
reqPreviewReady
|
||||||
reqPreviewEnqueue
|
reqPreviewEnqueue
|
||||||
reqPreviewDisplay
|
reqPreviewDisplay
|
||||||
reqPreviewRefresh
|
reqPreviewRefresh
|
||||||
@@ -826,7 +827,6 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
|||||||
headerLines: opts.HeaderLines,
|
headerLines: opts.HeaderLines,
|
||||||
header: []string{},
|
header: []string{},
|
||||||
header0: opts.Header,
|
header0: opts.Header,
|
||||||
ellipsis: opts.Ellipsis,
|
|
||||||
ansi: opts.Ansi,
|
ansi: opts.Ansi,
|
||||||
tabstop: opts.Tabstop,
|
tabstop: opts.Tabstop,
|
||||||
hasStartActions: false,
|
hasStartActions: false,
|
||||||
@@ -854,7 +854,6 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
|||||||
mutex: sync.Mutex{},
|
mutex: sync.Mutex{},
|
||||||
uiMutex: sync.Mutex{},
|
uiMutex: sync.Mutex{},
|
||||||
suppress: true,
|
suppress: true,
|
||||||
sigstop: false,
|
|
||||||
slab: util.MakeSlab(slab16Size, slab32Size),
|
slab: util.MakeSlab(slab16Size, slab32Size),
|
||||||
theme: opts.Theme,
|
theme: opts.Theme,
|
||||||
startChan: make(chan fitpad, 1),
|
startChan: make(chan fitpad, 1),
|
||||||
@@ -883,6 +882,15 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
|||||||
}
|
}
|
||||||
t.separator, t.separatorLen = t.ansiLabelPrinter(bar, &tui.ColSeparator, true)
|
t.separator, t.separatorLen = t.ansiLabelPrinter(bar, &tui.ColSeparator, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if opts.Ellipsis != nil {
|
||||||
|
t.ellipsis = *opts.Ellipsis
|
||||||
|
} else if t.unicode {
|
||||||
|
t.ellipsis = "··"
|
||||||
|
} else {
|
||||||
|
t.ellipsis = ".."
|
||||||
|
}
|
||||||
|
|
||||||
if t.unicode {
|
if t.unicode {
|
||||||
t.wrapSign = "↳ "
|
t.wrapSign = "↳ "
|
||||||
t.borderWidth = uniseg.StringWidth("│")
|
t.borderWidth = uniseg.StringWidth("│")
|
||||||
@@ -1067,7 +1075,7 @@ func (t *Terminal) parsePrompt(prompt string) (func(), int) {
|
|||||||
// // unless the part has a non-default ANSI state
|
// // unless the part has a non-default ANSI state
|
||||||
loc := whiteSuffix.FindStringIndex(trimmed)
|
loc := whiteSuffix.FindStringIndex(trimmed)
|
||||||
if loc != nil {
|
if loc != nil {
|
||||||
blankState := ansiOffset{[2]int32{int32(loc[0]), int32(loc[1])}, ansiState{-1, -1, tui.AttrClear, -1}}
|
blankState := ansiOffset{[2]int32{int32(loc[0]), int32(loc[1])}, ansiState{-1, -1, tui.AttrClear, -1, nil}}
|
||||||
if item.colors != nil {
|
if item.colors != nil {
|
||||||
lastColor := (*item.colors)[len(*item.colors)-1]
|
lastColor := (*item.colors)[len(*item.colors)-1]
|
||||||
if lastColor.offset[1] < int32(loc[1]) {
|
if lastColor.offset[1] < int32(loc[1]) {
|
||||||
@@ -2459,15 +2467,24 @@ func (t *Terminal) printColoredString(window tui.Window, text []rune, offsets []
|
|||||||
var substr string
|
var substr string
|
||||||
var prefixWidth int
|
var prefixWidth int
|
||||||
maxOffset := int32(len(text))
|
maxOffset := int32(len(text))
|
||||||
|
var url *url
|
||||||
for _, offset := range offsets {
|
for _, offset := range offsets {
|
||||||
b := util.Constrain32(offset.offset[0], index, maxOffset)
|
b := util.Constrain32(offset.offset[0], index, maxOffset)
|
||||||
e := util.Constrain32(offset.offset[1], index, maxOffset)
|
e := util.Constrain32(offset.offset[1], index, maxOffset)
|
||||||
|
if url != nil && offset.url == nil {
|
||||||
|
url = nil
|
||||||
|
window.LinkEnd()
|
||||||
|
}
|
||||||
|
|
||||||
substr, prefixWidth = t.processTabs(text[index:b], prefixWidth)
|
substr, prefixWidth = t.processTabs(text[index:b], prefixWidth)
|
||||||
window.CPrint(colBase, substr)
|
window.CPrint(colBase, substr)
|
||||||
|
|
||||||
if b < e {
|
if b < e {
|
||||||
substr, prefixWidth = t.processTabs(text[b:e], prefixWidth)
|
substr, prefixWidth = t.processTabs(text[b:e], prefixWidth)
|
||||||
|
if url == nil && offset.url != nil {
|
||||||
|
url = offset.url
|
||||||
|
window.LinkBegin(url.uri, url.params)
|
||||||
|
}
|
||||||
window.CPrint(offset.color, substr)
|
window.CPrint(offset.color, substr)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2476,6 +2493,9 @@ func (t *Terminal) printColoredString(window tui.Window, text []rune, offsets []
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if url != nil {
|
||||||
|
window.LinkEnd()
|
||||||
|
}
|
||||||
if index < maxOffset {
|
if index < maxOffset {
|
||||||
substr, _ = t.processTabs(text[index:], prefixWidth)
|
substr, _ = t.processTabs(text[index:], prefixWidth)
|
||||||
window.CPrint(colBase, substr)
|
window.CPrint(colBase, substr)
|
||||||
@@ -2667,12 +2687,21 @@ Loop:
|
|||||||
|
|
||||||
var fillRet tui.FillReturn
|
var fillRet tui.FillReturn
|
||||||
prefixWidth := 0
|
prefixWidth := 0
|
||||||
|
var url *url
|
||||||
_, _, ansi = extractColor(line, ansi, func(str string, ansi *ansiState) bool {
|
_, _, ansi = extractColor(line, ansi, func(str string, ansi *ansiState) bool {
|
||||||
trimmed := []rune(str)
|
trimmed := []rune(str)
|
||||||
isTrimmed := false
|
isTrimmed := false
|
||||||
if !t.previewOpts.wrap {
|
if !t.previewOpts.wrap {
|
||||||
trimmed, isTrimmed = t.trimRight(trimmed, maxWidth-t.pwindow.X())
|
trimmed, isTrimmed = t.trimRight(trimmed, maxWidth-t.pwindow.X())
|
||||||
}
|
}
|
||||||
|
if url == nil && ansi != nil && ansi.url != nil {
|
||||||
|
url = ansi.url
|
||||||
|
t.pwindow.LinkBegin(url.uri, url.params)
|
||||||
|
}
|
||||||
|
if url != nil && (ansi == nil || ansi.url == nil) {
|
||||||
|
url = nil
|
||||||
|
t.pwindow.LinkEnd()
|
||||||
|
}
|
||||||
str, width := t.processTabs(trimmed, prefixWidth)
|
str, width := t.processTabs(trimmed, prefixWidth)
|
||||||
if width > prefixWidth {
|
if width > prefixWidth {
|
||||||
prefixWidth = width
|
prefixWidth = width
|
||||||
@@ -2686,6 +2715,9 @@ Loop:
|
|||||||
return !isTrimmed &&
|
return !isTrimmed &&
|
||||||
(fillRet == tui.FillContinue || t.previewOpts.wrap && fillRet == tui.FillNextLine)
|
(fillRet == tui.FillContinue || t.previewOpts.wrap && fillRet == tui.FillNextLine)
|
||||||
})
|
})
|
||||||
|
if url != nil {
|
||||||
|
t.pwindow.LinkEnd()
|
||||||
|
}
|
||||||
t.previewer.scrollable = t.previewer.scrollable || t.pwindow.Y() == height-1 && t.pwindow.X() == t.pwindow.Width()
|
t.previewer.scrollable = t.previewer.scrollable || t.pwindow.Y() == height-1 && t.pwindow.X() == t.pwindow.Width()
|
||||||
if fillRet == tui.FillNextLine {
|
if fillRet == tui.FillNextLine {
|
||||||
continue
|
continue
|
||||||
@@ -2697,10 +2729,14 @@ Loop:
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
if lbg >= 0 {
|
if lbg >= 0 {
|
||||||
t.pwindow.CFill(-1, lbg, tui.AttrRegular,
|
fillRet = t.pwindow.CFill(-1, lbg, tui.AttrRegular,
|
||||||
strings.Repeat(" ", t.pwindow.Width()-t.pwindow.X())+"\n")
|
strings.Repeat(" ", t.pwindow.Width()-t.pwindow.X())+"\n")
|
||||||
} else {
|
} else {
|
||||||
t.pwindow.Fill("\n")
|
fillRet = t.pwindow.Fill("\n")
|
||||||
|
}
|
||||||
|
if fillRet == tui.FillSuspend {
|
||||||
|
t.previewed.filled = true
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lineNo++
|
lineNo++
|
||||||
@@ -3400,19 +3436,6 @@ func (t *Terminal) Loop() error {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
contChan := make(chan os.Signal, 1)
|
|
||||||
notifyOnCont(contChan)
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
|
||||||
case <-contChan:
|
|
||||||
t.reqBox.Set(reqReinit, nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if !t.tui.ShouldEmitResizeEvent() {
|
if !t.tui.ShouldEmitResizeEvent() {
|
||||||
resizeChan := make(chan os.Signal, 1)
|
resizeChan := make(chan os.Signal, 1)
|
||||||
notifyOnResize(resizeChan) // Non-portable
|
notifyOnResize(resizeChan) // Non-portable
|
||||||
@@ -3469,6 +3492,7 @@ func (t *Terminal) Loop() error {
|
|||||||
go func() {
|
go func() {
|
||||||
var version int64
|
var version int64
|
||||||
stop := false
|
stop := false
|
||||||
|
t.previewBox.WaitFor(reqPreviewReady)
|
||||||
for {
|
for {
|
||||||
var items []*Item
|
var items []*Item
|
||||||
var commandTemplate string
|
var commandTemplate string
|
||||||
@@ -3497,6 +3521,9 @@ func (t *Terminal) Loop() error {
|
|||||||
if stop {
|
if stop {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
if items == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
version++
|
version++
|
||||||
// We don't display preview window if no match
|
// We don't display preview window if no match
|
||||||
if items[0] != nil {
|
if items[0] != nil {
|
||||||
@@ -3738,12 +3765,15 @@ func (t *Terminal) Loop() error {
|
|||||||
t.printHeader()
|
t.printHeader()
|
||||||
case reqActivate:
|
case reqActivate:
|
||||||
t.suppress = false
|
t.suppress = false
|
||||||
|
if t.hasPreviewer() {
|
||||||
|
t.previewBox.Set(reqPreviewReady, nil)
|
||||||
|
}
|
||||||
case reqRedrawBorderLabel:
|
case reqRedrawBorderLabel:
|
||||||
t.printLabel(t.border, t.borderLabel, t.borderLabelOpts, t.borderLabelLen, t.borderShape, true)
|
t.printLabel(t.border, t.borderLabel, t.borderLabelOpts, t.borderLabelLen, t.borderShape, true)
|
||||||
case reqRedrawPreviewLabel:
|
case reqRedrawPreviewLabel:
|
||||||
t.printLabel(t.pborder, t.previewLabel, t.previewLabelOpts, t.previewLabelLen, t.previewOpts.border, true)
|
t.printLabel(t.pborder, t.previewLabel, t.previewLabelOpts, t.previewLabelLen, t.previewOpts.border, true)
|
||||||
case reqReinit:
|
case reqReinit:
|
||||||
t.tui.Resume(t.fullscreen, t.sigstop)
|
t.tui.Resume(t.fullscreen, true)
|
||||||
t.fullRedraw()
|
t.fullRedraw()
|
||||||
case reqResize, reqFullRedraw:
|
case reqResize, reqFullRedraw:
|
||||||
if req == reqResize {
|
if req == reqResize {
|
||||||
@@ -4483,11 +4513,11 @@ func (t *Terminal) Loop() error {
|
|||||||
case actSigStop:
|
case actSigStop:
|
||||||
p, err := os.FindProcess(os.Getpid())
|
p, err := os.FindProcess(os.Getpid())
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.sigstop = true
|
|
||||||
t.tui.Clear()
|
t.tui.Clear()
|
||||||
t.tui.Pause(t.fullscreen)
|
t.tui.Pause(t.fullscreen)
|
||||||
notifyStop(p)
|
notifyStop(p)
|
||||||
t.mutex.Unlock()
|
t.mutex.Unlock()
|
||||||
|
t.reqBox.Set(reqReinit, nil)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
case actMouse:
|
case actMouse:
|
||||||
@@ -4850,11 +4880,18 @@ func (t *Terminal) constrain() {
|
|||||||
linesSum := 0
|
linesSum := 0
|
||||||
|
|
||||||
add := func(i int) bool {
|
add := func(i int) bool {
|
||||||
lines, _ := t.numItemLines(t.merger.Get(i).item, numItems-linesSum)
|
lines, overflow := t.numItemLines(t.merger.Get(i).item, numItems-linesSum)
|
||||||
linesSum += lines
|
linesSum += lines
|
||||||
if linesSum >= numItems {
|
if linesSum >= numItems {
|
||||||
if numItemsFound == 0 {
|
/*
|
||||||
numItemsFound = 1
|
# Should show all 3 items
|
||||||
|
printf "file1\0file2\0file3\0" | fzf --height=5 --read0 --bind load:last --reverse
|
||||||
|
|
||||||
|
# Should not truncate the last item
|
||||||
|
printf "file\n1\0file\n2\0file\n3\0" | fzf --height=5 --read0 --bind load:last --reverse
|
||||||
|
*/
|
||||||
|
if numItemsFound == 0 || !overflow {
|
||||||
|
numItemsFound++
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@@ -20,9 +20,5 @@ func notifyStop(p *os.Process) {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
pid = pgid * -1
|
pid = pgid * -1
|
||||||
}
|
}
|
||||||
unix.Kill(pid, syscall.SIGSTOP)
|
unix.Kill(pid, syscall.SIGTSTP)
|
||||||
}
|
|
||||||
|
|
||||||
func notifyOnCont(resizeChan chan<- os.Signal) {
|
|
||||||
signal.Notify(resizeChan, syscall.SIGCONT)
|
|
||||||
}
|
}
|
||||||
|
@@ -13,7 +13,3 @@ func notifyOnResize(resizeChan chan<- os.Signal) {
|
|||||||
func notifyStop(p *os.Process) {
|
func notifyStop(p *os.Process) {
|
||||||
// NOOP
|
// NOOP
|
||||||
}
|
}
|
||||||
|
|
||||||
func notifyOnCont(resizeChan chan<- os.Signal) {
|
|
||||||
// NOOP
|
|
||||||
}
|
|
||||||
|
@@ -38,7 +38,7 @@ func runTmux(args []string, opts *Options) (int, error) {
|
|||||||
case posUp:
|
case posUp:
|
||||||
tmuxArgs = append(tmuxArgs, "-xC", "-y0")
|
tmuxArgs = append(tmuxArgs, "-xC", "-y0")
|
||||||
case posDown:
|
case posDown:
|
||||||
tmuxArgs = append(tmuxArgs, "-xC", "-yS")
|
tmuxArgs = append(tmuxArgs, "-xC", "-y9999")
|
||||||
case posLeft:
|
case posLeft:
|
||||||
tmuxArgs = append(tmuxArgs, "-x0", "-yC")
|
tmuxArgs = append(tmuxArgs, "-x0", "-yC")
|
||||||
case posRight:
|
case posRight:
|
||||||
|
@@ -1030,13 +1030,13 @@ func cleanse(str string) string {
|
|||||||
func (w *LightWindow) CPrint(pair ColorPair, text string) {
|
func (w *LightWindow) CPrint(pair ColorPair, text string) {
|
||||||
_, code := w.csiColor(pair.Fg(), pair.Bg(), pair.Attr())
|
_, code := w.csiColor(pair.Fg(), pair.Bg(), pair.Attr())
|
||||||
w.stderrInternal(cleanse(text), false, code)
|
w.stderrInternal(cleanse(text), false, code)
|
||||||
w.csi("m")
|
w.csi("0m")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *LightWindow) cprint2(fg Color, bg Color, attr Attr, text string) {
|
func (w *LightWindow) cprint2(fg Color, bg Color, attr Attr, text string) {
|
||||||
hasColors, code := w.csiColor(fg, bg, attr)
|
hasColors, code := w.csiColor(fg, bg, attr)
|
||||||
if hasColors {
|
if hasColors {
|
||||||
defer w.csi("m")
|
defer w.csi("0m")
|
||||||
}
|
}
|
||||||
w.stderrInternal(cleanse(text), false, code)
|
w.stderrInternal(cleanse(text), false, code)
|
||||||
}
|
}
|
||||||
@@ -1118,6 +1118,14 @@ func (w *LightWindow) setBg() string {
|
|||||||
return "\x1b[m"
|
return "\x1b[m"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *LightWindow) LinkBegin(uri string, params string) {
|
||||||
|
w.renderer.queued.WriteString("\x1b]8;" + params + ";" + uri + "\x1b\\")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *LightWindow) LinkEnd() {
|
||||||
|
w.renderer.queued.WriteString("\x1b]8;;\x1b\\")
|
||||||
|
}
|
||||||
|
|
||||||
func (w *LightWindow) Fill(text string) FillReturn {
|
func (w *LightWindow) Fill(text string) FillReturn {
|
||||||
w.Move(w.posy, w.posx)
|
w.Move(w.posy, w.posx)
|
||||||
code := w.setBg()
|
code := w.setBg()
|
||||||
@@ -1133,7 +1141,7 @@ func (w *LightWindow) CFill(fg Color, bg Color, attr Attr, text string) FillRetu
|
|||||||
bg = w.bg
|
bg = w.bg
|
||||||
}
|
}
|
||||||
if hasColors, resetCode := w.csiColor(fg, bg, attr); hasColors {
|
if hasColors, resetCode := w.csiColor(fg, bg, attr); hasColors {
|
||||||
defer w.csi("m")
|
defer w.csi("0m")
|
||||||
return w.fill(text, resetCode)
|
return w.fill(text, resetCode)
|
||||||
}
|
}
|
||||||
return w.fill(text, w.setBg())
|
return w.fill(text, w.setBg())
|
||||||
|
@@ -4,6 +4,7 @@ package tui
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"regexp"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
@@ -49,6 +50,8 @@ type TcellWindow struct {
|
|||||||
lastY int
|
lastY int
|
||||||
moveCursor bool
|
moveCursor bool
|
||||||
borderStyle BorderStyle
|
borderStyle BorderStyle
|
||||||
|
uri *string
|
||||||
|
params *string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) Top() int {
|
func (w *TcellWindow) Top() int {
|
||||||
@@ -601,6 +604,16 @@ func (w *TcellWindow) Print(text string) {
|
|||||||
w.printString(text, w.normal)
|
w.printString(text, w.normal)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *TcellWindow) withUrl(style tcell.Style) tcell.Style {
|
||||||
|
if w.uri != nil {
|
||||||
|
style = style.Url(*w.uri)
|
||||||
|
if md := regexp.MustCompile(`id=([^:]+)`).FindStringSubmatch(*w.params); len(md) > 1 {
|
||||||
|
style = style.UrlId(md[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return style
|
||||||
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) printString(text string, pair ColorPair) {
|
func (w *TcellWindow) printString(text string, pair ColorPair) {
|
||||||
lx := 0
|
lx := 0
|
||||||
a := pair.Attr()
|
a := pair.Attr()
|
||||||
@@ -615,6 +628,7 @@ func (w *TcellWindow) printString(text string, pair ColorPair) {
|
|||||||
Blink(a&Attr(tcell.AttrBlink) != 0).
|
Blink(a&Attr(tcell.AttrBlink) != 0).
|
||||||
Dim(a&Attr(tcell.AttrDim) != 0)
|
Dim(a&Attr(tcell.AttrDim) != 0)
|
||||||
}
|
}
|
||||||
|
style = w.withUrl(style)
|
||||||
|
|
||||||
gr := uniseg.NewGraphemes(text)
|
gr := uniseg.NewGraphemes(text)
|
||||||
for gr.Next() {
|
for gr.Next() {
|
||||||
@@ -665,6 +679,7 @@ func (w *TcellWindow) fillString(text string, pair ColorPair) FillReturn {
|
|||||||
Underline(a&Attr(tcell.AttrUnderline) != 0).
|
Underline(a&Attr(tcell.AttrUnderline) != 0).
|
||||||
StrikeThrough(a&Attr(tcell.AttrStrikeThrough) != 0).
|
StrikeThrough(a&Attr(tcell.AttrStrikeThrough) != 0).
|
||||||
Italic(a&Attr(tcell.AttrItalic) != 0)
|
Italic(a&Attr(tcell.AttrItalic) != 0)
|
||||||
|
style = w.withUrl(style)
|
||||||
|
|
||||||
gr := uniseg.NewGraphemes(text)
|
gr := uniseg.NewGraphemes(text)
|
||||||
Loop:
|
Loop:
|
||||||
@@ -716,6 +731,16 @@ func (w *TcellWindow) Fill(str string) FillReturn {
|
|||||||
return w.fillString(str, w.normal)
|
return w.fillString(str, w.normal)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *TcellWindow) LinkBegin(uri string, params string) {
|
||||||
|
w.uri = &uri
|
||||||
|
w.params = ¶ms
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *TcellWindow) LinkEnd() {
|
||||||
|
w.uri = nil
|
||||||
|
w.params = nil
|
||||||
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) CFill(fg Color, bg Color, a Attr, str string) FillReturn {
|
func (w *TcellWindow) CFill(fg Color, bg Color, a Attr, str string) FillReturn {
|
||||||
if fg == colDefault {
|
if fg == colDefault {
|
||||||
fg = w.normal.Fg()
|
fg = w.normal.Fg()
|
||||||
|
@@ -564,6 +564,8 @@ type Window interface {
|
|||||||
CPrint(color ColorPair, text string)
|
CPrint(color ColorPair, text string)
|
||||||
Fill(text string) FillReturn
|
Fill(text string) FillReturn
|
||||||
CFill(fg Color, bg Color, attr Attr, text string) FillReturn
|
CFill(fg Color, bg Color, attr Attr, text string) FillReturn
|
||||||
|
LinkBegin(uri string, params string)
|
||||||
|
LinkEnd()
|
||||||
Erase()
|
Erase()
|
||||||
EraseMaybe() bool
|
EraseMaybe() bool
|
||||||
}
|
}
|
||||||
|
@@ -1443,7 +1443,7 @@ class TestGoFZF < TestBase
|
|||||||
[0, 3, 6].each do |off|
|
[0, 3, 6].each do |off|
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
tmux.send_keys "#{FZF} --hscroll-off=#{off} -q 0 < #{tempname}", :Enter
|
tmux.send_keys "#{FZF} --hscroll-off=#{off} -q 0 < #{tempname}", :Enter
|
||||||
tmux.until { |lines| assert lines[-3]&.end_with?((0..off).to_a.join + '..') }
|
tmux.until { |lines| assert lines[-3]&.end_with?((0..off).to_a.join + '··') }
|
||||||
tmux.send_keys '9'
|
tmux.send_keys '9'
|
||||||
tmux.until { |lines| assert lines[-3]&.end_with?('789') }
|
tmux.until { |lines| assert lines[-3]&.end_with?('789') }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
@@ -3366,6 +3366,18 @@ class TestGoFZF < TestBase
|
|||||||
tmux.send_keys :Space
|
tmux.send_keys :Space
|
||||||
tmux.until { |lines| assert_includes lines[-3], 'bar' }
|
tmux.until { |lines| assert_includes lines[-3], 'bar' }
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
module TestShell
|
module TestShell
|
||||||
|
Reference in New Issue
Block a user