mirror of
https://github.com/junegunn/fzf.git
synced 2025-11-15 06:43:47 -05:00
Compare commits
54 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d01ae55109 | ||
|
|
8868d7d188 | ||
|
|
2eec9892be | ||
|
|
01ae621f11 | ||
|
|
f984aa0d2c | ||
|
|
0881a6bc17 | ||
|
|
2c6a73546d | ||
|
|
a29944660e | ||
|
|
f6ce624c6f | ||
|
|
c09ec8e4d1 | ||
|
|
31bbaad06e | ||
|
|
b9ca1fe830 | ||
|
|
e61585f2f3 | ||
|
|
0de1aacb0c | ||
|
|
168829b555 | ||
|
|
170fc517d0 | ||
|
|
0fbf1c7c71 | ||
|
|
694be39c71 | ||
|
|
dad26d81df | ||
|
|
bcaea097ea | ||
|
|
d56fe74e24 | ||
|
|
4603d540c3 | ||
|
|
f9d53303bb | ||
|
|
d04faa6505 | ||
|
|
07da058eae | ||
|
|
cefa6b9878 | ||
|
|
04d0b0223f | ||
|
|
78ad6d2d88 | ||
|
|
22cbd9fa58 | ||
|
|
984049586a | ||
|
|
cdfc2b92e3 | ||
|
|
4530abe8df | ||
|
|
586020b8b6 | ||
|
|
3a8626fd04 | ||
|
|
a6e483a434 | ||
|
|
6a942e56b1 | ||
|
|
87c91550ad | ||
|
|
731daf0f37 | ||
|
|
f931e53890 | ||
|
|
b5efc68737 | ||
|
|
b9e6e7926c | ||
|
|
845034c81c | ||
|
|
54d42e3f40 | ||
|
|
e03ac3136e | ||
|
|
6fb41a202a | ||
|
|
4bef330ce1 | ||
|
|
8a5f719964 | ||
|
|
209d5e8e90 | ||
|
|
9d041aa582 | ||
|
|
6532b3e655 | ||
|
|
c1c355160d | ||
|
|
83515d5610 | ||
|
|
aa10dccf90 | ||
|
|
f4fd53211a |
8
.github/workflows/codeql-analysis.yml
vendored
8
.github/workflows/codeql-analysis.yml
vendored
@@ -27,18 +27,18 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@629c2de402a417ea7690ca6ce3f33229e27606a5 # v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@1ed1437484560351c5be56cf73a48a279d116b78
|
uses: github/codeql-action/init@v2
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
|
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@1ed1437484560351c5be56cf73a48a279d116b78
|
uses: github/codeql-action/autobuild@v2
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@1ed1437484560351c5be56cf73a48a279d116b78
|
uses: github/codeql-action/analyze@v2
|
||||||
|
|||||||
14
.github/workflows/depsreview.yaml
vendored
Normal file
14
.github/workflows/depsreview.yaml
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
name: 'Dependency Review'
|
||||||
|
on: [pull_request]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
dependency-review:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: 'Checkout Repository'
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: 'Dependency Review'
|
||||||
|
uses: actions/dependency-review-action@v2
|
||||||
8
.github/workflows/linux.yml
vendored
8
.github/workflows/linux.yml
vendored
@@ -15,17 +15,17 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@629c2de402a417ea7690ca6ce3f33229e27606a5 # v2
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@f6164bd8c8acb4a71fb2791a8b6c4024ff038dab # v2
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: 1.18
|
go-version: 1.19
|
||||||
|
|
||||||
- name: Setup Ruby
|
- name: Setup Ruby
|
||||||
uses: ruby/setup-ruby@ebaea52cb20fea395b0904125276395e37183dac
|
uses: ruby/setup-ruby@v1
|
||||||
with:
|
with:
|
||||||
ruby-version: 3.0.0
|
ruby-version: 3.0.0
|
||||||
|
|
||||||
|
|||||||
6
.github/workflows/macos.yml
vendored
6
.github/workflows/macos.yml
vendored
@@ -15,17 +15,17 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@629c2de402a417ea7690ca6ce3f33229e27606a5 # v2
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@f6164bd8c8acb4a71fb2791a8b6c4024ff038dab # v2
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: 1.18
|
go-version: 1.18
|
||||||
|
|
||||||
- name: Setup Ruby
|
- name: Setup Ruby
|
||||||
uses: ruby/setup-ruby@ebaea52cb20fea395b0904125276395e37183dac
|
uses: ruby/setup-ruby@v1
|
||||||
with:
|
with:
|
||||||
ruby-version: 3.0.0
|
ruby-version: 3.0.0
|
||||||
|
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ builds:
|
|||||||
- amd64
|
- amd64
|
||||||
- arm
|
- arm
|
||||||
- arm64
|
- arm64
|
||||||
|
- loong64
|
||||||
goarm:
|
goarm:
|
||||||
- 5
|
- 5
|
||||||
- 6
|
- 6
|
||||||
@@ -99,6 +100,10 @@ archives:
|
|||||||
files:
|
files:
|
||||||
- non-existent*
|
- non-existent*
|
||||||
|
|
||||||
|
checksum:
|
||||||
|
extra_files:
|
||||||
|
- glob: ./dist/fzf-*darwin*.zip
|
||||||
|
|
||||||
release:
|
release:
|
||||||
github:
|
github:
|
||||||
owner: junegunn
|
owner: junegunn
|
||||||
|
|||||||
@@ -26,3 +26,5 @@ Style/OptionalBooleanParameter:
|
|||||||
Enabled: false
|
Enabled: false
|
||||||
Style/WordArray:
|
Style/WordArray:
|
||||||
MinSize: 1
|
MinSize: 1
|
||||||
|
Minitest/AssertEqual:
|
||||||
|
Enabled: false
|
||||||
|
|||||||
19
ADVANCED.md
19
ADVANCED.md
@@ -1,7 +1,7 @@
|
|||||||
Advanced fzf examples
|
Advanced fzf examples
|
||||||
======================
|
======================
|
||||||
|
|
||||||
*(Last update: 2021/05/22)*
|
*(Last update: 2022/08/25)*
|
||||||
|
|
||||||
<!-- vim-markdown-toc GFM -->
|
<!-- vim-markdown-toc GFM -->
|
||||||
|
|
||||||
@@ -496,9 +496,17 @@ pods() {
|
|||||||
Key bindings for git objects
|
Key bindings for git objects
|
||||||
----------------------------
|
----------------------------
|
||||||
|
|
||||||
I have [blogged](https://junegunn.kr/2016/07/fzf-git) about my fzf+git key
|
Oftentimes, you want to put the identifiers of various Git object to the
|
||||||
bindings a few years ago. I'm going to show them here again, because they are
|
command-line. For example, it is common to write commands like these:
|
||||||
seriously useful.
|
|
||||||
|
```sh
|
||||||
|
git checkout [SOME_COMMIT_HASH or BRANCH or TAG]
|
||||||
|
git diff [SOME_COMMIT_HASH or BRANCH or TAG] [SOME_COMMIT_HASH or BRANCH or TAG]
|
||||||
|
```
|
||||||
|
|
||||||
|
[fzf-git.sh](https://github.com/junegunn/fzf-git.sh) project defines a set of
|
||||||
|
fzf-based key bindings for Git objects. I strongly recommend that you check
|
||||||
|
them out because they are seriously useful.
|
||||||
|
|
||||||
### Files listed in `git status`
|
### Files listed in `git status`
|
||||||
|
|
||||||
@@ -518,9 +526,6 @@ seriously useful.
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
The full source code can be found [here](https://gist.github.com/junegunn/8b572b8d4b5eddd8b85e5f4d40f17236).
|
|
||||||
|
|
||||||
Color themes
|
Color themes
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
|||||||
98
CHANGELOG.md
98
CHANGELOG.md
@@ -1,6 +1,104 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
0.35.0
|
||||||
|
------
|
||||||
|
- Added `start` event that is triggered only once when fzf finder starts.
|
||||||
|
Since fzf consumes the input stream asynchronously, the input list is not
|
||||||
|
available unless you use `--sync`.
|
||||||
|
```sh
|
||||||
|
seq 100 | fzf --multi --sync --bind 'start:last+select-all+preview(echo welcome)'
|
||||||
|
```
|
||||||
|
- Added `--border-label` and `--border-label-pos` for putting label on the border
|
||||||
|
```sh
|
||||||
|
# ANSI color codes are supported
|
||||||
|
# (with https://github.com/busyloop/lolcat)
|
||||||
|
label=$(curl -s http://metaphorpsum.com/sentences/1 | lolcat -f)
|
||||||
|
|
||||||
|
# Border label at the center
|
||||||
|
fzf --height=10 --border --border-label="╢ $label ╟" --color=label:italic:black
|
||||||
|
|
||||||
|
# Left-aligned (positive integer)
|
||||||
|
fzf --height=10 --border --border-label="╢ $label ╟" --border-label-pos=3 --color=label:italic:black
|
||||||
|
|
||||||
|
# Right-aligned (negative integer) on the bottom line (:bottom)
|
||||||
|
fzf --height=10 --border --border-label="╢ $label ╟" --border-label-pos=-3:bottom --color=label:italic:black
|
||||||
|
```
|
||||||
|
- Also added `--preview-label` and `--preview-label-pos` for the border of the
|
||||||
|
preview window
|
||||||
|
```sh
|
||||||
|
fzf --preview 'cat {}' --border --preview-label=' Preview ' --preview-label-pos=2
|
||||||
|
```
|
||||||
|
- Info panel (match counter) will be followed by a horizontal separator by
|
||||||
|
default
|
||||||
|
- Use `--no-separator` or `--separator=''` to hide the separator
|
||||||
|
- You can specify an arbitrary string that is repeated to form the
|
||||||
|
horizontal separator. e.g. `--separator=╸`
|
||||||
|
- The color of the separator can be customized via `--color=separator:...`
|
||||||
|
- ANSI color codes are also supported
|
||||||
|
```sh
|
||||||
|
fzf --separator=╸ --color=separator:green
|
||||||
|
fzf --separator=$(lolcat -f -F 1.4 <<< ▁▁▂▃▄▅▆▆▅▄▃▂▁▁) --info=inline
|
||||||
|
```
|
||||||
|
- Added `--border=bold` and `--border=double` along with
|
||||||
|
`--preview-window=border-bold` and `--preview-window=border-double`
|
||||||
|
|
||||||
|
0.34.0
|
||||||
|
------
|
||||||
|
- Added support for adaptive `--height`. If the `--height` value is prefixed
|
||||||
|
with `~`, fzf will automatically determine the height in the range according
|
||||||
|
to the input size.
|
||||||
|
```sh
|
||||||
|
seq 1 | fzf --height ~70% --border --padding 1 --margin 1
|
||||||
|
seq 10 | fzf --height ~70% --border --padding 1 --margin 1
|
||||||
|
seq 100 | fzf --height ~70% --border --padding 1 --margin 1
|
||||||
|
```
|
||||||
|
- There are a few limitations
|
||||||
|
- Not compatible with percent top/bottom margin/padding
|
||||||
|
```sh
|
||||||
|
# This is not allowed (top/bottom margin in percent value)
|
||||||
|
fzf --height ~50% --border --margin 5%,10%
|
||||||
|
|
||||||
|
# This is allowed (top/bottom margin in fixed value)
|
||||||
|
fzf --height ~50% --border --margin 2,10%
|
||||||
|
```
|
||||||
|
- fzf will not start until it can determine the right height for the input
|
||||||
|
```sh
|
||||||
|
# fzf will open immediately
|
||||||
|
(sleep 2; seq 10) | fzf --height 50%
|
||||||
|
|
||||||
|
# fzf will open after 2 seconds
|
||||||
|
(sleep 2; seq 10) | fzf --height ~50%
|
||||||
|
(sleep 2; seq 1000) | fzf --height ~50%
|
||||||
|
```
|
||||||
|
- Fixed tcell renderer used to render full-screen fzf on Windows
|
||||||
|
- `--no-clear` is deprecated. Use `reload` action instead.
|
||||||
|
|
||||||
|
0.33.0
|
||||||
|
------
|
||||||
|
- Added `--scheme=[default|path|history]` option to choose scoring scheme
|
||||||
|
- (Experimental)
|
||||||
|
- We updated the scoring algorithm in 0.32.0, however we have learned that
|
||||||
|
this new scheme (`default`) is not always giving the optimal result
|
||||||
|
- `path`: Additional bonus point is only given to the characters after
|
||||||
|
path separator. You might want to choose this scheme if you have many
|
||||||
|
files with spaces in their paths.
|
||||||
|
- `history`: No additional bonus points are given so that we give more
|
||||||
|
weight to the chronological ordering. This is equivalent to the scoring
|
||||||
|
scheme before 0.32.0. This also sets `--tiebreak=index`.
|
||||||
|
- ANSI color sequences with colon delimiters are now supported.
|
||||||
|
```sh
|
||||||
|
printf "\e[38;5;208mOption 1\e[m\nOption 2" | fzf --ansi
|
||||||
|
printf "\e[38:5:208mOption 1\e[m\nOption 2" | fzf --ansi
|
||||||
|
```
|
||||||
|
- Support `border-{up,down}` as the synonyms for `border-{top,bottom}` in
|
||||||
|
`--preview-window`
|
||||||
|
- Added support for ANSI `strikethrough`
|
||||||
|
```sh
|
||||||
|
printf "\e[9mdeleted" | fzf --ansi
|
||||||
|
fzf --color fg+:strikethrough
|
||||||
|
```
|
||||||
|
|
||||||
0.32.1
|
0.32.1
|
||||||
------
|
------
|
||||||
- Fixed incorrect ordering of `--tiebreak=chunk`
|
- Fixed incorrect ordering of `--tiebreak=chunk`
|
||||||
|
|||||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2013-2021 Junegunn Choi
|
Copyright (c) 2013-2022 Junegunn Choi
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
1
Makefile
1
Makefile
@@ -155,6 +155,7 @@ target/$(BINARYLOONG64): $(SOURCES)
|
|||||||
GOARCH=loong64 $(GO) build $(BUILD_FLAGS) -o $@
|
GOARCH=loong64 $(GO) build $(BUILD_FLAGS) -o $@
|
||||||
|
|
||||||
bin/fzf: target/$(BINARY) | bin
|
bin/fzf: target/$(BINARY) | bin
|
||||||
|
-rm -f bin/fzf
|
||||||
cp -f target/$(BINARY) bin/fzf
|
cp -f target/$(BINARY) bin/fzf
|
||||||
|
|
||||||
docker:
|
docker:
|
||||||
|
|||||||
@@ -627,10 +627,7 @@ fzf --height 40% --layout reverse --info inline --border \
|
|||||||
|
|
||||||
See the man page (`man fzf`) for the full list of options.
|
See the man page (`man fzf`) for the full list of options.
|
||||||
|
|
||||||
For more advanced examples, see [Key bindings for git with fzf][fzf-git]
|
More advanced examples can be found [here](https://github.com/junegunn/fzf/blob/master/ADVANCED.md).
|
||||||
([code](https://gist.github.com/junegunn/8b572b8d4b5eddd8b85e5f4d40f17236)).
|
|
||||||
|
|
||||||
[fzf-git]: https://junegunn.kr/2016/07/fzf-git/
|
|
||||||
|
|
||||||
----
|
----
|
||||||
|
|
||||||
|
|||||||
14
go.mod
14
go.mod
@@ -1,20 +1,20 @@
|
|||||||
module github.com/junegunn/fzf
|
module github.com/junegunn/fzf
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gdamore/tcell v1.4.0
|
github.com/gdamore/tcell/v2 v2.5.3
|
||||||
github.com/mattn/go-isatty v0.0.14
|
github.com/mattn/go-isatty v0.0.16
|
||||||
github.com/mattn/go-runewidth v0.0.13
|
github.com/mattn/go-runewidth v0.0.14
|
||||||
github.com/mattn/go-shellwords v1.0.12
|
github.com/mattn/go-shellwords v1.0.12
|
||||||
github.com/rivo/uniseg v0.2.0
|
github.com/rivo/uniseg v0.4.2
|
||||||
github.com/saracen/walker v0.1.2
|
github.com/saracen/walker v0.1.3
|
||||||
golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gdamore/encoding v1.0.0 // indirect
|
github.com/gdamore/encoding v1.0.0 // indirect
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f // indirect
|
||||||
golang.org/x/text v0.3.7 // indirect
|
golang.org/x/text v0.3.7 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
34
go.sum
34
go.sum
@@ -1,29 +1,29 @@
|
|||||||
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
||||||
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/v2 v2.5.3 h1:b9XQrT6QGbgI7JvZOJXFNczOQeIYbo8BfeSMzt2sAV0=
|
||||||
github.com/gdamore/tcell v1.4.0/go.mod h1:vxEiSDZdW3L+Uhjii9c3375IlDmR05bzxY404ZVSMo0=
|
github.com/gdamore/tcell/v2 v2.5.3/go.mod h1:wSkrPaXoiIWZqW/g7Px4xc79di6FTcpB8tvaKJ6uGBo=
|
||||||
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 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
||||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
|
||||||
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
|
||||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
|
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
||||||
|
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
|
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
|
||||||
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
||||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/saracen/walker v0.1.2 h1:/o1TxP82n8thLvmL4GpJXduYaRmJ7qXp8u9dSlV0zmo=
|
github.com/rivo/uniseg v0.4.2 h1:YwD0ulJSJytLpiaWua0sBDusfsCZohxjxzVTYjwxfV8=
|
||||||
github.com/saracen/walker v0.1.2/go.mod h1:0oKYMsKVhSJ+ful4p/XbjvXbMgLEkLITZaxozsl4CGE=
|
github.com/rivo/uniseg v0.4.2/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
github.com/saracen/walker v0.1.3 h1:YtcKKmpRPy6XJTHJ75J2QYXXZYWnZNQxPCVqZSHVV/g=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
github.com/saracen/walker v0.1.3/go.mod h1:FU+7qU8DeQQgSZDmmThMJi93kPkLFgy0oVAcLxurjIk=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8=
|
||||||
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220318055525-2edf467146b5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12 h1:QyVthZKMsyaQwBTJE04jdNN0Pp5Fn9Qga0mrgxyERQM=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU=
|
||||||
golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
|||||||
3
install
3
install
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
set -u
|
set -u
|
||||||
|
|
||||||
version=0.32.1
|
version=0.35.0
|
||||||
auto_completion=
|
auto_completion=
|
||||||
key_bindings=
|
key_bindings=
|
||||||
update_config=2
|
update_config=2
|
||||||
@@ -175,6 +175,7 @@ case "$archi" in
|
|||||||
Linux\ armv7*) download fzf-$version-linux_armv7.tar.gz ;;
|
Linux\ armv7*) download fzf-$version-linux_armv7.tar.gz ;;
|
||||||
Linux\ armv8*) download fzf-$version-linux_arm64.tar.gz ;;
|
Linux\ armv8*) download fzf-$version-linux_arm64.tar.gz ;;
|
||||||
Linux\ aarch64*) download fzf-$version-linux_arm64.tar.gz ;;
|
Linux\ aarch64*) download fzf-$version-linux_arm64.tar.gz ;;
|
||||||
|
Linux\ loongarch64) download fzf-$version-linux_loong64.tar.gz ;;
|
||||||
Linux\ *64) download fzf-$version-linux_amd64.tar.gz ;;
|
Linux\ *64) download fzf-$version-linux_amd64.tar.gz ;;
|
||||||
FreeBSD\ *64) download fzf-$version-freebsd_amd64.tar.gz ;;
|
FreeBSD\ *64) download fzf-$version-freebsd_amd64.tar.gz ;;
|
||||||
OpenBSD\ *64) download fzf-$version-openbsd_amd64.tar.gz ;;
|
OpenBSD\ *64) download fzf-$version-openbsd_amd64.tar.gz ;;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
$version="0.32.1"
|
$version="0.35.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.32"
|
var version string = "0.35"
|
||||||
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 "Aug 2022" "fzf 0.32.1" "fzf-tmux - open fzf in tmux split pane"
|
.TH fzf-tmux 1 "Nov 2022" "fzf 0.35.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
|
||||||
|
|||||||
137
man/man1/fzf.1
137
man/man1/fzf.1
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
..
|
..
|
||||||
.TH fzf 1 "Aug 2022" "fzf 0.32.1" "fzf - a command-line fuzzy finder"
|
.TH fzf 1 "Nov 2022" "fzf 0.35.0" "fzf - a command-line fuzzy finder"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf - a command-line fuzzy finder
|
fzf - a command-line fuzzy finder
|
||||||
@@ -51,6 +51,18 @@ Case-sensitive match
|
|||||||
.B "--literal"
|
.B "--literal"
|
||||||
Do not normalize latin script letters for matching.
|
Do not normalize latin script letters for matching.
|
||||||
.TP
|
.TP
|
||||||
|
.BI "--scheme=" SCHEME
|
||||||
|
Choose scoring scheme tailored for different types of input.
|
||||||
|
|
||||||
|
.br
|
||||||
|
.BR default " Generic scoring scheme designed to work well with any type of input"
|
||||||
|
.br
|
||||||
|
.BR path " Scoring scheme for paths (additional bonus point only after path separator)
|
||||||
|
.br
|
||||||
|
.BR history " Scoring scheme for command history (no additional bonus points).
|
||||||
|
Sets \fB--tiebreak=index\fR as well.
|
||||||
|
.br
|
||||||
|
.TP
|
||||||
.BI "--algo=" TYPE
|
.BI "--algo=" TYPE
|
||||||
Fuzzy matching algorithm (default: v2)
|
Fuzzy matching algorithm (default: v2)
|
||||||
|
|
||||||
@@ -165,9 +177,11 @@ actions are affected:
|
|||||||
Label characters for \fBjump\fR and \fBjump-accept\fR
|
Label characters for \fBjump\fR and \fBjump-accept\fR
|
||||||
.SS Layout
|
.SS Layout
|
||||||
.TP
|
.TP
|
||||||
.BI "--height=" "HEIGHT[%]"
|
.BI "--height=" "[~]HEIGHT[%]"
|
||||||
Display fzf window below the cursor with the given height instead of using
|
Display fzf window below the cursor with the given height instead of using
|
||||||
the full screen.
|
the full screen. When prefixed with \fB~\fR, fzf will automatically determine
|
||||||
|
the height in the range according to the input size. Note that adaptive height
|
||||||
|
is not compatible with top/bottom margin and padding given in percent size.
|
||||||
.TP
|
.TP
|
||||||
.BI "--min-height=" "HEIGHT"
|
.BI "--min-height=" "HEIGHT"
|
||||||
Minimum height when \fB--height\fR is given in percent (default: 10).
|
Minimum height when \fB--height\fR is given in percent (default: 10).
|
||||||
@@ -189,7 +203,7 @@ Choose the layout (default: default)
|
|||||||
A synonym for \fB--layout=reverse\fB
|
A synonym for \fB--layout=reverse\fB
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "--border" [=STYLE]
|
.BI "--border" [=BORDER_OPT]
|
||||||
Draw border around the finder
|
Draw border around the finder
|
||||||
|
|
||||||
.br
|
.br
|
||||||
@@ -197,13 +211,17 @@ Draw border around the finder
|
|||||||
.br
|
.br
|
||||||
.BR sharp " Border with sharp corners"
|
.BR sharp " Border with sharp corners"
|
||||||
.br
|
.br
|
||||||
|
.BR bold " Border with bold lines"
|
||||||
|
.br
|
||||||
|
.BR double " Border with double lines"
|
||||||
|
.br
|
||||||
.BR horizontal " Horizontal lines above and below the finder"
|
.BR horizontal " Horizontal lines above and below the finder"
|
||||||
.br
|
.br
|
||||||
.BR vertical " Vertical lines on each side of the finder"
|
.BR vertical " Vertical lines on each side of the finder"
|
||||||
.br
|
.br
|
||||||
.BR top
|
.BR top " (up)"
|
||||||
.br
|
.br
|
||||||
.BR bottom
|
.BR bottom " (down)"
|
||||||
.br
|
.br
|
||||||
.BR left
|
.BR left
|
||||||
.br
|
.br
|
||||||
@@ -212,6 +230,50 @@ Draw border around the finder
|
|||||||
.BR none
|
.BR none
|
||||||
.br
|
.br
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BI "--border-label" [=LABEL]
|
||||||
|
Label to print on the horizontal border line. Should be used with one of the
|
||||||
|
following \fB--border\fR options.
|
||||||
|
|
||||||
|
.br
|
||||||
|
.B * rounded
|
||||||
|
.br
|
||||||
|
.B * sharp
|
||||||
|
.br
|
||||||
|
.B * bold
|
||||||
|
.br
|
||||||
|
.B * double
|
||||||
|
.br
|
||||||
|
.B * horizontal
|
||||||
|
.br
|
||||||
|
.BR "* top" " (up)"
|
||||||
|
.br
|
||||||
|
.BR "* bottom" " (down)"
|
||||||
|
.br
|
||||||
|
|
||||||
|
.br
|
||||||
|
e.g.
|
||||||
|
\fB# ANSI color codes are supported
|
||||||
|
# (with https://github.com/busyloop/lolcat)
|
||||||
|
label=$(curl -s http://metaphorpsum.com/sentences/1 | lolcat -f)
|
||||||
|
|
||||||
|
# Border label at the center
|
||||||
|
fzf --height=10 --border --border-label="╢ $label ╟" --color=label:italic:black
|
||||||
|
|
||||||
|
# Left-aligned (positive integer)
|
||||||
|
fzf --height=10 --border --border-label="╢ $label ╟" --border-label-pos=3 --color=label:italic:black
|
||||||
|
|
||||||
|
# Right-aligned (negative integer) on the bottom line (:bottom)
|
||||||
|
fzf --height=10 --border --border-label="╢ $label ╟" --border-label-pos=-3:bottom --color=label:italic:black\fR
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BI "--border-label-pos" [=N[:top|bottom]]
|
||||||
|
Position of the border label on the border line. Specify a positive integer as
|
||||||
|
the column position from the left. Specify a negative integer to right-align
|
||||||
|
the label. Label is printed on the top border line by default, add
|
||||||
|
\fB:bottom\fR to put it on the border line on the bottom. The default value
|
||||||
|
\fB0 (or \fBcenter\fR) will put the label at the center of the border line.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B "--no-unicode"
|
.B "--no-unicode"
|
||||||
Use ASCII characters instead of Unicode box drawing characters to draw border
|
Use ASCII characters instead of Unicode box drawing characters to draw border
|
||||||
@@ -267,7 +329,7 @@ e.g.
|
|||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "--info=" "STYLE"
|
.BI "--info=" "STYLE"
|
||||||
Determines the display style of finder info.
|
Determines the display style of finder info (match counters).
|
||||||
|
|
||||||
.br
|
.br
|
||||||
.BR default " Display on the next line to the prompt"
|
.BR default " Display on the next line to the prompt"
|
||||||
@@ -281,6 +343,18 @@ Determines the display style of finder info.
|
|||||||
.B "--no-info"
|
.B "--no-info"
|
||||||
A synonym for \fB--info=hidden\fB
|
A synonym for \fB--info=hidden\fB
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BI "--separator=" "STR"
|
||||||
|
The given string will be repeated to form the horizontal separator on the info
|
||||||
|
line (default: '─' or '-' depending on \fB--no-unicode\fR).
|
||||||
|
|
||||||
|
ANSI color codes are supported.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B "--no-separator"
|
||||||
|
Do not display horizontal separator on the info line. A synonym for
|
||||||
|
\fB--separator=''\fB
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "--prompt=" "STR"
|
.BI "--prompt=" "STR"
|
||||||
Input prompt (default: '> ')
|
Input prompt (default: '> ')
|
||||||
@@ -341,7 +415,9 @@ color mappings.
|
|||||||
\fBquery \fRQuery string
|
\fBquery \fRQuery string
|
||||||
\fBdisabled \fRQuery string when search is disabled
|
\fBdisabled \fRQuery string when search is disabled
|
||||||
\fBinfo \fRInfo line (match counters)
|
\fBinfo \fRInfo line (match counters)
|
||||||
|
\fBseparator \fRHorizontal separator on info line (match counters)
|
||||||
\fBborder \fRBorder around the window (\fB--border\fR and \fB--preview\fR)
|
\fBborder \fRBorder around the window (\fB--border\fR and \fB--preview\fR)
|
||||||
|
\fBlabel \fRBorder label (\fB--border-label\fR and \fB--preview-label\fR)
|
||||||
\fBprompt \fRPrompt
|
\fBprompt \fRPrompt
|
||||||
\fBpointer \fRPointer to the current line
|
\fBpointer \fRPointer to the current line
|
||||||
\fBmarker \fRMulti-select marker
|
\fBmarker \fRMulti-select marker
|
||||||
@@ -378,6 +454,7 @@ color mappings.
|
|||||||
\fBreverse\fR
|
\fBreverse\fR
|
||||||
\fBdim\fR
|
\fBdim\fR
|
||||||
\fBitalic\fR
|
\fBitalic\fR
|
||||||
|
\fBstrikethrough\fR
|
||||||
|
|
||||||
.B EXAMPLES:
|
.B EXAMPLES:
|
||||||
|
|
||||||
@@ -471,6 +548,37 @@ e.g.
|
|||||||
sleep 0.01
|
sleep 0.01
|
||||||
done'\fR
|
done'\fR
|
||||||
.RE
|
.RE
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BI "--preview-label" [=LABEL]
|
||||||
|
Label to print on the horizontal border line of the preview window.
|
||||||
|
Should be used with one of the following \fB--preview-window\fR options.
|
||||||
|
|
||||||
|
.br
|
||||||
|
.B * border-rounded (default)
|
||||||
|
.br
|
||||||
|
.B * border-sharp
|
||||||
|
.br
|
||||||
|
.B * border-bold
|
||||||
|
.br
|
||||||
|
.B * border-double
|
||||||
|
.br
|
||||||
|
.B * border-horizontal
|
||||||
|
.br
|
||||||
|
.B * border-top
|
||||||
|
.br
|
||||||
|
.B * border-bottom
|
||||||
|
.br
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BI "--preview-label-pos" [=N[:top|bottom]]
|
||||||
|
Position of the border label on the border line of the preview window. Specify
|
||||||
|
a positive integer as the column position from the left. Specify a negative
|
||||||
|
integer to right-align the label. Label is printed on the top border line by
|
||||||
|
default, add \fB:bottom\fR to put it on the border line on the bottom. The
|
||||||
|
default value 0 (or \fBcenter\fR) will put the label at the center of the
|
||||||
|
border line.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "--preview-window=" "[POSITION][,SIZE[%]][,border-BORDER_OPT][,[no]wrap][,[no]follow][,[no]cycle][,[no]hidden][,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES][,default][,<SIZE_THRESHOLD(ALTERNATIVE_LAYOUT)]"
|
.BI "--preview-window=" "[POSITION][,SIZE[%]][,border-BORDER_OPT][,[no]wrap][,[no]follow][,[no]cycle][,[no]hidden][,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES][,default][,<SIZE_THRESHOLD(ALTERNATIVE_LAYOUT)]"
|
||||||
|
|
||||||
@@ -603,12 +711,6 @@ Read input delimited by ASCII NUL characters instead of newline characters
|
|||||||
.B "--print0"
|
.B "--print0"
|
||||||
Print output delimited by ASCII NUL characters instead of newline characters
|
Print output delimited by ASCII NUL characters instead of newline characters
|
||||||
.TP
|
.TP
|
||||||
.B "--no-clear"
|
|
||||||
Do not clear finder interface on exit. If fzf was started in full screen mode,
|
|
||||||
it will not switch back to the original screen, so you'll have to manually run
|
|
||||||
\fBtput rmcup\fR to return. This option can be used to avoid flickering of the
|
|
||||||
screen when your application needs to start fzf multiple times in order.
|
|
||||||
.TP
|
|
||||||
.B "--sync"
|
.B "--sync"
|
||||||
Synchronous search for multi-staged filtering. If specified, fzf will launch
|
Synchronous search for multi-staged filtering. If specified, fzf will launch
|
||||||
ncurses finder only after the input stream is complete.
|
ncurses finder only after the input stream is complete.
|
||||||
@@ -802,6 +904,15 @@ e.g.
|
|||||||
or any single character
|
or any single character
|
||||||
|
|
||||||
.SS AVAILABLE EVENTS:
|
.SS AVAILABLE EVENTS:
|
||||||
|
\fIstart\fR
|
||||||
|
.RS
|
||||||
|
Triggered only once when fzf finder starts. Since fzf consumes the input stream
|
||||||
|
asynchronously, the input list is not available unless you use \fI--sync\fR.
|
||||||
|
|
||||||
|
e.g.
|
||||||
|
\fB# Move cursor to the last item and select all items
|
||||||
|
seq 1000 | fzf --multi --sync --bind start:last+select-all\fR
|
||||||
|
.RE
|
||||||
\fIchange\fR
|
\fIchange\fR
|
||||||
.RS
|
.RS
|
||||||
Triggered whenever the query string is changed
|
Triggered whenever the query string is changed
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ function! fzf#install()
|
|||||||
if !filereadable(script)
|
if !filereadable(script)
|
||||||
throw script.' not found'
|
throw script.' not found'
|
||||||
endif
|
endif
|
||||||
let script = 'powershell -ExecutionPolicy Bypass -file ' . script
|
let script = 'powershell -ExecutionPolicy Bypass -file ' . shellescape(script)
|
||||||
else
|
else
|
||||||
let script = s:base_dir.'/install'
|
let script = s:base_dir.'/install'
|
||||||
if !executable(script)
|
if !executable(script)
|
||||||
@@ -164,7 +164,7 @@ function s:get_version(bin)
|
|||||||
if has_key(s:versions, a:bin)
|
if has_key(s:versions, a:bin)
|
||||||
return s:versions[a:bin]
|
return s:versions[a:bin]
|
||||||
end
|
end
|
||||||
let command = (&shell =~ 'powershell' ? '&' : '') . shellescape(a:bin) . ' --version --no-height'
|
let command = (&shell =~ 'powershell' ? '&' : '') . s:fzf_call('shellescape', a:bin) . ' --version --no-height'
|
||||||
let output = systemlist(command)
|
let output = systemlist(command)
|
||||||
if v:shell_error || empty(output)
|
if v:shell_error || empty(output)
|
||||||
return ''
|
return ''
|
||||||
@@ -464,7 +464,7 @@ try
|
|||||||
let temps = { 'result': s:fzf_tempname() }
|
let temps = { 'result': s:fzf_tempname() }
|
||||||
let optstr = s:evaluate_opts(get(dict, 'options', ''))
|
let optstr = s:evaluate_opts(get(dict, 'options', ''))
|
||||||
try
|
try
|
||||||
let fzf_exec = fzf#shellescape(fzf#exec())
|
let fzf_exec = shellescape(fzf#exec())
|
||||||
catch
|
catch
|
||||||
throw v:exception
|
throw v:exception
|
||||||
endtry
|
endtry
|
||||||
@@ -973,16 +973,16 @@ function! s:callback(dict, lines) abort
|
|||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
if has('nvim')
|
if has('nvim')
|
||||||
function s:create_popup(hl, opts) abort
|
function s:create_popup(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 win = nvim_open_win(buf, v:true, opts)
|
let win = nvim_open_win(buf, v:true, opts)
|
||||||
call setwinvar(win, '&winhighlight', 'NormalFloat:'..a:hl)
|
silent! call setwinvar(win, '&winhighlight', 'Pmenu:,Normal:Normal')
|
||||||
call setwinvar(win, '&colorcolumn', '')
|
call setwinvar(win, '&colorcolumn', '')
|
||||||
return buf
|
return buf
|
||||||
endfunction
|
endfunction
|
||||||
else
|
else
|
||||||
function! s:create_popup(hl, opts) abort
|
function! s:create_popup(opts) abort
|
||||||
let s:popup_create = {buf -> popup_create(buf, #{
|
let s:popup_create = {buf -> popup_create(buf, #{
|
||||||
\ line: a:opts.row,
|
\ line: a:opts.row,
|
||||||
\ col: a:opts.col,
|
\ col: a:opts.col,
|
||||||
@@ -1017,7 +1017,7 @@ function! s:popup(opts) abort
|
|||||||
let row += !has('nvim')
|
let row += !has('nvim')
|
||||||
let col += !has('nvim')
|
let col += !has('nvim')
|
||||||
|
|
||||||
call s:create_popup('Normal', {
|
call s:create_popup({
|
||||||
\ 'row': row, 'col': col, 'width': width, 'height': height
|
\ 'row': row, 'col': col, 'width': width, 'height': height
|
||||||
\ })
|
\ })
|
||||||
endfunction
|
endfunction
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ bind '"\e[0n": redraw-current-line' 2> /dev/null
|
|||||||
__fzf_comprun() {
|
__fzf_comprun() {
|
||||||
if [[ "$(type -t _fzf_comprun 2>&1)" = function ]]; then
|
if [[ "$(type -t _fzf_comprun 2>&1)" = function ]]; then
|
||||||
_fzf_comprun "$@"
|
_fzf_comprun "$@"
|
||||||
elif [[ -n "$TMUX_PANE" ]] && { [[ "${FZF_TMUX:-0}" != 0 ]] || [[ -n "$FZF_TMUX_OPTS" ]]; }; then
|
elif [[ -n "${TMUX_PANE-}" ]] && { [[ "${FZF_TMUX:-0}" != 0 ]] || [[ -n "${FZF_TMUX_OPTS-}" ]]; }; then
|
||||||
shift
|
shift
|
||||||
fzf-tmux ${FZF_TMUX_OPTS:--d${FZF_TMUX_HEIGHT:-40%}} -- "$@"
|
fzf-tmux ${FZF_TMUX_OPTS:--d${FZF_TMUX_HEIGHT:-40%}} -- "$@"
|
||||||
else
|
else
|
||||||
@@ -55,8 +55,8 @@ __fzf_orig_completion() {
|
|||||||
cmd="${BASH_REMATCH[3]}"
|
cmd="${BASH_REMATCH[3]}"
|
||||||
[[ "$f" = _fzf_* ]] && continue
|
[[ "$f" = _fzf_* ]] && continue
|
||||||
printf -v "_fzf_orig_completion_${cmd//[^A-Za-z0-9_]/_}" "%s" "${comp} %s ${cmd} #${f}"
|
printf -v "_fzf_orig_completion_${cmd//[^A-Za-z0-9_]/_}" "%s" "${comp} %s ${cmd} #${f}"
|
||||||
if [[ "$l" = *" -o nospace "* ]] && [[ ! "$__fzf_nospace_commands" = *" $cmd "* ]]; then
|
if [[ "$l" = *" -o nospace "* ]] && [[ ! "${__fzf_nospace_commands-}" = *" $cmd "* ]]; then
|
||||||
__fzf_nospace_commands="$__fzf_nospace_commands $cmd "
|
__fzf_nospace_commands="${__fzf_nospace_commands-} $cmd "
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
@@ -139,17 +139,18 @@ _fzf_handle_dynamic_completion() {
|
|||||||
shift
|
shift
|
||||||
orig_cmd="$1"
|
orig_cmd="$1"
|
||||||
orig_var="_fzf_orig_completion_$cmd"
|
orig_var="_fzf_orig_completion_$cmd"
|
||||||
orig="${!orig_var##*#}"
|
orig="${!orig_var-}"
|
||||||
|
orig="${orig##*#}"
|
||||||
if [[ -n "$orig" ]] && type "$orig" > /dev/null 2>&1; then
|
if [[ -n "$orig" ]] && type "$orig" > /dev/null 2>&1; then
|
||||||
$orig "$@"
|
$orig "$@"
|
||||||
elif [[ -n "$_fzf_completion_loader" ]]; then
|
elif [[ -n "${_fzf_completion_loader-}" ]]; then
|
||||||
orig_complete=$(complete -p "$orig_cmd" 2> /dev/null)
|
orig_complete=$(complete -p "$orig_cmd" 2> /dev/null)
|
||||||
_completion_loader "$@"
|
_completion_loader "$@"
|
||||||
ret=$?
|
ret=$?
|
||||||
# _completion_loader may not have updated completion for the command
|
# _completion_loader may not have updated completion for the command
|
||||||
if [[ "$(complete -p "$orig_cmd" 2> /dev/null)" != "$orig_complete" ]]; then
|
if [[ "$(complete -p "$orig_cmd" 2> /dev/null)" != "$orig_complete" ]]; then
|
||||||
__fzf_orig_completion < <(complete -p "$orig_cmd" 2> /dev/null)
|
__fzf_orig_completion < <(complete -p "$orig_cmd" 2> /dev/null)
|
||||||
if [[ "$__fzf_nospace_commands" = *" $orig_cmd "* ]]; then
|
if [[ "${__fzf_nospace_commands-}" = *" $orig_cmd "* ]]; then
|
||||||
eval "${orig_complete/ -F / -o nospace -F }"
|
eval "${orig_complete/ -F / -o nospace -F }"
|
||||||
else
|
else
|
||||||
eval "$orig_complete"
|
eval "$orig_complete"
|
||||||
@@ -173,6 +174,7 @@ __fzf_generic_path_completion() {
|
|||||||
base=${cur:0:${#cur}-${#trigger}}
|
base=${cur:0:${#cur}-${#trigger}}
|
||||||
eval "base=$base"
|
eval "base=$base"
|
||||||
|
|
||||||
|
dir=
|
||||||
[[ $base = *"/"* ]] && dir="$base"
|
[[ $base = *"/"* ]] && dir="$base"
|
||||||
while true; do
|
while true; do
|
||||||
if [[ -z "$dir" ]] || [[ -d "$dir" ]]; then
|
if [[ -z "$dir" ]] || [[ -d "$dir" ]]; then
|
||||||
@@ -180,11 +182,11 @@ __fzf_generic_path_completion() {
|
|||||||
leftover=${leftover/#\/}
|
leftover=${leftover/#\/}
|
||||||
[[ -z "$dir" ]] && dir='.'
|
[[ -z "$dir" ]] && dir='.'
|
||||||
[[ "$dir" != "/" ]] && dir="${dir/%\//}"
|
[[ "$dir" != "/" ]] && dir="${dir/%\//}"
|
||||||
matches=$(eval "$1 $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS $2" __fzf_comprun "$4" -q "$leftover" | while read -r item; do
|
matches=$(eval "$1 $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-} $2" __fzf_comprun "$4" -q "$leftover" | while read -r item; do
|
||||||
printf "%q$3 " "$item"
|
printf "%q " "${item%$3}$3"
|
||||||
done)
|
done)
|
||||||
matches=${matches% }
|
matches=${matches% }
|
||||||
[[ -z "$3" ]] && [[ "$__fzf_nospace_commands" = *" ${COMP_WORDS[0]} "* ]] && matches="$matches "
|
[[ -z "$3" ]] && [[ "${__fzf_nospace_commands-}" = *" ${COMP_WORDS[0]} "* ]] && matches="$matches "
|
||||||
if [[ -n "$matches" ]]; then
|
if [[ -n "$matches" ]]; then
|
||||||
COMPREPLY=( "$matches" )
|
COMPREPLY=( "$matches" )
|
||||||
else
|
else
|
||||||
@@ -236,7 +238,7 @@ _fzf_complete() {
|
|||||||
if [[ "$cur" == *"$trigger" ]]; then
|
if [[ "$cur" == *"$trigger" ]]; then
|
||||||
cur=${cur:0:${#cur}-${#trigger}}
|
cur=${cur:0:${#cur}-${#trigger}}
|
||||||
|
|
||||||
selected=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS $str_arg" __fzf_comprun "${rest[0]}" "${args[@]}" -q "$cur" | $post | tr '\n' ' ')
|
selected=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-} $str_arg" __fzf_comprun "${rest[0]}" "${args[@]}" -q "$cur" | $post | tr '\n' ' ')
|
||||||
selected=${selected% } # Strip trailing space not to repeat "-o nospace"
|
selected=${selected% } # Strip trailing space not to repeat "-o nospace"
|
||||||
if [[ -n "$selected" ]]; then
|
if [[ -n "$selected" ]]; then
|
||||||
COMPREPLY=("$selected")
|
COMPREPLY=("$selected")
|
||||||
@@ -288,13 +290,13 @@ _fzf_host_completion() {
|
|||||||
|
|
||||||
_fzf_var_completion() {
|
_fzf_var_completion() {
|
||||||
_fzf_complete -m -- "$@" < <(
|
_fzf_complete -m -- "$@" < <(
|
||||||
declare -xp | sed 's/=.*//' | sed 's/.* //'
|
declare -xp | sed -En 's|^declare [^ ]+ ([^=]+).*|\1|p'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
_fzf_alias_completion() {
|
_fzf_alias_completion() {
|
||||||
_fzf_complete -m -- "$@" < <(
|
_fzf_complete -m -- "$@" < <(
|
||||||
alias | sed 's/=.*//' | sed 's/.* //'
|
alias | sed -En 's|^alias ([^=]+).*|\1|p'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,7 +331,7 @@ __fzf_defc() {
|
|||||||
func="$2"
|
func="$2"
|
||||||
opts="$3"
|
opts="$3"
|
||||||
orig_var="_fzf_orig_completion_${cmd//[^A-Za-z0-9_]/_}"
|
orig_var="_fzf_orig_completion_${cmd//[^A-Za-z0-9_]/_}"
|
||||||
orig="${!orig_var}"
|
orig="${!orig_var-}"
|
||||||
if [[ -n "$orig" ]]; then
|
if [[ -n "$orig" ]]; then
|
||||||
printf -v def "$orig" "$func"
|
printf -v def "$orig" "$func"
|
||||||
eval "$def"
|
eval "$def"
|
||||||
|
|||||||
@@ -99,9 +99,9 @@ fi
|
|||||||
__fzf_comprun() {
|
__fzf_comprun() {
|
||||||
if [[ "$(type _fzf_comprun 2>&1)" =~ function ]]; then
|
if [[ "$(type _fzf_comprun 2>&1)" =~ function ]]; then
|
||||||
_fzf_comprun "$@"
|
_fzf_comprun "$@"
|
||||||
elif [ -n "$TMUX_PANE" ] && { [ "${FZF_TMUX:-0}" != 0 ] || [ -n "$FZF_TMUX_OPTS" ]; }; then
|
elif [ -n "${TMUX_PANE-}" ] && { [ "${FZF_TMUX:-0}" != 0 ] || [ -n "${FZF_TMUX_OPTS-}" ]; }; then
|
||||||
shift
|
shift
|
||||||
if [ -n "$FZF_TMUX_OPTS" ]; then
|
if [ -n "${FZF_TMUX_OPTS-}" ]; then
|
||||||
fzf-tmux ${(Q)${(Z+n+)FZF_TMUX_OPTS}} -- "$@"
|
fzf-tmux ${(Q)${(Z+n+)FZF_TMUX_OPTS}} -- "$@"
|
||||||
else
|
else
|
||||||
fzf-tmux -d ${FZF_TMUX_HEIGHT:-40%} -- "$@"
|
fzf-tmux -d ${FZF_TMUX_HEIGHT:-40%} -- "$@"
|
||||||
@@ -145,8 +145,9 @@ __fzf_generic_path_completion() {
|
|||||||
leftover=${leftover/#\/}
|
leftover=${leftover/#\/}
|
||||||
[ -z "$dir" ] && dir='.'
|
[ -z "$dir" ] && dir='.'
|
||||||
[ "$dir" != "/" ] && dir="${dir/%\//}"
|
[ "$dir" != "/" ] && dir="${dir/%\//}"
|
||||||
matches=$(eval "$compgen $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS" __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" | while read item; do
|
matches=$(eval "$compgen $(printf %q "$dir")" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-}" __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" | while read item; do
|
||||||
echo -n "${(q)item}$suffix "
|
item="${item%$suffix}$suffix"
|
||||||
|
echo -n "${(q)item} "
|
||||||
done)
|
done)
|
||||||
matches=${matches% }
|
matches=${matches% }
|
||||||
if [ -n "$matches" ]; then
|
if [ -n "$matches" ]; then
|
||||||
@@ -183,7 +184,7 @@ _fzf_complete() {
|
|||||||
args=("$@")
|
args=("$@")
|
||||||
sep=
|
sep=
|
||||||
for i in {0..${#args[@]}}; do
|
for i in {0..${#args[@]}}; do
|
||||||
if [[ "${args[$i]}" = -- ]]; then
|
if [[ "${args[$i]-}" = -- ]]; then
|
||||||
sep=$i
|
sep=$i
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
@@ -207,7 +208,7 @@ _fzf_complete() {
|
|||||||
type $post > /dev/null 2>&1 || post=cat
|
type $post > /dev/null 2>&1 || post=cat
|
||||||
|
|
||||||
_fzf_feed_fifo "$fifo"
|
_fzf_feed_fifo "$fifo"
|
||||||
matches=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_COMPLETION_OPTS $str_arg" __fzf_comprun "$cmd" "${args[@]}" -q "${(Q)prefix}" < "$fifo" | $post | tr '\n' ' ')
|
matches=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-} $str_arg" __fzf_comprun "$cmd" "${args[@]}" -q "${(Q)prefix}" < "$fifo" | $post | tr '\n' ' ')
|
||||||
if [ -n "$matches" ]; then
|
if [ -n "$matches" ]; then
|
||||||
LBUFFER="$lbuf$matches"
|
LBUFFER="$lbuf$matches"
|
||||||
fi
|
fi
|
||||||
@@ -278,8 +279,8 @@ fzf-completion() {
|
|||||||
[ -z "$trigger" -a ${LBUFFER[-1]} = ' ' ] && tokens+=("")
|
[ -z "$trigger" -a ${LBUFFER[-1]} = ' ' ] && tokens+=("")
|
||||||
|
|
||||||
# When the trigger starts with ';', it becomes a separate token
|
# When the trigger starts with ';', it becomes a separate token
|
||||||
if [[ ${LBUFFER} = *"${tokens[-2]}${tokens[-1]}" ]]; then
|
if [[ ${LBUFFER} = *"${tokens[-2]-}${tokens[-1]}" ]]; then
|
||||||
tokens[-2]="${tokens[-2]}${tokens[-1]}"
|
tokens[-2]="${tokens[-2]-}${tokens[-1]}"
|
||||||
tokens=(${tokens[0,-2]})
|
tokens=(${tokens[0,-2]})
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ __fzf_select__() {
|
|||||||
-o -type f -print \
|
-o -type f -print \
|
||||||
-o -type d -print \
|
-o -type d -print \
|
||||||
-o -type l -print 2> /dev/null | cut -b3-"}"
|
-o -type l -print 2> /dev/null | cut -b3-"}"
|
||||||
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore --reverse $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS -m"
|
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore --reverse ${FZF_DEFAULT_OPTS-} ${FZF_CTRL_T_OPTS-} -m"
|
||||||
eval "$cmd" |
|
eval "$cmd" |
|
||||||
FZF_DEFAULT_OPTS="$opts" $(__fzfcmd) "$@" |
|
FZF_DEFAULT_OPTS="$opts" $(__fzfcmd) "$@" |
|
||||||
while read -r item; do
|
while read -r item; do
|
||||||
@@ -30,7 +30,7 @@ __fzf_select__() {
|
|||||||
if [[ $- =~ i ]]; then
|
if [[ $- =~ i ]]; then
|
||||||
|
|
||||||
__fzfcmd() {
|
__fzfcmd() {
|
||||||
[[ -n "$TMUX_PANE" ]] && { [[ "${FZF_TMUX:-0}" != 0 ]] || [[ -n "$FZF_TMUX_OPTS" ]]; } &&
|
[[ -n "${TMUX_PANE-}" ]] && { [[ "${FZF_TMUX:-0}" != 0 ]] || [[ -n "${FZF_TMUX_OPTS-}" ]]; } &&
|
||||||
echo "fzf-tmux ${FZF_TMUX_OPTS:--d${FZF_TMUX_HEIGHT:-40%}} -- " || echo "fzf"
|
echo "fzf-tmux ${FZF_TMUX_OPTS:--d${FZF_TMUX_HEIGHT:-40%}} -- " || echo "fzf"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,13 +44,13 @@ __fzf_cd__() {
|
|||||||
local cmd opts dir
|
local cmd opts dir
|
||||||
cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
||||||
-o -type d -print 2> /dev/null | cut -b3-"}"
|
-o -type d -print 2> /dev/null | cut -b3-"}"
|
||||||
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore --reverse $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS +m"
|
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore --reverse ${FZF_DEFAULT_OPTS-} ${FZF_ALT_C_OPTS-} +m"
|
||||||
dir=$(eval "$cmd" | FZF_DEFAULT_OPTS="$opts" $(__fzfcmd)) && printf 'builtin cd -- %q' "$dir"
|
dir=$(eval "$cmd" | FZF_DEFAULT_OPTS="$opts" $(__fzfcmd)) && printf 'builtin cd -- %q' "$dir"
|
||||||
}
|
}
|
||||||
|
|
||||||
__fzf_history__() {
|
__fzf_history__() {
|
||||||
local output opts script
|
local output opts script
|
||||||
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m --read0"
|
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} -n2..,.. --scheme=history --bind=ctrl-r:toggle-sort ${FZF_CTRL_R_OPTS-} +m --read0"
|
||||||
script='BEGIN { getc; $/ = "\n\t"; $HISTCOUNT = $ENV{last_hist} + 1 } s/^[ *]//; print $HISTCOUNT - $. . "\t$_" if !$seen{$_}++'
|
script='BEGIN { getc; $/ = "\n\t"; $HISTCOUNT = $ENV{last_hist} + 1 } s/^[ *]//; print $HISTCOUNT - $. . "\t$_" if !$seen{$_}++'
|
||||||
output=$(
|
output=$(
|
||||||
builtin fc -lnr -2147483648 |
|
builtin fc -lnr -2147483648 |
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ function fzf_key_bindings
|
|||||||
function fzf-history-widget -d "Show command history"
|
function fzf-history-widget -d "Show command history"
|
||||||
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
||||||
begin
|
begin
|
||||||
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT $FZF_DEFAULT_OPTS --tiebreak=index --bind=ctrl-r:toggle-sort,ctrl-z:ignore $FZF_CTRL_R_OPTS +m"
|
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT $FZF_DEFAULT_OPTS --scheme=history --bind=ctrl-r:toggle-sort,ctrl-z:ignore $FZF_CTRL_R_OPTS +m"
|
||||||
|
|
||||||
set -l FISH_MAJOR (echo $version | cut -f1 -d.)
|
set -l FISH_MAJOR (echo $version | cut -f1 -d.)
|
||||||
set -l FISH_MINOR (echo $version | cut -f2 -d.)
|
set -l FISH_MINOR (echo $version | cut -f2 -d.)
|
||||||
@@ -87,7 +87,7 @@ function fzf_key_bindings
|
|||||||
eval "$FZF_ALT_C_COMMAND | "(__fzfcmd)' +m --query "'$fzf_query'"' | read -l result
|
eval "$FZF_ALT_C_COMMAND | "(__fzfcmd)' +m --query "'$fzf_query'"' | read -l result
|
||||||
|
|
||||||
if [ -n "$result" ]
|
if [ -n "$result" ]
|
||||||
builtin cd -- $result
|
cd -- $result
|
||||||
|
|
||||||
# Remove last token from commandline.
|
# Remove last token from commandline.
|
||||||
commandline -t ""
|
commandline -t ""
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ __fsel() {
|
|||||||
-o -type l -print 2> /dev/null | cut -b3-"}"
|
-o -type l -print 2> /dev/null | cut -b3-"}"
|
||||||
setopt localoptions pipefail no_aliases 2> /dev/null
|
setopt localoptions pipefail no_aliases 2> /dev/null
|
||||||
local item
|
local item
|
||||||
eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS" $(__fzfcmd) -m "$@" | while read item; do
|
eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_CTRL_T_OPTS-}" $(__fzfcmd) -m "$@" | while read item; do
|
||||||
echo -n "${(q)item} "
|
echo -n "${(q)item} "
|
||||||
done
|
done
|
||||||
local ret=$?
|
local ret=$?
|
||||||
@@ -55,7 +55,7 @@ __fsel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
__fzfcmd() {
|
__fzfcmd() {
|
||||||
[ -n "$TMUX_PANE" ] && { [ "${FZF_TMUX:-0}" != 0 ] || [ -n "$FZF_TMUX_OPTS" ]; } &&
|
[ -n "${TMUX_PANE-}" ] && { [ "${FZF_TMUX:-0}" != 0 ] || [ -n "${FZF_TMUX_OPTS-}" ]; } &&
|
||||||
echo "fzf-tmux ${FZF_TMUX_OPTS:--d${FZF_TMUX_HEIGHT:-40%}} -- " || echo "fzf"
|
echo "fzf-tmux ${FZF_TMUX_OPTS:--d${FZF_TMUX_HEIGHT:-40%}} -- " || echo "fzf"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,7 +75,7 @@ fzf-cd-widget() {
|
|||||||
local cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
local cmd="${FZF_ALT_C_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
|
||||||
-o -type d -print 2> /dev/null | cut -b3-"}"
|
-o -type d -print 2> /dev/null | cut -b3-"}"
|
||||||
setopt localoptions pipefail no_aliases 2> /dev/null
|
setopt localoptions pipefail no_aliases 2> /dev/null
|
||||||
local dir="$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS" $(__fzfcmd) +m)"
|
local dir="$(eval "$cmd" | FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_ALT_C_OPTS-}" $(__fzfcmd) +m)"
|
||||||
if [[ -z "$dir" ]]; then
|
if [[ -z "$dir" ]]; then
|
||||||
zle redisplay
|
zle redisplay
|
||||||
return 0
|
return 0
|
||||||
@@ -98,7 +98,7 @@ fzf-history-widget() {
|
|||||||
local selected num
|
local selected num
|
||||||
setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases 2> /dev/null
|
setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases 2> /dev/null
|
||||||
selected=( $(fc -rl 1 | awk '{ cmd=$0; sub(/^[ \t]*[0-9]+\**[ \t]+/, "", cmd); if (!seen[cmd]++) print $0 }' |
|
selected=( $(fc -rl 1 | awk '{ cmd=$0; sub(/^[ \t]*[0-9]+\**[ \t]+/, "", cmd); if (!seen[cmd]++) print $0 }' |
|
||||||
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS -n2..,.. --tiebreak=index --bind=ctrl-r:toggle-sort,ctrl-z:ignore $FZF_CTRL_R_OPTS --query=${(qqq)LBUFFER} +m" $(__fzfcmd)) )
|
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} ${FZF_DEFAULT_OPTS-} -n2..,.. --scheme=history --bind=ctrl-r:toggle-sort,ctrl-z:ignore ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER} +m" $(__fzfcmd)) )
|
||||||
local ret=$?
|
local ret=$?
|
||||||
if [ -n "$selected" ]; then
|
if [ -n "$selected" ]; then
|
||||||
num=$selected[1]
|
num=$selected[1]
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ Scoring criteria
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
@@ -89,7 +90,8 @@ import (
|
|||||||
|
|
||||||
var DEBUG bool
|
var DEBUG bool
|
||||||
|
|
||||||
const delimiterChars = "/,:;|"
|
var delimiterChars = "/,:;|"
|
||||||
|
|
||||||
const whiteChars = " \t\n\v\f\r\x85\xA0"
|
const whiteChars = " \t\n\v\f\r\x85\xA0"
|
||||||
|
|
||||||
func indexAt(index int, max int, forward bool) int {
|
func indexAt(index int, max int, forward bool) int {
|
||||||
@@ -120,12 +122,6 @@ const (
|
|||||||
// in web2 dictionary and my file system.
|
// in web2 dictionary and my file system.
|
||||||
bonusBoundary = scoreMatch / 2
|
bonusBoundary = scoreMatch / 2
|
||||||
|
|
||||||
// Extra bonus for word boundary after whitespace character or beginning of the string
|
|
||||||
bonusBoundaryWhite = bonusBoundary + 2
|
|
||||||
|
|
||||||
// Extra bonus for word boundary after slash, colon, semi-colon, and comma
|
|
||||||
bonusBoundaryDelimiter = bonusBoundary + 1
|
|
||||||
|
|
||||||
// Although bonus point for non-word characters is non-contextual, we need it
|
// Although bonus point for non-word characters is non-contextual, we need it
|
||||||
// for computing bonus points for consecutive chunks starting with a non-word
|
// for computing bonus points for consecutive chunks starting with a non-word
|
||||||
// character.
|
// character.
|
||||||
@@ -149,6 +145,16 @@ const (
|
|||||||
bonusFirstCharMultiplier = 2
|
bonusFirstCharMultiplier = 2
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Extra bonus for word boundary after whitespace character or beginning of the string
|
||||||
|
bonusBoundaryWhite int16 = bonusBoundary + 2
|
||||||
|
|
||||||
|
// Extra bonus for word boundary after slash, colon, semi-colon, and comma
|
||||||
|
bonusBoundaryDelimiter int16 = bonusBoundary + 1
|
||||||
|
|
||||||
|
initialCharClass charClass = charWhite
|
||||||
|
)
|
||||||
|
|
||||||
type charClass int
|
type charClass int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -161,6 +167,29 @@ const (
|
|||||||
charNumber
|
charNumber
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func Init(scheme string) bool {
|
||||||
|
switch scheme {
|
||||||
|
case "default":
|
||||||
|
bonusBoundaryWhite = bonusBoundary + 2
|
||||||
|
bonusBoundaryDelimiter = bonusBoundary + 1
|
||||||
|
case "path":
|
||||||
|
bonusBoundaryWhite = bonusBoundary
|
||||||
|
bonusBoundaryDelimiter = bonusBoundary + 1
|
||||||
|
if os.PathSeparator == '/' {
|
||||||
|
delimiterChars = "/"
|
||||||
|
} else {
|
||||||
|
delimiterChars = string([]rune{os.PathSeparator, '/'})
|
||||||
|
}
|
||||||
|
initialCharClass = charDelimiter
|
||||||
|
case "history":
|
||||||
|
bonusBoundaryWhite = bonusBoundary
|
||||||
|
bonusBoundaryDelimiter = bonusBoundary
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func posArray(withPos bool, len int) *[]int {
|
func posArray(withPos bool, len int) *[]int {
|
||||||
if withPos {
|
if withPos {
|
||||||
pos := make([]int, 0, len)
|
pos := make([]int, 0, len)
|
||||||
@@ -407,7 +436,7 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
|
|||||||
// Phase 2. Calculate bonus for each point
|
// Phase 2. Calculate bonus for each point
|
||||||
maxScore, maxScorePos := int16(0), 0
|
maxScore, maxScorePos := int16(0), 0
|
||||||
pidx, lastIdx := 0, 0
|
pidx, lastIdx := 0, 0
|
||||||
pchar0, pchar, prevH0, prevClass, inGap := pattern[0], pattern[0], int16(0), charWhite, false
|
pchar0, pchar, prevH0, prevClass, inGap := pattern[0], pattern[0], int16(0), initialCharClass, false
|
||||||
Tsub := T[idx:]
|
Tsub := T[idx:]
|
||||||
H0sub, C0sub, Bsub := H0[idx:][:len(Tsub)], C0[idx:][:len(Tsub)], B[idx:][:len(Tsub)]
|
H0sub, C0sub, Bsub := H0[idx:][:len(Tsub)], C0[idx:][:len(Tsub)], B[idx:][:len(Tsub)]
|
||||||
for off, char := range Tsub {
|
for off, char := range Tsub {
|
||||||
@@ -910,8 +939,8 @@ func EqualMatch(caseSensitive bool, normalize bool, forward bool, text *util.Cha
|
|||||||
match = runesStr == string(pattern)
|
match = runesStr == string(pattern)
|
||||||
}
|
}
|
||||||
if match {
|
if match {
|
||||||
return Result{trimmedLen, trimmedLen + lenPattern, (scoreMatch+bonusBoundaryWhite)*lenPattern +
|
return Result{trimmedLen, trimmedLen + lenPattern, (scoreMatch+int(bonusBoundaryWhite))*lenPattern +
|
||||||
(bonusFirstCharMultiplier-1)*bonusBoundaryWhite}, nil
|
(bonusFirstCharMultiplier-1)*int(bonusBoundaryWhite)}, nil
|
||||||
}
|
}
|
||||||
return Result{-1, -1, 0}, nil
|
return Result{-1, -1, 0}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,29 +45,29 @@ func TestFuzzyMatch(t *testing.T) {
|
|||||||
assertMatch(t, fn, false, forward, "fooBarbaz1", "oBZ", 2, 9,
|
assertMatch(t, fn, false, forward, "fooBarbaz1", "oBZ", 2, 9,
|
||||||
scoreMatch*3+bonusCamel123+scoreGapStart+scoreGapExtension*3)
|
scoreMatch*3+bonusCamel123+scoreGapStart+scoreGapExtension*3)
|
||||||
assertMatch(t, fn, false, forward, "foo bar baz", "fbb", 0, 9,
|
assertMatch(t, fn, false, forward, "foo bar baz", "fbb", 0, 9,
|
||||||
scoreMatch*3+bonusBoundaryWhite*bonusFirstCharMultiplier+
|
scoreMatch*3+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+
|
||||||
bonusBoundaryWhite*2+2*scoreGapStart+4*scoreGapExtension)
|
int(bonusBoundaryWhite)*2+2*scoreGapStart+4*scoreGapExtension)
|
||||||
assertMatch(t, fn, false, forward, "/AutomatorDocument.icns", "rdoc", 9, 13,
|
assertMatch(t, fn, false, forward, "/AutomatorDocument.icns", "rdoc", 9, 13,
|
||||||
scoreMatch*4+bonusCamel123+bonusConsecutive*2)
|
scoreMatch*4+bonusCamel123+bonusConsecutive*2)
|
||||||
assertMatch(t, fn, false, forward, "/man1/zshcompctl.1", "zshc", 6, 10,
|
assertMatch(t, fn, false, forward, "/man1/zshcompctl.1", "zshc", 6, 10,
|
||||||
scoreMatch*4+bonusBoundaryDelimiter*bonusFirstCharMultiplier+bonusBoundaryDelimiter*3)
|
scoreMatch*4+int(bonusBoundaryDelimiter)*bonusFirstCharMultiplier+int(bonusBoundaryDelimiter)*3)
|
||||||
assertMatch(t, fn, false, forward, "/.oh-my-zsh/cache", "zshc", 8, 13,
|
assertMatch(t, fn, false, forward, "/.oh-my-zsh/cache", "zshc", 8, 13,
|
||||||
scoreMatch*4+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary*2+scoreGapStart+bonusBoundaryDelimiter)
|
scoreMatch*4+bonusBoundary*bonusFirstCharMultiplier+bonusBoundary*2+scoreGapStart+int(bonusBoundaryDelimiter))
|
||||||
assertMatch(t, fn, false, forward, "ab0123 456", "12356", 3, 10,
|
assertMatch(t, fn, false, forward, "ab0123 456", "12356", 3, 10,
|
||||||
scoreMatch*5+bonusConsecutive*3+scoreGapStart+scoreGapExtension)
|
scoreMatch*5+bonusConsecutive*3+scoreGapStart+scoreGapExtension)
|
||||||
assertMatch(t, fn, false, forward, "abc123 456", "12356", 3, 10,
|
assertMatch(t, fn, false, forward, "abc123 456", "12356", 3, 10,
|
||||||
scoreMatch*5+bonusCamel123*bonusFirstCharMultiplier+bonusCamel123*2+bonusConsecutive+scoreGapStart+scoreGapExtension)
|
scoreMatch*5+bonusCamel123*bonusFirstCharMultiplier+bonusCamel123*2+bonusConsecutive+scoreGapStart+scoreGapExtension)
|
||||||
assertMatch(t, fn, false, forward, "foo/bar/baz", "fbb", 0, 9,
|
assertMatch(t, fn, false, forward, "foo/bar/baz", "fbb", 0, 9,
|
||||||
scoreMatch*3+bonusBoundaryWhite*bonusFirstCharMultiplier+
|
scoreMatch*3+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+
|
||||||
bonusBoundaryDelimiter*2+2*scoreGapStart+4*scoreGapExtension)
|
int(bonusBoundaryDelimiter)*2+2*scoreGapStart+4*scoreGapExtension)
|
||||||
assertMatch(t, fn, false, forward, "fooBarBaz", "fbb", 0, 7,
|
assertMatch(t, fn, false, forward, "fooBarBaz", "fbb", 0, 7,
|
||||||
scoreMatch*3+bonusBoundaryWhite*bonusFirstCharMultiplier+
|
scoreMatch*3+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+
|
||||||
bonusCamel123*2+2*scoreGapStart+2*scoreGapExtension)
|
bonusCamel123*2+2*scoreGapStart+2*scoreGapExtension)
|
||||||
assertMatch(t, fn, false, forward, "foo barbaz", "fbb", 0, 8,
|
assertMatch(t, fn, false, forward, "foo barbaz", "fbb", 0, 8,
|
||||||
scoreMatch*3+bonusBoundaryWhite*bonusFirstCharMultiplier+bonusBoundaryWhite+
|
scoreMatch*3+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+int(bonusBoundaryWhite)+
|
||||||
scoreGapStart*2+scoreGapExtension*3)
|
scoreGapStart*2+scoreGapExtension*3)
|
||||||
assertMatch(t, fn, false, forward, "fooBar Baz", "foob", 0, 4,
|
assertMatch(t, fn, false, forward, "fooBar Baz", "foob", 0, 4,
|
||||||
scoreMatch*4+bonusBoundaryWhite*bonusFirstCharMultiplier+bonusBoundaryWhite*3)
|
scoreMatch*4+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+int(bonusBoundaryWhite)*3)
|
||||||
assertMatch(t, fn, false, forward, "xFoo-Bar Baz", "foo-b", 1, 6,
|
assertMatch(t, fn, false, forward, "xFoo-Bar Baz", "foo-b", 1, 6,
|
||||||
scoreMatch*5+bonusCamel123*bonusFirstCharMultiplier+bonusCamel123*2+
|
scoreMatch*5+bonusCamel123*bonusFirstCharMultiplier+bonusCamel123*2+
|
||||||
bonusNonWord+bonusBoundary)
|
bonusNonWord+bonusBoundary)
|
||||||
@@ -75,14 +75,14 @@ func TestFuzzyMatch(t *testing.T) {
|
|||||||
assertMatch(t, fn, true, forward, "fooBarbaz", "oBz", 2, 9,
|
assertMatch(t, fn, true, forward, "fooBarbaz", "oBz", 2, 9,
|
||||||
scoreMatch*3+bonusCamel123+scoreGapStart+scoreGapExtension*3)
|
scoreMatch*3+bonusCamel123+scoreGapStart+scoreGapExtension*3)
|
||||||
assertMatch(t, fn, true, forward, "Foo/Bar/Baz", "FBB", 0, 9,
|
assertMatch(t, fn, true, forward, "Foo/Bar/Baz", "FBB", 0, 9,
|
||||||
scoreMatch*3+bonusBoundaryWhite*bonusFirstCharMultiplier+bonusBoundaryDelimiter*2+
|
scoreMatch*3+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+int(bonusBoundaryDelimiter)*2+
|
||||||
scoreGapStart*2+scoreGapExtension*4)
|
scoreGapStart*2+scoreGapExtension*4)
|
||||||
assertMatch(t, fn, true, forward, "FooBarBaz", "FBB", 0, 7,
|
assertMatch(t, fn, true, forward, "FooBarBaz", "FBB", 0, 7,
|
||||||
scoreMatch*3+bonusBoundaryWhite*bonusFirstCharMultiplier+bonusCamel123*2+
|
scoreMatch*3+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+bonusCamel123*2+
|
||||||
scoreGapStart*2+scoreGapExtension*2)
|
scoreGapStart*2+scoreGapExtension*2)
|
||||||
assertMatch(t, fn, true, forward, "FooBar Baz", "FooB", 0, 4,
|
assertMatch(t, fn, true, forward, "FooBar Baz", "FooB", 0, 4,
|
||||||
scoreMatch*4+bonusBoundaryWhite*bonusFirstCharMultiplier+bonusBoundaryWhite*2+
|
scoreMatch*4+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+int(bonusBoundaryWhite)*2+
|
||||||
util.Max(bonusCamel123, bonusBoundaryWhite))
|
util.Max(bonusCamel123, int(bonusBoundaryWhite)))
|
||||||
|
|
||||||
// Consecutive bonus updated
|
// Consecutive bonus updated
|
||||||
assertMatch(t, fn, true, forward, "foo-bar", "o-ba", 2, 6,
|
assertMatch(t, fn, true, forward, "foo-bar", "o-ba", 2, 6,
|
||||||
@@ -98,10 +98,10 @@ func TestFuzzyMatch(t *testing.T) {
|
|||||||
|
|
||||||
func TestFuzzyMatchBackward(t *testing.T) {
|
func TestFuzzyMatchBackward(t *testing.T) {
|
||||||
assertMatch(t, FuzzyMatchV1, false, true, "foobar fb", "fb", 0, 4,
|
assertMatch(t, FuzzyMatchV1, false, true, "foobar fb", "fb", 0, 4,
|
||||||
scoreMatch*2+bonusBoundaryWhite*bonusFirstCharMultiplier+
|
scoreMatch*2+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+
|
||||||
scoreGapStart+scoreGapExtension)
|
scoreGapStart+scoreGapExtension)
|
||||||
assertMatch(t, FuzzyMatchV1, false, false, "foobar fb", "fb", 7, 9,
|
assertMatch(t, FuzzyMatchV1, false, false, "foobar fb", "fb", 7, 9,
|
||||||
scoreMatch*2+bonusBoundaryWhite*bonusFirstCharMultiplier+bonusBoundaryWhite)
|
scoreMatch*2+int(bonusBoundaryWhite)*bonusFirstCharMultiplier+int(bonusBoundaryWhite))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExactMatchNaive(t *testing.T) {
|
func TestExactMatchNaive(t *testing.T) {
|
||||||
@@ -114,9 +114,9 @@ func TestExactMatchNaive(t *testing.T) {
|
|||||||
assertMatch(t, ExactMatchNaive, false, dir, "/AutomatorDocument.icns", "rdoc", 9, 13,
|
assertMatch(t, ExactMatchNaive, false, dir, "/AutomatorDocument.icns", "rdoc", 9, 13,
|
||||||
scoreMatch*4+bonusCamel123+bonusConsecutive*2)
|
scoreMatch*4+bonusCamel123+bonusConsecutive*2)
|
||||||
assertMatch(t, ExactMatchNaive, false, dir, "/man1/zshcompctl.1", "zshc", 6, 10,
|
assertMatch(t, ExactMatchNaive, false, dir, "/man1/zshcompctl.1", "zshc", 6, 10,
|
||||||
scoreMatch*4+bonusBoundaryDelimiter*(bonusFirstCharMultiplier+3))
|
scoreMatch*4+int(bonusBoundaryDelimiter)*(bonusFirstCharMultiplier+3))
|
||||||
assertMatch(t, ExactMatchNaive, false, dir, "/.oh-my-zsh/cache", "zsh/c", 8, 13,
|
assertMatch(t, ExactMatchNaive, false, dir, "/.oh-my-zsh/cache", "zsh/c", 8, 13,
|
||||||
scoreMatch*5+bonusBoundary*(bonusFirstCharMultiplier+3)+bonusBoundaryDelimiter)
|
scoreMatch*5+bonusBoundary*(bonusFirstCharMultiplier+3)+int(bonusBoundaryDelimiter))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,7 +128,7 @@ func TestExactMatchNaiveBackward(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPrefixMatch(t *testing.T) {
|
func TestPrefixMatch(t *testing.T) {
|
||||||
score := scoreMatch*3 + bonusBoundaryWhite*bonusFirstCharMultiplier + bonusBoundaryWhite*2
|
score := scoreMatch*3 + int(bonusBoundaryWhite)*bonusFirstCharMultiplier + int(bonusBoundaryWhite)*2
|
||||||
|
|
||||||
for _, dir := range []bool{true, false} {
|
for _, dir := range []bool{true, false} {
|
||||||
assertMatch(t, PrefixMatch, true, dir, "fooBarbaz", "Foo", -1, -1, 0)
|
assertMatch(t, PrefixMatch, true, dir, "fooBarbaz", "Foo", -1, -1, 0)
|
||||||
@@ -159,7 +159,7 @@ func TestSuffixMatch(t *testing.T) {
|
|||||||
|
|
||||||
// Only when the pattern doesn't end with a space
|
// Only when the pattern doesn't end with a space
|
||||||
assertMatch(t, SuffixMatch, false, dir, "fooBarbaz ", "baz ", 6, 10,
|
assertMatch(t, SuffixMatch, false, dir, "fooBarbaz ", "baz ", 6, 10,
|
||||||
scoreMatch*4+bonusConsecutive*2+bonusBoundaryWhite)
|
scoreMatch*4+bonusConsecutive*2+int(bonusBoundaryWhite))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
58
src/ansi.go
58
src/ansi.go
@@ -55,6 +55,9 @@ func (s *ansiState) ToString() string {
|
|||||||
if s.attr&tui.Reverse > 0 {
|
if s.attr&tui.Reverse > 0 {
|
||||||
ret += "7;"
|
ret += "7;"
|
||||||
}
|
}
|
||||||
|
if s.attr&tui.StrikeThrough > 0 {
|
||||||
|
ret += "9;"
|
||||||
|
}
|
||||||
ret += toAnsiString(s.fg, 30) + toAnsiString(s.bg, 40)
|
ret += toAnsiString(s.fg, 30) + toAnsiString(s.bg, 40)
|
||||||
|
|
||||||
return "\x1b[" + strings.TrimSuffix(ret, ";") + "m"
|
return "\x1b[" + strings.TrimSuffix(ret, ";") + "m"
|
||||||
@@ -85,7 +88,7 @@ func isPrint(c uint8) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func matchOperatingSystemCommand(s string) int {
|
func matchOperatingSystemCommand(s string) int {
|
||||||
// `\x1b][0-9];[[:print:]]+(?:\x1b\\\\|\x07)`
|
// `\x1b][0-9][;:][[:print:]]+(?:\x1b\\\\|\x07)`
|
||||||
// ^ match starting here
|
// ^ match starting here
|
||||||
//
|
//
|
||||||
i := 5 // prefix matched in nextAnsiEscapeSequence()
|
i := 5 // prefix matched in nextAnsiEscapeSequence()
|
||||||
@@ -103,30 +106,37 @@ func matchOperatingSystemCommand(s string) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func matchControlSequence(s string) int {
|
func matchControlSequence(s string) int {
|
||||||
// `\x1b[\\[()][0-9;?]*[a-zA-Z@]`
|
// `\x1b[\\[()][0-9;:?]*[a-zA-Z@]`
|
||||||
// ^ match starting here
|
// ^ match starting here
|
||||||
//
|
//
|
||||||
i := 2 // prefix matched in nextAnsiEscapeSequence()
|
i := 2 // prefix matched in nextAnsiEscapeSequence()
|
||||||
for ; i < len(s) && (isNumeric(s[i]) || s[i] == ';' || s[i] == '?'); i++ {
|
for ; i < len(s); i++ {
|
||||||
}
|
|
||||||
if i < len(s) {
|
|
||||||
c := s[i]
|
c := s[i]
|
||||||
|
switch c {
|
||||||
|
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ';', ':', '?':
|
||||||
|
// ok
|
||||||
|
default:
|
||||||
if 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '@' {
|
if 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '@' {
|
||||||
return i + 1
|
return i + 1
|
||||||
}
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
func isCtrlSeqStart(c uint8) bool {
|
func isCtrlSeqStart(c uint8) bool {
|
||||||
return c == '\\' || c == '[' || c == '(' || c == ')'
|
switch c {
|
||||||
|
case '\\', '[', '(', ')':
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// nextAnsiEscapeSequence returns the ANSI escape sequence and is equivalent to
|
// nextAnsiEscapeSequence returns the ANSI escape sequence and is equivalent to
|
||||||
// calling FindStringIndex() on the below regex (which was originally used):
|
// 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)"
|
// "(?:\x1b[\\[()][0-9;:?]*[a-zA-Z@]|\x1b][0-9][;:][[:print:]]+(?:\x1b\\\\|\x07)|\x1b.|[\x0e\x0f]|.\x08)"
|
||||||
//
|
|
||||||
func nextAnsiEscapeSequence(s string) (int, int) {
|
func nextAnsiEscapeSequence(s string) (int, int) {
|
||||||
// fast check for ANSI escape sequences
|
// fast check for ANSI escape sequences
|
||||||
i := 0
|
i := 0
|
||||||
@@ -154,16 +164,16 @@ Loop:
|
|||||||
return i - n, i + 1
|
return i - n, i + 1
|
||||||
}
|
}
|
||||||
case '\x1b':
|
case '\x1b':
|
||||||
// match: `\x1b[\\[()][0-9;?]*[a-zA-Z@]`
|
// match: `\x1b[\\[()][0-9;:?]*[a-zA-Z@]`
|
||||||
if i+2 < len(s) && isCtrlSeqStart(s[i+1]) {
|
if i+2 < len(s) && isCtrlSeqStart(s[i+1]) {
|
||||||
if j := matchControlSequence(s[i:]); j != -1 {
|
if j := matchControlSequence(s[i:]); j != -1 {
|
||||||
return i, i + j
|
return i, i + j
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// match: `\x1b][0-9];[[:print:]]+(?:\x1b\\\\|\x07)`
|
// match: `\x1b][0-9][;:][[:print:]]+(?:\x1b\\\\|\x07)`
|
||||||
if i+5 < len(s) && s[i+1] == ']' && isNumeric(s[i+2]) &&
|
if i+5 < len(s) && s[i+1] == ']' && isNumeric(s[i+2]) &&
|
||||||
s[i+3] == ';' && isPrint(s[i+4]) {
|
(s[i+3] == ';' || s[i+3] == ':') && isPrint(s[i+4]) {
|
||||||
|
|
||||||
if j := matchOperatingSystemCommand(s[i:]); j != -1 {
|
if j := matchOperatingSystemCommand(s[i:]); j != -1 {
|
||||||
return i, i + j
|
return i, i + j
|
||||||
@@ -280,9 +290,20 @@ func extractColor(str string, state *ansiState, proc func(string, *ansiState) bo
|
|||||||
return trimmed, nil, state
|
return trimmed, nil, state
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseAnsiCode(s string) (int, string) {
|
func parseAnsiCode(s string, delimiter byte) (int, byte, string) {
|
||||||
var remaining string
|
var remaining string
|
||||||
if i := strings.IndexByte(s, ';'); i >= 0 {
|
i := -1
|
||||||
|
if delimiter == 0 {
|
||||||
|
// Faster than strings.IndexAny(";:")
|
||||||
|
i = strings.IndexByte(s, ';')
|
||||||
|
if i < 0 {
|
||||||
|
i = strings.IndexByte(s, ':')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
i = strings.IndexByte(s, delimiter)
|
||||||
|
}
|
||||||
|
if i >= 0 {
|
||||||
|
delimiter = s[i]
|
||||||
remaining = s[i+1:]
|
remaining = s[i+1:]
|
||||||
s = s[:i]
|
s = s[:i]
|
||||||
}
|
}
|
||||||
@@ -294,14 +315,14 @@ func parseAnsiCode(s string) (int, string) {
|
|||||||
for _, ch := range []byte(s) {
|
for _, ch := range []byte(s) {
|
||||||
ch -= '0'
|
ch -= '0'
|
||||||
if ch > 9 {
|
if ch > 9 {
|
||||||
return -1, remaining
|
return -1, delimiter, remaining
|
||||||
}
|
}
|
||||||
code = code*10 + int(ch)
|
code = code*10 + int(ch)
|
||||||
}
|
}
|
||||||
return code, remaining
|
return code, delimiter, remaining
|
||||||
}
|
}
|
||||||
|
|
||||||
return -1, remaining
|
return -1, delimiter, remaining
|
||||||
}
|
}
|
||||||
|
|
||||||
func interpretCode(ansiCode string, prevState *ansiState) ansiState {
|
func interpretCode(ansiCode string, prevState *ansiState) ansiState {
|
||||||
@@ -329,9 +350,10 @@ func interpretCode(ansiCode string, prevState *ansiState) ansiState {
|
|||||||
state256 := 0
|
state256 := 0
|
||||||
ptr := &state.fg
|
ptr := &state.fg
|
||||||
|
|
||||||
|
var delimiter byte = 0
|
||||||
for len(ansiCode) != 0 {
|
for len(ansiCode) != 0 {
|
||||||
var num int
|
var num int
|
||||||
if num, ansiCode = parseAnsiCode(ansiCode); num != -1 {
|
if num, delimiter, ansiCode = parseAnsiCode(ansiCode, delimiter); num != -1 {
|
||||||
switch state256 {
|
switch state256 {
|
||||||
case 0:
|
case 0:
|
||||||
switch num {
|
switch num {
|
||||||
@@ -357,6 +379,8 @@ func interpretCode(ansiCode string, prevState *ansiState) ansiState {
|
|||||||
state.attr = state.attr | tui.Blink
|
state.attr = state.attr | tui.Blink
|
||||||
case 7:
|
case 7:
|
||||||
state.attr = state.attr | tui.Reverse
|
state.attr = state.attr | tui.Reverse
|
||||||
|
case 9:
|
||||||
|
state.attr = state.attr | tui.StrikeThrough
|
||||||
case 23: // tput rmso
|
case 23: // tput rmso
|
||||||
state.attr = state.attr &^ tui.Italic
|
state.attr = state.attr &^ tui.Italic
|
||||||
case 24: // tput rmul
|
case 24: // tput rmul
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import (
|
|||||||
// (archived from http://ascii-table.com/ansi-escape-sequences-vt-100.php)
|
// (archived from http://ascii-table.com/ansi-escape-sequences-vt-100.php)
|
||||||
// - http://tldp.org/HOWTO/Bash-Prompt-HOWTO/x405.html
|
// - http://tldp.org/HOWTO/Bash-Prompt-HOWTO/x405.html
|
||||||
// - https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
|
// - https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
|
||||||
var ansiRegexReference = regexp.MustCompile("(?:\x1b[\\[()][0-9;]*[a-zA-Z@]|\x1b][0-9];[[:print:]]+(?:\x1b\\\\|\x07)|\x1b.|[\x0e\x0f]|.\x08)")
|
var ansiRegexReference = 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) {
|
func testParserReference(t testing.TB, str string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
@@ -358,6 +358,7 @@ func TestAnsiCodeStringConversion(t *testing.T) {
|
|||||||
assert("\x1b[31m", &ansiState{fg: 4, bg: 4, lbg: -1}, "\x1b[31;44m")
|
assert("\x1b[31m", &ansiState{fg: 4, bg: 4, lbg: -1}, "\x1b[31;44m")
|
||||||
assert("\x1b[1;2;31m", &ansiState{fg: 2, bg: -1, attr: tui.Reverse, lbg: -1}, "\x1b[1;2;7;31;49m")
|
assert("\x1b[1;2;31m", &ansiState{fg: 2, bg: -1, attr: tui.Reverse, lbg: -1}, "\x1b[1;2;7;31;49m")
|
||||||
assert("\x1b[38;5;100;48;5;200m", nil, "\x1b[38;5;100;48;5;200m")
|
assert("\x1b[38;5;100;48;5;200m", nil, "\x1b[38;5;100;48;5;200m")
|
||||||
|
assert("\x1b[38:5:100:48:5:200m", nil, "\x1b[38;5;100;48;5;200m")
|
||||||
assert("\x1b[48;5;100;38;5;200m", nil, "\x1b[38;5;200;48;5;100m")
|
assert("\x1b[48;5;100;38;5;200m", nil, "\x1b[38;5;200;48;5;100m")
|
||||||
assert("\x1b[48;5;100;38;2;10;20;30;1m", nil, "\x1b[1;38;2;10;20;30;48;5;100m")
|
assert("\x1b[48;5;100;38;2;10;20;30;1m", nil, "\x1b[1;38;2;10;20;30;48;5;100m")
|
||||||
assert("\x1b[48;5;100;38;2;10;20;30;7m",
|
assert("\x1b[48;5;100;38;2;10;20;30;7m",
|
||||||
@@ -377,7 +378,7 @@ func TestParseAnsiCode(t *testing.T) {
|
|||||||
{"-2", "", -1},
|
{"-2", "", -1},
|
||||||
}
|
}
|
||||||
for _, x := range tests {
|
for _, x := range tests {
|
||||||
n, s := parseAnsiCode(x.In)
|
n, _, s := parseAnsiCode(x.In, 0)
|
||||||
if n != x.N || s != x.Exp {
|
if n != x.N || s != x.Exp {
|
||||||
t.Fatalf("%q: got: (%d %q) want: (%d %q)", x.In, n, s, x.N, x.Exp)
|
t.Fatalf("%q: got: (%d %q) want: (%d %q)", x.In, n, s, x.N, x.Exp)
|
||||||
}
|
}
|
||||||
@@ -385,9 +386,9 @@ func TestParseAnsiCode(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// kernel/bpf/preload/iterators/README
|
// kernel/bpf/preload/iterators/README
|
||||||
const ansiBenchmarkString = "\x1b[38;5;81m\x1b[01;31m\x1b[Kkernel/\x1b[0m\x1b[38;5;81mbpf/" +
|
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:81mpreload/\x1b[0m\x1b[38;5;81miterators/" +
|
||||||
"\x1b[0m\x1b[38;5;149mMakefile\x1b[m\x1b[K\x1b[0m"
|
"\x1b[0m\x1b[38:5:149mMakefile\x1b[m\x1b[K\x1b[0m"
|
||||||
|
|
||||||
func BenchmarkNextAnsiEscapeSequence(b *testing.B) {
|
func BenchmarkNextAnsiEscapeSequence(b *testing.B) {
|
||||||
b.SetBytes(int64(len(ansiBenchmarkString)))
|
b.SetBytes(int64(len(ansiBenchmarkString)))
|
||||||
|
|||||||
61
src/core.go
61
src/core.go
@@ -1,28 +1,4 @@
|
|||||||
/*
|
// Package fzf implements fzf, a command-line fuzzy finder.
|
||||||
Package fzf implements fzf, a command-line fuzzy finder.
|
|
||||||
|
|
||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2013-2021 Junegunn Choi
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
package fzf
|
package fzf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -218,10 +194,17 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
|
|
||||||
// Terminal I/O
|
// Terminal I/O
|
||||||
terminal := NewTerminal(opts, eventBox)
|
terminal := NewTerminal(opts, eventBox)
|
||||||
|
maxFit := 0 // Maximum number of items that can fit on screen
|
||||||
|
padHeight := 0
|
||||||
|
heightUnknown := opts.Height.auto
|
||||||
|
if heightUnknown {
|
||||||
|
maxFit, padHeight = terminal.MaxFitAndPad(opts)
|
||||||
|
}
|
||||||
deferred := opts.Select1 || opts.Exit0
|
deferred := opts.Select1 || opts.Exit0
|
||||||
go terminal.Loop()
|
go terminal.Loop()
|
||||||
if !deferred {
|
if !deferred && !heightUnknown {
|
||||||
terminal.startChan <- true
|
// Start right away
|
||||||
|
terminal.startChan <- fitpad{-1, -1}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Event coordination
|
// Event coordination
|
||||||
@@ -240,7 +223,19 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
go reader.restart(command)
|
go reader.restart(command)
|
||||||
}
|
}
|
||||||
eventBox.Watch(EvtReadNew)
|
eventBox.Watch(EvtReadNew)
|
||||||
|
total := 0
|
||||||
query := []rune{}
|
query := []rune{}
|
||||||
|
determine := func(final bool) {
|
||||||
|
if heightUnknown {
|
||||||
|
if total >= maxFit || final {
|
||||||
|
heightUnknown = false
|
||||||
|
terminal.startChan <- fitpad{util.Min(total, maxFit), padHeight}
|
||||||
|
}
|
||||||
|
} else if deferred {
|
||||||
|
deferred = false
|
||||||
|
terminal.startChan <- fitpad{-1, -1}
|
||||||
|
}
|
||||||
|
}
|
||||||
for {
|
for {
|
||||||
delay := true
|
delay := true
|
||||||
ticks++
|
ticks++
|
||||||
@@ -273,11 +268,15 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
reading = reading && evt == EvtReadNew
|
reading = reading && evt == EvtReadNew
|
||||||
}
|
}
|
||||||
snapshot, count := chunkList.Snapshot()
|
snapshot, count := chunkList.Snapshot()
|
||||||
terminal.UpdateCount(count, !reading, value.(*string))
|
total = count
|
||||||
|
terminal.UpdateCount(total, !reading, value.(*string))
|
||||||
if opts.Sync {
|
if opts.Sync {
|
||||||
opts.Sync = false
|
opts.Sync = false
|
||||||
terminal.UpdateList(PassMerger(&snapshot, opts.Tac), false)
|
terminal.UpdateList(PassMerger(&snapshot, opts.Tac), false)
|
||||||
}
|
}
|
||||||
|
if heightUnknown && !deferred {
|
||||||
|
determine(!reading)
|
||||||
|
}
|
||||||
reset := clearCache()
|
reset := clearCache()
|
||||||
matcher.Reset(snapshot, input(reset), false, !reading, sort, reset)
|
matcher.Reset(snapshot, input(reset), false, !reading, sort, reset)
|
||||||
|
|
||||||
@@ -319,8 +318,7 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
if deferred {
|
if deferred {
|
||||||
count := val.Length()
|
count := val.Length()
|
||||||
if opts.Select1 && count > 1 || opts.Exit0 && !opts.Select1 && count > 0 {
|
if opts.Select1 && count > 1 || opts.Exit0 && !opts.Select1 && count > 0 {
|
||||||
deferred = false
|
determine(val.final)
|
||||||
terminal.startChan <- true
|
|
||||||
} else if val.final {
|
} else if val.final {
|
||||||
if opts.Exit0 && count == 0 || opts.Select1 && count == 1 {
|
if opts.Exit0 && count == 0 || opts.Select1 && count == 1 {
|
||||||
if opts.PrintQuery {
|
if opts.PrintQuery {
|
||||||
@@ -337,8 +335,7 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
}
|
}
|
||||||
os.Exit(exitNoMatch)
|
os.Exit(exitNoMatch)
|
||||||
}
|
}
|
||||||
deferred = false
|
determine(val.final)
|
||||||
terminal.startChan <- true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
terminal.UpdateList(val, clearSelection())
|
terminal.UpdateList(val, clearSelection())
|
||||||
|
|||||||
183
src/options.go
183
src/options.go
@@ -21,9 +21,9 @@ const usage = `usage: fzf [options]
|
|||||||
-x, --extended Extended-search mode
|
-x, --extended Extended-search mode
|
||||||
(enabled by default; +x or --no-extended to disable)
|
(enabled by default; +x or --no-extended to disable)
|
||||||
-e, --exact Enable Exact-match
|
-e, --exact Enable Exact-match
|
||||||
--algo=TYPE Fuzzy matching algorithm: [v1|v2] (default: v2)
|
|
||||||
-i Case-insensitive match (default: smart-case match)
|
-i Case-insensitive match (default: smart-case match)
|
||||||
+i Case-sensitive match
|
+i Case-sensitive match
|
||||||
|
--scheme=SCHEME Scoring scheme [default|path|history]
|
||||||
--literal Do not normalize latin script letters before matching
|
--literal Do not normalize latin script letters before matching
|
||||||
-n, --nth=N[,..] Comma-separated list of field index expressions
|
-n, --nth=N[,..] Comma-separated list of field index expressions
|
||||||
for limiting search scope. Each can be a non-zero
|
for limiting search scope. Each can be a non-zero
|
||||||
@@ -53,17 +53,26 @@ const usage = `usage: fzf [options]
|
|||||||
--jump-labels=CHARS Label characters for jump and jump-accept
|
--jump-labels=CHARS Label characters for jump and jump-accept
|
||||||
|
|
||||||
Layout
|
Layout
|
||||||
--height=HEIGHT[%] Display fzf window below the cursor with the given
|
--height=[~]HEIGHT[%] Display fzf window below the cursor with the given
|
||||||
height instead of using fullscreen
|
height instead of using fullscreen.
|
||||||
|
If prefixed with '~', fzf will determine the height
|
||||||
|
according to the input size.
|
||||||
--min-height=HEIGHT Minimum height when --height is given in percent
|
--min-height=HEIGHT Minimum height when --height is given in percent
|
||||||
(default: 10)
|
(default: 10)
|
||||||
--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|none] (default: rounded)
|
top|bottom|left|right|none] (default: rounded)
|
||||||
|
--border-label=LABEL Label to print on the border
|
||||||
|
--border-label-pos=COL Position of the border label
|
||||||
|
[POSITIVE_INTEGER: columns from left|
|
||||||
|
NEGATIVE_INTEGER: columns from right][:bottom]
|
||||||
|
(default: 0 or center)
|
||||||
--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]
|
||||||
|
--separator=STR String to form horizontal separator on info line
|
||||||
|
--no-separator Hide info line separator
|
||||||
--prompt=STR Input prompt (default: '> ')
|
--prompt=STR Input prompt (default: '> ')
|
||||||
--pointer=STR Pointer to the current line (default: '>')
|
--pointer=STR Pointer to the current line (default: '>')
|
||||||
--marker=STR Multi-select marker (default: '>')
|
--marker=STR Multi-select marker (default: '>')
|
||||||
@@ -90,6 +99,9 @@ const usage = `usage: fzf [options]
|
|||||||
[,border-BORDER_OPT]
|
[,border-BORDER_OPT]
|
||||||
[,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES]
|
[,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES]
|
||||||
[,default][,<SIZE_THRESHOLD(ALTERNATIVE_LAYOUT)]
|
[,default][,<SIZE_THRESHOLD(ALTERNATIVE_LAYOUT)]
|
||||||
|
--preview-label=LABEL
|
||||||
|
--preview-label-pos=N Same as --border-label and --border-label-pos,
|
||||||
|
but for preview window
|
||||||
|
|
||||||
Scripting
|
Scripting
|
||||||
-q, --query=STR Start the finder with the given query
|
-q, --query=STR Start the finder with the given query
|
||||||
@@ -131,6 +143,12 @@ const (
|
|||||||
byEnd
|
byEnd
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type heightSpec struct {
|
||||||
|
size float64
|
||||||
|
percent bool
|
||||||
|
auto bool
|
||||||
|
}
|
||||||
|
|
||||||
type sizeSpec struct {
|
type sizeSpec struct {
|
||||||
size float64
|
size float64
|
||||||
percent bool
|
percent bool
|
||||||
@@ -165,6 +183,12 @@ const (
|
|||||||
infoHidden
|
infoHidden
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type labelOpts struct {
|
||||||
|
label string
|
||||||
|
column int
|
||||||
|
bottom bool
|
||||||
|
}
|
||||||
|
|
||||||
type previewOpts struct {
|
type previewOpts struct {
|
||||||
command string
|
command string
|
||||||
position windowPosition
|
position windowPosition
|
||||||
@@ -180,6 +204,27 @@ type previewOpts struct {
|
|||||||
alternative *previewOpts
|
alternative *previewOpts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseLabelPosition(opts *labelOpts, arg string) {
|
||||||
|
opts.column = 0
|
||||||
|
opts.bottom = false
|
||||||
|
for _, token := range splitRegexp.Split(strings.ToLower(arg), -1) {
|
||||||
|
switch token {
|
||||||
|
case "center":
|
||||||
|
opts.column = 0
|
||||||
|
case "bottom":
|
||||||
|
opts.bottom = true
|
||||||
|
case "top":
|
||||||
|
opts.bottom = false
|
||||||
|
default:
|
||||||
|
opts.column = atoi(token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a previewOpts) aboveOrBelow() bool {
|
||||||
|
return a.size.size > 0 && (a.position == posUp || a.position == posDown)
|
||||||
|
}
|
||||||
|
|
||||||
func (a previewOpts) sameLayout(b previewOpts) bool {
|
func (a previewOpts) sameLayout(b previewOpts) bool {
|
||||||
return a.size == b.size && a.position == b.position && a.border == b.border && a.hidden == b.hidden && a.threshold == b.threshold &&
|
return a.size == b.size && a.position == b.position && a.border == b.border && a.hidden == b.hidden && a.threshold == b.threshold &&
|
||||||
(a.alternative != nil && b.alternative != nil && a.alternative.sameLayout(*b.alternative) ||
|
(a.alternative != nil && b.alternative != nil && a.alternative.sameLayout(*b.alternative) ||
|
||||||
@@ -194,6 +239,7 @@ func (a previewOpts) sameContentLayout(b previewOpts) bool {
|
|||||||
type Options struct {
|
type Options struct {
|
||||||
Fuzzy bool
|
Fuzzy bool
|
||||||
FuzzyAlgo algo.Algo
|
FuzzyAlgo algo.Algo
|
||||||
|
Scheme string
|
||||||
Extended bool
|
Extended bool
|
||||||
Phony bool
|
Phony bool
|
||||||
Case Case
|
Case Case
|
||||||
@@ -210,7 +256,7 @@ type Options struct {
|
|||||||
Theme *tui.ColorTheme
|
Theme *tui.ColorTheme
|
||||||
Black bool
|
Black bool
|
||||||
Bold bool
|
Bold bool
|
||||||
Height sizeSpec
|
Height heightSpec
|
||||||
MinHeight int
|
MinHeight int
|
||||||
Layout layoutType
|
Layout layoutType
|
||||||
Cycle bool
|
Cycle bool
|
||||||
@@ -220,6 +266,7 @@ type Options struct {
|
|||||||
ScrollOff int
|
ScrollOff int
|
||||||
FileWord bool
|
FileWord bool
|
||||||
InfoStyle infoStyle
|
InfoStyle infoStyle
|
||||||
|
Separator *string
|
||||||
JumpLabels string
|
JumpLabels string
|
||||||
Prompt string
|
Prompt string
|
||||||
Pointer string
|
Pointer string
|
||||||
@@ -245,6 +292,8 @@ type Options struct {
|
|||||||
Margin [4]sizeSpec
|
Margin [4]sizeSpec
|
||||||
Padding [4]sizeSpec
|
Padding [4]sizeSpec
|
||||||
BorderShape tui.BorderShape
|
BorderShape tui.BorderShape
|
||||||
|
BorderLabel labelOpts
|
||||||
|
PreviewLabel labelOpts
|
||||||
Unicode bool
|
Unicode bool
|
||||||
Tabstop int
|
Tabstop int
|
||||||
ClearOnExit bool
|
ClearOnExit bool
|
||||||
@@ -259,6 +308,7 @@ func defaultOptions() *Options {
|
|||||||
return &Options{
|
return &Options{
|
||||||
Fuzzy: true,
|
Fuzzy: true,
|
||||||
FuzzyAlgo: algo.FuzzyMatchV2,
|
FuzzyAlgo: algo.FuzzyMatchV2,
|
||||||
|
Scheme: "default",
|
||||||
Extended: true,
|
Extended: true,
|
||||||
Phony: false,
|
Phony: false,
|
||||||
Case: CaseSmart,
|
Case: CaseSmart,
|
||||||
@@ -284,6 +334,7 @@ func defaultOptions() *Options {
|
|||||||
ScrollOff: 0,
|
ScrollOff: 0,
|
||||||
FileWord: false,
|
FileWord: false,
|
||||||
InfoStyle: infoDefault,
|
InfoStyle: infoDefault,
|
||||||
|
Separator: nil,
|
||||||
JumpLabels: defaultJumpLabels,
|
JumpLabels: defaultJumpLabels,
|
||||||
Prompt: "> ",
|
Prompt: "> ",
|
||||||
Pointer: ">",
|
Pointer: ">",
|
||||||
@@ -310,6 +361,8 @@ func defaultOptions() *Options {
|
|||||||
Padding: defaultMargin(),
|
Padding: defaultMargin(),
|
||||||
Unicode: true,
|
Unicode: true,
|
||||||
Tabstop: 8,
|
Tabstop: 8,
|
||||||
|
BorderLabel: labelOpts{},
|
||||||
|
PreviewLabel: labelOpts{},
|
||||||
ClearOnExit: true,
|
ClearOnExit: true,
|
||||||
Version: false}
|
Version: false}
|
||||||
}
|
}
|
||||||
@@ -441,12 +494,25 @@ func parseAlgo(str string) algo.Algo {
|
|||||||
return algo.FuzzyMatchV2
|
return algo.FuzzyMatchV2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func processScheme(opts *Options) {
|
||||||
|
if !algo.Init(opts.Scheme) {
|
||||||
|
errorExit("invalid scoring scheme (expected: default|path|history)")
|
||||||
|
}
|
||||||
|
if opts.Scheme == "history" {
|
||||||
|
opts.Criteria = []criterion{byScore}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func parseBorder(str string, optional bool) tui.BorderShape {
|
func parseBorder(str string, optional bool) tui.BorderShape {
|
||||||
switch str {
|
switch str {
|
||||||
case "rounded":
|
case "rounded":
|
||||||
return tui.BorderRounded
|
return tui.BorderRounded
|
||||||
case "sharp":
|
case "sharp":
|
||||||
return tui.BorderSharp
|
return tui.BorderSharp
|
||||||
|
case "bold":
|
||||||
|
return tui.BorderBold
|
||||||
|
case "double":
|
||||||
|
return tui.BorderDouble
|
||||||
case "horizontal":
|
case "horizontal":
|
||||||
return tui.BorderHorizontal
|
return tui.BorderHorizontal
|
||||||
case "vertical":
|
case "vertical":
|
||||||
@@ -465,7 +531,7 @@ func parseBorder(str string, optional bool) tui.BorderShape {
|
|||||||
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|none)")
|
errorExit("invalid border style (expected: rounded|sharp|bold|double|horizontal|vertical|top|bottom|left|right|none)")
|
||||||
}
|
}
|
||||||
return tui.BorderNone
|
return tui.BorderNone
|
||||||
}
|
}
|
||||||
@@ -520,6 +586,8 @@ func parseKeyChords(str string, message string) map[tui.Event]string {
|
|||||||
add(tui.Change)
|
add(tui.Change)
|
||||||
case "backward-eof":
|
case "backward-eof":
|
||||||
add(tui.BackwardEOF)
|
add(tui.BackwardEOF)
|
||||||
|
case "start":
|
||||||
|
add(tui.Start)
|
||||||
case "alt-enter", "alt-return":
|
case "alt-enter", "alt-return":
|
||||||
chords[tui.CtrlAltKey('m')] = key
|
chords[tui.CtrlAltKey('m')] = key
|
||||||
case "alt-space":
|
case "alt-space":
|
||||||
@@ -700,6 +768,8 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
|
|||||||
cattr.Attr |= tui.Blink
|
cattr.Attr |= tui.Blink
|
||||||
case "reverse":
|
case "reverse":
|
||||||
cattr.Attr |= tui.Reverse
|
cattr.Attr |= tui.Reverse
|
||||||
|
case "strikethrough":
|
||||||
|
cattr.Attr |= tui.StrikeThrough
|
||||||
case "black":
|
case "black":
|
||||||
cattr.Color = tui.Color(0)
|
cattr.Color = tui.Color(0)
|
||||||
case "red":
|
case "red":
|
||||||
@@ -771,6 +841,10 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
|
|||||||
mergeAttr(&theme.CurrentMatch)
|
mergeAttr(&theme.CurrentMatch)
|
||||||
case "border":
|
case "border":
|
||||||
mergeAttr(&theme.Border)
|
mergeAttr(&theme.Border)
|
||||||
|
case "separator":
|
||||||
|
mergeAttr(&theme.Separator)
|
||||||
|
case "label":
|
||||||
|
mergeAttr(&theme.BorderLabel)
|
||||||
case "prompt":
|
case "prompt":
|
||||||
mergeAttr(&theme.Prompt)
|
mergeAttr(&theme.Prompt)
|
||||||
case "spinner":
|
case "spinner":
|
||||||
@@ -791,7 +865,10 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
|
|||||||
return theme
|
return theme
|
||||||
}
|
}
|
||||||
|
|
||||||
var executeRegexp *regexp.Regexp
|
var (
|
||||||
|
executeRegexp *regexp.Regexp
|
||||||
|
splitRegexp *regexp.Regexp
|
||||||
|
)
|
||||||
|
|
||||||
func firstKey(keymap map[tui.Event]string) tui.Event {
|
func firstKey(keymap map[tui.Event]string) tui.Event {
|
||||||
for k := range keymap {
|
for k := range keymap {
|
||||||
@@ -811,6 +888,7 @@ func init() {
|
|||||||
// "~!@#$%^&*;/|".each_char.map { |c| Regexp.escape(c) }.map { |c| "#{c}[^#{c}]*#{c}" }.join('|')
|
// "~!@#$%^&*;/|".each_char.map { |c| Regexp.escape(c) }.map { |c| "#{c}[^#{c}]*#{c}" }.join('|')
|
||||||
executeRegexp = regexp.MustCompile(
|
executeRegexp = regexp.MustCompile(
|
||||||
`(?si)[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt|change-preview-window|change-preview|(?:re|un)bind):.+|[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt|change-preview-window|change-preview|(?:re|un)bind)(\([^)]*\)|\[[^\]]*\]|~[^~]*~|![^!]*!|@[^@]*@|\#[^\#]*\#|\$[^\$]*\$|%[^%]*%|\^[^\^]*\^|&[^&]*&|\*[^\*]*\*|;[^;]*;|/[^/]*/|\|[^\|]*\|)`)
|
`(?si)[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt|change-preview-window|change-preview|(?:re|un)bind):.+|[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt|change-preview-window|change-preview|(?:re|un)bind)(\([^)]*\)|\[[^\]]*\]|~[^~]*~|![^!]*!|@[^@]*@|\#[^\#]*\#|\$[^\$]*\$|%[^%]*%|\^[^\^]*\^|&[^&]*&|\*[^\*]*\*|;[^;]*;|/[^/]*/|\|[^\|]*\|)`)
|
||||||
|
splitRegexp = regexp.MustCompile("[,:]+")
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseKeymap(keymap map[tui.Event][]*action, str string) {
|
func parseKeymap(keymap map[tui.Event][]*action, str string) {
|
||||||
@@ -1063,11 +1141,6 @@ func parseKeymap(keymap map[tui.Event][]*action, str string) {
|
|||||||
}
|
}
|
||||||
if t == actUnbind || t == actRebind {
|
if t == actUnbind || t == actRebind {
|
||||||
parseKeyChords(actionArg, spec[0:offset]+" target required")
|
parseKeyChords(actionArg, spec[0:offset]+" target required")
|
||||||
} else if t == actChangePreviewWindow {
|
|
||||||
opts := previewOpts{}
|
|
||||||
for _, arg := range strings.Split(actionArg, "|") {
|
|
||||||
parsePreviewWindow(&opts, arg)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1147,9 +1220,17 @@ func parseSize(str string, maxPercent float64, label string) sizeSpec {
|
|||||||
return sizeSpec{val, percent}
|
return sizeSpec{val, percent}
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseHeight(str string) sizeSpec {
|
func parseHeight(str string) heightSpec {
|
||||||
|
heightSpec := heightSpec{}
|
||||||
|
if strings.HasPrefix(str, "~") {
|
||||||
|
heightSpec.auto = true
|
||||||
|
str = str[1:]
|
||||||
|
}
|
||||||
|
|
||||||
size := parseSize(str, 100, "height")
|
size := parseSize(str, 100, "height")
|
||||||
return size
|
heightSpec.size = size.size
|
||||||
|
heightSpec.percent = size.percent
|
||||||
|
return heightSpec
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseLayout(str string) layoutType {
|
func parseLayout(str string) layoutType {
|
||||||
@@ -1175,7 +1256,7 @@ func parseInfoStyle(str string) infoStyle {
|
|||||||
case "hidden":
|
case "hidden":
|
||||||
return infoHidden
|
return infoHidden
|
||||||
default:
|
default:
|
||||||
errorExit("invalid info style (expected: default / inline / hidden)")
|
errorExit("invalid info style (expected: default|inline|hidden)")
|
||||||
}
|
}
|
||||||
return infoDefault
|
return infoDefault
|
||||||
}
|
}
|
||||||
@@ -1222,15 +1303,19 @@ func parsePreviewWindow(opts *previewOpts, input string) {
|
|||||||
opts.border = tui.BorderRounded
|
opts.border = tui.BorderRounded
|
||||||
case "sharp", "border-sharp":
|
case "sharp", "border-sharp":
|
||||||
opts.border = tui.BorderSharp
|
opts.border = tui.BorderSharp
|
||||||
|
case "border-bold":
|
||||||
|
opts.border = tui.BorderBold
|
||||||
|
case "border-double":
|
||||||
|
opts.border = tui.BorderDouble
|
||||||
case "noborder", "border-none":
|
case "noborder", "border-none":
|
||||||
opts.border = tui.BorderNone
|
opts.border = tui.BorderNone
|
||||||
case "border-horizontal":
|
case "border-horizontal":
|
||||||
opts.border = tui.BorderHorizontal
|
opts.border = tui.BorderHorizontal
|
||||||
case "border-vertical":
|
case "border-vertical":
|
||||||
opts.border = tui.BorderVertical
|
opts.border = tui.BorderVertical
|
||||||
case "border-top":
|
case "border-up", "border-top":
|
||||||
opts.border = tui.BorderTop
|
opts.border = tui.BorderTop
|
||||||
case "border-bottom":
|
case "border-down", "border-bottom":
|
||||||
opts.border = tui.BorderBottom
|
opts.border = tui.BorderBottom
|
||||||
case "border-left":
|
case "border-left":
|
||||||
opts.border = tui.BorderLeft
|
opts.border = tui.BorderLeft
|
||||||
@@ -1343,6 +1428,8 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.Normalize = true
|
opts.Normalize = true
|
||||||
case "--algo":
|
case "--algo":
|
||||||
opts.FuzzyAlgo = parseAlgo(nextString(allArgs, &i, "algorithm required (v1|v2)"))
|
opts.FuzzyAlgo = parseAlgo(nextString(allArgs, &i, "algorithm required (v1|v2)"))
|
||||||
|
case "--scheme":
|
||||||
|
opts.Scheme = strings.ToLower(nextString(allArgs, &i, "scoring scheme required (default|path|history)"))
|
||||||
case "--expect":
|
case "--expect":
|
||||||
for k, v := range parseKeyChords(nextString(allArgs, &i, "key names required"), "key names required") {
|
for k, v := range parseKeyChords(nextString(allArgs, &i, "key names required"), "key names required") {
|
||||||
opts.Expect[k] = v
|
opts.Expect[k] = v
|
||||||
@@ -1442,6 +1529,12 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.InfoStyle = infoInline
|
opts.InfoStyle = infoInline
|
||||||
case "--no-inline-info":
|
case "--no-inline-info":
|
||||||
opts.InfoStyle = infoDefault
|
opts.InfoStyle = infoDefault
|
||||||
|
case "--separator":
|
||||||
|
separator := nextString(allArgs, &i, "separator character required")
|
||||||
|
opts.Separator = &separator
|
||||||
|
case "--no-separator":
|
||||||
|
nosep := ""
|
||||||
|
opts.Separator = &nosep
|
||||||
case "--jump-labels":
|
case "--jump-labels":
|
||||||
opts.JumpLabels = nextString(allArgs, &i, "label characters required")
|
opts.JumpLabels = nextString(allArgs, &i, "label characters required")
|
||||||
validateJumpLabels = true
|
validateJumpLabels = true
|
||||||
@@ -1510,11 +1603,11 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
parsePreviewWindow(&opts.Preview,
|
parsePreviewWindow(&opts.Preview,
|
||||||
nextString(allArgs, &i, "preview window layout required: [up|down|left|right][,SIZE[%]][,border-BORDER_OPT][,wrap][,cycle][,hidden][,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES][,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":
|
||||||
opts.MinHeight = nextInt(allArgs, &i, "height required: HEIGHT")
|
opts.MinHeight = nextInt(allArgs, &i, "height required: HEIGHT")
|
||||||
case "--no-height":
|
case "--no-height":
|
||||||
opts.Height = sizeSpec{}
|
opts.Height = heightSpec{}
|
||||||
case "--no-margin":
|
case "--no-margin":
|
||||||
opts.Margin = defaultMargin()
|
opts.Margin = defaultMargin()
|
||||||
case "--no-padding":
|
case "--no-padding":
|
||||||
@@ -1524,6 +1617,20 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
case "--border":
|
case "--border":
|
||||||
hasArg, arg := optionalNextString(allArgs, &i)
|
hasArg, arg := optionalNextString(allArgs, &i)
|
||||||
opts.BorderShape = parseBorder(arg, !hasArg)
|
opts.BorderShape = parseBorder(arg, !hasArg)
|
||||||
|
case "--no-border-label":
|
||||||
|
opts.BorderLabel.label = ""
|
||||||
|
case "--border-label":
|
||||||
|
opts.BorderLabel.label = nextString(allArgs, &i, "label required")
|
||||||
|
case "--border-label-pos":
|
||||||
|
pos := nextString(allArgs, &i, "label position required (positive or negative integer or 'center')")
|
||||||
|
parseLabelPosition(&opts.BorderLabel, pos)
|
||||||
|
case "--no-preview-label":
|
||||||
|
opts.PreviewLabel.label = ""
|
||||||
|
case "--preview-label":
|
||||||
|
opts.PreviewLabel.label = nextString(allArgs, &i, "preview label required")
|
||||||
|
case "--preview-label-pos":
|
||||||
|
pos := nextString(allArgs, &i, "preview label position required (positive or negative integer or 'center')")
|
||||||
|
parseLabelPosition(&opts.PreviewLabel, pos)
|
||||||
case "--no-unicode":
|
case "--no-unicode":
|
||||||
opts.Unicode = false
|
opts.Unicode = false
|
||||||
case "--unicode":
|
case "--unicode":
|
||||||
@@ -1549,6 +1656,8 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
default:
|
default:
|
||||||
if match, value := optString(arg, "--algo="); match {
|
if match, value := optString(arg, "--algo="); match {
|
||||||
opts.FuzzyAlgo = parseAlgo(value)
|
opts.FuzzyAlgo = parseAlgo(value)
|
||||||
|
} else if match, value := optString(arg, "--scheme="); match {
|
||||||
|
opts.Scheme = strings.ToLower(value)
|
||||||
} else if match, value := optString(arg, "-q", "--query="); match {
|
} else if match, value := optString(arg, "-q", "--query="); match {
|
||||||
opts.Query = value
|
opts.Query = value
|
||||||
} else if match, value := optString(arg, "-f", "--filter="); match {
|
} else if match, value := optString(arg, "-f", "--filter="); match {
|
||||||
@@ -1557,6 +1666,14 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.Delimiter = delimiterRegexp(value)
|
opts.Delimiter = delimiterRegexp(value)
|
||||||
} else if match, value := optString(arg, "--border="); match {
|
} else if match, value := optString(arg, "--border="); match {
|
||||||
opts.BorderShape = parseBorder(value, false)
|
opts.BorderShape = parseBorder(value, false)
|
||||||
|
} else if match, value := optString(arg, "--border-label="); match {
|
||||||
|
opts.BorderLabel.label = value
|
||||||
|
} else if match, value := optString(arg, "--border-label-pos="); match {
|
||||||
|
parseLabelPosition(&opts.BorderLabel, value)
|
||||||
|
} else if match, value := optString(arg, "--preview-label="); match {
|
||||||
|
opts.PreviewLabel.label = value
|
||||||
|
} else if match, value := optString(arg, "--preview-label-pos="); match {
|
||||||
|
parseLabelPosition(&opts.PreviewLabel, value)
|
||||||
} else if match, value := optString(arg, "--prompt="); match {
|
} else if match, value := optString(arg, "--prompt="); match {
|
||||||
opts.Prompt = value
|
opts.Prompt = value
|
||||||
} else if match, value := optString(arg, "--pointer="); match {
|
} else if match, value := optString(arg, "--pointer="); match {
|
||||||
@@ -1581,6 +1698,8 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.Layout = parseLayout(value)
|
opts.Layout = parseLayout(value)
|
||||||
} else if match, value := optString(arg, "--info="); match {
|
} else if match, value := optString(arg, "--info="); match {
|
||||||
opts.InfoStyle = parseInfoStyle(value)
|
opts.InfoStyle = parseInfoStyle(value)
|
||||||
|
} else if match, value := optString(arg, "--separator="); match {
|
||||||
|
opts.Separator = &value
|
||||||
} else if match, value := optString(arg, "--toggle-sort="); match {
|
} else if match, value := optString(arg, "--toggle-sort="); match {
|
||||||
parseToggleSort(opts.Keymap, value)
|
parseToggleSort(opts.Keymap, value)
|
||||||
} else if match, value := optString(arg, "--expect="); match {
|
} else if match, value := optString(arg, "--expect="); match {
|
||||||
@@ -1692,6 +1811,7 @@ func postProcessOptions(opts *Options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Extend the default key map
|
// Extend the default key map
|
||||||
|
previewEnabled := len(opts.Preview.command) > 0 || hasPreviewAction(opts)
|
||||||
keymap := defaultKeymap()
|
keymap := defaultKeymap()
|
||||||
for key, actions := range opts.Keymap {
|
for key, actions := range opts.Keymap {
|
||||||
var lastChangePreviewWindow *action
|
var lastChangePreviewWindow *action
|
||||||
@@ -1702,8 +1822,18 @@ func postProcessOptions(opts *Options) {
|
|||||||
opts.ToggleSort = true
|
opts.ToggleSort = true
|
||||||
case actChangePreviewWindow:
|
case actChangePreviewWindow:
|
||||||
lastChangePreviewWindow = act
|
lastChangePreviewWindow = act
|
||||||
|
if !previewEnabled {
|
||||||
|
// Doesn't matter
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
opts := previewOpts{}
|
||||||
|
for _, arg := range strings.Split(act.a, "|") {
|
||||||
|
// Make sure that each expression is valid
|
||||||
|
parsePreviewWindow(&opts, arg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Re-organize actions so that we only keep the last change-preview-window
|
// Re-organize actions so that we only keep the last change-preview-window
|
||||||
// and it comes first in the list.
|
// and it comes first in the list.
|
||||||
// * change-preview-window(up,+10)+preview(sleep 3; cat {})+change-preview-window(up,+20)
|
// * change-preview-window(up,+10)+preview(sleep 3; cat {})+change-preview-window(up,+20)
|
||||||
@@ -1721,6 +1851,19 @@ func postProcessOptions(opts *Options) {
|
|||||||
}
|
}
|
||||||
opts.Keymap = keymap
|
opts.Keymap = keymap
|
||||||
|
|
||||||
|
if opts.Height.auto {
|
||||||
|
for _, s := range []sizeSpec{opts.Margin[0], opts.Margin[2]} {
|
||||||
|
if s.percent {
|
||||||
|
errorExit("adaptive height is not compatible with top/bottom percent margin")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, s := range []sizeSpec{opts.Padding[0], opts.Padding[2]} {
|
||||||
|
if s.percent {
|
||||||
|
errorExit("adaptive height is not compatible with top/bottom percent padding")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If we're not using extended search mode, --nth option becomes irrelevant
|
// If we're not using extended search mode, --nth option becomes irrelevant
|
||||||
// if it contains the whole range
|
// if it contains the whole range
|
||||||
if !opts.Extended || len(opts.Nth) == 1 {
|
if !opts.Extended || len(opts.Nth) == 1 {
|
||||||
@@ -1750,6 +1893,10 @@ func postProcessOptions(opts *Options) {
|
|||||||
theme.Cursor = boldify(theme.Cursor)
|
theme.Cursor = boldify(theme.Cursor)
|
||||||
theme.Spinner = boldify(theme.Spinner)
|
theme.Spinner = boldify(theme.Spinner)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if opts.Scheme != "default" {
|
||||||
|
processScheme(opts)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func expectsArbitraryString(opt string) bool {
|
func expectsArbitraryString(opt string) bool {
|
||||||
|
|||||||
287
src/terminal.go
287
src/terminal.go
@@ -100,15 +100,30 @@ type itemLine struct {
|
|||||||
result Result
|
result Result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type fitpad struct {
|
||||||
|
fit int
|
||||||
|
pad int
|
||||||
|
}
|
||||||
|
|
||||||
var emptyLine = itemLine{}
|
var emptyLine = itemLine{}
|
||||||
|
|
||||||
|
type labelPrinter func(tui.Window, int)
|
||||||
|
|
||||||
// Terminal represents terminal input/output
|
// Terminal represents terminal input/output
|
||||||
type Terminal struct {
|
type Terminal struct {
|
||||||
initDelay time.Duration
|
initDelay time.Duration
|
||||||
infoStyle infoStyle
|
infoStyle infoStyle
|
||||||
|
separator labelPrinter
|
||||||
|
separatorLen int
|
||||||
spinner []string
|
spinner []string
|
||||||
prompt func()
|
prompt func()
|
||||||
promptLen int
|
promptLen int
|
||||||
|
borderLabel labelPrinter
|
||||||
|
borderLabelLen int
|
||||||
|
borderLabelOpts labelOpts
|
||||||
|
previewLabel labelPrinter
|
||||||
|
previewLabelLen int
|
||||||
|
previewLabelOpts labelOpts
|
||||||
pointer string
|
pointer string
|
||||||
pointerLen int
|
pointerLen int
|
||||||
pointerEmpty string
|
pointerEmpty string
|
||||||
@@ -183,7 +198,7 @@ type Terminal struct {
|
|||||||
prevLines []itemLine
|
prevLines []itemLine
|
||||||
suppress bool
|
suppress bool
|
||||||
sigstop bool
|
sigstop bool
|
||||||
startChan chan bool
|
startChan chan fitpad
|
||||||
killChan chan int
|
killChan chan int
|
||||||
slab *util.Slab
|
slab *util.Slab
|
||||||
theme *tui.ColorTheme
|
theme *tui.ColorTheme
|
||||||
@@ -439,6 +454,13 @@ func makeSpinner(unicode bool) []string {
|
|||||||
return []string{`-`, `\`, `|`, `/`, `-`, `\`, `|`, `/`}
|
return []string{`-`, `\`, `|`, `/`, `-`, `\`, `|`, `/`}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func evaluateHeight(opts *Options, termHeight int) int {
|
||||||
|
if opts.Height.percent {
|
||||||
|
return util.Max(int(opts.Height.size*float64(termHeight)/100.0), opts.MinHeight)
|
||||||
|
}
|
||||||
|
return int(opts.Height.size)
|
||||||
|
}
|
||||||
|
|
||||||
// NewTerminal returns new Terminal object
|
// NewTerminal returns new Terminal object
|
||||||
func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
||||||
input := trimQuery(opts.Query)
|
input := trimQuery(opts.Query)
|
||||||
@@ -465,7 +487,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
strongAttr = tui.AttrRegular
|
strongAttr = tui.AttrRegular
|
||||||
}
|
}
|
||||||
var renderer tui.Renderer
|
var renderer tui.Renderer
|
||||||
fullscreen := opts.Height.size == 0 || opts.Height.percent && opts.Height.size == 100
|
fullscreen := !opts.Height.auto && (opts.Height.size == 0 || opts.Height.percent && opts.Height.size == 100)
|
||||||
if fullscreen {
|
if fullscreen {
|
||||||
if tui.HasFullscreenRenderer() {
|
if tui.HasFullscreenRenderer() {
|
||||||
renderer = tui.NewFullscreenRenderer(opts.Theme, opts.Black, opts.Mouse)
|
renderer = tui.NewFullscreenRenderer(opts.Theme, opts.Black, opts.Mouse)
|
||||||
@@ -475,24 +497,16 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
maxHeightFunc := func(termHeight int) int {
|
maxHeightFunc := func(termHeight int) int {
|
||||||
var maxHeight int
|
// Minimum height required to render fzf excluding margin and padding
|
||||||
if opts.Height.percent {
|
|
||||||
maxHeight = util.Max(int(opts.Height.size*float64(termHeight)/100.0), opts.MinHeight)
|
|
||||||
} else {
|
|
||||||
maxHeight = int(opts.Height.size)
|
|
||||||
}
|
|
||||||
|
|
||||||
effectiveMinHeight := minHeight
|
effectiveMinHeight := minHeight
|
||||||
if previewBox != nil && (opts.Preview.position == posUp || opts.Preview.position == posDown) {
|
if previewBox != nil && opts.Preview.aboveOrBelow() {
|
||||||
effectiveMinHeight *= 2
|
effectiveMinHeight += 1 + borderLines(opts.Preview.border)
|
||||||
}
|
}
|
||||||
if opts.InfoStyle != infoDefault {
|
if opts.InfoStyle != infoDefault {
|
||||||
effectiveMinHeight--
|
effectiveMinHeight--
|
||||||
}
|
}
|
||||||
if opts.BorderShape != tui.BorderNone {
|
effectiveMinHeight += borderLines(opts.BorderShape)
|
||||||
effectiveMinHeight += 2
|
return util.Min(termHeight, util.Max(evaluateHeight(opts, termHeight), effectiveMinHeight))
|
||||||
}
|
|
||||||
return util.Min(termHeight, util.Max(maxHeight, effectiveMinHeight))
|
|
||||||
}
|
}
|
||||||
renderer = tui.NewLightRenderer(opts.Theme, opts.Black, opts.Mouse, opts.Tabstop, opts.ClearOnExit, false, maxHeightFunc)
|
renderer = tui.NewLightRenderer(opts.Theme, opts.Black, opts.Mouse, opts.Tabstop, opts.ClearOnExit, false, maxHeightFunc)
|
||||||
}
|
}
|
||||||
@@ -510,6 +524,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
t := Terminal{
|
t := Terminal{
|
||||||
initDelay: delay,
|
initDelay: delay,
|
||||||
infoStyle: opts.InfoStyle,
|
infoStyle: opts.InfoStyle,
|
||||||
|
separator: nil,
|
||||||
spinner: makeSpinner(opts.Unicode),
|
spinner: makeSpinner(opts.Unicode),
|
||||||
queryLen: [2]int{0, 0},
|
queryLen: [2]int{0, 0},
|
||||||
layout: opts.Layout,
|
layout: opts.Layout,
|
||||||
@@ -540,6 +555,10 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
padding: opts.Padding,
|
padding: opts.Padding,
|
||||||
unicode: opts.Unicode,
|
unicode: opts.Unicode,
|
||||||
borderShape: opts.BorderShape,
|
borderShape: opts.BorderShape,
|
||||||
|
borderLabel: nil,
|
||||||
|
borderLabelOpts: opts.BorderLabel,
|
||||||
|
previewLabel: nil,
|
||||||
|
previewLabelOpts: opts.PreviewLabel,
|
||||||
cleanExit: opts.ClearOnExit,
|
cleanExit: opts.ClearOnExit,
|
||||||
paused: opts.Phony,
|
paused: opts.Phony,
|
||||||
strong: strongAttr,
|
strong: strongAttr,
|
||||||
@@ -572,7 +591,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
sigstop: false,
|
sigstop: false,
|
||||||
slab: util.MakeSlab(slab16Size, slab32Size),
|
slab: util.MakeSlab(slab16Size, slab32Size),
|
||||||
theme: opts.Theme,
|
theme: opts.Theme,
|
||||||
startChan: make(chan bool, 1),
|
startChan: make(chan fitpad, 1),
|
||||||
killChan: make(chan int),
|
killChan: make(chan int),
|
||||||
tui: renderer,
|
tui: renderer,
|
||||||
initFunc: func() { renderer.Init() },
|
initFunc: func() { renderer.Init() },
|
||||||
@@ -583,10 +602,106 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
// Pre-calculated empty pointer and marker signs
|
// Pre-calculated empty pointer and marker signs
|
||||||
t.pointerEmpty = strings.Repeat(" ", t.pointerLen)
|
t.pointerEmpty = strings.Repeat(" ", t.pointerLen)
|
||||||
t.markerEmpty = strings.Repeat(" ", t.markerLen)
|
t.markerEmpty = strings.Repeat(" ", t.markerLen)
|
||||||
|
t.borderLabel, t.borderLabelLen = t.ansiLabelPrinter(opts.BorderLabel.label, &tui.ColBorderLabel, false)
|
||||||
|
t.previewLabel, t.previewLabelLen = t.ansiLabelPrinter(opts.PreviewLabel.label, &tui.ColBorderLabel, false)
|
||||||
|
if opts.Separator == nil || len(*opts.Separator) > 0 {
|
||||||
|
bar := "─"
|
||||||
|
if opts.Separator != nil {
|
||||||
|
bar = *opts.Separator
|
||||||
|
} else if !t.unicode {
|
||||||
|
bar = "-"
|
||||||
|
}
|
||||||
|
t.separator, t.separatorLen = t.ansiLabelPrinter(bar, &tui.ColSeparator, true)
|
||||||
|
}
|
||||||
|
|
||||||
return &t
|
return &t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func borderLines(shape tui.BorderShape) int {
|
||||||
|
switch shape {
|
||||||
|
case tui.BorderHorizontal, tui.BorderRounded, tui.BorderSharp, tui.BorderBold, tui.BorderDouble:
|
||||||
|
return 2
|
||||||
|
case tui.BorderTop, tui.BorderBottom:
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extra number of lines needed to display fzf
|
||||||
|
func (t *Terminal) extraLines() int {
|
||||||
|
extra := len(t.header0) + t.headerLines + 1
|
||||||
|
if !t.noInfoLine() {
|
||||||
|
extra++
|
||||||
|
}
|
||||||
|
return extra
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) MaxFitAndPad(opts *Options) (int, int) {
|
||||||
|
_, screenHeight, marginInt, paddingInt := t.adjustMarginAndPadding()
|
||||||
|
padHeight := marginInt[0] + marginInt[2] + paddingInt[0] + paddingInt[2]
|
||||||
|
fit := screenHeight - padHeight - t.extraLines()
|
||||||
|
return fit, padHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) ansiLabelPrinter(str string, color *tui.ColorPair, fill bool) (labelPrinter, int) {
|
||||||
|
// Nothing to do
|
||||||
|
if len(str) == 0 {
|
||||||
|
return nil, 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract ANSI color codes
|
||||||
|
text, colors, _ := extractColor(str, nil, nil)
|
||||||
|
runes := []rune(text)
|
||||||
|
|
||||||
|
// Simpler printer for strings without ANSI colors or tab characters
|
||||||
|
if colors == nil && strings.IndexRune(str, '\t') < 0 {
|
||||||
|
length := runewidth.StringWidth(str)
|
||||||
|
if length == 0 {
|
||||||
|
return nil, 0
|
||||||
|
}
|
||||||
|
printFn := func(window tui.Window, limit int) {
|
||||||
|
if length > limit {
|
||||||
|
trimmedRunes, _ := t.trimRight(runes, limit)
|
||||||
|
window.CPrint(*color, string(trimmedRunes))
|
||||||
|
} else if fill {
|
||||||
|
window.CPrint(*color, util.RepeatToFill(str, length, limit))
|
||||||
|
} else {
|
||||||
|
window.CPrint(*color, str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return printFn, len(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Printer that correctly handles ANSI color codes and tab characters
|
||||||
|
item := &Item{text: util.RunesToChars(runes), colors: colors}
|
||||||
|
length := t.displayWidth(runes)
|
||||||
|
if length == 0 {
|
||||||
|
return nil, 0
|
||||||
|
}
|
||||||
|
result := Result{item: item}
|
||||||
|
var offsets []colorOffset
|
||||||
|
printFn := func(window tui.Window, limit int) {
|
||||||
|
if offsets == nil {
|
||||||
|
// tui.Col* are not initialized until renderer.Init()
|
||||||
|
offsets = result.colorOffsets(nil, t.theme, *color, *color, false)
|
||||||
|
}
|
||||||
|
for limit > 0 {
|
||||||
|
if length > limit {
|
||||||
|
trimmedRunes, _ := t.trimRight(runes, limit)
|
||||||
|
t.printColoredString(window, trimmedRunes, offsets, *color)
|
||||||
|
break
|
||||||
|
} else if fill {
|
||||||
|
t.printColoredString(window, runes, offsets, *color)
|
||||||
|
limit -= length
|
||||||
|
} else {
|
||||||
|
t.printColoredString(window, runes, offsets, *color)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return printFn, length
|
||||||
|
}
|
||||||
|
|
||||||
func (t *Terminal) parsePrompt(prompt string) (func(), int) {
|
func (t *Terminal) parsePrompt(prompt string) (func(), int) {
|
||||||
var state *ansiState
|
var state *ansiState
|
||||||
trimmed, colors, _ := extractColor(prompt, state, nil)
|
trimmed, colors, _ := extractColor(prompt, state, nil)
|
||||||
@@ -725,22 +840,23 @@ func (t *Terminal) displayWidth(runes []rune) int {
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
minWidth = 4
|
minWidth = 4
|
||||||
minHeight = 4
|
minHeight = 3
|
||||||
)
|
)
|
||||||
|
|
||||||
func calculateSize(base int, size sizeSpec, occupied int, minSize int, pad int) int {
|
func calculateSize(base int, size sizeSpec, occupied int, minSize int, pad int) int {
|
||||||
max := base - occupied
|
max := base - occupied
|
||||||
|
if max < minSize {
|
||||||
|
max = minSize
|
||||||
|
}
|
||||||
if size.percent {
|
if size.percent {
|
||||||
return util.Constrain(int(float64(base)*0.01*size.size), minSize, max)
|
return util.Constrain(int(float64(base)*0.01*size.size), minSize, max)
|
||||||
}
|
}
|
||||||
return util.Constrain(int(size.size)+pad, minSize, max)
|
return util.Constrain(int(size.size)+pad, minSize, max)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) resizeWindows() {
|
func (t *Terminal) adjustMarginAndPadding() (int, int, [4]int, [4]int) {
|
||||||
screenWidth := t.tui.MaxX()
|
screenWidth := t.tui.MaxX()
|
||||||
screenHeight := t.tui.MaxY()
|
screenHeight := t.tui.MaxY()
|
||||||
t.prevLines = make([]itemLine, screenHeight)
|
|
||||||
|
|
||||||
marginInt := [4]int{} // TRBL
|
marginInt := [4]int{} // TRBL
|
||||||
paddingInt := [4]int{} // TRBL
|
paddingInt := [4]int{} // TRBL
|
||||||
sizeSpecToInt := func(index int, spec sizeSpec) int {
|
sizeSpecToInt := func(index int, spec sizeSpec) int {
|
||||||
@@ -782,14 +898,16 @@ func (t *Terminal) resizeWindows() {
|
|||||||
if idx == 3 {
|
if idx == 3 {
|
||||||
extraMargin[idx] += 2
|
extraMargin[idx] += 2
|
||||||
}
|
}
|
||||||
case tui.BorderRounded, tui.BorderSharp:
|
case tui.BorderRounded, tui.BorderSharp, tui.BorderBold, tui.BorderDouble:
|
||||||
extraMargin[idx] += 1 + idx%2
|
extraMargin[idx] += 1 + idx%2
|
||||||
}
|
}
|
||||||
marginInt[idx] = sizeSpecToInt(idx, sizeSpec) + extraMargin[idx]
|
marginInt[idx] = sizeSpecToInt(idx, sizeSpec) + extraMargin[idx]
|
||||||
}
|
}
|
||||||
|
|
||||||
adjust := func(idx1 int, idx2 int, max int, min int) {
|
adjust := func(idx1 int, idx2 int, max int, min int) {
|
||||||
if max >= min {
|
if min > max {
|
||||||
|
min = max
|
||||||
|
}
|
||||||
margin := marginInt[idx1] + marginInt[idx2] + paddingInt[idx1] + paddingInt[idx2]
|
margin := marginInt[idx1] + marginInt[idx2] + paddingInt[idx1] + paddingInt[idx2]
|
||||||
if max-margin < min {
|
if max-margin < min {
|
||||||
desired := max - min
|
desired := max - min
|
||||||
@@ -799,21 +917,36 @@ func (t *Terminal) resizeWindows() {
|
|||||||
marginInt[idx2] = util.Max(extraMargin[idx2], desired*marginInt[idx2]/margin)
|
marginInt[idx2] = util.Max(extraMargin[idx2], desired*marginInt[idx2]/margin)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
previewVisible := t.isPreviewEnabled() && t.previewOpts.size.size > 0
|
|
||||||
minAreaWidth := minWidth
|
minAreaWidth := minWidth
|
||||||
minAreaHeight := minHeight
|
minAreaHeight := minHeight
|
||||||
if previewVisible {
|
if t.noInfoLine() {
|
||||||
|
minAreaHeight -= 1
|
||||||
|
}
|
||||||
|
if t.isPreviewVisible() {
|
||||||
|
minPreviewHeight := 1 + borderLines(t.previewOpts.border)
|
||||||
|
minPreviewWidth := 5
|
||||||
switch t.previewOpts.position {
|
switch t.previewOpts.position {
|
||||||
case posUp, posDown:
|
case posUp, posDown:
|
||||||
minAreaHeight *= 2
|
minAreaHeight += minPreviewHeight
|
||||||
|
minAreaWidth = util.Max(minPreviewWidth, minAreaWidth)
|
||||||
case posLeft, posRight:
|
case posLeft, posRight:
|
||||||
minAreaWidth *= 2
|
minAreaWidth += minPreviewWidth
|
||||||
|
minAreaHeight = util.Max(minPreviewHeight, minAreaHeight)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
adjust(1, 3, screenWidth, minAreaWidth)
|
adjust(1, 3, screenWidth, minAreaWidth)
|
||||||
adjust(0, 2, screenHeight, minAreaHeight)
|
adjust(0, 2, screenHeight, minAreaHeight)
|
||||||
|
|
||||||
|
return screenWidth, screenHeight, marginInt, paddingInt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) resizeWindows() {
|
||||||
|
screenWidth, screenHeight, marginInt, paddingInt := t.adjustMarginAndPadding()
|
||||||
|
width := screenWidth - marginInt[1] - marginInt[3]
|
||||||
|
height := screenHeight - marginInt[0] - marginInt[2]
|
||||||
|
|
||||||
|
t.prevLines = make([]itemLine, screenHeight)
|
||||||
if t.border != nil {
|
if t.border != nil {
|
||||||
t.border.Close()
|
t.border.Close()
|
||||||
}
|
}
|
||||||
@@ -832,8 +965,6 @@ func (t *Terminal) resizeWindows() {
|
|||||||
// Reset preview version so that full redraw occurs
|
// Reset preview version so that full redraw occurs
|
||||||
t.previewed.version = 0
|
t.previewed.version = 0
|
||||||
|
|
||||||
width := screenWidth - marginInt[1] - marginInt[3]
|
|
||||||
height := screenHeight - marginInt[0] - marginInt[2]
|
|
||||||
switch t.borderShape {
|
switch t.borderShape {
|
||||||
case tui.BorderHorizontal:
|
case tui.BorderHorizontal:
|
||||||
t.border = t.tui.NewWindow(
|
t.border = t.tui.NewWindow(
|
||||||
@@ -859,22 +990,22 @@ func (t *Terminal) resizeWindows() {
|
|||||||
t.border = t.tui.NewWindow(
|
t.border = t.tui.NewWindow(
|
||||||
marginInt[0], marginInt[3], width+2, height,
|
marginInt[0], marginInt[3], width+2, height,
|
||||||
false, tui.MakeBorderStyle(tui.BorderRight, t.unicode))
|
false, tui.MakeBorderStyle(tui.BorderRight, t.unicode))
|
||||||
case tui.BorderRounded, tui.BorderSharp:
|
case tui.BorderRounded, tui.BorderSharp, tui.BorderBold, tui.BorderDouble:
|
||||||
t.border = t.tui.NewWindow(
|
t.border = t.tui.NewWindow(
|
||||||
marginInt[0]-1, marginInt[3]-2, width+4, height+2,
|
marginInt[0]-1, marginInt[3]-2, width+4, height+2,
|
||||||
false, tui.MakeBorderStyle(t.borderShape, t.unicode))
|
false, tui.MakeBorderStyle(t.borderShape, t.unicode))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add padding
|
// Add padding to margin
|
||||||
for idx, val := range paddingInt {
|
for idx, val := range paddingInt {
|
||||||
marginInt[idx] += val
|
marginInt[idx] += val
|
||||||
}
|
}
|
||||||
width = screenWidth - marginInt[1] - marginInt[3]
|
width -= paddingInt[1] + paddingInt[3]
|
||||||
height = screenHeight - marginInt[0] - marginInt[2]
|
height -= paddingInt[0] + paddingInt[2]
|
||||||
|
|
||||||
// Set up preview window
|
// Set up preview window
|
||||||
noBorder := tui.MakeBorderStyle(tui.BorderNone, t.unicode)
|
noBorder := tui.MakeBorderStyle(tui.BorderNone, t.unicode)
|
||||||
if previewVisible {
|
if t.isPreviewVisible() {
|
||||||
var resizePreviewWindows func(previewOpts previewOpts)
|
var resizePreviewWindows func(previewOpts previewOpts)
|
||||||
resizePreviewWindows = func(previewOpts previewOpts) {
|
resizePreviewWindows = func(previewOpts previewOpts) {
|
||||||
hasThreshold := previewOpts.threshold > 0 && previewOpts.alternative != nil
|
hasThreshold := previewOpts.threshold > 0 && previewOpts.alternative != nil
|
||||||
@@ -889,7 +1020,7 @@ func (t *Terminal) resizeWindows() {
|
|||||||
}
|
}
|
||||||
t.pborder = t.tui.NewWindow(y, x, w, h, true, previewBorder)
|
t.pborder = t.tui.NewWindow(y, x, w, h, true, previewBorder)
|
||||||
switch previewOpts.border {
|
switch previewOpts.border {
|
||||||
case tui.BorderSharp, tui.BorderRounded:
|
case tui.BorderSharp, tui.BorderRounded, tui.BorderBold, tui.BorderDouble:
|
||||||
pwidth -= 4
|
pwidth -= 4
|
||||||
pheight -= 2
|
pheight -= 2
|
||||||
x += 2
|
x += 2
|
||||||
@@ -969,6 +1100,34 @@ func (t *Terminal) resizeWindows() {
|
|||||||
width,
|
width,
|
||||||
height, false, noBorder)
|
height, false, noBorder)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Print border label
|
||||||
|
printLabel := func(window tui.Window, render labelPrinter, opts labelOpts, length int, borderShape tui.BorderShape) {
|
||||||
|
if window == nil || render == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch borderShape {
|
||||||
|
case tui.BorderHorizontal, tui.BorderTop, tui.BorderBottom, tui.BorderRounded, tui.BorderSharp, tui.BorderBold, tui.BorderDouble:
|
||||||
|
var col int
|
||||||
|
if opts.column == 0 {
|
||||||
|
col = util.Max(0, (window.Width()-length)/2)
|
||||||
|
} else if opts.column < 0 {
|
||||||
|
col = util.Max(0, window.Width()+opts.column+1-length)
|
||||||
|
} else {
|
||||||
|
col = util.Min(opts.column-1, window.Width()-length)
|
||||||
|
}
|
||||||
|
row := 0
|
||||||
|
if borderShape == tui.BorderBottom || opts.bottom {
|
||||||
|
row = window.Height() - 1
|
||||||
|
}
|
||||||
|
window.Move(row, col)
|
||||||
|
render(window, window.Width())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
printLabel(t.border, t.borderLabel, t.borderLabelOpts, t.borderLabelLen, t.borderShape)
|
||||||
|
printLabel(t.pborder, t.previewLabel, t.previewLabelOpts, t.previewLabelLen, t.previewOpts.border)
|
||||||
|
|
||||||
for i := 0; i < t.window.Height(); i++ {
|
for i := 0; i < t.window.Height(); i++ {
|
||||||
t.window.MoveAndClear(i, 0)
|
t.window.MoveAndClear(i, 0)
|
||||||
}
|
}
|
||||||
@@ -1108,8 +1267,15 @@ func (t *Terminal) printInfo() {
|
|||||||
if t.failed != nil && t.count == 0 {
|
if t.failed != nil && t.count == 0 {
|
||||||
output = fmt.Sprintf("[Command failed: %s]", *t.failed)
|
output = fmt.Sprintf("[Command failed: %s]", *t.failed)
|
||||||
}
|
}
|
||||||
output = t.trimMessage(output, t.window.Width()-pos)
|
maxWidth := t.window.Width() - pos
|
||||||
|
output = t.trimMessage(output, maxWidth)
|
||||||
t.window.CPrint(tui.ColInfo, output)
|
t.window.CPrint(tui.ColInfo, output)
|
||||||
|
|
||||||
|
fillLength := maxWidth - len(output) - 2
|
||||||
|
if t.separatorLen > 0 && fillLength > 0 {
|
||||||
|
t.window.CPrint(tui.ColSeparator, " ")
|
||||||
|
t.separator(t.window, fillLength)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) printHeader() {
|
func (t *Terminal) printHeader() {
|
||||||
@@ -1348,6 +1514,11 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
|
|||||||
displayWidth = t.displayWidthWithLimit(text, 0, displayWidth)
|
displayWidth = t.displayWidthWithLimit(text, 0, displayWidth)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
t.printColoredString(t.window, text, offsets, colBase)
|
||||||
|
return displayWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) printColoredString(window tui.Window, text []rune, offsets []colorOffset, colBase tui.ColorPair) {
|
||||||
var index int32
|
var index int32
|
||||||
var substr string
|
var substr string
|
||||||
var prefixWidth int
|
var prefixWidth int
|
||||||
@@ -1357,11 +1528,11 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
|
|||||||
e := util.Constrain32(offset.offset[1], index, maxOffset)
|
e := util.Constrain32(offset.offset[1], index, maxOffset)
|
||||||
|
|
||||||
substr, prefixWidth = t.processTabs(text[index:b], prefixWidth)
|
substr, prefixWidth = t.processTabs(text[index:b], prefixWidth)
|
||||||
t.window.CPrint(colBase, substr)
|
window.CPrint(colBase, substr)
|
||||||
|
|
||||||
if b < e {
|
if b < e {
|
||||||
substr, prefixWidth = t.processTabs(text[b:e], prefixWidth)
|
substr, prefixWidth = t.processTabs(text[b:e], prefixWidth)
|
||||||
t.window.CPrint(offset.color, substr)
|
window.CPrint(offset.color, substr)
|
||||||
}
|
}
|
||||||
|
|
||||||
index = e
|
index = e
|
||||||
@@ -1371,9 +1542,8 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
|
|||||||
}
|
}
|
||||||
if index < maxOffset {
|
if index < maxOffset {
|
||||||
substr, _ = t.processTabs(text[index:], prefixWidth)
|
substr, _ = t.processTabs(text[index:], prefixWidth)
|
||||||
t.window.CPrint(colBase, substr)
|
window.CPrint(colBase, substr)
|
||||||
}
|
}
|
||||||
return displayWidth
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) renderPreviewSpinner() {
|
func (t *Terminal) renderPreviewSpinner() {
|
||||||
@@ -1863,6 +2033,10 @@ func (t *Terminal) isPreviewEnabled() bool {
|
|||||||
return t.hasPreviewer() && t.previewer.enabled
|
return t.hasPreviewer() && t.previewer.enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Terminal) isPreviewVisible() bool {
|
||||||
|
return t.isPreviewEnabled() && t.previewOpts.size.size > 0
|
||||||
|
}
|
||||||
|
|
||||||
func (t *Terminal) hasPreviewWindow() bool {
|
func (t *Terminal) hasPreviewWindow() bool {
|
||||||
return t.pwindow != nil && t.isPreviewEnabled()
|
return t.pwindow != nil && t.isPreviewEnabled()
|
||||||
}
|
}
|
||||||
@@ -1962,7 +2136,28 @@ func (t *Terminal) cancelPreview() {
|
|||||||
// Loop is called to start Terminal I/O
|
// Loop is called to start Terminal I/O
|
||||||
func (t *Terminal) Loop() {
|
func (t *Terminal) Loop() {
|
||||||
// prof := profile.Start(profile.ProfilePath("/tmp/"))
|
// prof := profile.Start(profile.ProfilePath("/tmp/"))
|
||||||
<-t.startChan
|
fitpad := <-t.startChan
|
||||||
|
fit := fitpad.fit
|
||||||
|
if fit >= 0 {
|
||||||
|
pad := fitpad.pad
|
||||||
|
t.tui.Resize(func(termHeight int) int {
|
||||||
|
contentHeight := fit + t.extraLines()
|
||||||
|
if t.hasPreviewer() {
|
||||||
|
if t.previewOpts.aboveOrBelow() {
|
||||||
|
if t.previewOpts.size.percent {
|
||||||
|
newContentHeight := int(float64(contentHeight) * 100. / (100. - t.previewOpts.size.size))
|
||||||
|
contentHeight = util.Max(contentHeight+1+borderLines(t.previewOpts.border), newContentHeight)
|
||||||
|
} else {
|
||||||
|
contentHeight += int(t.previewOpts.size.size) + borderLines(t.previewOpts.border)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Minimum height if preview window can appear
|
||||||
|
contentHeight = util.Max(contentHeight, 1+borderLines(t.previewOpts.border))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return util.Min(termHeight, contentHeight+pad)
|
||||||
|
})
|
||||||
|
}
|
||||||
{ // Late initialization
|
{ // Late initialization
|
||||||
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)
|
||||||
@@ -2291,13 +2486,21 @@ func (t *Terminal) Loop() {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
looping := true
|
looping := true
|
||||||
|
_, startEvent := t.keymap[tui.Start.AsEvent()]
|
||||||
|
|
||||||
for looping {
|
for looping {
|
||||||
var newCommand *string
|
var newCommand *string
|
||||||
changed := false
|
changed := false
|
||||||
beof := false
|
beof := false
|
||||||
queryChanged := false
|
queryChanged := false
|
||||||
|
|
||||||
event := t.tui.GetChar()
|
var event tui.Event
|
||||||
|
if startEvent {
|
||||||
|
event = tui.Start.AsEvent()
|
||||||
|
startEvent = false
|
||||||
|
} else {
|
||||||
|
event = t.tui.GetChar()
|
||||||
|
}
|
||||||
|
|
||||||
t.mutex.Lock()
|
t.mutex.Lock()
|
||||||
previousInput := t.input
|
previousInput := t.input
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ func (a Attr) Merge(b Attr) Attr {
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
AttrUndefined = Attr(0)
|
AttrUndefined = Attr(0)
|
||||||
AttrRegular = Attr(1 << 7)
|
AttrRegular = Attr(1 << 8)
|
||||||
AttrClear = Attr(1 << 8)
|
AttrClear = Attr(1 << 9)
|
||||||
|
|
||||||
Bold = Attr(1)
|
Bold = Attr(1)
|
||||||
Dim = Attr(1 << 1)
|
Dim = Attr(1 << 1)
|
||||||
@@ -24,9 +24,11 @@ const (
|
|||||||
Blink = Attr(1 << 4)
|
Blink = Attr(1 << 4)
|
||||||
Blink2 = Attr(1 << 5)
|
Blink2 = Attr(1 << 5)
|
||||||
Reverse = Attr(1 << 6)
|
Reverse = Attr(1 << 6)
|
||||||
|
StrikeThrough = Attr(1 << 7)
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *FullscreenRenderer) Init() {}
|
func (r *FullscreenRenderer) Init() {}
|
||||||
|
func (r *FullscreenRenderer) Resize(maxHeightFunc func(int) int) {}
|
||||||
func (r *FullscreenRenderer) Pause(bool) {}
|
func (r *FullscreenRenderer) Pause(bool) {}
|
||||||
func (r *FullscreenRenderer) Resume(bool, bool) {}
|
func (r *FullscreenRenderer) Resume(bool, bool) {}
|
||||||
func (r *FullscreenRenderer) Clear() {}
|
func (r *FullscreenRenderer) Clear() {}
|
||||||
|
|||||||
@@ -189,6 +189,10 @@ func (r *LightRenderer) Init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) Resize(maxHeightFunc func(int) int) {
|
||||||
|
r.maxHeightFunc = maxHeightFunc
|
||||||
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) makeSpace() {
|
func (r *LightRenderer) makeSpace() {
|
||||||
r.stderr("\n")
|
r.stderr("\n")
|
||||||
r.csi("G")
|
r.csi("G")
|
||||||
@@ -676,6 +680,9 @@ func (r *LightRenderer) MaxX() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) MaxY() int {
|
func (r *LightRenderer) MaxY() int {
|
||||||
|
if r.height == 0 {
|
||||||
|
r.updateTerminalSize()
|
||||||
|
}
|
||||||
return r.height
|
return r.height
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -705,7 +712,7 @@ func (r *LightRenderer) NewWindow(top int, left int, width int, height int, prev
|
|||||||
|
|
||||||
func (w *LightWindow) drawBorder() {
|
func (w *LightWindow) drawBorder() {
|
||||||
switch w.border.shape {
|
switch w.border.shape {
|
||||||
case BorderRounded, BorderSharp:
|
case BorderRounded, BorderSharp, BorderBold, BorderDouble:
|
||||||
w.drawBorderAround()
|
w.drawBorderAround()
|
||||||
case BorderHorizontal:
|
case BorderHorizontal:
|
||||||
w.drawBorderHorizontal(true, true)
|
w.drawBorderHorizontal(true, true)
|
||||||
@@ -856,6 +863,9 @@ func attrCodes(attr Attr) []string {
|
|||||||
if (attr & Reverse) > 0 {
|
if (attr & Reverse) > 0 {
|
||||||
codes = append(codes, "7")
|
codes = append(codes, "7")
|
||||||
}
|
}
|
||||||
|
if (attr & StrikeThrough) > 0 {
|
||||||
|
codes = append(codes, "9")
|
||||||
|
}
|
||||||
return codes
|
return codes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
|
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
"github.com/gdamore/tcell"
|
"github.com/gdamore/tcell/v2"
|
||||||
"github.com/gdamore/tcell/encoding"
|
"github.com/gdamore/tcell/v2/encoding"
|
||||||
|
|
||||||
"github.com/mattn/go-runewidth"
|
"github.com/mattn/go-runewidth"
|
||||||
"github.com/rivo/uniseg"
|
"github.com/rivo/uniseg"
|
||||||
@@ -19,12 +19,24 @@ func HasFullscreenRenderer() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p ColorPair) style() tcell.Style {
|
func asTcellColor(color Color) tcell.Color {
|
||||||
style := tcell.StyleDefault
|
if color == colDefault {
|
||||||
return style.Foreground(tcell.Color(p.Fg())).Background(tcell.Color(p.Bg()))
|
return tcell.ColorDefault
|
||||||
}
|
}
|
||||||
|
|
||||||
type Attr tcell.Style
|
value := uint64(tcell.ColorValid) + uint64(color)
|
||||||
|
if color.is24() {
|
||||||
|
value = value | uint64(tcell.ColorIsRGB)
|
||||||
|
}
|
||||||
|
return tcell.Color(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ColorPair) style() tcell.Style {
|
||||||
|
style := tcell.StyleDefault
|
||||||
|
return style.Foreground(asTcellColor(p.Fg())).Background(asTcellColor(p.Bg()))
|
||||||
|
}
|
||||||
|
|
||||||
|
type Attr int32
|
||||||
|
|
||||||
type TcellWindow struct {
|
type TcellWindow struct {
|
||||||
color bool
|
color bool
|
||||||
@@ -63,8 +75,6 @@ func (w *TcellWindow) Refresh() {
|
|||||||
}
|
}
|
||||||
w.lastX = 0
|
w.lastX = 0
|
||||||
w.lastY = 0
|
w.lastY = 0
|
||||||
|
|
||||||
w.drawBorder()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) FinishFill() {
|
func (w *TcellWindow) FinishFill() {
|
||||||
@@ -77,6 +87,7 @@ const (
|
|||||||
Blink = Attr(tcell.AttrBlink)
|
Blink = Attr(tcell.AttrBlink)
|
||||||
Reverse = Attr(tcell.AttrReverse)
|
Reverse = Attr(tcell.AttrReverse)
|
||||||
Underline = Attr(tcell.AttrUnderline)
|
Underline = Attr(tcell.AttrUnderline)
|
||||||
|
StrikeThrough = Attr(tcell.AttrStrikeThrough)
|
||||||
Italic = Attr(tcell.AttrItalic)
|
Italic = Attr(tcell.AttrItalic)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -86,6 +97,8 @@ const (
|
|||||||
AttrClear = Attr(1 << 8)
|
AttrClear = Attr(1 << 8)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (r *FullscreenRenderer) Resize(maxHeightFunc func(int) int) {}
|
||||||
|
|
||||||
func (r *FullscreenRenderer) defaultTheme() *ColorTheme {
|
func (r *FullscreenRenderer) defaultTheme() *ColorTheme {
|
||||||
if _screen.Colors() >= 256 {
|
if _screen.Colors() >= 256 {
|
||||||
return Dark256
|
return Dark256
|
||||||
@@ -502,7 +515,7 @@ func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int,
|
|||||||
if preview {
|
if preview {
|
||||||
normal = ColPreview
|
normal = ColPreview
|
||||||
}
|
}
|
||||||
return &TcellWindow{
|
w := &TcellWindow{
|
||||||
color: r.theme.Colored,
|
color: r.theme.Colored,
|
||||||
preview: preview,
|
preview: preview,
|
||||||
top: top,
|
top: top,
|
||||||
@@ -511,6 +524,8 @@ func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int,
|
|||||||
height: height,
|
height: height,
|
||||||
normal: normal,
|
normal: normal,
|
||||||
borderStyle: borderStyle}
|
borderStyle: borderStyle}
|
||||||
|
w.drawBorder()
|
||||||
|
return w
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) Close() {
|
func (w *TcellWindow) Close() {
|
||||||
@@ -561,6 +576,7 @@ func (w *TcellWindow) printString(text string, pair ColorPair) {
|
|||||||
style = style.
|
style = style.
|
||||||
Reverse(a&Attr(tcell.AttrReverse) != 0).
|
Reverse(a&Attr(tcell.AttrReverse) != 0).
|
||||||
Underline(a&Attr(tcell.AttrUnderline) != 0).
|
Underline(a&Attr(tcell.AttrUnderline) != 0).
|
||||||
|
StrikeThrough(a&Attr(tcell.AttrStrikeThrough) != 0).
|
||||||
Italic(a&Attr(tcell.AttrItalic) != 0).
|
Italic(a&Attr(tcell.AttrItalic) != 0).
|
||||||
Blink(a&Attr(tcell.AttrBlink) != 0).
|
Blink(a&Attr(tcell.AttrBlink) != 0).
|
||||||
Dim(a&Attr(tcell.AttrDim) != 0)
|
Dim(a&Attr(tcell.AttrDim) != 0)
|
||||||
@@ -612,6 +628,7 @@ func (w *TcellWindow) fillString(text string, pair ColorPair) FillReturn {
|
|||||||
Dim(a&Attr(tcell.AttrDim) != 0).
|
Dim(a&Attr(tcell.AttrDim) != 0).
|
||||||
Reverse(a&Attr(tcell.AttrReverse) != 0).
|
Reverse(a&Attr(tcell.AttrReverse) != 0).
|
||||||
Underline(a&Attr(tcell.AttrUnderline) != 0).
|
Underline(a&Attr(tcell.AttrUnderline) != 0).
|
||||||
|
StrikeThrough(a&Attr(tcell.AttrStrikeThrough) != 0).
|
||||||
Italic(a&Attr(tcell.AttrItalic) != 0)
|
Italic(a&Attr(tcell.AttrItalic) != 0)
|
||||||
|
|
||||||
gr := uniseg.NewGraphemes(text)
|
gr := uniseg.NewGraphemes(text)
|
||||||
@@ -688,31 +705,31 @@ func (w *TcellWindow) drawBorder() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch shape {
|
switch shape {
|
||||||
case BorderRounded, BorderSharp, BorderHorizontal, BorderTop:
|
case BorderRounded, BorderSharp, BorderBold, BorderDouble, BorderHorizontal, BorderTop:
|
||||||
for x := left; x < right; x++ {
|
for x := left; x < right; x++ {
|
||||||
_screen.SetContent(x, top, w.borderStyle.horizontal, nil, style)
|
_screen.SetContent(x, top, w.borderStyle.horizontal, nil, style)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch shape {
|
switch shape {
|
||||||
case BorderRounded, BorderSharp, BorderHorizontal, BorderBottom:
|
case BorderRounded, BorderSharp, BorderBold, BorderDouble, BorderHorizontal, BorderBottom:
|
||||||
for x := left; x < right; x++ {
|
for x := left; x < right; x++ {
|
||||||
_screen.SetContent(x, bot-1, w.borderStyle.horizontal, nil, style)
|
_screen.SetContent(x, bot-1, w.borderStyle.horizontal, nil, style)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch shape {
|
switch shape {
|
||||||
case BorderRounded, BorderSharp, BorderVertical, BorderLeft:
|
case BorderRounded, BorderSharp, BorderBold, BorderDouble, BorderVertical, BorderLeft:
|
||||||
for y := top; y < bot; y++ {
|
for y := top; y < bot; y++ {
|
||||||
_screen.SetContent(left, y, w.borderStyle.vertical, nil, style)
|
_screen.SetContent(left, y, w.borderStyle.vertical, nil, style)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch shape {
|
switch shape {
|
||||||
case BorderRounded, BorderSharp, BorderVertical, BorderRight:
|
case BorderRounded, BorderSharp, BorderBold, BorderDouble, BorderVertical, BorderRight:
|
||||||
for y := top; y < bot; y++ {
|
for y := top; y < bot; y++ {
|
||||||
_screen.SetContent(right-1, y, w.borderStyle.vertical, nil, style)
|
_screen.SetContent(right-1, y, w.borderStyle.vertical, nil, style)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch shape {
|
switch shape {
|
||||||
case BorderRounded, BorderSharp:
|
case BorderRounded, BorderSharp, BorderBold, BorderDouble:
|
||||||
_screen.SetContent(left, top, w.borderStyle.topLeft, nil, style)
|
_screen.SetContent(left, top, w.borderStyle.topLeft, nil, style)
|
||||||
_screen.SetContent(right-1, top, w.borderStyle.topRight, nil, style)
|
_screen.SetContent(right-1, top, w.borderStyle.topRight, nil, style)
|
||||||
_screen.SetContent(left, bot-1, w.borderStyle.bottomLeft, nil, style)
|
_screen.SetContent(left, bot-1, w.borderStyle.bottomLeft, nil, style)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ package tui
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/gdamore/tcell"
|
"github.com/gdamore/tcell/v2"
|
||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ const (
|
|||||||
|
|
||||||
Change
|
Change
|
||||||
BackwardEOF
|
BackwardEOF
|
||||||
|
Start
|
||||||
|
|
||||||
AltBS
|
AltBS
|
||||||
|
|
||||||
@@ -266,7 +267,9 @@ type ColorTheme struct {
|
|||||||
Cursor ColorAttr
|
Cursor ColorAttr
|
||||||
Selected ColorAttr
|
Selected ColorAttr
|
||||||
Header ColorAttr
|
Header ColorAttr
|
||||||
|
Separator ColorAttr
|
||||||
Border ColorAttr
|
Border ColorAttr
|
||||||
|
BorderLabel ColorAttr
|
||||||
}
|
}
|
||||||
|
|
||||||
type Event struct {
|
type Event struct {
|
||||||
@@ -291,6 +294,8 @@ const (
|
|||||||
BorderNone BorderShape = iota
|
BorderNone BorderShape = iota
|
||||||
BorderRounded
|
BorderRounded
|
||||||
BorderSharp
|
BorderSharp
|
||||||
|
BorderBold
|
||||||
|
BorderDouble
|
||||||
BorderHorizontal
|
BorderHorizontal
|
||||||
BorderVertical
|
BorderVertical
|
||||||
BorderTop
|
BorderTop
|
||||||
@@ -312,18 +317,19 @@ type BorderStyle struct {
|
|||||||
type BorderCharacter int
|
type BorderCharacter int
|
||||||
|
|
||||||
func MakeBorderStyle(shape BorderShape, unicode bool) BorderStyle {
|
func MakeBorderStyle(shape BorderShape, unicode bool) BorderStyle {
|
||||||
if unicode {
|
if !unicode {
|
||||||
if shape == BorderRounded {
|
|
||||||
return BorderStyle{
|
return BorderStyle{
|
||||||
shape: shape,
|
shape: shape,
|
||||||
horizontal: '─',
|
horizontal: '-',
|
||||||
vertical: '│',
|
vertical: '|',
|
||||||
topLeft: '╭',
|
topLeft: '+',
|
||||||
topRight: '╮',
|
topRight: '+',
|
||||||
bottomLeft: '╰',
|
bottomLeft: '+',
|
||||||
bottomRight: '╯',
|
bottomRight: '+',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
switch shape {
|
||||||
|
case BorderSharp:
|
||||||
return BorderStyle{
|
return BorderStyle{
|
||||||
shape: shape,
|
shape: shape,
|
||||||
horizontal: '─',
|
horizontal: '─',
|
||||||
@@ -333,15 +339,35 @@ func MakeBorderStyle(shape BorderShape, unicode bool) BorderStyle {
|
|||||||
bottomLeft: '└',
|
bottomLeft: '└',
|
||||||
bottomRight: '┘',
|
bottomRight: '┘',
|
||||||
}
|
}
|
||||||
|
case BorderBold:
|
||||||
|
return BorderStyle{
|
||||||
|
shape: shape,
|
||||||
|
horizontal: '━',
|
||||||
|
vertical: '┃',
|
||||||
|
topLeft: '┏',
|
||||||
|
topRight: '┓',
|
||||||
|
bottomLeft: '┗',
|
||||||
|
bottomRight: '┛',
|
||||||
|
}
|
||||||
|
case BorderDouble:
|
||||||
|
return BorderStyle{
|
||||||
|
shape: shape,
|
||||||
|
horizontal: '═',
|
||||||
|
vertical: '║',
|
||||||
|
topLeft: '╔',
|
||||||
|
topRight: '╗',
|
||||||
|
bottomLeft: '╚',
|
||||||
|
bottomRight: '╝',
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return BorderStyle{
|
return BorderStyle{
|
||||||
shape: shape,
|
shape: shape,
|
||||||
horizontal: '-',
|
horizontal: '─',
|
||||||
vertical: '|',
|
vertical: '│',
|
||||||
topLeft: '+',
|
topLeft: '╭',
|
||||||
topRight: '+',
|
topRight: '╮',
|
||||||
bottomLeft: '+',
|
bottomLeft: '╰',
|
||||||
bottomRight: '+',
|
bottomRight: '╯',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -358,6 +384,7 @@ func MakeTransparentBorder() BorderStyle {
|
|||||||
|
|
||||||
type Renderer interface {
|
type Renderer interface {
|
||||||
Init()
|
Init()
|
||||||
|
Resize(maxHeightFunc func(int) int)
|
||||||
Pause(clear bool)
|
Pause(clear bool)
|
||||||
Resume(clear bool, sigcont bool)
|
Resume(clear bool, sigcont bool)
|
||||||
Clear()
|
Clear()
|
||||||
@@ -436,9 +463,11 @@ var (
|
|||||||
ColSpinner ColorPair
|
ColSpinner ColorPair
|
||||||
ColInfo ColorPair
|
ColInfo ColorPair
|
||||||
ColHeader ColorPair
|
ColHeader ColorPair
|
||||||
|
ColSeparator ColorPair
|
||||||
ColBorder ColorPair
|
ColBorder ColorPair
|
||||||
ColPreview ColorPair
|
ColPreview ColorPair
|
||||||
ColPreviewBorder ColorPair
|
ColPreviewBorder ColorPair
|
||||||
|
ColBorderLabel ColorPair
|
||||||
)
|
)
|
||||||
|
|
||||||
func EmptyTheme() *ColorTheme {
|
func EmptyTheme() *ColorTheme {
|
||||||
@@ -461,7 +490,10 @@ func EmptyTheme() *ColorTheme {
|
|||||||
Cursor: ColorAttr{colUndefined, AttrUndefined},
|
Cursor: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Selected: ColorAttr{colUndefined, AttrUndefined},
|
Selected: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Header: ColorAttr{colUndefined, AttrUndefined},
|
Header: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Border: ColorAttr{colUndefined, AttrUndefined}}
|
Separator: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
Border: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
BorderLabel: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NoColorTheme() *ColorTheme {
|
func NoColorTheme() *ColorTheme {
|
||||||
@@ -484,7 +516,10 @@ func NoColorTheme() *ColorTheme {
|
|||||||
Cursor: ColorAttr{colDefault, AttrRegular},
|
Cursor: ColorAttr{colDefault, AttrRegular},
|
||||||
Selected: ColorAttr{colDefault, AttrRegular},
|
Selected: ColorAttr{colDefault, AttrRegular},
|
||||||
Header: ColorAttr{colDefault, AttrRegular},
|
Header: ColorAttr{colDefault, AttrRegular},
|
||||||
Border: ColorAttr{colDefault, AttrRegular}}
|
Separator: ColorAttr{colDefault, AttrRegular},
|
||||||
|
Border: ColorAttr{colDefault, AttrRegular},
|
||||||
|
BorderLabel: ColorAttr{colDefault, AttrRegular},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func errorExit(message string) {
|
func errorExit(message string) {
|
||||||
@@ -512,7 +547,10 @@ func init() {
|
|||||||
Cursor: ColorAttr{colRed, AttrUndefined},
|
Cursor: ColorAttr{colRed, AttrUndefined},
|
||||||
Selected: ColorAttr{colMagenta, AttrUndefined},
|
Selected: ColorAttr{colMagenta, AttrUndefined},
|
||||||
Header: ColorAttr{colCyan, AttrUndefined},
|
Header: ColorAttr{colCyan, AttrUndefined},
|
||||||
Border: ColorAttr{colBlack, AttrUndefined}}
|
Separator: ColorAttr{colBlack, AttrUndefined},
|
||||||
|
Border: ColorAttr{colBlack, AttrUndefined},
|
||||||
|
BorderLabel: ColorAttr{colWhite, AttrUndefined},
|
||||||
|
}
|
||||||
Dark256 = &ColorTheme{
|
Dark256 = &ColorTheme{
|
||||||
Colored: true,
|
Colored: true,
|
||||||
Input: ColorAttr{colDefault, AttrUndefined},
|
Input: ColorAttr{colDefault, AttrUndefined},
|
||||||
@@ -532,7 +570,10 @@ func init() {
|
|||||||
Cursor: ColorAttr{161, AttrUndefined},
|
Cursor: ColorAttr{161, AttrUndefined},
|
||||||
Selected: ColorAttr{168, AttrUndefined},
|
Selected: ColorAttr{168, AttrUndefined},
|
||||||
Header: ColorAttr{109, AttrUndefined},
|
Header: ColorAttr{109, AttrUndefined},
|
||||||
Border: ColorAttr{59, AttrUndefined}}
|
Separator: ColorAttr{59, AttrUndefined},
|
||||||
|
Border: ColorAttr{59, AttrUndefined},
|
||||||
|
BorderLabel: ColorAttr{145, AttrUndefined},
|
||||||
|
}
|
||||||
Light256 = &ColorTheme{
|
Light256 = &ColorTheme{
|
||||||
Colored: true,
|
Colored: true,
|
||||||
Input: ColorAttr{colDefault, AttrUndefined},
|
Input: ColorAttr{colDefault, AttrUndefined},
|
||||||
@@ -552,7 +593,10 @@ func init() {
|
|||||||
Cursor: ColorAttr{161, AttrUndefined},
|
Cursor: ColorAttr{161, AttrUndefined},
|
||||||
Selected: ColorAttr{168, AttrUndefined},
|
Selected: ColorAttr{168, AttrUndefined},
|
||||||
Header: ColorAttr{31, AttrUndefined},
|
Header: ColorAttr{31, AttrUndefined},
|
||||||
Border: ColorAttr{145, AttrUndefined}}
|
Separator: ColorAttr{145, AttrUndefined},
|
||||||
|
Border: ColorAttr{145, AttrUndefined},
|
||||||
|
BorderLabel: ColorAttr{59, AttrUndefined},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
|
func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
|
||||||
@@ -587,7 +631,9 @@ func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
|
|||||||
theme.Cursor = o(baseTheme.Cursor, theme.Cursor)
|
theme.Cursor = o(baseTheme.Cursor, theme.Cursor)
|
||||||
theme.Selected = o(baseTheme.Selected, theme.Selected)
|
theme.Selected = o(baseTheme.Selected, theme.Selected)
|
||||||
theme.Header = o(baseTheme.Header, theme.Header)
|
theme.Header = o(baseTheme.Header, theme.Header)
|
||||||
|
theme.Separator = o(baseTheme.Separator, theme.Separator)
|
||||||
theme.Border = o(baseTheme.Border, theme.Border)
|
theme.Border = o(baseTheme.Border, theme.Border)
|
||||||
|
theme.BorderLabel = o(baseTheme.BorderLabel, theme.BorderLabel)
|
||||||
|
|
||||||
initPalette(theme)
|
initPalette(theme)
|
||||||
}
|
}
|
||||||
@@ -619,7 +665,9 @@ func initPalette(theme *ColorTheme) {
|
|||||||
ColSpinner = pair(theme.Spinner, theme.Bg)
|
ColSpinner = pair(theme.Spinner, theme.Bg)
|
||||||
ColInfo = pair(theme.Info, theme.Bg)
|
ColInfo = pair(theme.Info, theme.Bg)
|
||||||
ColHeader = pair(theme.Header, theme.Bg)
|
ColHeader = pair(theme.Header, theme.Bg)
|
||||||
|
ColSeparator = pair(theme.Separator, theme.Bg)
|
||||||
ColBorder = pair(theme.Border, theme.Bg)
|
ColBorder = pair(theme.Border, theme.Bg)
|
||||||
|
ColBorderLabel = pair(theme.BorderLabel, theme.Bg)
|
||||||
ColPreview = pair(theme.PreviewFg, theme.PreviewBg)
|
ColPreview = pair(theme.PreviewFg, theme.PreviewBg)
|
||||||
ColPreviewBorder = pair(theme.Border, theme.PreviewBg)
|
ColPreviewBorder = pair(theme.Border, theme.PreviewBg)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -153,3 +153,23 @@ func Once(nextResponse bool) func() bool {
|
|||||||
return prevState
|
return prevState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RepeatToFill repeats the given string to fill the given width
|
||||||
|
func RepeatToFill(str string, length int, limit int) string {
|
||||||
|
times := limit / length
|
||||||
|
rest := limit % length
|
||||||
|
output := strings.Repeat(str, times)
|
||||||
|
if rest > 0 {
|
||||||
|
for _, r := range str {
|
||||||
|
rest -= runewidth.RuneWidth(r)
|
||||||
|
if rest < 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
output += string(r)
|
||||||
|
if rest == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|||||||
168
test/test_go.rb
168
test/test_go.rb
@@ -192,6 +192,13 @@ class TestBase < Minitest::Test
|
|||||||
tmux.prepare
|
tmux.prepare
|
||||||
end
|
end
|
||||||
|
|
||||||
|
alias assert_equal_org assert_equal
|
||||||
|
def assert_equal(expected, actual)
|
||||||
|
# Ignore info separator
|
||||||
|
actual = actual&.sub(/\s*─+$/, '') if actual.is_a?(String) && actual&.match?(%r{\d+/\d+})
|
||||||
|
assert_equal_org(expected, actual)
|
||||||
|
end
|
||||||
|
|
||||||
def fzf(*opts)
|
def fzf(*opts)
|
||||||
fzf!(*opts) + " > #{tempname}.tmp; mv #{tempname}.tmp #{tempname}"
|
fzf!(*opts) + " > #{tempname}.tmp; mv #{tempname}.tmp #{tempname}"
|
||||||
end
|
end
|
||||||
@@ -255,7 +262,7 @@ class TestGoFZF < TestBase
|
|||||||
|
|
||||||
def test_fzf_default_command_failure
|
def test_fzf_default_command_failure
|
||||||
tmux.send_keys fzf.sub('FZF_DEFAULT_COMMAND=', 'FZF_DEFAULT_COMMAND=false'), :Enter
|
tmux.send_keys fzf.sub('FZF_DEFAULT_COMMAND=', 'FZF_DEFAULT_COMMAND=false'), :Enter
|
||||||
tmux.until { |lines| assert_equal ' [Command failed: false]', lines[-2] }
|
tmux.until { |lines| assert_includes lines[-2], ' [Command failed: false] ─' }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -447,7 +454,7 @@ class TestGoFZF < TestBase
|
|||||||
def test_scroll
|
def test_scroll
|
||||||
[true, false].each do |rev|
|
[true, false].each do |rev|
|
||||||
tmux.send_keys "seq 1 100 | #{fzf(rev && :reverse)}", :Enter
|
tmux.send_keys "seq 1 100 | #{fzf(rev && :reverse)}", :Enter
|
||||||
tmux.until { |lines| assert_includes lines, ' 100/100' }
|
tmux.until { |lines| assert_equal ' 100/100', lines[rev ? 1 : -2] }
|
||||||
tmux.send_keys(*Array.new(110) { rev ? :Down : :Up })
|
tmux.send_keys(*Array.new(110) { rev ? :Down : :Up })
|
||||||
tmux.until { |lines| assert_includes lines, '> 100' }
|
tmux.until { |lines| assert_includes lines, '> 100' }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
@@ -2245,6 +2252,158 @@ class TestGoFZF < TestBase
|
|||||||
tmux.until { |lines| assert_equal 1, lines.match_count }
|
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||||
tmux.until { |lines| assert_match(/^> SNIPSNIP.*SNIPSNIP$/, lines[-3]) }
|
tmux.until { |lines| assert_match(/^> SNIPSNIP.*SNIPSNIP$/, lines[-3]) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def assert_block(expected, lines)
|
||||||
|
cols = expected.lines.map(&:chomp).map(&:length).max
|
||||||
|
actual = lines.reverse.take(expected.lines.length).reverse.map { _1[0, cols].rstrip + "\n" }.join
|
||||||
|
assert_equal_org expected, actual
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_height_range_fit
|
||||||
|
tmux.send_keys 'seq 3 | fzf --height ~100% --info=inline --border', :Enter
|
||||||
|
expected = <<~OUTPUT
|
||||||
|
╭──────────
|
||||||
|
│ 3
|
||||||
|
│ 2
|
||||||
|
│ > 1
|
||||||
|
│ > < 3/3
|
||||||
|
╰──────────
|
||||||
|
OUTPUT
|
||||||
|
tmux.until { assert_block(expected, _1) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_height_range_fit_preview_above
|
||||||
|
tmux.send_keys 'seq 3 | fzf --height ~100% --info=inline --border --preview "seq {}" --preview-window up,60%', :Enter
|
||||||
|
expected = <<~OUTPUT
|
||||||
|
╭──────────
|
||||||
|
│ ╭────────
|
||||||
|
│ │ 1
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ │
|
||||||
|
│ ╰────────
|
||||||
|
│ 3
|
||||||
|
│ 2
|
||||||
|
│ > 1
|
||||||
|
│ > < 3/3
|
||||||
|
╰──────────
|
||||||
|
OUTPUT
|
||||||
|
tmux.until { assert_block(expected, _1) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_height_range_fit_preview_above_alternative
|
||||||
|
tmux.send_keys 'seq 3 | fzf --height ~100% --border=sharp --preview "seq {}" --preview-window up,40%,border-bottom --padding 1 --exit-0 --header hello --header-lines=2', :Enter
|
||||||
|
expected = <<~OUTPUT
|
||||||
|
┌─────────
|
||||||
|
│
|
||||||
|
│ 1
|
||||||
|
│ 2
|
||||||
|
│ 3
|
||||||
|
│ ───────
|
||||||
|
│ > 3
|
||||||
|
│ 2
|
||||||
|
│ 1
|
||||||
|
│ hello
|
||||||
|
│ 1/1 ─
|
||||||
|
│ >
|
||||||
|
│
|
||||||
|
└─────────
|
||||||
|
OUTPUT
|
||||||
|
tmux.until { assert_block(expected, _1) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_height_range_fit_preview_left
|
||||||
|
tmux.send_keys "seq 3 | fzf --height ~100% --border=vertical --preview 'seq {}' --preview-window left,5,border-right --padding 1 --exit-0 --header $'hello\\nworld' --header-lines=2", :Enter
|
||||||
|
expected = <<~OUTPUT
|
||||||
|
│
|
||||||
|
│ 1 │> 3
|
||||||
|
│ 2 │ 2
|
||||||
|
│ 3 │ 1
|
||||||
|
│ │ hello
|
||||||
|
│ │ world
|
||||||
|
│ │ 1/1 ─
|
||||||
|
│ │>
|
||||||
|
│
|
||||||
|
OUTPUT
|
||||||
|
tmux.until { assert_block(expected, _1) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_height_range_overflow
|
||||||
|
tmux.send_keys 'seq 100 | fzf --height ~5 --info=inline --border', :Enter
|
||||||
|
expected = <<~OUTPUT
|
||||||
|
╭──────────────
|
||||||
|
│ 2
|
||||||
|
│ > 1
|
||||||
|
│ > < 100/100
|
||||||
|
╰──────────────
|
||||||
|
OUTPUT
|
||||||
|
tmux.until { assert_block(expected, _1) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_start_event
|
||||||
|
tmux.send_keys 'seq 100 | fzf --multi --sync --preview-window border-none --bind "start:select-all+last+preview(echo welcome)"', :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_match(/>100.*welcome/, lines[0])
|
||||||
|
assert_includes(lines[-2], '100/100 (100)')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_labels_center
|
||||||
|
tmux.send_keys ': | fzf --border --border-label foobar --preview : --preview-label barfoo', :Enter
|
||||||
|
tmux.until do
|
||||||
|
assert_includes(_1[0], '─foobar─')
|
||||||
|
assert_includes(_1[1], '─barfoo─')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_labels_left
|
||||||
|
tmux.send_keys ': | fzf --border --border-label foobar --border-label-pos 2 --preview : --preview-label barfoo --preview-label-pos 2', :Enter
|
||||||
|
tmux.until do
|
||||||
|
assert_includes(_1[0], '╭foobar─')
|
||||||
|
assert_includes(_1[1], '╭barfoo─')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_labels_right
|
||||||
|
tmux.send_keys ': | fzf --border --border-label foobar --border-label-pos -2 --preview : --preview-label barfoo --preview-label-pos -2', :Enter
|
||||||
|
tmux.until do
|
||||||
|
assert_includes(_1[0], '─foobar╮')
|
||||||
|
assert_includes(_1[1], '─barfoo╮')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_labels_bottom
|
||||||
|
tmux.send_keys ': | fzf --border --border-label foobar --border-label-pos 2:bottom --preview : --preview-label barfoo --preview-label-pos -2:bottom', :Enter
|
||||||
|
tmux.until do
|
||||||
|
assert_includes(_1[-1], '╰foobar─')
|
||||||
|
assert_includes(_1[-2], '─barfoo╯')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_info_separator_unicode
|
||||||
|
tmux.send_keys 'seq 100 | fzf -q55', :Enter
|
||||||
|
tmux.until { assert_includes(_1[-2], ' 1/100 ─') }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_info_separator_no_unicode
|
||||||
|
tmux.send_keys 'seq 100 | fzf -q55 --no-unicode', :Enter
|
||||||
|
tmux.until { assert_includes(_1[-2], ' 1/100 -') }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_info_separator_repeat
|
||||||
|
tmux.send_keys 'seq 100 | fzf -q55 --separator _-', :Enter
|
||||||
|
tmux.until { assert_includes(_1[-2], ' 1/100 _-_-') }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_info_separator_ansi_colors_and_tabs
|
||||||
|
tmux.send_keys "seq 100 | fzf -q55 --tabstop 4 --separator $'\\x1b[33ma\\tb'", :Enter
|
||||||
|
tmux.until { assert_includes(_1[-2], ' 1/100 a ba ba') }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_info_no_separator
|
||||||
|
tmux.send_keys 'seq 100 | fzf -q55 --no-separator', :Enter
|
||||||
|
tmux.until { assert(_1[-2] == ' 1/100') }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
module TestShell
|
module TestShell
|
||||||
@@ -2671,6 +2830,7 @@ class TestFish < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
__END__
|
__END__
|
||||||
|
set -u
|
||||||
PS1= PROMPT_COMMAND= HISTFILE= HISTSIZE=100
|
PS1= PROMPT_COMMAND= HISTFILE= HISTSIZE=100
|
||||||
unset <%= UNSETS.join(' ') %>
|
unset <%= UNSETS.join(' ') %>
|
||||||
unset $(env | sed -n /^_fzf_orig/s/=.*//p)
|
unset $(env | sed -n /^_fzf_orig/s/=.*//p)
|
||||||
@@ -2714,8 +2874,8 @@ _fzf_complete_g_post() {
|
|||||||
awk '{print "g" $0 $0}'
|
awk '{print "g" $0 $0}'
|
||||||
}
|
}
|
||||||
|
|
||||||
[ -n "$BASH" ] && complete -F _fzf_complete_f -o default -o bashdefault f
|
[ -n "${BASH-}" ] && complete -F _fzf_complete_f -o default -o bashdefault f
|
||||||
[ -n "$BASH" ] && complete -F _fzf_complete_g -o default -o bashdefault g
|
[ -n "${BASH-}" ] && complete -F _fzf_complete_g -o default -o bashdefault g
|
||||||
|
|
||||||
_comprun() {
|
_comprun() {
|
||||||
local command=$1
|
local command=$1
|
||||||
|
|||||||
Reference in New Issue
Block a user