mirror of
https://github.com/junegunn/fzf.git
synced 2025-11-15 06:43:47 -05:00
Compare commits
53 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
19759ed36e | ||
|
|
1a7ae8e7b9 | ||
|
|
da1f645670 | ||
|
|
3a2015ee26 | ||
|
|
c440418ce6 | ||
|
|
3d37a5ba1d | ||
|
|
15f4cfb6d9 | ||
|
|
be36de2482 | ||
|
|
391237f7df | ||
|
|
977e5effd9 | ||
|
|
8b36a4cb19 | ||
|
|
c8cd94a772 | ||
|
|
764316a53d | ||
|
|
2048fd4042 | ||
|
|
f84b3de24b | ||
|
|
6a1f3ec08b | ||
|
|
2e353aee96 | ||
|
|
8edfd14a37 | ||
|
|
1a191ec6f7 | ||
|
|
e7171e94b4 | ||
|
|
398d937419 | ||
|
|
34fe5ab143 | ||
|
|
1b08f43f82 | ||
|
|
b24a2e2fdc | ||
|
|
4c4c6e626e | ||
|
|
7310370a31 | ||
|
|
8ae94f0059 | ||
|
|
8fccf20892 | ||
|
|
5a874ae241 | ||
|
|
f4e1ed25f2 | ||
|
|
cbfbb49ab4 | ||
|
|
489b16efce | ||
|
|
b82c1693c0 | ||
|
|
019bfc4e35 | ||
|
|
dfda5c054a | ||
|
|
f657169616 | ||
|
|
4c06da8b70 | ||
|
|
9fe2393a00 | ||
|
|
e2e8d94b14 | ||
|
|
4f9a7f8c87 | ||
|
|
bb0502ff44 | ||
|
|
c256442245 | ||
|
|
1137404190 | ||
|
|
d57c6d0284 | ||
|
|
76bbf57b3d | ||
|
|
806a47a7cc | ||
|
|
29851c18aa | ||
|
|
dea950c2c8 | ||
|
|
a367dfb22e | ||
|
|
9fe1a7b373 | ||
|
|
8e2d21c548 | ||
|
|
bedf1cd357 | ||
|
|
13f180a70c |
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
custom: ["https://paypal.me/junegunn", "https://www.buymeacoffee.com/junegunn"]
|
||||||
7
.github/workflows/linux.yml
vendored
7
.github/workflows/linux.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go: [1.14, 1.15]
|
go: [1.14, 1.16]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
@@ -23,16 +23,13 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go }}
|
go-version: ${{ matrix.go }}
|
||||||
|
|
||||||
- name: fish-actions/install-fish
|
|
||||||
uses: fish-actions/install-fish@v1.0.0
|
|
||||||
|
|
||||||
- name: Setup Ruby
|
- name: Setup Ruby
|
||||||
uses: ruby/setup-ruby@v1.62.0
|
uses: ruby/setup-ruby@v1.62.0
|
||||||
with:
|
with:
|
||||||
ruby-version: 3.0.0
|
ruby-version: 3.0.0
|
||||||
|
|
||||||
- name: Install packages
|
- name: Install packages
|
||||||
run: sudo apt-get install --yes zsh 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.14.2 rubocop:1.0.0 rubocop-minitest:0.10.1 rubocop-performance:1.8.1
|
run: sudo gem install --no-document minitest:5.14.2 rubocop:1.0.0 rubocop-minitest:0.10.1 rubocop-performance:1.8.1
|
||||||
|
|||||||
2
.github/workflows/macos.yml
vendored
2
.github/workflows/macos.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
|||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go: [1.14, 1.15]
|
go: [1.14, 1.16]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
|
|||||||
11
.gon.hcl
11
.gon.hcl
@@ -1,11 +0,0 @@
|
|||||||
source = ["./dist/fzf-macos_darwin_amd64/fzf"]
|
|
||||||
bundle_id = "kr.junegunn.fzf"
|
|
||||||
|
|
||||||
apple_id {
|
|
||||||
username = "junegunn.c@gmail.com"
|
|
||||||
password = "@env:AC_PASSWORD"
|
|
||||||
}
|
|
||||||
|
|
||||||
sign {
|
|
||||||
application_identity = "Apple Development: junegunn.c@gmail.com"
|
|
||||||
}
|
|
||||||
@@ -14,10 +14,18 @@ builds:
|
|||||||
- amd64
|
- amd64
|
||||||
ldflags:
|
ldflags:
|
||||||
- "-s -w -X main.version={{ .Version }} -X main.revision={{ .ShortCommit }}"
|
- "-s -w -X main.version={{ .Version }} -X main.revision={{ .ShortCommit }}"
|
||||||
hooks:
|
|
||||||
post: gon .gon.hcl
|
|
||||||
|
|
||||||
- goos:
|
- id: fzf-macos-arm
|
||||||
|
binary: fzf
|
||||||
|
goos:
|
||||||
|
- darwin
|
||||||
|
goarch:
|
||||||
|
- arm64
|
||||||
|
ldflags:
|
||||||
|
- "-s -w -X main.version={{ .Version }} -X main.revision={{ .ShortCommit }}"
|
||||||
|
|
||||||
|
- id: fzf
|
||||||
|
goos:
|
||||||
- linux
|
- linux
|
||||||
- windows
|
- windows
|
||||||
- freebsd
|
- freebsd
|
||||||
@@ -44,6 +52,8 @@ builds:
|
|||||||
|
|
||||||
archives:
|
archives:
|
||||||
- name_template: "{{ .ProjectName }}-{{ .Version }}-{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}"
|
- name_template: "{{ .ProjectName }}-{{ .Version }}-{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}"
|
||||||
|
builds:
|
||||||
|
- fzf
|
||||||
format: tar.gz
|
format: tar.gz
|
||||||
format_overrides:
|
format_overrides:
|
||||||
- goos: windows
|
- goos: windows
|
||||||
@@ -51,12 +61,61 @@ archives:
|
|||||||
files:
|
files:
|
||||||
- non-existent*
|
- non-existent*
|
||||||
|
|
||||||
|
signs:
|
||||||
|
- id: fzf-macos-sign
|
||||||
|
ids: [fzf-macos]
|
||||||
|
artifacts: all
|
||||||
|
cmd: sh
|
||||||
|
args:
|
||||||
|
- "-c"
|
||||||
|
- |-
|
||||||
|
cat > /tmp/fzf-gon-amd64.hcl << EOF
|
||||||
|
source = ["./dist/fzf-macos_darwin_amd64/fzf"]
|
||||||
|
bundle_id = "kr.junegunn.fzf"
|
||||||
|
apple_id {
|
||||||
|
username = "junegunn.c@gmail.com"
|
||||||
|
password = "@env:AC_PASSWORD"
|
||||||
|
}
|
||||||
|
sign {
|
||||||
|
application_identity = "Developer ID Application: Junegunn Choi (Y254DRW44Z)"
|
||||||
|
}
|
||||||
|
zip {
|
||||||
|
output_path = "./dist/fzf-{{ .Version }}-darwin_amd64.zip"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
gon /tmp/fzf-gon-amd64.hcl
|
||||||
|
|
||||||
|
- id: fzf-macos-arm-sign
|
||||||
|
ids: [fzf-macos-arm]
|
||||||
|
artifacts: all
|
||||||
|
cmd: sh
|
||||||
|
args:
|
||||||
|
- "-c"
|
||||||
|
- |-
|
||||||
|
cat > /tmp/fzf-gon-arm64.hcl << EOF
|
||||||
|
source = ["./dist/fzf-macos-arm_darwin_arm64/fzf"]
|
||||||
|
bundle_id = "kr.junegunn.fzf"
|
||||||
|
apple_id {
|
||||||
|
username = "junegunn.c@gmail.com"
|
||||||
|
password = "@env:AC_PASSWORD"
|
||||||
|
}
|
||||||
|
sign {
|
||||||
|
application_identity = "Developer ID Application: Junegunn Choi (Y254DRW44Z)"
|
||||||
|
}
|
||||||
|
zip {
|
||||||
|
output_path = "./dist/fzf-{{ .Version }}-darwin_arm64.zip"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
gon /tmp/fzf-gon-arm64.hcl
|
||||||
|
|
||||||
release:
|
release:
|
||||||
github:
|
github:
|
||||||
owner: junegunn
|
owner: junegunn
|
||||||
name: fzf
|
name: fzf
|
||||||
prerelease: auto
|
prerelease: auto
|
||||||
name_template: '{{ .Tag }}'
|
name_template: '{{ .Tag }}'
|
||||||
|
extra_files:
|
||||||
|
- glob: ./dist/fzf-*darwin*.zip
|
||||||
|
|
||||||
snapshot:
|
snapshot:
|
||||||
name_template: "{{ .Tag }}-devel"
|
name_template: "{{ .Tag }}-devel"
|
||||||
|
|||||||
49
CHANGELOG.md
49
CHANGELOG.md
@@ -1,6 +1,55 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
0.27.0
|
||||||
|
------
|
||||||
|
- More border options for `--preview-window`
|
||||||
|
```sh
|
||||||
|
fzf --preview 'cat {}' --preview-window border-left
|
||||||
|
fzf --preview 'cat {}' --preview-window border-left --border horizontal
|
||||||
|
fzf --preview 'cat {}' --preview-window top:border-bottom
|
||||||
|
fzf --preview 'cat {}' --preview-window top:border-horizontal
|
||||||
|
```
|
||||||
|
- Automatically set `/dev/tty` as STDIN on execute action
|
||||||
|
```sh
|
||||||
|
# Redirect /dev/tty to suppress "Vim: Warning: Input is not from a terminal"
|
||||||
|
# ls | fzf --bind "enter:execute(vim {} < /dev/tty)"
|
||||||
|
|
||||||
|
# "< /dev/tty" part is no longer needed
|
||||||
|
ls | fzf --bind "enter:execute(vim {})"
|
||||||
|
```
|
||||||
|
- Bug fixes and improvements
|
||||||
|
- Signed and notarized macOS binaries
|
||||||
|
(Huge thanks to [BACKERS.md](https://github.com/junegunn/junegunn/blob/main/BACKERS.md)!)
|
||||||
|
|
||||||
|
0.26.0
|
||||||
|
------
|
||||||
|
- Added support for fixed header in preview window
|
||||||
|
```sh
|
||||||
|
# Display top 3 lines as the fixed header
|
||||||
|
fzf --preview 'bat --style=header,grid --color=always {}' --preview-window '~3'
|
||||||
|
```
|
||||||
|
- More advanced preview offset expression to better support the fixed header
|
||||||
|
```sh
|
||||||
|
# Preview with bat, matching line in the middle of the window below
|
||||||
|
# the fixed header of the top 3 lines
|
||||||
|
#
|
||||||
|
# ~3 Top 3 lines as the fixed header
|
||||||
|
# +{2} Base scroll offset extracted from the second field
|
||||||
|
# +3 Extra offset to compensate for the 3-line header
|
||||||
|
# /2 Put in the middle of the preview area
|
||||||
|
#
|
||||||
|
git grep --line-number '' |
|
||||||
|
fzf --delimiter : \
|
||||||
|
--preview 'bat --style=full --color=always --highlight-line {2} {1}' \
|
||||||
|
--preview-window '~3:+{2}+3/2'
|
||||||
|
```
|
||||||
|
- Added `select` and `deselect` action for unconditionally selecting or
|
||||||
|
deselecting a single item in `--multi` mode. Complements `toggle` action.
|
||||||
|
- Sigificant performance improvement in ANSI code processing
|
||||||
|
- Bug fixes and improvements
|
||||||
|
- Built with Go 1.16
|
||||||
|
|
||||||
0.25.1
|
0.25.1
|
||||||
------
|
------
|
||||||
- Added `close` action
|
- Added `close` action
|
||||||
|
|||||||
6
Makefile
6
Makefile
@@ -66,12 +66,16 @@ 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 \
|
||||||
github.com/junegunn/fzf/src/tui \
|
github.com/junegunn/fzf/src/tui \
|
||||||
github.com/junegunn/fzf/src/util
|
github.com/junegunn/fzf/src/util
|
||||||
|
|
||||||
|
bench:
|
||||||
|
cd src && SHELL=/bin/sh GOOS= $(GO) test -v -tags "$(TAGS)" -run=Bench -bench=. -benchmem
|
||||||
|
|
||||||
install: bin/fzf
|
install: bin/fzf
|
||||||
|
|
||||||
build:
|
build:
|
||||||
@@ -152,4 +156,4 @@ update:
|
|||||||
$(GO) get -u
|
$(GO) get -u
|
||||||
$(GO) mod tidy
|
$(GO) mod tidy
|
||||||
|
|
||||||
.PHONY: all build release test install clean docker docker-test update
|
.PHONY: all build release test bench install clean docker docker-test update
|
||||||
|
|||||||
14
README.md
14
README.md
@@ -25,7 +25,7 @@ Table of Contents
|
|||||||
<!-- vim-markdown-toc GFM -->
|
<!-- vim-markdown-toc GFM -->
|
||||||
|
|
||||||
* [Installation](#installation)
|
* [Installation](#installation)
|
||||||
* [Using Homebrew or Linuxbrew](#using-homebrew-or-linuxbrew)
|
* [Using Homebrew](#using-homebrew)
|
||||||
* [Using git](#using-git)
|
* [Using git](#using-git)
|
||||||
* [Using Linux package managers](#using-linux-package-managers)
|
* [Using Linux package managers](#using-linux-package-managers)
|
||||||
* [Windows](#windows)
|
* [Windows](#windows)
|
||||||
@@ -84,9 +84,9 @@ stuff.
|
|||||||
|
|
||||||
[bin]: https://github.com/junegunn/fzf/releases
|
[bin]: https://github.com/junegunn/fzf/releases
|
||||||
|
|
||||||
### Using Homebrew or Linuxbrew
|
### Using Homebrew
|
||||||
|
|
||||||
You can use [Homebrew](http://brew.sh/) or [Linuxbrew](http://linuxbrew.sh/)
|
You can use [Homebrew](http://brew.sh/) (on macOS or Linux)
|
||||||
to install fzf.
|
to install fzf.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -411,7 +411,7 @@ unalias **<TAB>
|
|||||||
export FZF_COMPLETION_TRIGGER='~~'
|
export FZF_COMPLETION_TRIGGER='~~'
|
||||||
|
|
||||||
# Options to fzf command
|
# Options to fzf command
|
||||||
export FZF_COMPLETION_OPTS='+c -x'
|
export FZF_COMPLETION_OPTS='--border --info=inline'
|
||||||
|
|
||||||
# Use fd (https://github.com/sharkdp/fd) instead of the default find
|
# Use fd (https://github.com/sharkdp/fd) instead of the default find
|
||||||
# command for listing path candidates.
|
# command for listing path candidates.
|
||||||
@@ -572,8 +572,8 @@ FZF_DEFAULT_COMMAND='find . -type f' \
|
|||||||
The following example uses fzf as the selector interface for ripgrep. We bound
|
The following example uses fzf as the selector interface for ripgrep. We bound
|
||||||
`reload` action to `change` event, so every time you type on fzf, the ripgrep
|
`reload` action to `change` event, so every time you type on fzf, the ripgrep
|
||||||
process will restart with the updated query string denoted by the placeholder
|
process will restart with the updated query string denoted by the placeholder
|
||||||
expression `{q}`. Also, note that we used `--phony` option so that fzf doesn't
|
expression `{q}`. Also, note that we used `--disabled` option so that fzf
|
||||||
perform any secondary filtering.
|
doesn't perform any secondary filtering.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
INITIAL_QUERY=""
|
INITIAL_QUERY=""
|
||||||
@@ -615,7 +615,7 @@ You can customize the size, position, and border of the preview window using
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
fzf --height 40% --layout reverse --info inline --border \
|
fzf --height 40% --layout reverse --info inline --border \
|
||||||
--preview 'file {}' --preview-window down:1:noborder \
|
--preview 'file {}' --preview-window up,1,border-horizontal \
|
||||||
--color 'fg:#bbccdd,fg+:#ddeeff,bg:#334455,preview-bg:#223344,border:#778899'
|
--color 'fg:#bbccdd,fg+:#ddeeff,bg:#334455,preview-bg:#223344,border:#778899'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
11
bin/fzf-tmux
11
bin/fzf-tmux
@@ -204,7 +204,16 @@ if [[ "$opt" =~ "-K -E" ]]; then
|
|||||||
cat <<< "\"$fzf\" $opts < $fifo1 > $fifo2; out=\$? $close; exit \$out" >> $argsf
|
cat <<< "\"$fzf\" $opts < $fifo1 > $fifo2; out=\$? $close; exit \$out" >> $argsf
|
||||||
cat <&0 > $fifo1 &
|
cat <&0 > $fifo1 &
|
||||||
fi
|
fi
|
||||||
tmux popup -d "$PWD" "${tmux_args[@]}" $opt -R "bash $argsf" > /dev/null 2>&1
|
|
||||||
|
# tmux dropped the support for `-K`, `-R` to popup command
|
||||||
|
# TODO: We can remove this once tmux 3.2 is released
|
||||||
|
if [[ ! "$(tmux popup --help 2>&1)" =~ '-R shell-command' ]]; then
|
||||||
|
opt="${opt/-K/}"
|
||||||
|
else
|
||||||
|
opt="${opt} -R"
|
||||||
|
fi
|
||||||
|
|
||||||
|
tmux popup -d "$PWD" "${tmux_args[@]}" $opt "bash $argsf" > /dev/null 2>&1
|
||||||
exit $?
|
exit $?
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
16
go.mod
16
go.mod
@@ -2,14 +2,16 @@ module github.com/junegunn/fzf
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gdamore/tcell v1.4.0
|
github.com/gdamore/tcell v1.4.0
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.12
|
github.com/mattn/go-isatty v0.0.12
|
||||||
github.com/mattn/go-runewidth v0.0.9
|
github.com/mattn/go-runewidth v0.0.12
|
||||||
github.com/mattn/go-shellwords v1.0.10
|
github.com/mattn/go-shellwords v1.0.11
|
||||||
github.com/saracen/walker v0.1.1
|
github.com/rivo/uniseg v0.2.0 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897
|
github.com/saracen/walker v0.1.2
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 // indirect
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
||||||
golang.org/x/sys v0.0.0-20201026173827-119d4633e4d1
|
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57
|
||||||
golang.org/x/text v0.3.3 // indirect
|
golang.org/x/term v0.0.0-20210317153231-de623e64d2a6
|
||||||
|
golang.org/x/text v0.3.6 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
go 1.13
|
go 1.13
|
||||||
|
|||||||
42
go.sum
42
go.sum
@@ -2,34 +2,32 @@ github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdk
|
|||||||
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||||
github.com/gdamore/tcell v1.4.0 h1:vUnHwJRvcPQa3tzi+0QI4U9JINXYJlOz9yiaiPQ2wMU=
|
github.com/gdamore/tcell v1.4.0 h1:vUnHwJRvcPQa3tzi+0QI4U9JINXYJlOz9yiaiPQ2wMU=
|
||||||
github.com/gdamore/tcell v1.4.0/go.mod h1:vxEiSDZdW3L+Uhjii9c3375IlDmR05bzxY404ZVSMo0=
|
github.com/gdamore/tcell v1.4.0/go.mod h1:vxEiSDZdW3L+Uhjii9c3375IlDmR05bzxY404ZVSMo0=
|
||||||
github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
|
|
||||||
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
github.com/mattn/go-runewidth v0.0.12 h1:Y41i/hVW3Pgwr8gV+J23B9YEY0zxjptBuCWEaxmAOow=
|
||||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||||
github.com/mattn/go-shellwords v1.0.10 h1:Y7Xqm8piKOO3v10Thp7Z36h4FYFjt5xB//6XvOrs2Gw=
|
github.com/mattn/go-shellwords v1.0.11 h1:vCoR9VPpsk/TZFW2JwK5I9S0xdrtUq2bph6/YjEPnaw=
|
||||||
github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
github.com/mattn/go-shellwords v1.0.11/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
||||||
github.com/saracen/walker v0.1.1 h1:Ou2QIKTWqo0QxhtuHVmtObbmhjMCEUyJ82xp0uV+MGI=
|
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/saracen/walker v0.1.1/go.mod h1:0oKYMsKVhSJ+ful4p/XbjvXbMgLEkLITZaxozsl4CGE=
|
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
github.com/saracen/walker v0.1.2 h1:/o1TxP82n8thLvmL4GpJXduYaRmJ7qXp8u9dSlV0zmo=
|
||||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E=
|
github.com/saracen/walker v0.1.2/go.mod h1:0oKYMsKVhSJ+ful4p/XbjvXbMgLEkLITZaxozsl4CGE=
|
||||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756 h1:9nuHUbU8dRnRRfj9KjWUVrJeoexdbeMjttk6Oh1rD10=
|
|
||||||
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201026173827-119d4633e4d1 h1:/DtoiOYKoQCcIFXQjz07RnWNPRCbqmSXSpgEzhC9ZHM=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201026173827-119d4633e4d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 h1:F5Gozwx4I1xtr/sr/8CFbb57iKi3297KFs0QDbGN60A=
|
||||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/term v0.0.0-20210317153231-de623e64d2a6 h1:EC6+IGYTjPpRfv9a2b/6Puw0W+hLtAhkV1tPsXhutqs=
|
||||||
|
golang.org/x/term v0.0.0-20210317153231-de623e64d2a6/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
|||||||
5
install
5
install
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
set -u
|
set -u
|
||||||
|
|
||||||
version=0.25.1
|
version=0.27.0
|
||||||
auto_completion=
|
auto_completion=
|
||||||
key_bindings=
|
key_bindings=
|
||||||
update_config=2
|
update_config=2
|
||||||
@@ -168,7 +168,8 @@ archi=$(uname -sm)
|
|||||||
binary_available=1
|
binary_available=1
|
||||||
binary_error=""
|
binary_error=""
|
||||||
case "$archi" in
|
case "$archi" in
|
||||||
Darwin\ *64) download fzf-$version-darwin_amd64.tar.gz ;;
|
Darwin\ arm64) download fzf-$version-darwin_arm64.zip ;;
|
||||||
|
Darwin\ x86_64) download fzf-$version-darwin_amd64.zip ;;
|
||||||
Linux\ armv5*) download fzf-$version-linux_armv5.tar.gz ;;
|
Linux\ armv5*) download fzf-$version-linux_armv5.tar.gz ;;
|
||||||
Linux\ armv6*) download fzf-$version-linux_armv6.tar.gz ;;
|
Linux\ armv6*) download fzf-$version-linux_armv6.tar.gz ;;
|
||||||
Linux\ armv7*) download fzf-$version-linux_armv7.tar.gz ;;
|
Linux\ armv7*) download fzf-$version-linux_armv7.tar.gz ;;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
$version="0.25.1"
|
$version="0.27.0"
|
||||||
|
|
||||||
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||||
|
|
||||||
|
|||||||
2
main.go
2
main.go
@@ -5,7 +5,7 @@ import (
|
|||||||
"github.com/junegunn/fzf/src/protector"
|
"github.com/junegunn/fzf/src/protector"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version string = "0.25"
|
var version string = "0.27"
|
||||||
var revision string = "devel"
|
var revision string = "devel"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
..
|
..
|
||||||
.TH fzf-tmux 1 "Feb 2021" "fzf 0.25.1" "fzf-tmux - open fzf in tmux split pane"
|
.TH fzf-tmux 1 "Apr 2021" "fzf 0.27.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 "Feb 2021" "fzf 0.25.1" "fzf - a command-line fuzzy finder"
|
.TH fzf 1 "Apr 2021" "fzf 0.27.0" "fzf - a command-line fuzzy finder"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf - a command-line fuzzy finder
|
fzf - a command-line fuzzy finder
|
||||||
@@ -203,6 +203,8 @@ Draw border around the finder
|
|||||||
.br
|
.br
|
||||||
.BR right
|
.BR right
|
||||||
.br
|
.br
|
||||||
|
.BR none
|
||||||
|
.br
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B "--no-unicode"
|
.B "--no-unicode"
|
||||||
@@ -442,7 +444,7 @@ e.g.
|
|||||||
done'\fR
|
done'\fR
|
||||||
.RE
|
.RE
|
||||||
.TP
|
.TP
|
||||||
.BI "--preview-window=" "[POSITION][:SIZE[%]][:rounded|sharp|noborder][:[no]wrap][:[no]follow][:[no]cycle][:[no]hidden][:+SCROLL[-OFFSET]][:default]"
|
.BI "--preview-window=" "[POSITION][,SIZE[%]][,border-BORDER_OPT][,[no]wrap][,[no]follow][,[no]cycle][,[no]hidden][,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES][,default]"
|
||||||
|
|
||||||
.RS
|
.RS
|
||||||
.B POSITION: (default: right)
|
.B POSITION: (default: right)
|
||||||
@@ -477,35 +479,51 @@ e.g.
|
|||||||
* Cyclic scrolling is enabled with \fB:cycle\fR flag.
|
* Cyclic scrolling is enabled with \fB:cycle\fR flag.
|
||||||
|
|
||||||
* To change the style of the border of the preview window, specify one of
|
* To change the style of the border of the preview window, specify one of
|
||||||
\fBrounded\fR (border with rounded edges, default), \fBsharp\fR (border with
|
the options for \fB--border\fR with \fBborder-\fR prefix.
|
||||||
sharp edges), or \fBnoborder\fR (no border).
|
e.g. \fBborder-rounded\fR (border with rounded edges, default),
|
||||||
|
\fBborder-sharp\fR (border with sharp edges), \fBborder-left\fR,
|
||||||
|
\fBborder-none\fR, etc.
|
||||||
|
|
||||||
* \fB+SCROLL[-OFFSET]\fR determines the initial scroll offset of the preview
|
* \fB[:+SCROLL[OFFSETS][/DENOM]]\fR determines the initial scroll offset of the
|
||||||
window. \fBSCROLL\fR can be either a numeric integer or a single-field index
|
preview window.
|
||||||
expression that refers to a numeric integer. The optional \fB-OFFSET\fR part is
|
|
||||||
for adjusting the base offset so that you can see the text above it. It should
|
- \fBSCROLL\fR can be either a numeric integer or a single-field index expression that refers to a numeric integer.
|
||||||
be given as a numeric integer (\fB-INTEGER\fR), or as a denominator form
|
|
||||||
(\fB-/INTEGER\fR) for specifying a fraction of the preview window height.
|
- The optional \fBOFFSETS\fR part is for adjusting the base offset. It should be given as a series of signed integers (\fB-INTEGER\fR or \fB+INTEGER\fR).
|
||||||
|
|
||||||
|
- The final \fB/DENOM\fR part is for specifying a fraction of the preview window height.
|
||||||
|
|
||||||
|
* \fB~HEADER_LINES\fR keeps the top N lines as the fixed header so that they
|
||||||
|
are always visible.
|
||||||
|
|
||||||
* \fBdefault\fR resets all options previously set to the default.
|
* \fBdefault\fR resets all options previously set to the default.
|
||||||
|
|
||||||
.RS
|
.RS
|
||||||
e.g.
|
e.g.
|
||||||
\fB# Non-default scroll window positions and sizes
|
\fB# Non-default scroll window positions and sizes
|
||||||
fzf --preview="head {}" --preview-window=up:30%
|
fzf --preview="head {}" --preview-window=up,30%
|
||||||
fzf --preview="file {}" --preview-window=down:1
|
fzf --preview="file {}" --preview-window=down,1
|
||||||
|
|
||||||
# Initial scroll offset is set to the line number of each line of
|
# Initial scroll offset is set to the line number of each line of
|
||||||
# git grep output *minus* 5 lines (-5)
|
# git grep output *minus* 5 lines (-5)
|
||||||
git grep --line-number '' |
|
git grep --line-number '' |
|
||||||
fzf --delimiter : --preview 'nl {1}' --preview-window +{2}-5
|
fzf --delimiter : --preview 'nl {1}' --preview-window '+{2}-5'
|
||||||
|
|
||||||
# Preview with bat, matching line in the middle of the window (-/2)
|
# Preview with bat, matching line in the middle of the window below
|
||||||
|
# the fixed header of the top 3 lines
|
||||||
|
#
|
||||||
|
# ~3 Top 3 lines as the fixed header
|
||||||
|
# +{2} Base scroll offset extracted from the second field
|
||||||
|
# +3 Extra offset to compensate for the 3-line header
|
||||||
|
# /2 Put in the middle of the preview area
|
||||||
|
#
|
||||||
git grep --line-number '' |
|
git grep --line-number '' |
|
||||||
fzf --delimiter : \\
|
fzf --delimiter : \\
|
||||||
--preview 'bat --style=numbers --color=always --highlight-line {2} {1}' \\
|
--preview 'bat --style=full --color=always --highlight-line {2} {1}' \\
|
||||||
--preview-window +{2}-/2\fR
|
--preview-window '~3,+{2}+3/2'
|
||||||
|
|
||||||
|
# Display top 3 lines as the fixed header
|
||||||
|
fzf --preview 'bat --style=full --color=always {}' --preview-window '~3'\fR
|
||||||
.RE
|
.RE
|
||||||
|
|
||||||
.SS Scripting
|
.SS Scripting
|
||||||
@@ -784,6 +802,7 @@ A key or an event can be bound to one or more of the following actions.
|
|||||||
\fBclear-query\fR (clear query string)
|
\fBclear-query\fR (clear query string)
|
||||||
\fBdelete-char\fR \fIdel\fR
|
\fBdelete-char\fR \fIdel\fR
|
||||||
\fBdelete-char/eof\fR \fIctrl-d\fR (same as \fBdelete-char\fR except aborts fzf if query is empty)
|
\fBdelete-char/eof\fR \fIctrl-d\fR (same as \fBdelete-char\fR except aborts fzf if query is empty)
|
||||||
|
\fBdeselect\fR
|
||||||
\fBdeselect-all\fR (deselect all matches)
|
\fBdeselect-all\fR (deselect all matches)
|
||||||
\fBdisable-search\fR (disable search functionality)
|
\fBdisable-search\fR (disable search functionality)
|
||||||
\fBdown\fR \fIctrl-j ctrl-n down\fR
|
\fBdown\fR \fIctrl-j ctrl-n down\fR
|
||||||
@@ -819,6 +838,7 @@ A key or an event can be bound to one or more of the following actions.
|
|||||||
\fBrefresh-preview\fR
|
\fBrefresh-preview\fR
|
||||||
\fBreload(...)\fR (see below for the details)
|
\fBreload(...)\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)
|
||||||
|
\fBselect\fR
|
||||||
\fBselect-all\fR (select all matches)
|
\fBselect-all\fR (select all matches)
|
||||||
\fBtoggle\fR (\fIright-click\fR)
|
\fBtoggle\fR (\fIright-click\fR)
|
||||||
\fBtoggle-all\fR (toggle all matches)
|
\fBtoggle-all\fR (toggle all matches)
|
||||||
|
|||||||
115
plugin/fzf.vim
115
plugin/fzf.vim
@@ -154,46 +154,79 @@ function! fzf#install()
|
|||||||
endif
|
endif
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:version_requirement(val, min)
|
let s:versions = {}
|
||||||
let val = split(a:val, '\.')
|
function s:get_version(bin)
|
||||||
let min = split(a:min, '\.')
|
if has_key(s:versions, a:bin)
|
||||||
for idx in range(0, len(min) - 1)
|
return s:versions[a:bin]
|
||||||
let v = get(val, idx, 0)
|
end
|
||||||
if v < min[idx] | return 0
|
let command = a:bin . ' --version'
|
||||||
elseif v > min[idx] | return 1
|
let output = systemlist(command)
|
||||||
|
if v:shell_error || empty(output)
|
||||||
|
return ''
|
||||||
|
endif
|
||||||
|
let ver = matchstr(output[-1], '[0-9.]\+')
|
||||||
|
let s:versions[a:bin] = ver
|
||||||
|
return ver
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! s:compare_versions(a, b)
|
||||||
|
let a = split(a:a, '\.')
|
||||||
|
let b = split(a:b, '\.')
|
||||||
|
for idx in range(0, max([len(a), len(b)]) - 1)
|
||||||
|
let v1 = str2nr(get(a, idx, 0))
|
||||||
|
let v2 = str2nr(get(b, idx, 0))
|
||||||
|
if v1 < v2 | return -1
|
||||||
|
elseif v1 > v2 | return 1
|
||||||
endif
|
endif
|
||||||
endfor
|
endfor
|
||||||
return 1
|
return 0
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! s:compare_binary_versions(a, b)
|
||||||
|
return s:compare_versions(s:get_version(a:a), s:get_version(a:b))
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
let s:checked = {}
|
let s:checked = {}
|
||||||
function! fzf#exec(...)
|
function! fzf#exec(...)
|
||||||
if !exists('s:exec')
|
if !exists('s:exec')
|
||||||
if executable(s:fzf_go)
|
let binaries = []
|
||||||
let s:exec = s:fzf_go
|
if executable('fzf')
|
||||||
elseif executable('fzf')
|
call add(binaries, 'fzf')
|
||||||
let s:exec = 'fzf'
|
|
||||||
elseif input('fzf executable not found. Download binary? (y/n) ') =~? '^y'
|
|
||||||
redraw
|
|
||||||
call fzf#install()
|
|
||||||
return fzf#exec()
|
|
||||||
else
|
|
||||||
redraw
|
|
||||||
throw 'fzf executable not found'
|
|
||||||
endif
|
endif
|
||||||
|
if executable(s:fzf_go)
|
||||||
|
call add(binaries, s:fzf_go)
|
||||||
|
endif
|
||||||
|
|
||||||
|
if empty(binaries)
|
||||||
|
if input('fzf executable not found. Download binary? (y/n) ') =~? '^y'
|
||||||
|
redraw
|
||||||
|
call fzf#install()
|
||||||
|
return fzf#exec()
|
||||||
|
else
|
||||||
|
redraw
|
||||||
|
throw 'fzf executable not found'
|
||||||
|
endif
|
||||||
|
elseif len(binaries) > 1
|
||||||
|
call sort(binaries, 's:compare_binary_versions')
|
||||||
|
endif
|
||||||
|
|
||||||
|
let s:exec = binaries[-1]
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if a:0 && !has_key(s:checked, a:1)
|
if a:0 && !has_key(s:checked, a:1)
|
||||||
let command = s:exec . ' --version'
|
let fzf_version = s:get_version(s:exec)
|
||||||
let output = systemlist(command)
|
if empty(fzf_version)
|
||||||
if v:shell_error || empty(output)
|
let message = printf('Failed to run "%s --version"', s:exec)
|
||||||
throw printf('Failed to run "%s": %s', command, output)
|
unlet s:exec
|
||||||
endif
|
throw message
|
||||||
let fzf_version = matchstr(output[-1], '[0-9.]\+')
|
end
|
||||||
if s:version_requirement(fzf_version, a:1)
|
|
||||||
|
if s:compare_versions(fzf_version, a:1) >= 0
|
||||||
let s:checked[a:1] = 1
|
let s:checked[a:1] = 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) ', a:1, fzf_version)) =~? '^y'
|
||||||
|
let s:versions = {}
|
||||||
|
unlet s:exec
|
||||||
redraw
|
redraw
|
||||||
call fzf#install()
|
call fzf#install()
|
||||||
return fzf#exec(a:1, 1)
|
return fzf#exec(a:1, 1)
|
||||||
@@ -764,6 +797,13 @@ function! s:split(dict)
|
|||||||
endtry
|
endtry
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
|
nnoremap <silent> <Plug>(fzf-insert) i
|
||||||
|
nnoremap <silent> <Plug>(fzf-normal) <Nop>
|
||||||
|
if exists(':tnoremap')
|
||||||
|
tnoremap <silent> <Plug>(fzf-insert) <C-\><C-n>i
|
||||||
|
tnoremap <silent> <Plug>(fzf-normal) <C-\><C-n>
|
||||||
|
endif
|
||||||
|
|
||||||
function! s:execute_term(dict, command, temps) abort
|
function! s:execute_term(dict, command, temps) abort
|
||||||
let winrest = winrestcmd()
|
let winrest = winrestcmd()
|
||||||
let pbuf = bufnr('')
|
let pbuf = bufnr('')
|
||||||
@@ -776,7 +816,7 @@ function! s:execute_term(dict, command, temps) abort
|
|||||||
function! fzf.switch_back(inplace)
|
function! fzf.switch_back(inplace)
|
||||||
if a:inplace && bufnr('') == self.buf
|
if a:inplace && bufnr('') == self.buf
|
||||||
if bufexists(self.pbuf)
|
if bufexists(self.pbuf)
|
||||||
execute 'keepalt b' self.pbuf
|
execute 'keepalt keepjumps b' self.pbuf
|
||||||
endif
|
endif
|
||||||
" No other listed buffer
|
" No other listed buffer
|
||||||
if bufnr('') == self.buf
|
if bufnr('') == self.buf
|
||||||
@@ -792,8 +832,6 @@ function! s:execute_term(dict, command, temps) abort
|
|||||||
call self.switch_back(1)
|
call self.switch_back(1)
|
||||||
else
|
else
|
||||||
if bufnr('') == self.buf
|
if bufnr('') == self.buf
|
||||||
" Exit terminal mode first (see neovim#13769)
|
|
||||||
call feedkeys("\<C-\>\<C-n>", 'n')
|
|
||||||
" We use close instead of bd! since Vim does not close the split when
|
" We use close instead of bd! since Vim does not close the split when
|
||||||
" there's no other listed buffer (nvim +'set nobuflisted')
|
" there's no other listed buffer (nvim +'set nobuflisted')
|
||||||
close
|
close
|
||||||
@@ -818,6 +856,10 @@ function! s:execute_term(dict, command, temps) abort
|
|||||||
call s:pushd(self.dict)
|
call s:pushd(self.dict)
|
||||||
call s:callback(self.dict, lines)
|
call s:callback(self.dict, lines)
|
||||||
call self.switch_back(s:getpos() == self.ppos)
|
call self.switch_back(s:getpos() == self.ppos)
|
||||||
|
|
||||||
|
if &buftype == 'terminal'
|
||||||
|
call feedkeys(&filetype == 'fzf' ? "\<Plug>(fzf-insert)" : "\<Plug>(fzf-normal)")
|
||||||
|
endif
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
try
|
try
|
||||||
@@ -833,16 +875,16 @@ function! s:execute_term(dict, command, temps) abort
|
|||||||
if has('nvim')
|
if has('nvim')
|
||||||
call termopen(command, fzf)
|
call termopen(command, fzf)
|
||||||
else
|
else
|
||||||
let term_opts = {'exit_cb': function(fzf.on_exit), 'term_kill': 'term'}
|
let term_opts = {'exit_cb': function(fzf.on_exit)}
|
||||||
|
if v:version >= 802
|
||||||
|
let term_opts.term_kill = 'term'
|
||||||
|
endif
|
||||||
if is_popup
|
if is_popup
|
||||||
let term_opts.hidden = 1
|
let term_opts.hidden = 1
|
||||||
else
|
else
|
||||||
let term_opts.curwin = 1
|
let term_opts.curwin = 1
|
||||||
endif
|
endif
|
||||||
let fzf.buf = term_start([&shell, &shellcmdflag, command], term_opts)
|
let fzf.buf = term_start([&shell, &shellcmdflag, command], term_opts)
|
||||||
if exists('&termwinkey')
|
|
||||||
call setbufvar(fzf.buf, '&termwinkey', '<c-z>')
|
|
||||||
endif
|
|
||||||
if is_popup && exists('#TerminalWinOpen')
|
if is_popup && exists('#TerminalWinOpen')
|
||||||
doautocmd <nomodeline> TerminalWinOpen
|
doautocmd <nomodeline> TerminalWinOpen
|
||||||
endif
|
endif
|
||||||
@@ -851,6 +893,9 @@ function! s:execute_term(dict, command, temps) abort
|
|||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
tnoremap <buffer> <c-z> <nop>
|
tnoremap <buffer> <c-z> <nop>
|
||||||
|
if exists('&termwinkey') && (empty(&termwinkey) || &termwinkey =~? '<c-w>')
|
||||||
|
tnoremap <buffer> <c-w> <c-w>.
|
||||||
|
endif
|
||||||
finally
|
finally
|
||||||
call s:dopopd()
|
call s:dopopd()
|
||||||
endtry
|
endtry
|
||||||
@@ -906,13 +951,9 @@ if has('nvim')
|
|||||||
function s:create_popup(hl, opts) abort
|
function s:create_popup(hl, opts) abort
|
||||||
let buf = nvim_create_buf(v:false, v:true)
|
let buf = nvim_create_buf(v:false, v:true)
|
||||||
let opts = extend({'relative': 'editor', 'style': 'minimal'}, a:opts)
|
let opts = extend({'relative': 'editor', 'style': 'minimal'}, a:opts)
|
||||||
let border = has_key(opts, 'border') ? remove(opts, 'border') : []
|
|
||||||
let win = nvim_open_win(buf, v:true, opts)
|
let win = nvim_open_win(buf, v:true, opts)
|
||||||
call setwinvar(win, '&winhighlight', 'NormalFloat:'..a:hl)
|
call setwinvar(win, '&winhighlight', 'NormalFloat:'..a:hl)
|
||||||
call setwinvar(win, '&colorcolumn', '')
|
call setwinvar(win, '&colorcolumn', '')
|
||||||
if !empty(border)
|
|
||||||
call nvim_buf_set_lines(buf, 0, -1, v:true, border)
|
|
||||||
endif
|
|
||||||
return buf
|
return buf
|
||||||
endfunction
|
endfunction
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ function fzf_key_bindings
|
|||||||
set -l commandline (__fzf_parse_commandline)
|
set -l commandline (__fzf_parse_commandline)
|
||||||
set -l dir $commandline[1]
|
set -l dir $commandline[1]
|
||||||
set -l fzf_query $commandline[2]
|
set -l fzf_query $commandline[2]
|
||||||
|
set -l prefix $commandline[3]
|
||||||
|
|
||||||
# "-path \$dir'*/\\.*'" matches hidden files/folders inside $dir but not
|
# "-path \$dir'*/\\.*'" matches hidden files/folders inside $dir but not
|
||||||
# $dir itself, even if hidden.
|
# $dir itself, even if hidden.
|
||||||
@@ -42,6 +43,7 @@ function fzf_key_bindings
|
|||||||
commandline -t ""
|
commandline -t ""
|
||||||
end
|
end
|
||||||
for i in $result
|
for i in $result
|
||||||
|
commandline -it -- $prefix
|
||||||
commandline -it -- (string escape $i)
|
commandline -it -- (string escape $i)
|
||||||
commandline -it -- ' '
|
commandline -it -- ' '
|
||||||
end
|
end
|
||||||
@@ -74,6 +76,7 @@ function fzf_key_bindings
|
|||||||
set -l commandline (__fzf_parse_commandline)
|
set -l commandline (__fzf_parse_commandline)
|
||||||
set -l dir $commandline[1]
|
set -l dir $commandline[1]
|
||||||
set -l fzf_query $commandline[2]
|
set -l fzf_query $commandline[2]
|
||||||
|
set -l prefix $commandline[3]
|
||||||
|
|
||||||
test -n "$FZF_ALT_C_COMMAND"; or set -l FZF_ALT_C_COMMAND "
|
test -n "$FZF_ALT_C_COMMAND"; or set -l FZF_ALT_C_COMMAND "
|
||||||
command find -L \$dir -mindepth 1 \\( -path \$dir'*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' \\) -prune \
|
command find -L \$dir -mindepth 1 \\( -path \$dir'*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' \\) -prune \
|
||||||
@@ -88,6 +91,7 @@ function fzf_key_bindings
|
|||||||
|
|
||||||
# Remove last token from commandline.
|
# Remove last token from commandline.
|
||||||
commandline -t ""
|
commandline -t ""
|
||||||
|
commandline -it -- $prefix
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -116,9 +120,15 @@ function fzf_key_bindings
|
|||||||
bind -M insert \ec fzf-cd-widget
|
bind -M insert \ec fzf-cd-widget
|
||||||
end
|
end
|
||||||
|
|
||||||
function __fzf_parse_commandline -d 'Parse the current command line token and return split of existing filepath and rest of token'
|
function __fzf_parse_commandline -d 'Parse the current command line token and return split of existing filepath, fzf query, and optional -option= prefix'
|
||||||
|
set -l commandline (commandline -t)
|
||||||
|
|
||||||
|
# strip -option= from token if present
|
||||||
|
set -l prefix (string match -r -- '^-[^\s=]+=' $commandline)
|
||||||
|
set commandline (string replace -- "$prefix" '' $commandline)
|
||||||
|
|
||||||
# eval is used to do shell expansion on paths
|
# eval is used to do shell expansion on paths
|
||||||
set -l commandline (eval "printf '%s' "(commandline -t))
|
eval set commandline $commandline
|
||||||
|
|
||||||
if [ -z $commandline ]
|
if [ -z $commandline ]
|
||||||
# Default to current directory with no --query
|
# Default to current directory with no --query
|
||||||
@@ -138,6 +148,7 @@ function fzf_key_bindings
|
|||||||
|
|
||||||
echo $dir
|
echo $dir
|
||||||
echo $fzf_query
|
echo $fzf_query
|
||||||
|
echo $prefix
|
||||||
end
|
end
|
||||||
|
|
||||||
function __fzf_get_dir -d 'Find the longest existing filepath from input string'
|
function __fzf_get_dir -d 'Find the longest existing filepath from input string'
|
||||||
|
|||||||
263
src/ansi.go
263
src/ansi.go
@@ -1,8 +1,6 @@
|
|||||||
package fzf
|
package fzf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
@@ -82,73 +80,154 @@ func toAnsiString(color tui.Color, offset int) string {
|
|||||||
return ret + ";"
|
return ret + ";"
|
||||||
}
|
}
|
||||||
|
|
||||||
var ansiRegex *regexp.Regexp
|
func isPrint(c uint8) bool {
|
||||||
|
return '\x20' <= c && c <= '\x7e'
|
||||||
func init() {
|
|
||||||
/*
|
|
||||||
References:
|
|
||||||
- https://github.com/gnachman/iTerm2
|
|
||||||
- http://ascii-table.com/ansi-escape-sequences.php
|
|
||||||
- http://ascii-table.com/ansi-escape-sequences-vt-100.php
|
|
||||||
- http://tldp.org/HOWTO/Bash-Prompt-HOWTO/x405.html
|
|
||||||
- https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
|
|
||||||
*/
|
|
||||||
// The following regular expression will include not all but most of the
|
|
||||||
// frequently used ANSI sequences
|
|
||||||
ansiRegex = regexp.MustCompile("(?:\x1b[\\[()][0-9;]*[a-zA-Z@]|\x1b][0-9];[[:print:]]+(?:\x1b\\\\|\x07)|\x1b.|[\x0e\x0f]|.\x08)")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func findAnsiStart(str string) int {
|
func matchOperatingSystemCommand(s string) int {
|
||||||
idx := 0
|
// `\x1b][0-9];[[:print:]]+(?:\x1b\\\\|\x07)`
|
||||||
for ; idx < len(str); idx++ {
|
// ^ match starting here
|
||||||
b := str[idx]
|
//
|
||||||
if b == 0x1b || b == 0x0e || b == 0x0f {
|
i := 5 // prefix matched in nextAnsiEscapeSequence()
|
||||||
return idx
|
for ; i < len(s) && isPrint(s[i]); i++ {
|
||||||
|
}
|
||||||
|
if i < len(s) {
|
||||||
|
if s[i] == '\x07' {
|
||||||
|
return i + 1
|
||||||
}
|
}
|
||||||
if b == 0x08 && idx > 0 {
|
if s[i] == '\x1b' && i < len(s)-1 && s[i+1] == '\\' {
|
||||||
return idx - 1
|
return i + 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return idx
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func matchControlSequence(s string) int {
|
||||||
|
// `\x1b[\\[()][0-9;?]*[a-zA-Z@]`
|
||||||
|
// ^ match starting here
|
||||||
|
//
|
||||||
|
i := 2 // prefix matched in nextAnsiEscapeSequence()
|
||||||
|
for ; i < len(s) && (isNumeric(s[i]) || s[i] == ';' || s[i] == '?'); i++ {
|
||||||
|
}
|
||||||
|
if i < len(s) {
|
||||||
|
c := s[i]
|
||||||
|
if 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '@' {
|
||||||
|
return i + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func isCtrlSeqStart(c uint8) bool {
|
||||||
|
return c == '\\' || c == '[' || c == '(' || c == ')'
|
||||||
|
}
|
||||||
|
|
||||||
|
// nextAnsiEscapeSequence returns the ANSI escape sequence and is equivalent to
|
||||||
|
// calling FindStringIndex() on the below regex (which was originally used):
|
||||||
|
//
|
||||||
|
// "(?:\x1b[\\[()][0-9;?]*[a-zA-Z@]|\x1b][0-9];[[:print:]]+(?:\x1b\\\\|\x07)|\x1b.|[\x0e\x0f]|.\x08)"
|
||||||
|
//
|
||||||
|
func nextAnsiEscapeSequence(s string) (int, int) {
|
||||||
|
// fast check for ANSI escape sequences
|
||||||
|
i := 0
|
||||||
|
for ; i < len(s); i++ {
|
||||||
|
switch s[i] {
|
||||||
|
case '\x0e', '\x0f', '\x1b', '\x08':
|
||||||
|
// We ignore the fact that '\x08' cannot be the first char
|
||||||
|
// in the string and be an escape sequence for the sake of
|
||||||
|
// speed and simplicity.
|
||||||
|
goto Loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1, -1
|
||||||
|
|
||||||
|
Loop:
|
||||||
|
for ; i < len(s); i++ {
|
||||||
|
switch s[i] {
|
||||||
|
case '\x08':
|
||||||
|
// backtrack to match: `.\x08`
|
||||||
|
if i > 0 && s[i-1] != '\n' {
|
||||||
|
if s[i-1] < utf8.RuneSelf {
|
||||||
|
return i - 1, i + 1
|
||||||
|
}
|
||||||
|
_, n := utf8.DecodeLastRuneInString(s[:i])
|
||||||
|
return i - n, i + 1
|
||||||
|
}
|
||||||
|
case '\x1b':
|
||||||
|
// match: `\x1b[\\[()][0-9;?]*[a-zA-Z@]`
|
||||||
|
if i+2 < len(s) && isCtrlSeqStart(s[i+1]) {
|
||||||
|
if j := matchControlSequence(s[i:]); j != -1 {
|
||||||
|
return i, i + j
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// match: `\x1b][0-9];[[:print:]]+(?:\x1b\\\\|\x07)`
|
||||||
|
if i+5 < len(s) && s[i+1] == ']' && isNumeric(s[i+2]) &&
|
||||||
|
s[i+3] == ';' && isPrint(s[i+4]) {
|
||||||
|
|
||||||
|
if j := matchOperatingSystemCommand(s[i:]); j != -1 {
|
||||||
|
return i, i + j
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// match: `\x1b.`
|
||||||
|
if i+1 < len(s) && s[i+1] != '\n' {
|
||||||
|
if s[i+1] < utf8.RuneSelf {
|
||||||
|
return i, i + 2
|
||||||
|
}
|
||||||
|
_, n := utf8.DecodeRuneInString(s[i+1:])
|
||||||
|
return i, i + n + 1
|
||||||
|
}
|
||||||
|
case '\x0e', '\x0f':
|
||||||
|
// match: `[\x0e\x0f]`
|
||||||
|
return i, i + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1, -1
|
||||||
}
|
}
|
||||||
|
|
||||||
func extractColor(str string, state *ansiState, proc func(string, *ansiState) bool) (string, *[]ansiOffset, *ansiState) {
|
func extractColor(str string, state *ansiState, proc func(string, *ansiState) bool) (string, *[]ansiOffset, *ansiState) {
|
||||||
var offsets []ansiOffset
|
// We append to a stack allocated variable that we'll
|
||||||
var output bytes.Buffer
|
// later copy and return, to save on allocations.
|
||||||
|
offsets := make([]ansiOffset, 0, 32)
|
||||||
|
|
||||||
if state != nil {
|
if state != nil {
|
||||||
offsets = append(offsets, ansiOffset{[2]int32{0, 0}, *state})
|
offsets = append(offsets, ansiOffset{[2]int32{0, 0}, *state})
|
||||||
}
|
}
|
||||||
|
|
||||||
prevIdx := 0
|
var (
|
||||||
runeCount := 0
|
pstate *ansiState // lazily allocated
|
||||||
|
output strings.Builder
|
||||||
|
prevIdx int
|
||||||
|
runeCount int
|
||||||
|
)
|
||||||
for idx := 0; idx < len(str); {
|
for idx := 0; idx < len(str); {
|
||||||
idx += findAnsiStart(str[idx:])
|
// Make sure that we found an ANSI code
|
||||||
if idx == len(str) {
|
start, end := nextAnsiEscapeSequence(str[idx:])
|
||||||
|
if start == -1 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
start += idx
|
||||||
// Make sure that we found an ANSI code
|
idx += end
|
||||||
offset := ansiRegex.FindStringIndex(str[idx:])
|
|
||||||
if len(offset) < 2 {
|
|
||||||
idx++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
offset[0] += idx
|
|
||||||
offset[1] += idx
|
|
||||||
idx = offset[1]
|
|
||||||
|
|
||||||
// Check if we should continue
|
// Check if we should continue
|
||||||
prev := str[prevIdx:offset[0]]
|
prev := str[prevIdx:start]
|
||||||
if proc != nil && !proc(prev, state) {
|
if proc != nil && !proc(prev, state) {
|
||||||
return "", nil, nil
|
return "", nil, nil
|
||||||
}
|
}
|
||||||
|
prevIdx = idx
|
||||||
|
|
||||||
prevIdx = offset[1]
|
if len(prev) != 0 {
|
||||||
runeCount += utf8.RuneCountInString(prev)
|
runeCount += utf8.RuneCountInString(prev)
|
||||||
output.WriteString(prev)
|
// Grow the buffer size to the maximum possible length (string length
|
||||||
|
// containing ansi codes) to avoid repetitive allocation
|
||||||
|
if output.Cap() == 0 {
|
||||||
|
output.Grow(len(str))
|
||||||
|
}
|
||||||
|
output.WriteString(prev)
|
||||||
|
}
|
||||||
|
|
||||||
newState := interpretCode(str[offset[0]:offset[1]], state)
|
newState := interpretCode(str[start:idx], state)
|
||||||
if !newState.equals(state) {
|
if !newState.equals(state) {
|
||||||
if state != nil {
|
if state != nil {
|
||||||
// Update last offset
|
// Update last offset
|
||||||
@@ -157,8 +236,15 @@ func extractColor(str string, state *ansiState, proc func(string, *ansiState) bo
|
|||||||
|
|
||||||
if newState.colored() {
|
if newState.colored() {
|
||||||
// Append new offset
|
// Append new offset
|
||||||
state = newState
|
if pstate == nil {
|
||||||
offsets = append(offsets, ansiOffset{[2]int32{int32(runeCount), int32(runeCount)}, *state})
|
pstate = &ansiState{}
|
||||||
|
}
|
||||||
|
*pstate = newState
|
||||||
|
state = pstate
|
||||||
|
offsets = append(offsets, ansiOffset{
|
||||||
|
[2]int32{int32(runeCount), int32(runeCount)},
|
||||||
|
newState,
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
// Discard state
|
// Discard state
|
||||||
state = nil
|
state = nil
|
||||||
@@ -168,7 +254,6 @@ func extractColor(str string, state *ansiState, proc func(string, *ansiState) bo
|
|||||||
|
|
||||||
var rest string
|
var rest string
|
||||||
var trimmed string
|
var trimmed string
|
||||||
|
|
||||||
if prevIdx == 0 {
|
if prevIdx == 0 {
|
||||||
// No ANSI code found
|
// No ANSI code found
|
||||||
rest = str
|
rest = str
|
||||||
@@ -178,51 +263,75 @@ func extractColor(str string, state *ansiState, proc func(string, *ansiState) bo
|
|||||||
output.WriteString(rest)
|
output.WriteString(rest)
|
||||||
trimmed = output.String()
|
trimmed = output.String()
|
||||||
}
|
}
|
||||||
if len(rest) > 0 && state != nil {
|
|
||||||
// Update last offset
|
|
||||||
runeCount += utf8.RuneCountInString(rest)
|
|
||||||
(&offsets[len(offsets)-1]).offset[1] = int32(runeCount)
|
|
||||||
}
|
|
||||||
if proc != nil {
|
if proc != nil {
|
||||||
proc(rest, state)
|
proc(rest, state)
|
||||||
}
|
}
|
||||||
if len(offsets) == 0 {
|
if len(offsets) > 0 {
|
||||||
return trimmed, nil, state
|
if len(rest) > 0 && state != nil {
|
||||||
|
// Update last offset
|
||||||
|
runeCount += utf8.RuneCountInString(rest)
|
||||||
|
(&offsets[len(offsets)-1]).offset[1] = int32(runeCount)
|
||||||
|
}
|
||||||
|
// Return a copy of the offsets slice
|
||||||
|
a := make([]ansiOffset, len(offsets))
|
||||||
|
copy(a, offsets)
|
||||||
|
return trimmed, &a, state
|
||||||
}
|
}
|
||||||
return trimmed, &offsets, state
|
return trimmed, nil, state
|
||||||
}
|
}
|
||||||
|
|
||||||
func interpretCode(ansiCode string, prevState *ansiState) *ansiState {
|
func parseAnsiCode(s string) (int, string) {
|
||||||
// State
|
var remaining string
|
||||||
var state *ansiState
|
if i := strings.IndexByte(s, ';'); i >= 0 {
|
||||||
|
remaining = s[i+1:]
|
||||||
|
s = s[:i]
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(s) > 0 {
|
||||||
|
// Inlined version of strconv.Atoi() that only handles positive
|
||||||
|
// integers and does not allocate on error.
|
||||||
|
code := 0
|
||||||
|
for _, ch := range []byte(s) {
|
||||||
|
ch -= '0'
|
||||||
|
if ch > 9 {
|
||||||
|
return -1, remaining
|
||||||
|
}
|
||||||
|
code = code*10 + int(ch)
|
||||||
|
}
|
||||||
|
return code, remaining
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1, remaining
|
||||||
|
}
|
||||||
|
|
||||||
|
func interpretCode(ansiCode string, prevState *ansiState) ansiState {
|
||||||
|
var state ansiState
|
||||||
if prevState == nil {
|
if prevState == nil {
|
||||||
state = &ansiState{-1, -1, 0, -1}
|
state = ansiState{-1, -1, 0, -1}
|
||||||
} else {
|
} else {
|
||||||
state = &ansiState{prevState.fg, prevState.bg, prevState.attr, prevState.lbg}
|
state = ansiState{prevState.fg, prevState.bg, prevState.attr, prevState.lbg}
|
||||||
}
|
}
|
||||||
if ansiCode[0] != '\x1b' || ansiCode[1] != '[' || ansiCode[len(ansiCode)-1] != 'm' {
|
if ansiCode[0] != '\x1b' || ansiCode[1] != '[' || ansiCode[len(ansiCode)-1] != 'm' {
|
||||||
if strings.HasSuffix(ansiCode, "0K") && prevState != nil {
|
if prevState != nil && strings.HasSuffix(ansiCode, "0K") {
|
||||||
state.lbg = prevState.bg
|
state.lbg = prevState.bg
|
||||||
}
|
}
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
ptr := &state.fg
|
if len(ansiCode) <= 3 {
|
||||||
state256 := 0
|
|
||||||
|
|
||||||
init := func() {
|
|
||||||
state.fg = -1
|
state.fg = -1
|
||||||
state.bg = -1
|
state.bg = -1
|
||||||
state.attr = 0
|
state.attr = 0
|
||||||
state256 = 0
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
ansiCode = ansiCode[2 : len(ansiCode)-1]
|
ansiCode = ansiCode[2 : len(ansiCode)-1]
|
||||||
if len(ansiCode) == 0 {
|
|
||||||
init()
|
state256 := 0
|
||||||
}
|
ptr := &state.fg
|
||||||
for _, code := range strings.Split(ansiCode, ";") {
|
|
||||||
if num, err := strconv.Atoi(code); err == nil {
|
for len(ansiCode) != 0 {
|
||||||
|
var num int
|
||||||
|
if num, ansiCode = parseAnsiCode(ansiCode); num != -1 {
|
||||||
switch state256 {
|
switch state256 {
|
||||||
case 0:
|
case 0:
|
||||||
switch num {
|
switch num {
|
||||||
@@ -253,7 +362,10 @@ func interpretCode(ansiCode string, prevState *ansiState) *ansiState {
|
|||||||
case 24: // tput rmul
|
case 24: // tput rmul
|
||||||
state.attr = state.attr &^ tui.Underline
|
state.attr = state.attr &^ tui.Underline
|
||||||
case 0:
|
case 0:
|
||||||
init()
|
state.fg = -1
|
||||||
|
state.bg = -1
|
||||||
|
state.attr = 0
|
||||||
|
state256 = 0
|
||||||
default:
|
default:
|
||||||
if num >= 30 && num <= 37 {
|
if num >= 30 && num <= 37 {
|
||||||
state.fg = tui.Color(num - 30)
|
state.fg = tui.Color(num - 30)
|
||||||
@@ -289,6 +401,7 @@ func interpretCode(ansiCode string, prevState *ansiState) *ansiState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if state256 > 0 {
|
if state256 > 0 {
|
||||||
*ptr = -1
|
*ptr = -1
|
||||||
}
|
}
|
||||||
|
|||||||
239
src/ansi_test.go
239
src/ansi_test.go
@@ -2,12 +2,190 @@ package fzf
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/tui"
|
"github.com/junegunn/fzf/src/tui"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// The following regular expression will include not all but most of the
|
||||||
|
// frequently used ANSI sequences. This regex is used as a reference for
|
||||||
|
// testing nextAnsiEscapeSequence().
|
||||||
|
//
|
||||||
|
// References:
|
||||||
|
// - https://github.com/gnachman/iTerm2
|
||||||
|
// - http://ascii-table.com/ansi-escape-sequences.php
|
||||||
|
// - http://ascii-table.com/ansi-escape-sequences-vt-100.php
|
||||||
|
// - http://tldp.org/HOWTO/Bash-Prompt-HOWTO/x405.html
|
||||||
|
// - https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
|
||||||
|
var ansiRegexRefence = regexp.MustCompile("(?:\x1b[\\[()][0-9;]*[a-zA-Z@]|\x1b][0-9];[[:print:]]+(?:\x1b\\\\|\x07)|\x1b.|[\x0e\x0f]|.\x08)")
|
||||||
|
|
||||||
|
func testParserReference(t testing.TB, str string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
toSlice := func(start, end int) []int {
|
||||||
|
if start == -1 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return []int{start, end}
|
||||||
|
}
|
||||||
|
|
||||||
|
s := str
|
||||||
|
for i := 0; ; i++ {
|
||||||
|
got := toSlice(nextAnsiEscapeSequence(s))
|
||||||
|
exp := ansiRegexRefence.FindStringIndex(s)
|
||||||
|
|
||||||
|
equal := len(got) == len(exp)
|
||||||
|
if equal {
|
||||||
|
for i := 0; i < len(got); i++ {
|
||||||
|
if got[i] != exp[i] {
|
||||||
|
equal = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !equal {
|
||||||
|
var exps, gots []rune
|
||||||
|
if len(got) == 2 {
|
||||||
|
gots = []rune(s[got[0]:got[1]])
|
||||||
|
}
|
||||||
|
if len(exp) == 2 {
|
||||||
|
exps = []rune(s[exp[0]:exp[1]])
|
||||||
|
}
|
||||||
|
t.Errorf("%d: %q: got: %v (%q) want: %v (%q)", i, s, got, gots, exp, exps)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(exp) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s = s[exp[1]:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNextAnsiEscapeSequence(t *testing.T) {
|
||||||
|
testStrs := []string{
|
||||||
|
"\x1b[0mhello world",
|
||||||
|
"\x1b[1mhello world",
|
||||||
|
"椙\x1b[1m椙",
|
||||||
|
"椙\x1b[1椙m椙",
|
||||||
|
"\x1b[1mhello \x1b[mw\x1b7o\x1b8r\x1b(Bl\x1b[2@d",
|
||||||
|
"\x1b[1mhello \x1b[Kworld",
|
||||||
|
"hello \x1b[34;45;1mworld",
|
||||||
|
"hello \x1b[34;45;1mwor\x1b[34;45;1mld",
|
||||||
|
"hello \x1b[34;45;1mwor\x1b[0mld",
|
||||||
|
"hello \x1b[34;48;5;233;1mwo\x1b[38;5;161mr\x1b[0ml\x1b[38;5;161md",
|
||||||
|
"hello \x1b[38;5;38;48;5;48;1mwor\x1b[38;5;48;48;5;38ml\x1b[0md",
|
||||||
|
"hello \x1b[32;1mworld",
|
||||||
|
"hello world",
|
||||||
|
"hello \x1b[0;38;5;200;48;5;100mworld",
|
||||||
|
"\x1b椙",
|
||||||
|
"椙\x08",
|
||||||
|
"\n\x08",
|
||||||
|
"X\x08",
|
||||||
|
"",
|
||||||
|
"\x1b]4;3;rgb:aa/bb/cc\x07 ",
|
||||||
|
"\x1b]4;3;rgb:aa/bb/cc\x1b\\ ",
|
||||||
|
ansiBenchmarkString,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range testStrs {
|
||||||
|
testParserReference(t, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNextAnsiEscapeSequence_Fuzz_Modified(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("short test")
|
||||||
|
}
|
||||||
|
|
||||||
|
testStrs := []string{
|
||||||
|
"\x1b[0mhello world",
|
||||||
|
"\x1b[1mhello world",
|
||||||
|
"椙\x1b[1m椙",
|
||||||
|
"椙\x1b[1椙m椙",
|
||||||
|
"\x1b[1mhello \x1b[mw\x1b7o\x1b8r\x1b(Bl\x1b[2@d",
|
||||||
|
"\x1b[1mhello \x1b[Kworld",
|
||||||
|
"hello \x1b[34;45;1mworld",
|
||||||
|
"hello \x1b[34;45;1mwor\x1b[34;45;1mld",
|
||||||
|
"hello \x1b[34;45;1mwor\x1b[0mld",
|
||||||
|
"hello \x1b[34;48;5;233;1mwo\x1b[38;5;161mr\x1b[0ml\x1b[38;5;161md",
|
||||||
|
"hello \x1b[38;5;38;48;5;48;1mwor\x1b[38;5;48;48;5;38ml\x1b[0md",
|
||||||
|
"hello \x1b[32;1mworld",
|
||||||
|
"hello world",
|
||||||
|
"hello \x1b[0;38;5;200;48;5;100mworld",
|
||||||
|
ansiBenchmarkString,
|
||||||
|
}
|
||||||
|
|
||||||
|
replacementBytes := [...]rune{'\x0e', '\x0f', '\x1b', '\x08'}
|
||||||
|
|
||||||
|
modifyString := func(s string, rr *rand.Rand) string {
|
||||||
|
n := rr.Intn(len(s))
|
||||||
|
b := []rune(s)
|
||||||
|
for ; n >= 0 && len(b) != 0; n-- {
|
||||||
|
i := rr.Intn(len(b))
|
||||||
|
switch x := rr.Intn(4); x {
|
||||||
|
case 0:
|
||||||
|
b = append(b[:i], b[i+1:]...)
|
||||||
|
case 1:
|
||||||
|
j := rr.Intn(len(replacementBytes) - 1)
|
||||||
|
b[i] = replacementBytes[j]
|
||||||
|
case 2:
|
||||||
|
x := rune(rr.Intn(utf8.MaxRune))
|
||||||
|
for !utf8.ValidRune(x) {
|
||||||
|
x = rune(rr.Intn(utf8.MaxRune))
|
||||||
|
}
|
||||||
|
b[i] = x
|
||||||
|
case 3:
|
||||||
|
b[i] = rune(rr.Intn(utf8.MaxRune)) // potentially invalid
|
||||||
|
default:
|
||||||
|
t.Fatalf("unsupported value: %d", x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
rr := rand.New(rand.NewSource(1))
|
||||||
|
for _, s := range testStrs {
|
||||||
|
for i := 1_000; i >= 0; i-- {
|
||||||
|
testParserReference(t, modifyString(s, rr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNextAnsiEscapeSequence_Fuzz_Random(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("short test")
|
||||||
|
}
|
||||||
|
|
||||||
|
randomString := func(rr *rand.Rand) string {
|
||||||
|
numChars := rand.Intn(50)
|
||||||
|
codePoints := make([]rune, numChars)
|
||||||
|
for i := 0; i < len(codePoints); i++ {
|
||||||
|
var r rune
|
||||||
|
for n := 0; n < 1000; n++ {
|
||||||
|
r = rune(rr.Intn(utf8.MaxRune))
|
||||||
|
// Allow 10% of runes to be invalid
|
||||||
|
if utf8.ValidRune(r) || rr.Float64() < 0.10 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
codePoints[i] = r
|
||||||
|
}
|
||||||
|
return string(codePoints)
|
||||||
|
}
|
||||||
|
|
||||||
|
rr := rand.New(rand.NewSource(1))
|
||||||
|
for i := 0; i < 100_000; i++ {
|
||||||
|
testParserReference(t, randomString(rr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestExtractColor(t *testing.T) {
|
func TestExtractColor(t *testing.T) {
|
||||||
assert := func(offset ansiOffset, b int32, e int32, fg tui.Color, bg tui.Color, bold bool) {
|
assert := func(offset ansiOffset, b int32, e int32, fg tui.Color, bg tui.Color, bold bool) {
|
||||||
var attr tui.Attr
|
var attr tui.Attr
|
||||||
@@ -185,3 +363,64 @@ func TestAnsiCodeStringConversion(t *testing.T) {
|
|||||||
&ansiState{attr: tui.Dim | tui.Italic, fg: 1, bg: 1},
|
&ansiState{attr: tui.Dim | tui.Italic, fg: 1, bg: 1},
|
||||||
"\x1b[2;3;7;38;2;10;20;30;48;5;100m")
|
"\x1b[2;3;7;38;2;10;20;30;48;5;100m")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseAnsiCode(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
In, Exp string
|
||||||
|
N int
|
||||||
|
}{
|
||||||
|
{"123", "", 123},
|
||||||
|
{"1a", "", -1},
|
||||||
|
{"1a;12", "12", -1},
|
||||||
|
{"12;a", "a", 12},
|
||||||
|
{"-2", "", -1},
|
||||||
|
}
|
||||||
|
for _, x := range tests {
|
||||||
|
n, s := parseAnsiCode(x.In)
|
||||||
|
if n != x.N || s != x.Exp {
|
||||||
|
t.Fatalf("%q: got: (%d %q) want: (%d %q)", x.In, n, s, x.N, x.Exp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// kernel/bpf/preload/iterators/README
|
||||||
|
const ansiBenchmarkString = "\x1b[38;5;81m\x1b[01;31m\x1b[Kkernel/\x1b[0m\x1b[38;5;81mbpf/" +
|
||||||
|
"\x1b[0m\x1b[38;5;81mpreload/\x1b[0m\x1b[38;5;81miterators/" +
|
||||||
|
"\x1b[0m\x1b[38;5;149mMakefile\x1b[m\x1b[K\x1b[0m"
|
||||||
|
|
||||||
|
func BenchmarkNextAnsiEscapeSequence(b *testing.B) {
|
||||||
|
b.SetBytes(int64(len(ansiBenchmarkString)))
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
s := ansiBenchmarkString
|
||||||
|
for {
|
||||||
|
_, o := nextAnsiEscapeSequence(s)
|
||||||
|
if o == -1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
s = s[o:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Baseline test to compare the speed of nextAnsiEscapeSequence() to the
|
||||||
|
// previously used regex based implementation.
|
||||||
|
func BenchmarkNextAnsiEscapeSequence_Regex(b *testing.B) {
|
||||||
|
b.SetBytes(int64(len(ansiBenchmarkString)))
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
s := ansiBenchmarkString
|
||||||
|
for {
|
||||||
|
a := ansiRegexRefence.FindStringIndex(s)
|
||||||
|
if len(a) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
s = s[a[1]:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkExtractColor(b *testing.B) {
|
||||||
|
b.SetBytes(int64(len(ansiBenchmarkString)))
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
extractColor(ansiBenchmarkString, nil, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ func TestChunkCache(t *testing.T) {
|
|||||||
cache := NewChunkCache()
|
cache := NewChunkCache()
|
||||||
chunk1p := &Chunk{}
|
chunk1p := &Chunk{}
|
||||||
chunk2p := &Chunk{count: chunkSize}
|
chunk2p := &Chunk{count: chunkSize}
|
||||||
items1 := []Result{Result{}}
|
items1 := []Result{{}}
|
||||||
items2 := []Result{Result{}, Result{}}
|
items2 := []Result{{}, {}}
|
||||||
cache.Add(chunk1p, "foo", items1)
|
cache.Add(chunk1p, "foo", items1)
|
||||||
cache.Add(chunk2p, "foo", items1)
|
cache.Add(chunk2p, "foo", items1)
|
||||||
cache.Add(chunk2p, "bar", items2)
|
cache.Add(chunk2p, "bar", items2)
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ const (
|
|||||||
EvtSearchFin
|
EvtSearchFin
|
||||||
EvtHeader
|
EvtHeader
|
||||||
EvtReady
|
EvtReady
|
||||||
|
EvtQuit
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -254,7 +254,11 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
}
|
}
|
||||||
for evt, value := range *events {
|
for evt, value := range *events {
|
||||||
switch evt {
|
switch evt {
|
||||||
|
case EvtQuit:
|
||||||
|
if reading {
|
||||||
|
reader.terminate()
|
||||||
|
}
|
||||||
|
os.Exit(value.(int))
|
||||||
case EvtReadNew, EvtReadFin:
|
case EvtReadNew, EvtReadFin:
|
||||||
if evt == EvtReadFin && nextCommand != nil {
|
if evt == EvtReadFin && nextCommand != nil {
|
||||||
restart(*nextCommand)
|
restart(*nextCommand)
|
||||||
|
|||||||
@@ -106,7 +106,6 @@ func (mg *Merger) mergedGet(idx int) Result {
|
|||||||
minIdx = listIdx
|
minIdx = listIdx
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mg.cursors[listIdx] = cursor
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if minIdx >= 0 {
|
if minIdx >= 0 {
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ const usage = `usage: fzf [options]
|
|||||||
--layout=LAYOUT Choose layout: [default|reverse|reverse-list]
|
--layout=LAYOUT Choose layout: [default|reverse|reverse-list]
|
||||||
--border[=STYLE] Draw border around the finder
|
--border[=STYLE] Draw border around the finder
|
||||||
[rounded|sharp|horizontal|vertical|
|
[rounded|sharp|horizontal|vertical|
|
||||||
top|bottom|left|right] (default: rounded)
|
top|bottom|left|right|none] (default: rounded)
|
||||||
--margin=MARGIN Screen margin (TRBL | TB,RL | T,RL,B | T,R,B,L)
|
--margin=MARGIN Screen margin (TRBL | TB,RL | T,RL,B | T,R,B,L)
|
||||||
--padding=PADDING Padding inside border (TRBL | TB,RL | T,RL,B | T,R,B,L)
|
--padding=PADDING Padding inside border (TRBL | TB,RL | T,RL,B | T,R,B,L)
|
||||||
--info=STYLE Finder info style [default|inline|hidden]
|
--info=STYLE Finder info style [default|inline|hidden]
|
||||||
@@ -81,11 +81,11 @@ const usage = `usage: fzf [options]
|
|||||||
Preview
|
Preview
|
||||||
--preview=COMMAND Command to preview highlighted line ({})
|
--preview=COMMAND Command to preview highlighted line ({})
|
||||||
--preview-window=OPT Preview window layout (default: right:50%)
|
--preview-window=OPT Preview window layout (default: right:50%)
|
||||||
[up|down|left|right][:SIZE[%]]
|
[up|down|left|right][,SIZE[%]]
|
||||||
[:[no]wrap][:[no]cycle][:[no]follow][:[no]hidden]
|
[,[no]wrap][,[no]cycle][,[no]follow][,[no]hidden]
|
||||||
[:rounded|sharp|noborder]
|
[,border-BORDER_OPT]
|
||||||
[:+SCROLL[-OFFSET]]
|
[,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES]
|
||||||
[:default]
|
[,default]
|
||||||
|
|
||||||
Scripting
|
Scripting
|
||||||
-q, --query=STR Start the finder with the given query
|
-q, --query=STR Start the finder with the given query
|
||||||
@@ -161,15 +161,16 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type previewOpts struct {
|
type previewOpts struct {
|
||||||
command string
|
command string
|
||||||
position windowPosition
|
position windowPosition
|
||||||
size sizeSpec
|
size sizeSpec
|
||||||
scroll string
|
scroll string
|
||||||
hidden bool
|
hidden bool
|
||||||
wrap bool
|
wrap bool
|
||||||
cycle bool
|
cycle bool
|
||||||
follow bool
|
follow bool
|
||||||
border tui.BorderShape
|
border tui.BorderShape
|
||||||
|
headerLines int
|
||||||
}
|
}
|
||||||
|
|
||||||
// Options stores the values of command-line options
|
// Options stores the values of command-line options
|
||||||
@@ -231,7 +232,7 @@ type Options struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func defaultPreviewOpts(command string) previewOpts {
|
func defaultPreviewOpts(command string) previewOpts {
|
||||||
return previewOpts{command, posRight, sizeSpec{50, true}, "", false, false, false, false, tui.BorderRounded}
|
return previewOpts{command, posRight, sizeSpec{50, true}, "", false, false, false, false, tui.BorderRounded, 0}
|
||||||
}
|
}
|
||||||
|
|
||||||
func defaultOptions() *Options {
|
func defaultOptions() *Options {
|
||||||
@@ -435,11 +436,13 @@ func parseBorder(str string, optional bool) tui.BorderShape {
|
|||||||
return tui.BorderLeft
|
return tui.BorderLeft
|
||||||
case "right":
|
case "right":
|
||||||
return tui.BorderRight
|
return tui.BorderRight
|
||||||
|
case "none":
|
||||||
|
return tui.BorderNone
|
||||||
default:
|
default:
|
||||||
if optional && str == "" {
|
if optional && str == "" {
|
||||||
return tui.BorderRounded
|
return tui.BorderRounded
|
||||||
}
|
}
|
||||||
errorExit("invalid border style (expected: rounded|sharp|horizontal|vertical|top|bottom|left|right)")
|
errorExit("invalid border style (expected: rounded|sharp|horizontal|vertical|top|bottom|left|right|none)")
|
||||||
}
|
}
|
||||||
return tui.BorderNone
|
return tui.BorderNone
|
||||||
}
|
}
|
||||||
@@ -839,6 +842,8 @@ func parseKeymap(keymap map[tui.Event][]action, str string) {
|
|||||||
appendAction(actDeleteChar)
|
appendAction(actDeleteChar)
|
||||||
case "delete-char/eof":
|
case "delete-char/eof":
|
||||||
appendAction(actDeleteCharEOF)
|
appendAction(actDeleteCharEOF)
|
||||||
|
case "deselect":
|
||||||
|
appendAction(actDeselect)
|
||||||
case "end-of-line":
|
case "end-of-line":
|
||||||
appendAction(actEndOfLine)
|
appendAction(actEndOfLine)
|
||||||
case "cancel":
|
case "cancel":
|
||||||
@@ -879,6 +884,8 @@ func parseKeymap(keymap map[tui.Event][]action, str string) {
|
|||||||
appendAction(actToggleAll)
|
appendAction(actToggleAll)
|
||||||
case "toggle-search":
|
case "toggle-search":
|
||||||
appendAction(actToggleSearch)
|
appendAction(actToggleSearch)
|
||||||
|
case "select":
|
||||||
|
appendAction(actSelect)
|
||||||
case "select-all":
|
case "select-all":
|
||||||
appendAction(actSelectAll)
|
appendAction(actSelectAll)
|
||||||
case "deselect-all":
|
case "deselect-all":
|
||||||
@@ -1071,9 +1078,11 @@ func parseInfoStyle(str string) infoStyle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func parsePreviewWindow(opts *previewOpts, input string) {
|
func parsePreviewWindow(opts *previewOpts, input string) {
|
||||||
tokens := strings.Split(input, ":")
|
delimRegex := regexp.MustCompile("[:,]") // : for backward compatibility
|
||||||
sizeRegex := regexp.MustCompile("^[0-9]+%?$")
|
sizeRegex := regexp.MustCompile("^[0-9]+%?$")
|
||||||
offsetRegex := regexp.MustCompile("^\\+([0-9]+|{-?[0-9]+})(-[0-9]+|-/[1-9][0-9]*)?$")
|
offsetRegex := regexp.MustCompile(`^(\+{-?[0-9]+})?([+-][0-9]+)*(-?/[1-9][0-9]*)?$`)
|
||||||
|
headerRegex := regexp.MustCompile("^~(0|[1-9][0-9]*)$")
|
||||||
|
tokens := delimRegex.Split(input, -1)
|
||||||
for _, token := range tokens {
|
for _, token := range tokens {
|
||||||
switch token {
|
switch token {
|
||||||
case "":
|
case "":
|
||||||
@@ -1099,21 +1108,35 @@ func parsePreviewWindow(opts *previewOpts, input string) {
|
|||||||
opts.position = posLeft
|
opts.position = posLeft
|
||||||
case "right":
|
case "right":
|
||||||
opts.position = posRight
|
opts.position = posRight
|
||||||
case "rounded", "border":
|
case "rounded", "border", "border-rounded":
|
||||||
opts.border = tui.BorderRounded
|
opts.border = tui.BorderRounded
|
||||||
case "sharp":
|
case "sharp", "border-sharp":
|
||||||
opts.border = tui.BorderSharp
|
opts.border = tui.BorderSharp
|
||||||
case "noborder":
|
case "noborder", "border-none":
|
||||||
opts.border = tui.BorderNone
|
opts.border = tui.BorderNone
|
||||||
|
case "border-horizontal":
|
||||||
|
opts.border = tui.BorderHorizontal
|
||||||
|
case "border-vertical":
|
||||||
|
opts.border = tui.BorderVertical
|
||||||
|
case "border-top":
|
||||||
|
opts.border = tui.BorderTop
|
||||||
|
case "border-bottom":
|
||||||
|
opts.border = tui.BorderBottom
|
||||||
|
case "border-left":
|
||||||
|
opts.border = tui.BorderLeft
|
||||||
|
case "border-right":
|
||||||
|
opts.border = tui.BorderRight
|
||||||
case "follow":
|
case "follow":
|
||||||
opts.follow = true
|
opts.follow = true
|
||||||
case "nofollow":
|
case "nofollow":
|
||||||
opts.follow = false
|
opts.follow = false
|
||||||
default:
|
default:
|
||||||
if sizeRegex.MatchString(token) {
|
if headerRegex.MatchString(token) {
|
||||||
|
opts.headerLines = atoi(token[1:])
|
||||||
|
} else if sizeRegex.MatchString(token) {
|
||||||
opts.size = parseSize(token, 99, "window size")
|
opts.size = parseSize(token, 99, "window size")
|
||||||
} else if offsetRegex.MatchString(token) {
|
} else if offsetRegex.MatchString(token) {
|
||||||
opts.scroll = token[1:]
|
opts.scroll = token
|
||||||
} else {
|
} else {
|
||||||
errorExit("invalid preview window option: " + token)
|
errorExit("invalid preview window option: " + token)
|
||||||
}
|
}
|
||||||
@@ -1360,7 +1383,7 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.Preview.command = ""
|
opts.Preview.command = ""
|
||||||
case "--preview-window":
|
case "--preview-window":
|
||||||
parsePreviewWindow(&opts.Preview,
|
parsePreviewWindow(&opts.Preview,
|
||||||
nextString(allArgs, &i, "preview window layout required: [up|down|left|right][:SIZE[%]][:rounded|sharp|noborder][:wrap][:cycle][:hidden][:+SCROLL[-OFFSET]][:default]"))
|
nextString(allArgs, &i, "preview window layout required: [up|down|left|right][,SIZE[%]][,border-BORDER_OPT][,wrap][,cycle][,hidden][,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES][,default]"))
|
||||||
case "--height":
|
case "--height":
|
||||||
opts.Height = parseHeight(nextString(allArgs, &i, "height required: HEIGHT[%]"))
|
opts.Height = parseHeight(nextString(allArgs, &i, "height required: HEIGHT[%]"))
|
||||||
case "--min-height":
|
case "--min-height":
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ func TestIrrelevantNth(t *testing.T) {
|
|||||||
t.Errorf("nth should be empty: %v", opts.Nth)
|
t.Errorf("nth should be empty: %v", opts.Nth)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, words := range [][]string{[]string{"--nth", "..,3", "+x"}, []string{"--nth", "3,1..", "+x"}, []string{"--nth", "..-1,1", "+x"}} {
|
for _, words := range [][]string{{"--nth", "..,3", "+x"}, {"--nth", "3,1..", "+x"}, {"--nth", "..-1,1", "+x"}} {
|
||||||
{
|
{
|
||||||
opts := defaultOptions()
|
opts := defaultOptions()
|
||||||
parseOptions(opts, words)
|
parseOptions(opts, words)
|
||||||
@@ -384,23 +384,23 @@ func TestPreviewOpts(t *testing.T) {
|
|||||||
opts.Preview.size.size == 50) {
|
opts.Preview.size.size == 50) {
|
||||||
t.Error()
|
t.Error()
|
||||||
}
|
}
|
||||||
opts = optsFor("--preview", "cat {}", "--preview-window=left:15:hidden:wrap:+{1}-/2")
|
opts = optsFor("--preview", "cat {}", "--preview-window=left:15,hidden,wrap:+{1}-/2")
|
||||||
if !(opts.Preview.command == "cat {}" &&
|
if !(opts.Preview.command == "cat {}" &&
|
||||||
opts.Preview.hidden == true &&
|
opts.Preview.hidden == true &&
|
||||||
opts.Preview.wrap == true &&
|
opts.Preview.wrap == true &&
|
||||||
opts.Preview.position == posLeft &&
|
opts.Preview.position == posLeft &&
|
||||||
opts.Preview.scroll == "{1}-/2" &&
|
opts.Preview.scroll == "+{1}-/2" &&
|
||||||
opts.Preview.size.percent == false &&
|
opts.Preview.size.percent == false &&
|
||||||
opts.Preview.size.size == 15) {
|
opts.Preview.size.size == 15) {
|
||||||
t.Error(opts.Preview)
|
t.Error(opts.Preview)
|
||||||
}
|
}
|
||||||
opts = optsFor("--preview-window=up:15:wrap:hidden:+{1}-/2", "--preview-window=down", "--preview-window=cycle")
|
opts = optsFor("--preview-window=up,15,wrap,hidden,+{1}+3-1-2/2", "--preview-window=down", "--preview-window=cycle")
|
||||||
if !(opts.Preview.command == "" &&
|
if !(opts.Preview.command == "" &&
|
||||||
opts.Preview.hidden == true &&
|
opts.Preview.hidden == true &&
|
||||||
opts.Preview.wrap == true &&
|
opts.Preview.wrap == true &&
|
||||||
opts.Preview.cycle == true &&
|
opts.Preview.cycle == true &&
|
||||||
opts.Preview.position == posDown &&
|
opts.Preview.position == posDown &&
|
||||||
opts.Preview.scroll == "{1}-/2" &&
|
opts.Preview.scroll == "+{1}+3-1-2/2" &&
|
||||||
opts.Preview.size.percent == false &&
|
opts.Preview.size.percent == false &&
|
||||||
opts.Preview.size.size == 15) {
|
opts.Preview.size.size == 15) {
|
||||||
t.Error(opts.Preview.size.size)
|
t.Error(opts.Preview.size.size)
|
||||||
|
|||||||
@@ -337,7 +337,7 @@ func (p *Pattern) MatchItem(item *Item, withPos bool, slab *util.Slab) (*Result,
|
|||||||
func (p *Pattern) basicMatch(item *Item, withPos bool, slab *util.Slab) (Offset, int, *[]int) {
|
func (p *Pattern) basicMatch(item *Item, withPos bool, slab *util.Slab) (Offset, int, *[]int) {
|
||||||
var input []Token
|
var input []Token
|
||||||
if len(p.nth) == 0 {
|
if len(p.nth) == 0 {
|
||||||
input = []Token{Token{text: &item.text, prefixLength: 0}}
|
input = []Token{{text: &item.text, prefixLength: 0}}
|
||||||
} else {
|
} else {
|
||||||
input = p.transformInput(item)
|
input = p.transformInput(item)
|
||||||
}
|
}
|
||||||
@@ -350,7 +350,7 @@ func (p *Pattern) basicMatch(item *Item, withPos bool, slab *util.Slab) (Offset,
|
|||||||
func (p *Pattern) extendedMatch(item *Item, withPos bool, slab *util.Slab) ([]Offset, int, *[]int) {
|
func (p *Pattern) extendedMatch(item *Item, withPos bool, slab *util.Slab) ([]Offset, int, *[]int) {
|
||||||
var input []Token
|
var input []Token
|
||||||
if len(p.nth) == 0 {
|
if len(p.nth) == 0 {
|
||||||
input = []Token{Token{text: &item.text, prefixLength: 0}}
|
input = []Token{{text: &item.text, prefixLength: 0}}
|
||||||
} else {
|
} else {
|
||||||
input = p.transformInput(item)
|
input = p.transformInput(item)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ func TestCaseSensitivity(t *testing.T) {
|
|||||||
func TestOrigTextAndTransformed(t *testing.T) {
|
func TestOrigTextAndTransformed(t *testing.T) {
|
||||||
pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, true, []Range{}, Delimiter{}, []rune("jg"))
|
pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, true, []Range{}, Delimiter{}, []rune("jg"))
|
||||||
tokens := Tokenize("junegunn", Delimiter{})
|
tokens := Tokenize("junegunn", Delimiter{})
|
||||||
trans := Transform(tokens, []Range{Range{1, 1}})
|
trans := Transform(tokens, []Range{{1, 1}})
|
||||||
|
|
||||||
origBytes := []byte("junegunn.choi")
|
origBytes := []byte("junegunn.choi")
|
||||||
for _, extended := range []bool{false, true} {
|
for _, extended := range []bool{false, true} {
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ func withIndex(i *Item, index int) *Item {
|
|||||||
|
|
||||||
func TestOffsetSort(t *testing.T) {
|
func TestOffsetSort(t *testing.T) {
|
||||||
offsets := []Offset{
|
offsets := []Offset{
|
||||||
Offset{3, 5}, Offset{2, 7},
|
{3, 5}, {2, 7},
|
||||||
Offset{1, 3}, Offset{2, 9}}
|
{1, 3}, {2, 9}}
|
||||||
sort.Sort(ByOrder(offsets))
|
sort.Sort(ByOrder(offsets))
|
||||||
|
|
||||||
if offsets[0][0] != 1 || offsets[0][1] != 3 ||
|
if offsets[0][0] != 1 || offsets[0][1] != 3 ||
|
||||||
@@ -84,13 +84,13 @@ func TestResultRank(t *testing.T) {
|
|||||||
|
|
||||||
// Sort by relevance
|
// Sort by relevance
|
||||||
item3 := buildResult(
|
item3 := buildResult(
|
||||||
withIndex(&Item{}, 2), []Offset{Offset{1, 3}, Offset{5, 7}}, 3)
|
withIndex(&Item{}, 2), []Offset{{1, 3}, {5, 7}}, 3)
|
||||||
item4 := buildResult(
|
item4 := buildResult(
|
||||||
withIndex(&Item{}, 2), []Offset{Offset{1, 2}, Offset{6, 7}}, 4)
|
withIndex(&Item{}, 2), []Offset{{1, 2}, {6, 7}}, 4)
|
||||||
item5 := buildResult(
|
item5 := buildResult(
|
||||||
withIndex(&Item{}, 2), []Offset{Offset{1, 3}, Offset{5, 7}}, 5)
|
withIndex(&Item{}, 2), []Offset{{1, 3}, {5, 7}}, 5)
|
||||||
item6 := buildResult(
|
item6 := buildResult(
|
||||||
withIndex(&Item{}, 2), []Offset{Offset{1, 2}, Offset{6, 7}}, 6)
|
withIndex(&Item{}, 2), []Offset{{1, 2}, {6, 7}}, 6)
|
||||||
items = []Result{item1, item2, item3, item4, item5, item6}
|
items = []Result{item1, item2, item3, item4, item5, item6}
|
||||||
sort.Sort(ByRelevance(items))
|
sort.Sort(ByRelevance(items))
|
||||||
if !(items[0] == item6 && items[1] == item5 &&
|
if !(items[0] == item6 && items[1] == item5 &&
|
||||||
|
|||||||
230
src/terminal.go
230
src/terminal.go
@@ -22,8 +22,9 @@ import (
|
|||||||
// import "github.com/pkg/profile"
|
// import "github.com/pkg/profile"
|
||||||
|
|
||||||
var placeholder *regexp.Regexp
|
var placeholder *regexp.Regexp
|
||||||
var numericPrefix *regexp.Regexp
|
|
||||||
var whiteSuffix *regexp.Regexp
|
var whiteSuffix *regexp.Regexp
|
||||||
|
var offsetComponentRegex *regexp.Regexp
|
||||||
|
var offsetTrimCharsRegex *regexp.Regexp
|
||||||
var activeTempFiles []string
|
var activeTempFiles []string
|
||||||
|
|
||||||
const ellipsis string = ".."
|
const ellipsis string = ".."
|
||||||
@@ -31,8 +32,9 @@ const clearCode string = "\x1b[2J"
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
placeholder = regexp.MustCompile(`\\?(?:{[+sf]*[0-9,-.]*}|{q}|{\+?f?nf?})`)
|
placeholder = regexp.MustCompile(`\\?(?:{[+sf]*[0-9,-.]*}|{q}|{\+?f?nf?})`)
|
||||||
numericPrefix = regexp.MustCompile(`^[[:punct:]]*([0-9]+)`)
|
|
||||||
whiteSuffix = regexp.MustCompile(`\s*$`)
|
whiteSuffix = regexp.MustCompile(`\s*$`)
|
||||||
|
offsetComponentRegex = regexp.MustCompile(`([+-][0-9]+)|(-?/[1-9][0-9]*)`)
|
||||||
|
offsetTrimCharsRegex = regexp.MustCompile(`[^0-9/+-]`)
|
||||||
activeTempFiles = []string{}
|
activeTempFiles = []string{}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,6 +135,7 @@ type Terminal struct {
|
|||||||
count int
|
count int
|
||||||
progress int
|
progress int
|
||||||
reading bool
|
reading bool
|
||||||
|
running bool
|
||||||
failed *string
|
failed *string
|
||||||
jumping jumpMode
|
jumping jumpMode
|
||||||
jumpLabels string
|
jumpLabels string
|
||||||
@@ -157,6 +160,7 @@ type Terminal struct {
|
|||||||
slab *util.Slab
|
slab *util.Slab
|
||||||
theme *tui.ColorTheme
|
theme *tui.ColorTheme
|
||||||
tui tui.Renderer
|
tui tui.Renderer
|
||||||
|
executing *util.AtomicBool
|
||||||
}
|
}
|
||||||
|
|
||||||
type selectedItem struct {
|
type selectedItem struct {
|
||||||
@@ -276,6 +280,8 @@ const (
|
|||||||
actReload
|
actReload
|
||||||
actDisableSearch
|
actDisableSearch
|
||||||
actEnableSearch
|
actEnableSearch
|
||||||
|
actSelect
|
||||||
|
actDeselect
|
||||||
)
|
)
|
||||||
|
|
||||||
type placeholderFlags struct {
|
type placeholderFlags struct {
|
||||||
@@ -502,6 +508,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
ansi: opts.Ansi,
|
ansi: opts.Ansi,
|
||||||
tabstop: opts.Tabstop,
|
tabstop: opts.Tabstop,
|
||||||
reading: true,
|
reading: true,
|
||||||
|
running: true,
|
||||||
failed: nil,
|
failed: nil,
|
||||||
jumping: jumpDisabled,
|
jumping: jumpDisabled,
|
||||||
jumpLabels: opts.JumpLabels,
|
jumpLabels: opts.JumpLabels,
|
||||||
@@ -523,7 +530,8 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
startChan: make(chan bool, 1),
|
startChan: make(chan bool, 1),
|
||||||
killChan: make(chan int),
|
killChan: make(chan int),
|
||||||
tui: renderer,
|
tui: renderer,
|
||||||
initFunc: func() { renderer.Init() }}
|
initFunc: func() { renderer.Init() },
|
||||||
|
executing: util.NewAtomicBool(false)}
|
||||||
t.prompt, t.promptLen = t.parsePrompt(opts.Prompt)
|
t.prompt, t.promptLen = t.parsePrompt(opts.Prompt)
|
||||||
t.pointer, t.pointerLen = t.processTabs([]rune(opts.Pointer), 0)
|
t.pointer, t.pointerLen = t.processTabs([]rune(opts.Pointer), 0)
|
||||||
t.marker, t.markerLen = t.processTabs([]rune(opts.Marker), 0)
|
t.marker, t.markerLen = t.processTabs([]rune(opts.Marker), 0)
|
||||||
@@ -823,16 +831,33 @@ func (t *Terminal) resizeWindows() {
|
|||||||
createPreviewWindow := func(y int, x int, w int, h int) {
|
createPreviewWindow := func(y int, x int, w int, h int) {
|
||||||
pwidth := w
|
pwidth := w
|
||||||
pheight := h
|
pheight := h
|
||||||
if t.previewOpts.border != tui.BorderNone {
|
var previewBorder tui.BorderStyle
|
||||||
previewBorder := tui.MakeBorderStyle(t.previewOpts.border, t.unicode)
|
if t.previewOpts.border == tui.BorderNone {
|
||||||
t.pborder = t.tui.NewWindow(y, x, w, h, true, previewBorder)
|
previewBorder = tui.MakeTransparentBorder()
|
||||||
|
} else {
|
||||||
|
previewBorder = tui.MakeBorderStyle(t.previewOpts.border, t.unicode)
|
||||||
|
}
|
||||||
|
t.pborder = t.tui.NewWindow(y, x, w, h, true, previewBorder)
|
||||||
|
switch t.previewOpts.border {
|
||||||
|
case tui.BorderSharp, tui.BorderRounded:
|
||||||
pwidth -= 4
|
pwidth -= 4
|
||||||
pheight -= 2
|
pheight -= 2
|
||||||
x += 2
|
x += 2
|
||||||
y += 1
|
y += 1
|
||||||
} else {
|
case tui.BorderLeft:
|
||||||
previewBorder := tui.MakeTransparentBorder()
|
pwidth -= 2
|
||||||
t.pborder = t.tui.NewWindow(y, x, w, h, true, previewBorder)
|
x += 2
|
||||||
|
case tui.BorderRight:
|
||||||
|
pwidth -= 2
|
||||||
|
case tui.BorderTop:
|
||||||
|
pheight -= 1
|
||||||
|
y += 1
|
||||||
|
case tui.BorderBottom:
|
||||||
|
pheight -= 1
|
||||||
|
case tui.BorderHorizontal:
|
||||||
|
pheight -= 2
|
||||||
|
y += 1
|
||||||
|
case tui.BorderVertical:
|
||||||
pwidth -= 4
|
pwidth -= 4
|
||||||
x += 2
|
x += 2
|
||||||
}
|
}
|
||||||
@@ -840,9 +865,13 @@ func (t *Terminal) resizeWindows() {
|
|||||||
}
|
}
|
||||||
verticalPad := 2
|
verticalPad := 2
|
||||||
minPreviewHeight := 3
|
minPreviewHeight := 3
|
||||||
if t.previewOpts.border == tui.BorderNone {
|
switch t.previewOpts.border {
|
||||||
|
case tui.BorderNone, tui.BorderVertical, tui.BorderLeft, tui.BorderRight:
|
||||||
verticalPad = 0
|
verticalPad = 0
|
||||||
minPreviewHeight = 1
|
minPreviewHeight = 1
|
||||||
|
case tui.BorderTop, tui.BorderBottom:
|
||||||
|
verticalPad = 1
|
||||||
|
minPreviewHeight = 2
|
||||||
}
|
}
|
||||||
switch t.previewOpts.position {
|
switch t.previewOpts.position {
|
||||||
case posUp:
|
case posUp:
|
||||||
@@ -1140,8 +1169,8 @@ func (t *Terminal) trimLeft(runes []rune, width int) ([]rune, int32) {
|
|||||||
width = util.Max(0, width)
|
width = util.Max(0, width)
|
||||||
var trimmed int32
|
var trimmed int32
|
||||||
// Assume that each rune takes at least one column on screen
|
// Assume that each rune takes at least one column on screen
|
||||||
if len(runes) > width {
|
if len(runes) > width+2 {
|
||||||
diff := len(runes) - width
|
diff := len(runes) - width - 2
|
||||||
trimmed = int32(diff)
|
trimmed = int32(diff)
|
||||||
runes = runes[diff:]
|
runes = runes[diff:]
|
||||||
}
|
}
|
||||||
@@ -1289,18 +1318,37 @@ func (t *Terminal) renderPreviewSpinner() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) renderPreviewText(unchanged bool) {
|
func (t *Terminal) renderPreviewArea(unchanged bool) {
|
||||||
maxWidth := t.pwindow.Width()
|
|
||||||
lineNo := -t.previewer.offset
|
|
||||||
height := t.pwindow.Height()
|
|
||||||
if unchanged {
|
if unchanged {
|
||||||
t.pwindow.MoveAndClear(0, 0)
|
t.pwindow.MoveAndClear(0, 0) // Clear scroll offset display
|
||||||
} else {
|
} else {
|
||||||
t.previewed.filled = false
|
t.previewed.filled = false
|
||||||
t.pwindow.Erase()
|
t.pwindow.Erase()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
height := t.pwindow.Height()
|
||||||
|
header := []string{}
|
||||||
|
body := t.previewer.lines
|
||||||
|
headerLines := t.previewOpts.headerLines
|
||||||
|
// Do not enable preview header lines if it's value is too large
|
||||||
|
if headerLines > 0 && headerLines < util.Min(len(body), height) {
|
||||||
|
header = t.previewer.lines[0:headerLines]
|
||||||
|
body = t.previewer.lines[headerLines:]
|
||||||
|
// Always redraw header
|
||||||
|
t.renderPreviewText(height, header, 0, false)
|
||||||
|
t.pwindow.MoveAndClear(t.pwindow.Y(), 0)
|
||||||
|
}
|
||||||
|
t.renderPreviewText(height, body, -t.previewer.offset+headerLines, unchanged)
|
||||||
|
|
||||||
|
if !unchanged {
|
||||||
|
t.pwindow.FinishFill()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) renderPreviewText(height int, lines []string, lineNo int, unchanged bool) {
|
||||||
|
maxWidth := t.pwindow.Width()
|
||||||
var ansi *ansiState
|
var ansi *ansiState
|
||||||
for _, line := range t.previewer.lines {
|
for _, line := range lines {
|
||||||
var lbg tui.Color = -1
|
var lbg tui.Color = -1
|
||||||
if ansi != nil {
|
if ansi != nil {
|
||||||
ansi.lbg = -1
|
ansi.lbg = -1
|
||||||
@@ -1314,8 +1362,9 @@ func (t *Terminal) renderPreviewText(unchanged bool) {
|
|||||||
prefixWidth := 0
|
prefixWidth := 0
|
||||||
_, _, 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)
|
||||||
|
trimmedLen := 0
|
||||||
if !t.previewOpts.wrap {
|
if !t.previewOpts.wrap {
|
||||||
trimmed, _ = t.trimRight(trimmed, maxWidth-t.pwindow.X())
|
trimmed, trimmedLen = t.trimRight(trimmed, maxWidth-t.pwindow.X())
|
||||||
}
|
}
|
||||||
str, width := t.processTabs(trimmed, prefixWidth)
|
str, width := t.processTabs(trimmed, prefixWidth)
|
||||||
prefixWidth += width
|
prefixWidth += width
|
||||||
@@ -1325,7 +1374,8 @@ func (t *Terminal) renderPreviewText(unchanged bool) {
|
|||||||
} else {
|
} else {
|
||||||
fillRet = t.pwindow.CFill(tui.ColPreview.Fg(), tui.ColPreview.Bg(), tui.AttrRegular, str)
|
fillRet = t.pwindow.CFill(tui.ColPreview.Fg(), tui.ColPreview.Bg(), tui.AttrRegular, str)
|
||||||
}
|
}
|
||||||
return fillRet == tui.FillContinue
|
return trimmedLen == 0 &&
|
||||||
|
(fillRet == tui.FillContinue || t.previewOpts.wrap && fillRet == tui.FillNextLine)
|
||||||
})
|
})
|
||||||
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 {
|
||||||
@@ -1346,9 +1396,6 @@ func (t *Terminal) renderPreviewText(unchanged bool) {
|
|||||||
}
|
}
|
||||||
lineNo++
|
lineNo++
|
||||||
}
|
}
|
||||||
if !unchanged {
|
|
||||||
t.pwindow.FinishFill()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) printPreview() {
|
func (t *Terminal) printPreview() {
|
||||||
@@ -1361,7 +1408,7 @@ func (t *Terminal) printPreview() {
|
|||||||
t.previewer.version == t.previewed.version &&
|
t.previewer.version == t.previewed.version &&
|
||||||
t.previewer.offset == t.previewed.offset
|
t.previewer.offset == t.previewed.offset
|
||||||
t.previewer.scrollable = t.previewer.offset > 0 || numLines > height
|
t.previewer.scrollable = t.previewer.offset > 0 || numLines > height
|
||||||
t.renderPreviewText(unchanged)
|
t.renderPreviewArea(unchanged)
|
||||||
t.renderPreviewSpinner()
|
t.renderPreviewSpinner()
|
||||||
t.previewed.numLines = numLines
|
t.previewed.numLines = numLines
|
||||||
t.previewed.version = t.previewer.version
|
t.previewed.version = t.previewer.version
|
||||||
@@ -1374,7 +1421,7 @@ func (t *Terminal) printPreviewDelayed() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
t.previewer.scrollable = false
|
t.previewer.scrollable = false
|
||||||
t.renderPreviewText(true)
|
t.renderPreviewArea(true)
|
||||||
|
|
||||||
message := t.trimMessage("Loading ..", t.pwindow.Width())
|
message := t.trimMessage("Loading ..", t.pwindow.Width())
|
||||||
pos := t.pwindow.Width() - len(message)
|
pos := t.pwindow.Width() - len(message)
|
||||||
@@ -1567,43 +1614,33 @@ func (t *Terminal) replacePlaceholder(template string, forcePlus bool, input str
|
|||||||
template, t.ansi, t.delimiter, t.printsep, forcePlus, input, list)
|
template, t.ansi, t.delimiter, t.printsep, forcePlus, input, list)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ascii to positive integer
|
|
||||||
func atopi(s string) int {
|
|
||||||
matches := numericPrefix.FindStringSubmatch(s)
|
|
||||||
if len(matches) < 2 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
n, e := strconv.Atoi(matches[1])
|
|
||||||
if e != nil || n < 1 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Terminal) evaluateScrollOffset(list []*Item, height int) int {
|
func (t *Terminal) evaluateScrollOffset(list []*Item, height int) int {
|
||||||
offsetExpr := t.replacePlaceholder(t.previewOpts.scroll, false, "", list)
|
offsetExpr := offsetTrimCharsRegex.ReplaceAllString(
|
||||||
nums := strings.Split(offsetExpr, "-")
|
t.replacePlaceholder(t.previewOpts.scroll, false, "", list), "")
|
||||||
switch len(nums) {
|
|
||||||
case 0:
|
atoi := func(s string) int {
|
||||||
return 0
|
n, e := strconv.Atoi(s)
|
||||||
case 1, 2:
|
if e != nil {
|
||||||
base := atopi(nums[0])
|
|
||||||
if base == 0 {
|
|
||||||
return 0
|
return 0
|
||||||
} else if len(nums) == 1 {
|
|
||||||
return base - 1
|
|
||||||
}
|
}
|
||||||
if nums[1][0] == '/' {
|
return n
|
||||||
denom := atopi(nums[1][1:])
|
}
|
||||||
|
|
||||||
|
base := -1
|
||||||
|
for _, component := range offsetComponentRegex.FindAllString(offsetExpr, -1) {
|
||||||
|
if strings.HasPrefix(component, "-/") {
|
||||||
|
component = component[1:]
|
||||||
|
}
|
||||||
|
if component[0] == '/' {
|
||||||
|
denom := atoi(component[1:])
|
||||||
if denom == 0 {
|
if denom == 0 {
|
||||||
return base
|
return base
|
||||||
}
|
}
|
||||||
return base - height/denom
|
return base - height/denom
|
||||||
}
|
}
|
||||||
return base - atopi(nums[1]) - 1
|
base += atoi(component)
|
||||||
default:
|
|
||||||
return 0
|
|
||||||
}
|
}
|
||||||
|
return base
|
||||||
}
|
}
|
||||||
|
|
||||||
func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, printsep string, forcePlus bool, query string, allItems []*Item) string {
|
func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, printsep string, forcePlus bool, query string, allItems []*Item) string {
|
||||||
@@ -1706,8 +1743,9 @@ func (t *Terminal) executeCommand(template string, forcePlus bool, background bo
|
|||||||
}
|
}
|
||||||
command := t.replacePlaceholder(template, forcePlus, string(t.input), list)
|
command := t.replacePlaceholder(template, forcePlus, string(t.input), list)
|
||||||
cmd := util.ExecCommand(command, false)
|
cmd := util.ExecCommand(command, false)
|
||||||
|
t.executing.Set(true)
|
||||||
if !background {
|
if !background {
|
||||||
cmd.Stdin = os.Stdin
|
cmd.Stdin = tui.TtyIn()
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
t.tui.Pause(true)
|
t.tui.Pause(true)
|
||||||
@@ -1720,6 +1758,7 @@ func (t *Terminal) executeCommand(template string, forcePlus bool, background bo
|
|||||||
cmd.Run()
|
cmd.Run()
|
||||||
t.tui.Resume(false, false)
|
t.tui.Resume(false, false)
|
||||||
}
|
}
|
||||||
|
t.executing.Set(false)
|
||||||
cleanTemporaryFiles()
|
cleanTemporaryFiles()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1785,11 +1824,26 @@ func (t *Terminal) selectItem(item *Item) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) selectItemChanged(item *Item) bool {
|
||||||
|
if _, found := t.selected[item.Index()]; found {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return t.selectItem(item)
|
||||||
|
}
|
||||||
|
|
||||||
func (t *Terminal) deselectItem(item *Item) {
|
func (t *Terminal) deselectItem(item *Item) {
|
||||||
delete(t.selected, item.Index())
|
delete(t.selected, item.Index())
|
||||||
t.version++
|
t.version++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) deselectItemChanged(item *Item) bool {
|
||||||
|
if _, found := t.selected[item.Index()]; found {
|
||||||
|
t.deselectItem(item)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (t *Terminal) toggleItem(item *Item) bool {
|
func (t *Terminal) toggleItem(item *Item) bool {
|
||||||
if _, found := t.selected[item.Index()]; !found {
|
if _, found := t.selected[item.Index()]; !found {
|
||||||
return t.selectItem(item)
|
return t.selectItem(item)
|
||||||
@@ -1803,7 +1857,7 @@ func (t *Terminal) killPreview(code int) {
|
|||||||
case t.killChan <- code:
|
case t.killChan <- code:
|
||||||
default:
|
default:
|
||||||
if code != exitCancel {
|
if code != exitCancel {
|
||||||
os.Exit(code)
|
t.eventBox.Set(EvtQuit, code)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1820,8 +1874,12 @@ func (t *Terminal) Loop() {
|
|||||||
intChan := make(chan os.Signal, 1)
|
intChan := make(chan os.Signal, 1)
|
||||||
signal.Notify(intChan, os.Interrupt, syscall.SIGTERM)
|
signal.Notify(intChan, os.Interrupt, syscall.SIGTERM)
|
||||||
go func() {
|
go func() {
|
||||||
<-intChan
|
for s := range intChan {
|
||||||
t.reqBox.Set(reqQuit, nil)
|
// Don't quit by SIGINT while executing because it should be for the executing command and not for fzf itself
|
||||||
|
if !(s == os.Interrupt && t.executing.Get()) {
|
||||||
|
t.reqBox.Set(reqQuit, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
contChan := make(chan os.Signal, 1)
|
contChan := make(chan os.Signal, 1)
|
||||||
@@ -1898,7 +1956,7 @@ func (t *Terminal) Loop() {
|
|||||||
cmd := util.ExecCommand(command, true)
|
cmd := util.ExecCommand(command, true)
|
||||||
if pwindow != nil {
|
if pwindow != nil {
|
||||||
height := pwindow.Height()
|
height := pwindow.Height()
|
||||||
initialOffset = util.Max(0, t.evaluateScrollOffset(items, height))
|
initialOffset = util.Max(0, t.evaluateScrollOffset(items, util.Max(0, height-t.previewOpts.headerLines)))
|
||||||
env := os.Environ()
|
env := os.Environ()
|
||||||
lines := fmt.Sprintf("LINES=%d", height)
|
lines := fmt.Sprintf("LINES=%d", height)
|
||||||
columns := fmt.Sprintf("COLUMNS=%d", pwindow.Width())
|
columns := fmt.Sprintf("COLUMNS=%d", pwindow.Width())
|
||||||
@@ -1931,6 +1989,7 @@ func (t *Terminal) Loop() {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
// Goroutine 2 periodically requests rendering
|
// Goroutine 2 periodically requests rendering
|
||||||
|
rendered := util.NewAtomicBool(false)
|
||||||
go func(version int64) {
|
go func(version int64) {
|
||||||
lines := []string{}
|
lines := []string{}
|
||||||
spinner := makeSpinner(t.unicode)
|
spinner := makeSpinner(t.unicode)
|
||||||
@@ -1945,6 +2004,7 @@ func (t *Terminal) Loop() {
|
|||||||
if spinnerIndex >= 0 {
|
if spinnerIndex >= 0 {
|
||||||
spin := spinner[spinnerIndex%len(spinner)]
|
spin := spinner[spinnerIndex%len(spinner)]
|
||||||
t.reqBox.Set(reqPreviewDisplay, previewResult{version, lines, offset, spin})
|
t.reqBox.Set(reqPreviewDisplay, previewResult{version, lines, offset, spin})
|
||||||
|
rendered.Set(true)
|
||||||
offset = -1
|
offset = -1
|
||||||
}
|
}
|
||||||
spinnerIndex++
|
spinnerIndex++
|
||||||
@@ -1964,6 +2024,7 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.reqBox.Set(reqPreviewDisplay, previewResult{version, lines, offset, ""})
|
t.reqBox.Set(reqPreviewDisplay, previewResult{version, lines, offset, ""})
|
||||||
|
rendered.Set(true)
|
||||||
break Loop
|
break Loop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1983,9 +2044,15 @@ func (t *Terminal) Loop() {
|
|||||||
case code := <-t.killChan:
|
case code := <-t.killChan:
|
||||||
if code != exitCancel {
|
if code != exitCancel {
|
||||||
util.KillCommand(cmd)
|
util.KillCommand(cmd)
|
||||||
os.Exit(code)
|
t.eventBox.Set(EvtQuit, code)
|
||||||
} else {
|
} else {
|
||||||
timer := time.NewTimer(previewCancelWait)
|
// We can immediately kill a long-running preview program
|
||||||
|
// once we started rendering its partial output
|
||||||
|
delay := previewCancelWait
|
||||||
|
if rendered.Get() {
|
||||||
|
delay = 0
|
||||||
|
}
|
||||||
|
timer := time.NewTimer(delay)
|
||||||
select {
|
select {
|
||||||
case <-timer.C:
|
case <-timer.C:
|
||||||
util.KillCommand(cmd)
|
util.KillCommand(cmd)
|
||||||
@@ -2020,16 +2087,6 @@ func (t *Terminal) Loop() {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
exit := func(getCode func() int) {
|
|
||||||
t.tui.Close()
|
|
||||||
code := getCode()
|
|
||||||
if code <= exitNoMatch && t.history != nil {
|
|
||||||
t.history.append(string(t.input))
|
|
||||||
}
|
|
||||||
// prof.Stop()
|
|
||||||
t.killPreview(code)
|
|
||||||
}
|
|
||||||
|
|
||||||
refreshPreview := func(command string) {
|
refreshPreview := func(command string) {
|
||||||
if len(command) > 0 && t.isPreviewEnabled() {
|
if len(command) > 0 && t.isPreviewEnabled() {
|
||||||
_, list := t.buildPlusList(command, false)
|
_, list := t.buildPlusList(command, false)
|
||||||
@@ -2041,7 +2098,19 @@ func (t *Terminal) Loop() {
|
|||||||
go func() {
|
go func() {
|
||||||
var focusedIndex int32 = minItem.Index()
|
var focusedIndex int32 = minItem.Index()
|
||||||
var version int64 = -1
|
var version int64 = -1
|
||||||
for {
|
running := true
|
||||||
|
code := exitError
|
||||||
|
exit := func(getCode func() int) {
|
||||||
|
t.tui.Close()
|
||||||
|
code = getCode()
|
||||||
|
if code <= exitNoMatch && t.history != nil {
|
||||||
|
t.history.append(string(t.input))
|
||||||
|
}
|
||||||
|
running = false
|
||||||
|
t.mutex.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
for running {
|
||||||
t.reqBox.Wait(func(events *util.Events) {
|
t.reqBox.Wait(func(events *util.Events) {
|
||||||
defer events.Clear()
|
defer events.Clear()
|
||||||
t.mutex.Lock()
|
t.mutex.Lock()
|
||||||
@@ -2087,6 +2156,7 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
return exitNoMatch
|
return exitNoMatch
|
||||||
})
|
})
|
||||||
|
return
|
||||||
case reqPreviewDisplay:
|
case reqPreviewDisplay:
|
||||||
result := value.(previewResult)
|
result := value.(previewResult)
|
||||||
if t.previewer.version != result.version {
|
if t.previewer.version != result.version {
|
||||||
@@ -2098,7 +2168,7 @@ func (t *Terminal) Loop() {
|
|||||||
if t.previewer.following {
|
if t.previewer.following {
|
||||||
t.previewer.offset = len(t.previewer.lines) - t.pwindow.Height()
|
t.previewer.offset = len(t.previewer.lines) - t.pwindow.Height()
|
||||||
} else if result.offset >= 0 {
|
} else if result.offset >= 0 {
|
||||||
t.previewer.offset = util.Constrain(result.offset, 0, len(t.previewer.lines)-1)
|
t.previewer.offset = util.Constrain(result.offset, t.previewOpts.headerLines, len(t.previewer.lines)-1)
|
||||||
}
|
}
|
||||||
t.printPreview()
|
t.printPreview()
|
||||||
case reqPreviewRefresh:
|
case reqPreviewRefresh:
|
||||||
@@ -2111,14 +2181,18 @@ func (t *Terminal) Loop() {
|
|||||||
t.printer(string(t.input))
|
t.printer(string(t.input))
|
||||||
return exitOk
|
return exitOk
|
||||||
})
|
})
|
||||||
|
return
|
||||||
case reqQuit:
|
case reqQuit:
|
||||||
exit(func() int { return exitInterrupt })
|
exit(func() int { return exitInterrupt })
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
t.refresh()
|
t.refresh()
|
||||||
t.mutex.Unlock()
|
t.mutex.Unlock()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
// prof.Stop()
|
||||||
|
t.killPreview(code)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
looping := true
|
looping := true
|
||||||
@@ -2167,7 +2241,7 @@ func (t *Terminal) Loop() {
|
|||||||
if t.previewOpts.cycle {
|
if t.previewOpts.cycle {
|
||||||
newOffset = (newOffset + numLines) % numLines
|
newOffset = (newOffset + numLines) % numLines
|
||||||
}
|
}
|
||||||
newOffset = util.Constrain(newOffset, 0, numLines-1)
|
newOffset = util.Constrain(newOffset, t.previewOpts.headerLines, numLines-1)
|
||||||
if t.previewer.offset != newOffset {
|
if t.previewer.offset != newOffset {
|
||||||
t.previewer.offset = newOffset
|
t.previewer.offset = newOffset
|
||||||
req(reqPreviewRefresh)
|
req(reqPreviewRefresh)
|
||||||
@@ -2341,6 +2415,16 @@ func (t *Terminal) Loop() {
|
|||||||
} else {
|
} else {
|
||||||
req(reqQuit)
|
req(reqQuit)
|
||||||
}
|
}
|
||||||
|
case actSelect:
|
||||||
|
current := t.currentItem()
|
||||||
|
if t.multi > 0 && current != nil && t.selectItemChanged(current) {
|
||||||
|
req(reqList, reqInfo)
|
||||||
|
}
|
||||||
|
case actDeselect:
|
||||||
|
current := t.currentItem()
|
||||||
|
if t.multi > 0 && current != nil && t.deselectItemChanged(current) {
|
||||||
|
req(reqList, reqInfo)
|
||||||
|
}
|
||||||
case actToggle:
|
case actToggle:
|
||||||
if t.multi > 0 && t.merger.Length() > 0 && toggle() {
|
if t.multi > 0 && t.merger.Length() > 0 && toggle() {
|
||||||
req(reqList)
|
req(reqList)
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
|
|
||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
|
|
||||||
"golang.org/x/crypto/ssh/terminal"
|
"golang.org/x/term"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -74,7 +74,7 @@ type LightRenderer struct {
|
|||||||
clickY []int
|
clickY []int
|
||||||
ttyin *os.File
|
ttyin *os.File
|
||||||
buffer []byte
|
buffer []byte
|
||||||
origState *terminal.State
|
origState *term.State
|
||||||
width int
|
width int
|
||||||
height int
|
height int
|
||||||
yoffset int
|
yoffset int
|
||||||
@@ -693,13 +693,17 @@ func (w *LightWindow) drawBorder() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *LightWindow) drawBorderHorizontal(top, bottom bool) {
|
func (w *LightWindow) drawBorderHorizontal(top, bottom bool) {
|
||||||
|
color := ColBorder
|
||||||
|
if w.preview {
|
||||||
|
color = ColPreviewBorder
|
||||||
|
}
|
||||||
if top {
|
if top {
|
||||||
w.Move(0, 0)
|
w.Move(0, 0)
|
||||||
w.CPrint(ColBorder, repeat(w.border.horizontal, w.width))
|
w.CPrint(color, repeat(w.border.horizontal, w.width))
|
||||||
}
|
}
|
||||||
if bottom {
|
if bottom {
|
||||||
w.Move(w.height-1, 0)
|
w.Move(w.height-1, 0)
|
||||||
w.CPrint(ColBorder, repeat(w.border.horizontal, w.width))
|
w.CPrint(color, repeat(w.border.horizontal, w.width))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -708,14 +712,18 @@ func (w *LightWindow) drawBorderVertical(left, right bool) {
|
|||||||
if !left || !right {
|
if !left || !right {
|
||||||
width++
|
width++
|
||||||
}
|
}
|
||||||
|
color := ColBorder
|
||||||
|
if w.preview {
|
||||||
|
color = ColPreviewBorder
|
||||||
|
}
|
||||||
for y := 0; y < w.height; y++ {
|
for y := 0; y < w.height; y++ {
|
||||||
w.Move(y, 0)
|
w.Move(y, 0)
|
||||||
if left {
|
if left {
|
||||||
w.CPrint(ColBorder, string(w.border.vertical))
|
w.CPrint(color, string(w.border.vertical))
|
||||||
}
|
}
|
||||||
w.CPrint(ColBorder, repeat(' ', width))
|
w.CPrint(color, repeat(' ', width))
|
||||||
if right {
|
if right {
|
||||||
w.CPrint(ColBorder, string(w.border.vertical))
|
w.CPrint(color, string(w.border.vertical))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -906,12 +914,6 @@ func (w *LightWindow) fill(str string, onMove func()) FillReturn {
|
|||||||
for i, line := range allLines {
|
for i, line := range allLines {
|
||||||
lines := wrapLine(line, w.posx, w.width, w.tabstop)
|
lines := wrapLine(line, w.posx, w.width, w.tabstop)
|
||||||
for j, wl := range lines {
|
for j, wl := range lines {
|
||||||
if w.posx >= w.Width()-1 && wl.displayWidth == 0 {
|
|
||||||
if w.posy < w.height-1 {
|
|
||||||
w.Move(w.posy+1, 0)
|
|
||||||
}
|
|
||||||
return FillNextLine
|
|
||||||
}
|
|
||||||
w.stderrInternal(wl.text, false)
|
w.stderrInternal(wl.text, false)
|
||||||
w.posx += wl.displayWidth
|
w.posx += wl.displayWidth
|
||||||
|
|
||||||
@@ -926,6 +928,14 @@ func (w *LightWindow) fill(str string, onMove func()) FillReturn {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if w.posx+1 >= w.Width() {
|
||||||
|
if w.posy+1 >= w.height {
|
||||||
|
return FillSuspend
|
||||||
|
}
|
||||||
|
w.Move(w.posy+1, 0)
|
||||||
|
onMove()
|
||||||
|
return FillNextLine
|
||||||
|
}
|
||||||
return FillContinue
|
return FillContinue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
"golang.org/x/crypto/ssh/terminal"
|
"golang.org/x/term"
|
||||||
)
|
)
|
||||||
|
|
||||||
func IsLightRendererSupported() bool {
|
func IsLightRendererSupported() bool {
|
||||||
@@ -34,12 +34,12 @@ func (r *LightRenderer) fd() int {
|
|||||||
|
|
||||||
func (r *LightRenderer) initPlatform() error {
|
func (r *LightRenderer) initPlatform() error {
|
||||||
fd := r.fd()
|
fd := r.fd()
|
||||||
origState, err := terminal.GetState(fd)
|
origState, err := term.GetState(fd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
r.origState = origState
|
r.origState = origState
|
||||||
terminal.MakeRaw(fd)
|
term.MakeRaw(fd)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,15 +63,15 @@ func openTtyIn() *os.File {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) setupTerminal() {
|
func (r *LightRenderer) setupTerminal() {
|
||||||
terminal.MakeRaw(r.fd())
|
term.MakeRaw(r.fd())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) restoreTerminal() {
|
func (r *LightRenderer) restoreTerminal() {
|
||||||
terminal.Restore(r.fd(), r.origState)
|
term.Restore(r.fd(), r.origState)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) updateTerminalSize() {
|
func (r *LightRenderer) updateTerminalSize() {
|
||||||
width, height, err := terminal.GetSize(r.fd())
|
width, height, err := term.GetSize(r.fd())
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
r.width = width
|
r.width = width
|
||||||
|
|||||||
@@ -5,11 +5,16 @@ package tui
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
"golang.org/x/sys/windows"
|
"golang.org/x/sys/windows"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
timeoutInterval = 10
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
consoleFlagsInput = uint32(windows.ENABLE_VIRTUAL_TERMINAL_INPUT | windows.ENABLE_PROCESSED_INPUT | windows.ENABLE_EXTENDED_FLAGS)
|
consoleFlagsInput = uint32(windows.ENABLE_VIRTUAL_TERMINAL_INPUT | windows.ENABLE_PROCESSED_INPUT | windows.ENABLE_EXTENDED_FLAGS)
|
||||||
consoleFlagsOutput = uint32(windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING | windows.ENABLE_PROCESSED_OUTPUT | windows.DISABLE_NEWLINE_AUTO_RETURN)
|
consoleFlagsOutput = uint32(windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING | windows.ENABLE_PROCESSED_OUTPUT | windows.DISABLE_NEWLINE_AUTO_RETURN)
|
||||||
@@ -60,7 +65,7 @@ func (r *LightRenderer) initPlatform() error {
|
|||||||
|
|
||||||
// channel for non-blocking reads. Buffer to make sure
|
// channel for non-blocking reads. Buffer to make sure
|
||||||
// we get the ESC sets:
|
// we get the ESC sets:
|
||||||
r.ttyinChannel = make(chan byte, 12)
|
r.ttyinChannel = make(chan byte, 1024)
|
||||||
|
|
||||||
// the following allows for non-blocking IO.
|
// the following allows for non-blocking IO.
|
||||||
// syscall.SetNonblock() is a NOOP under Windows.
|
// syscall.SetNonblock() is a NOOP under Windows.
|
||||||
@@ -68,9 +73,6 @@ func (r *LightRenderer) initPlatform() error {
|
|||||||
fd := int(r.inHandle)
|
fd := int(r.inHandle)
|
||||||
b := make([]byte, 1)
|
b := make([]byte, 1)
|
||||||
for {
|
for {
|
||||||
// HACK: if run from PSReadline, something resets ConsoleMode to remove ENABLE_VIRTUAL_TERMINAL_INPUT.
|
|
||||||
_ = windows.SetConsoleMode(windows.Handle(r.inHandle), consoleFlagsInput)
|
|
||||||
|
|
||||||
_, err := util.Read(fd, b)
|
_, err := util.Read(fd, b)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
r.ttyinChannel <- b[0]
|
r.ttyinChannel <- b[0]
|
||||||
@@ -130,7 +132,7 @@ func (r *LightRenderer) getch(nonblock bool) (int, bool) {
|
|||||||
select {
|
select {
|
||||||
case bc := <-r.ttyinChannel:
|
case bc := <-r.ttyinChannel:
|
||||||
return int(bc), true
|
return int(bc), true
|
||||||
default:
|
case <-time.After(timeoutInterval * time.Millisecond):
|
||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ package tui
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
"syscall"
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -29,3 +30,18 @@ func ttyname() string {
|
|||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TtyIn returns terminal device to be used as STDIN, falls back to os.Stdin
|
||||||
|
func TtyIn() *os.File {
|
||||||
|
in, err := os.OpenFile(consoleDevice, syscall.O_RDONLY, 0)
|
||||||
|
if err != nil {
|
||||||
|
tty := ttyname()
|
||||||
|
if len(tty) > 0 {
|
||||||
|
if in, err := os.OpenFile(tty, syscall.O_RDONLY, 0); err == nil {
|
||||||
|
return in
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return os.Stdin
|
||||||
|
}
|
||||||
|
return in
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,13 @@
|
|||||||
|
|
||||||
package tui
|
package tui
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
func ttyname() string {
|
func ttyname() string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TtyIn on Windows returns os.Stdin
|
||||||
|
func TtyIn() *os.File {
|
||||||
|
return os.Stdin
|
||||||
|
}
|
||||||
|
|||||||
154
test/test_go.rb
154
test/test_go.rb
@@ -1890,6 +1890,158 @@ class TestGoFZF < TestBase
|
|||||||
tmux.send_keys 'C-l', 'closed'
|
tmux.send_keys 'C-l', 'closed'
|
||||||
tmux.until { |lines| assert_includes lines[0], 'closed' }
|
tmux.until { |lines| assert_includes lines[0], 'closed' }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_select_deselect
|
||||||
|
tmux.send_keys "seq 3 | #{FZF} --multi --bind up:deselect+up,down:select+down", :Enter
|
||||||
|
tmux.until { |lines| assert_equal 3, lines.match_count }
|
||||||
|
tmux.send_keys :Tab
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.select_count }
|
||||||
|
tmux.send_keys :Up
|
||||||
|
tmux.until { |lines| assert_equal 0, lines.select_count }
|
||||||
|
tmux.send_keys :Down, :Down
|
||||||
|
tmux.until { |lines| assert_equal 2, lines.select_count }
|
||||||
|
tmux.send_keys :Tab
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.select_count }
|
||||||
|
tmux.send_keys :Down, :Down
|
||||||
|
tmux.until { |lines| assert_equal 2, lines.select_count }
|
||||||
|
tmux.send_keys :Up
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.select_count }
|
||||||
|
tmux.send_keys :Down
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.select_count }
|
||||||
|
tmux.send_keys :Down
|
||||||
|
tmux.until { |lines| assert_equal 2, lines.select_count }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_interrupt_execute
|
||||||
|
tmux.send_keys "seq 100 | #{FZF} --bind 'ctrl-l:execute:echo executing {}; sleep 100'", :Enter
|
||||||
|
tmux.until { |lines| assert_equal 100, lines.item_count }
|
||||||
|
tmux.send_keys 'C-l'
|
||||||
|
tmux.until { |lines| assert lines.any_include?('executing 1') }
|
||||||
|
tmux.send_keys 'C-c'
|
||||||
|
tmux.until { |lines| assert_equal 100, lines.item_count }
|
||||||
|
tmux.send_keys 99
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_kill_default_command_on_abort
|
||||||
|
script = tempname + '.sh'
|
||||||
|
writelines(script,
|
||||||
|
['#!/usr/bin/env bash',
|
||||||
|
"echo 'Started'",
|
||||||
|
'while :; do sleep 1; done'])
|
||||||
|
system("chmod +x #{script}")
|
||||||
|
|
||||||
|
tmux.send_keys fzf.sub('FZF_DEFAULT_COMMAND=', "FZF_DEFAULT_COMMAND=#{script}"), :Enter
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.item_count }
|
||||||
|
tmux.send_keys 'C-c'
|
||||||
|
tmux.send_keys 'C-l', 'closed'
|
||||||
|
tmux.until { |lines| assert_includes lines[0], 'closed' }
|
||||||
|
wait { refute system("pgrep -f #{script}") }
|
||||||
|
ensure
|
||||||
|
system("pkill -9 -f #{script}")
|
||||||
|
begin
|
||||||
|
File.unlink(script)
|
||||||
|
rescue StandardError
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_kill_default_command_on_accept
|
||||||
|
script = tempname + '.sh'
|
||||||
|
writelines(script,
|
||||||
|
['#!/usr/bin/env bash',
|
||||||
|
"echo 'Started'",
|
||||||
|
'while :; do sleep 1; done'])
|
||||||
|
system("chmod +x #{script}")
|
||||||
|
|
||||||
|
tmux.send_keys fzf.sub('FZF_DEFAULT_COMMAND=', "FZF_DEFAULT_COMMAND=#{script}"), :Enter
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.item_count }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
assert_equal 'Started', readonce.chomp
|
||||||
|
wait { refute system("pgrep -f #{script}") }
|
||||||
|
ensure
|
||||||
|
system("pkill -9 -f #{script}")
|
||||||
|
begin
|
||||||
|
File.unlink(script)
|
||||||
|
rescue StandardError
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_kill_reload_command_on_abort
|
||||||
|
script = tempname + '.sh'
|
||||||
|
writelines(script,
|
||||||
|
['#!/usr/bin/env bash',
|
||||||
|
"echo 'Started'",
|
||||||
|
'while :; do sleep 1; done'])
|
||||||
|
system("chmod +x #{script}")
|
||||||
|
|
||||||
|
tmux.send_keys "seq 1 3 | #{fzf("--bind 'ctrl-r:reload(#{script})'")}", :Enter
|
||||||
|
tmux.until { |lines| assert_equal 3, lines.item_count }
|
||||||
|
tmux.send_keys 'C-r'
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.item_count }
|
||||||
|
tmux.send_keys 'C-c'
|
||||||
|
tmux.send_keys 'C-l', 'closed'
|
||||||
|
tmux.until { |lines| assert_includes lines[0], 'closed' }
|
||||||
|
wait { refute system("pgrep -f #{script}") }
|
||||||
|
ensure
|
||||||
|
system("pkill -9 -f #{script}")
|
||||||
|
begin
|
||||||
|
File.unlink(script)
|
||||||
|
rescue StandardError
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_kill_reload_command_on_accept
|
||||||
|
script = tempname + '.sh'
|
||||||
|
writelines(script,
|
||||||
|
['#!/usr/bin/env bash',
|
||||||
|
"echo 'Started'",
|
||||||
|
'while :; do sleep 1; done'])
|
||||||
|
system("chmod +x #{script}")
|
||||||
|
|
||||||
|
tmux.send_keys "seq 1 3 | #{fzf("--bind 'ctrl-r:reload(#{script})'")}", :Enter
|
||||||
|
tmux.until { |lines| assert_equal 3, lines.item_count }
|
||||||
|
tmux.send_keys 'C-r'
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.item_count }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
assert_equal 'Started', readonce.chomp
|
||||||
|
wait { refute system("pgrep -f #{script}") }
|
||||||
|
ensure
|
||||||
|
system("pkill -9 -f #{script}")
|
||||||
|
begin
|
||||||
|
File.unlink(script)
|
||||||
|
rescue StandardError
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_preview_header
|
||||||
|
tmux.send_keys "seq 100 | #{FZF} --bind ctrl-k:preview-up+preview-up,ctrl-j:preview-down+preview-down+preview-down --preview 'seq 1000' --preview-window 'top:+{1}:~3'", :Enter
|
||||||
|
tmux.until { |lines| assert_equal 100, lines.item_count }
|
||||||
|
top5 = ->(lines) { lines.drop(1).take(5).map { |s| s[/[0-9]+/] } }
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_includes lines[1], '4/1000'
|
||||||
|
assert_equal(%w[1 2 3 4 5], top5[lines])
|
||||||
|
end
|
||||||
|
tmux.send_keys '55'
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 1, lines.match_count
|
||||||
|
assert_equal(%w[1 2 3 55 56], top5[lines])
|
||||||
|
end
|
||||||
|
tmux.send_keys 'C-J'
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal(%w[1 2 3 58 59], top5[lines])
|
||||||
|
end
|
||||||
|
tmux.send_keys :BSpace
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 19, lines.match_count
|
||||||
|
assert_equal(%w[1 2 3 5 6], top5[lines])
|
||||||
|
end
|
||||||
|
tmux.send_keys 'C-K'
|
||||||
|
tmux.until { |lines| assert_equal(%w[1 2 3 4 5], top5[lines]) }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
module TestShell
|
module TestShell
|
||||||
@@ -2060,7 +2212,7 @@ module CompletionTest
|
|||||||
end
|
end
|
||||||
|
|
||||||
# ~USERNAME**<TAB>
|
# ~USERNAME**<TAB>
|
||||||
user = ENV['USER']
|
user = `whoami`.chomp
|
||||||
tmux.send_keys 'C-u'
|
tmux.send_keys 'C-u'
|
||||||
tmux.send_keys "cat ~#{user}**", :Tab
|
tmux.send_keys "cat ~#{user}**", :Tab
|
||||||
tmux.until { |lines| assert_operator lines.match_count, :>, 0 }
|
tmux.until { |lines| assert_operator lines.match_count, :>, 0 }
|
||||||
|
|||||||
Reference in New Issue
Block a user