mirror of
https://github.com/junegunn/fzf.git
synced 2025-08-24 00:43:49 -07:00
Compare commits
206 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
65db7352b7 | ||
|
a4db8bd7b5 | ||
|
f1c1b02d77 | ||
|
6580f32b43 | ||
|
b028cbd8bd | ||
|
a1a5418318 | ||
|
5a32634b74 | ||
|
c1875af70b | ||
|
0a10d14e19 | ||
|
ed8ceec66f | ||
|
03760011d7 | ||
|
0d5aebb806 | ||
|
1313510890 | ||
|
b712f2bb6a | ||
|
938c15ec63 | ||
|
3e7f032ec2 | ||
|
b42f5bfb19 | ||
|
717562b264 | ||
|
9d6637c1b3 | ||
|
56fef7c8df | ||
|
ba0935c71f | ||
|
d83eb2800a | ||
|
6f943112a9 | ||
|
f422893b8e | ||
|
22b498489c | ||
|
5460517bd2 | ||
|
9a6e557e52 | ||
|
4fdc07927f | ||
|
9030b67e4f | ||
|
43eafdf4b7 | ||
|
dfb88edb5e | ||
|
bd3e65df4d | ||
|
d7b13f3408 | ||
|
14ef8e8051 | ||
|
cc1d9f124e | ||
|
93c0299606 | ||
|
55e3c73221 | ||
|
6783417504 | ||
|
fa3f706e71 | ||
|
9c2f6cae88 | ||
|
a30181e240 | ||
|
b4ccf64e62 | ||
|
88d768bf6b | ||
|
6444cc7905 | ||
|
328af1f397 | ||
|
5ae60e2e80 | ||
|
0e0b868342 | ||
|
a5beb08ed7 | ||
|
45fc7b903d | ||
|
4f2c274942 | ||
|
93415493b4 | ||
|
8e4d338de9 | ||
|
8a71e091a8 | ||
|
120cd7f25a | ||
|
fb3bf6c984 | ||
|
d57e1f8baa | ||
|
15ca9ad8eb | ||
|
c2e1861747 | ||
|
543d41f3dd | ||
|
e5cfc988ec | ||
|
ee3916be17 | ||
|
fd513f8af8 | ||
|
9a2b7f559c | ||
|
b8d2b0df7e | ||
|
fe3a9c603e | ||
|
97030d4cb1 | ||
|
b2c3e567da | ||
|
ca5e633399 | ||
|
e60a9a628b | ||
|
0167691941 | ||
|
3b0f976380 | ||
|
7bd298b536 | ||
|
0476a65fca | ||
|
2cb2af115a | ||
|
789226ff6d | ||
|
805efc5bf1 | ||
|
cdcab26766 | ||
|
ec3acb1932 | ||
|
d30e37434e | ||
|
20d5b2e20e | ||
|
6c6be4ab1a | ||
|
d004eb1f7c | ||
|
3148b0f3e8 | ||
|
3fc0bd26a5 | ||
|
6c9025ff17 | ||
|
289997e373 | ||
|
db44cbdff0 | ||
|
da9179335c | ||
|
cdf641fa3e | ||
|
66dbee10f5 | ||
|
19e9b620ba | ||
|
e4e4700aff | ||
|
bb55045596 | ||
|
d7e51cdeb5 | ||
|
7f4964b366 | ||
|
a6957aba11 | ||
|
b5f94f961d | ||
|
e182d3db7a | ||
|
3e6e0528a6 | ||
|
ac508a1ce4 | ||
|
d7fc1e09b1 | ||
|
3b0c86e401 | ||
|
61d10d8ffa | ||
|
7d9548919e | ||
|
bee80a730f | ||
|
ac3e24c99c | ||
|
e7e852bdb3 | ||
|
2b7f168571 | ||
|
5b3da1d878 | ||
|
99f1bc0177 | ||
|
ed76f076dd | ||
|
4d357d1063 | ||
|
961ae1541c | ||
|
add1aec685 | ||
|
03d6ba7496 | ||
|
71e4d5cc51 | ||
|
215ab48222 | ||
|
0c64c68781 | ||
|
3ec035c68b | ||
|
20c7dcfbca | ||
|
c1b8780b9c | ||
|
64c61603e9 | ||
|
57c08d925f | ||
|
51623a5f6a | ||
|
ca3f6181d7 | ||
|
9c94f9c3d0 | ||
|
4a85843bcf | ||
|
d4d9b99879 | ||
|
6816b7d95b | ||
|
acdf265d7a | ||
|
19495eb9bb | ||
|
bacc8609ee | ||
|
99163f5afa | ||
|
0607227730 | ||
|
d938fdc496 | ||
|
dcb4c3d84a | ||
|
82ebcd9209 | ||
|
ff1687744d | ||
|
782c870fb2 | ||
|
71fad63829 | ||
|
d65c6101a8 | ||
|
3c40b1bd51 | ||
|
90a8800bb5 | ||
|
97f1dae2d1 | ||
|
e54ec05709 | ||
|
a24eb99679 | ||
|
ad113d00b7 | ||
|
7bd5884d12 | ||
|
c3505858a6 | ||
|
e76aa37fd4 | ||
|
1a32220ca9 | ||
|
4161403a1d | ||
|
53bcdc4294 | ||
|
30a8ef28cd | ||
|
855f90727a | ||
|
2191a44e36 | ||
|
952276dc2d | ||
|
2286edb329 | ||
|
a0f28583e7 | ||
|
8af0af3400 | ||
|
769e5cbb2d | ||
|
fc69308057 | ||
|
c6d620c99e | ||
|
f510a4def6 | ||
|
4ae3069c6f | ||
|
c0f27751d3 | ||
|
efbcd5a683 | ||
|
6a67712944 | ||
|
e8a690928d | ||
|
0eee95af57 | ||
|
a09c6e991a | ||
|
d06c5ab990 | ||
|
e0924d27b8 | ||
|
2775b771f2 | ||
|
cf7a3c6a0e | ||
|
230cc6acc3 | ||
|
626a23a585 | ||
|
74f196eebb | ||
|
cf2242aea3 | ||
|
8cb59e6fca | ||
|
5cce17e80a | ||
|
ee5302fb2d | ||
|
387c6ef664 | ||
|
581734c369 | ||
|
d90a969c00 | ||
|
2c8a96bb27 | ||
|
5da168db30 | ||
|
e215e2daf3 | ||
|
e28f5aa45b | ||
|
a2d0e8f233 | ||
|
303d04106a | ||
|
c423c496a1 | ||
|
4e85f72f0e | ||
|
dd0737aac0 | ||
|
f90985845d | ||
|
af4917dbb6 | ||
|
d9404fcce4 | ||
|
5c01fee5a9 | ||
|
9b27d68a37 | ||
|
b99d884e57 | ||
|
587df594b8 | ||
|
b896e0d314 | ||
|
559fb7ee45 | ||
|
62545cd983 | ||
|
c50812518e | ||
|
4cc5609d8b |
2
.github/workflows/linux.yml
vendored
2
.github/workflows/linux.yml
vendored
@@ -36,7 +36,7 @@ jobs:
|
|||||||
run: sudo apt-get install --yes zsh fish tmux
|
run: sudo apt-get install --yes zsh fish tmux
|
||||||
|
|
||||||
- name: Install Ruby gems
|
- name: Install Ruby gems
|
||||||
run: sudo gem install --no-document minitest:5.17.0 rubocop:1.43.0 rubocop-minitest:0.25.1 rubocop-performance:1.15.2
|
run: sudo gem install --no-document minitest:5.25.1 rubocop:1.65.0 rubocop-minitest:0.35.1 rubocop-performance:1.21.1
|
||||||
|
|
||||||
- name: Rubocop
|
- name: Rubocop
|
||||||
run: rubocop --require rubocop-minitest --require rubocop-performance
|
run: rubocop --require rubocop-minitest --require rubocop-performance
|
||||||
|
2
.github/workflows/typos.yml
vendored
2
.github/workflows/typos.yml
vendored
@@ -7,4 +7,4 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: crate-ci/typos@v1.23.1
|
- uses: crate-ci/typos@v1.28.4
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
---
|
---
|
||||||
|
version: 2
|
||||||
project_name: fzf
|
project_name: fzf
|
||||||
|
|
||||||
before:
|
before:
|
||||||
@@ -6,60 +7,9 @@ before:
|
|||||||
- go mod download
|
- go mod download
|
||||||
|
|
||||||
builds:
|
builds:
|
||||||
- id: fzf-macos
|
|
||||||
binary: fzf
|
|
||||||
goos:
|
|
||||||
- darwin
|
|
||||||
goarch:
|
|
||||||
- amd64
|
|
||||||
flags:
|
|
||||||
- -trimpath
|
|
||||||
ldflags:
|
|
||||||
- "-s -w -X main.version={{ .Version }} -X main.revision={{ .ShortCommit }}"
|
|
||||||
hooks:
|
|
||||||
post: |
|
|
||||||
sh -c '
|
|
||||||
cat > /tmp/fzf-gon-amd64.hcl << EOF
|
|
||||||
source = ["./dist/fzf-macos_darwin_amd64_v1/fzf"]
|
|
||||||
bundle_id = "junegunn.fzf"
|
|
||||||
sign {
|
|
||||||
application_identity = "Developer ID Application: Junegunn Choi (Y254DRW44Z)"
|
|
||||||
}
|
|
||||||
zip {
|
|
||||||
output_path = "./dist/fzf-{{ .Version }}-darwin_amd64.zip"
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
gon /tmp/fzf-gon-amd64.hcl
|
|
||||||
'
|
|
||||||
|
|
||||||
- id: fzf-macos-arm
|
|
||||||
binary: fzf
|
|
||||||
goos:
|
|
||||||
- darwin
|
|
||||||
goarch:
|
|
||||||
- arm64
|
|
||||||
flags:
|
|
||||||
- -trimpath
|
|
||||||
ldflags:
|
|
||||||
- "-s -w -X main.version={{ .Version }} -X main.revision={{ .ShortCommit }}"
|
|
||||||
hooks:
|
|
||||||
post: |
|
|
||||||
sh -c '
|
|
||||||
cat > /tmp/fzf-gon-arm64.hcl << EOF
|
|
||||||
source = ["./dist/fzf-macos-arm_darwin_arm64/fzf"]
|
|
||||||
bundle_id = "junegunn.fzf"
|
|
||||||
sign {
|
|
||||||
application_identity = "Developer ID Application: Junegunn Choi (Y254DRW44Z)"
|
|
||||||
}
|
|
||||||
zip {
|
|
||||||
output_path = "./dist/fzf-{{ .Version }}-darwin_arm64.zip"
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
gon /tmp/fzf-gon-arm64.hcl
|
|
||||||
'
|
|
||||||
|
|
||||||
- id: fzf
|
- id: fzf
|
||||||
goos:
|
goos:
|
||||||
|
- darwin
|
||||||
- linux
|
- linux
|
||||||
- windows
|
- windows
|
||||||
- freebsd
|
- freebsd
|
||||||
@@ -89,6 +39,42 @@ builds:
|
|||||||
- goos: openbsd
|
- goos: openbsd
|
||||||
goarch: arm64
|
goarch: arm64
|
||||||
|
|
||||||
|
# .goreleaser.yaml
|
||||||
|
notarize:
|
||||||
|
macos:
|
||||||
|
- # Whether this configuration is enabled or not.
|
||||||
|
#
|
||||||
|
# Default: false.
|
||||||
|
# Templates: allowed.
|
||||||
|
enabled: "{{ not .IsSnapshot }}"
|
||||||
|
|
||||||
|
# Before notarizing, we need to sign the binary.
|
||||||
|
# This blocks defines the configuration for doing so.
|
||||||
|
sign:
|
||||||
|
# The .p12 certificate file path or its base64'd contents.
|
||||||
|
certificate: "{{.Env.MACOS_SIGN_P12}}"
|
||||||
|
|
||||||
|
# The password to be used to open the certificate.
|
||||||
|
password: "{{.Env.MACOS_SIGN_PASSWORD}}"
|
||||||
|
|
||||||
|
# Then, we notarize the binaries.
|
||||||
|
notarize:
|
||||||
|
# The issuer ID.
|
||||||
|
# Its the UUID you see when creating the App Store Connect key.
|
||||||
|
issuer_id: "{{.Env.MACOS_NOTARY_ISSUER_ID}}"
|
||||||
|
|
||||||
|
# Key ID.
|
||||||
|
# You can see it in the list of App Store Connect Keys.
|
||||||
|
# It will also be in the ApiKey filename.
|
||||||
|
key_id: "{{.Env.MACOS_NOTARY_KEY_ID}}"
|
||||||
|
|
||||||
|
# The .p8 key file path or its base64'd contents.
|
||||||
|
key: "{{.Env.MACOS_NOTARY_KEY}}"
|
||||||
|
|
||||||
|
# Whether to wait for the notarization to finish.
|
||||||
|
# Not recommended, as it could take a really long time.
|
||||||
|
wait: true
|
||||||
|
|
||||||
archives:
|
archives:
|
||||||
- name_template: "{{ .ProjectName }}-{{ .Version }}-{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}"
|
- name_template: "{{ .ProjectName }}-{{ .Version }}-{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}"
|
||||||
builds:
|
builds:
|
||||||
@@ -100,18 +86,12 @@ archives:
|
|||||||
files:
|
files:
|
||||||
- non-existent*
|
- non-existent*
|
||||||
|
|
||||||
checksum:
|
|
||||||
extra_files:
|
|
||||||
- glob: ./dist/fzf-*darwin*.zip
|
|
||||||
|
|
||||||
release:
|
release:
|
||||||
github:
|
github:
|
||||||
owner: junegunn
|
owner: junegunn
|
||||||
name: fzf
|
name: fzf
|
||||||
prerelease: auto
|
prerelease: auto
|
||||||
name_template: '{{ .Version }}'
|
name_template: '{{ .Version }}'
|
||||||
extra_files:
|
|
||||||
- glob: ./dist/fzf-*darwin*.zip
|
|
||||||
|
|
||||||
snapshot:
|
snapshot:
|
||||||
name_template: "{{ .Version }}-devel"
|
name_template: "{{ .Version }}-devel"
|
||||||
|
@@ -6,7 +6,7 @@ Lint/ShadowingOuterLocalVariable:
|
|||||||
Enabled: false
|
Enabled: false
|
||||||
Style/MethodCallWithArgsParentheses:
|
Style/MethodCallWithArgsParentheses:
|
||||||
Enabled: true
|
Enabled: true
|
||||||
IgnoredMethods:
|
AllowedMethods:
|
||||||
- assert
|
- assert
|
||||||
- exit
|
- exit
|
||||||
- paste
|
- paste
|
||||||
@@ -15,7 +15,7 @@ Style/MethodCallWithArgsParentheses:
|
|||||||
- refute
|
- refute
|
||||||
- require
|
- require
|
||||||
- send_keys
|
- send_keys
|
||||||
IgnoredPatterns:
|
AllowedPatterns:
|
||||||
- ^assert_
|
- ^assert_
|
||||||
- ^refute_
|
- ^refute_
|
||||||
Style/NumericPredicate:
|
Style/NumericPredicate:
|
||||||
|
@@ -128,7 +128,7 @@ fzf --height 70% --tmux 70%
|
|||||||
You can also specify the position, width, and height of the popup window in
|
You can also specify the position, width, and height of the popup window in
|
||||||
the following format:
|
the following format:
|
||||||
|
|
||||||
* `[center|top|bottom|left|right][,SIZE[%]][,SIZE[%]]`
|
* `[center|top|bottom|left|right][,SIZE[%]][,SIZE[%][,border-native]]`
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# 100% width and 60% height
|
# 100% width and 60% height
|
||||||
|
213
CHANGELOG.md
213
CHANGELOG.md
@@ -1,6 +1,219 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
0.58.0
|
||||||
|
------
|
||||||
|
_Release highlights: https://junegunn.github.io/fzf/releases/0.58.0/_
|
||||||
|
|
||||||
|
This version introduces three new border types, `--list-border`, `--input-border`, and `--header-border`, offering much greater flexibility for customizing the user interface.
|
||||||
|
|
||||||
|
<img src="https://raw.githubusercontent.com/junegunn/i/master/fzf-4-borders.png" />
|
||||||
|
|
||||||
|
Also, fzf now offers "style presets" for quick customization, which can be activated using the `--style` option.
|
||||||
|
|
||||||
|
| Preset | Screenshot |
|
||||||
|
| :--- | :--- |
|
||||||
|
| `default` | <img src="https://raw.githubusercontent.com/junegunn/i/master/fzf-style-default.png"/> |
|
||||||
|
| `full` | <img src="https://raw.githubusercontent.com/junegunn/i/master/fzf-style-full.png"/> |
|
||||||
|
| `minimal` | <img src="https://raw.githubusercontent.com/junegunn/i/master/fzf-style-minimal.png"/> |
|
||||||
|
|
||||||
|
- Style presets (#4160)
|
||||||
|
- `--style=full[:BORDER_STYLE]`
|
||||||
|
- `--style=default`
|
||||||
|
- `--style=minimal`
|
||||||
|
- Border and label for the list section (#4148)
|
||||||
|
- Options
|
||||||
|
- `--list-border[=STYLE]`
|
||||||
|
- `--list-label=LABEL`
|
||||||
|
- `--list-label-pos=COL[:bottom]`
|
||||||
|
- Colors
|
||||||
|
- `list-fg`
|
||||||
|
- `list-bg`
|
||||||
|
- `list-border`
|
||||||
|
- `list-label`
|
||||||
|
- Actions
|
||||||
|
- `change-list-label`
|
||||||
|
- `transform-list-label`
|
||||||
|
- Border and label for the input section (prompt line and info line) (#4154)
|
||||||
|
- Options
|
||||||
|
- `--input-border[=STYLE]`
|
||||||
|
- `--input-label=LABEL`
|
||||||
|
- `--input-label-pos=COL[:bottom]`
|
||||||
|
- Colors
|
||||||
|
- `input-fg` (`query`)
|
||||||
|
- `input-bg`
|
||||||
|
- `input-border`
|
||||||
|
- `input-label`
|
||||||
|
- Actions
|
||||||
|
- `change-input-label`
|
||||||
|
- `transform-input-label`
|
||||||
|
- Border and label for the header section (#4159)
|
||||||
|
- Options
|
||||||
|
- `--header-border[=STYLE]`
|
||||||
|
- `--header-label=LABEL`
|
||||||
|
- `--header-label-pos=COL[:bottom]`
|
||||||
|
- Colors
|
||||||
|
- `header-fg` (`header`)
|
||||||
|
- `header-bg`
|
||||||
|
- `header-border`
|
||||||
|
- `header-label`
|
||||||
|
- Actions
|
||||||
|
- `change-header-label`
|
||||||
|
- `transform-header-label`
|
||||||
|
- Added `--preview-border[=STYLE]` as short for `--preview-window=border[-STYLE]`
|
||||||
|
- Added new preview border style `line` which draws a single separator line between the preview window and the rest of the interface
|
||||||
|
- fzf will now render a dashed line (`┈┈`) in each `--gap` for better visual separation.
|
||||||
|
```sh
|
||||||
|
# All bash/zsh functions, highlighted
|
||||||
|
declare -f |
|
||||||
|
perl -0 -pe 's/^}\n/}\0/gm' |
|
||||||
|
bat --plain --language bash --color always |
|
||||||
|
fzf --read0 --ansi --layout reverse --multi --highlight-line --gap
|
||||||
|
```
|
||||||
|
* You can customize the line using `--gap-line[=STR]`.
|
||||||
|
- You can specify `border-native` to `--tmux` so that native tmux border is used instead of `--border`. This can be useful if you start a different program from inside the popup.
|
||||||
|
```sh
|
||||||
|
fzf --tmux border-native --bind 'enter:execute:less {}'
|
||||||
|
```
|
||||||
|
- Added `toggle-multi-line` action
|
||||||
|
- Added `toggle-hscroll` action
|
||||||
|
- Added `change-nth` action for dynamically changing the value of the `--nth` option
|
||||||
|
```sh
|
||||||
|
# Start with --nth 1, then 2, then 3, then back to the default, 1
|
||||||
|
echo 'foo foobar foobarbaz' | fzf --bind 'space:change-nth(2|3|)' --nth 1 -q foo
|
||||||
|
```
|
||||||
|
- `--nth` parts of each line can now be rendered in a different text style
|
||||||
|
```sh
|
||||||
|
# nth in a different style
|
||||||
|
ls -al | fzf --nth -1 --color nth:italic
|
||||||
|
ls -al | fzf --nth -1 --color nth:reverse
|
||||||
|
ls -al | fzf --nth -1 --color nth:reverse:bold
|
||||||
|
|
||||||
|
# Dim the other parts
|
||||||
|
ls -al | fzf --nth -1 --color nth:regular,fg:dim
|
||||||
|
|
||||||
|
# With 'change-nth'. The current nth option is exported as $FZF_NTH.
|
||||||
|
ps -ef | fzf --reverse --header-lines 1 --header-border bottom --input-border \
|
||||||
|
--color nth:regular,fg:dim \
|
||||||
|
--bind 'ctrl-n:change-nth(8..|1|2|3|4|5|6|7|)' \
|
||||||
|
--bind 'result:transform-prompt:echo "${FZF_NTH}> "'
|
||||||
|
```
|
||||||
|
- A single-character delimiter is now treated as a plain string delimiter rather than a regular expression delimiter, even if it's a regular expression meta-character.
|
||||||
|
- This means you can just write `--delimiter '|'` instead of escaping it as `--delimiter '\|'`
|
||||||
|
- Bug fixes
|
||||||
|
- Bug fixes and improvements in fish scripts (thanks to @bitraid)
|
||||||
|
|
||||||
|
0.57.0
|
||||||
|
------
|
||||||
|
- You can now resize the preview window by dragging the border
|
||||||
|
- Built-in walker improvements
|
||||||
|
- `--walker-root` can take multiple directory arguments. e.g. `--walker-root include src lib`
|
||||||
|
- `--walker-skip` can handle multi-component patterns. e.g. `--walker-skip target/build`
|
||||||
|
- Removed long processing delay when displaying images in the preview window
|
||||||
|
- `FZF_PREVIEW_*` environment variables are exported to all child processes (#4098)
|
||||||
|
- Bug fixes in fish scripts
|
||||||
|
|
||||||
|
0.56.3
|
||||||
|
------
|
||||||
|
- Bug fixes in zsh scripts
|
||||||
|
- fix(zsh): handle backtick trigger edge case (#4090)
|
||||||
|
- revert(zsh): remove 'fc -RI' call in the history widget (#4093)
|
||||||
|
- Thanks to @LangLangBart for the contributions
|
||||||
|
|
||||||
|
0.56.2
|
||||||
|
------
|
||||||
|
- Bug fixes
|
||||||
|
- Fixed abnormal scrolling behavior when `--wrap` is set (#4083)
|
||||||
|
- [zsh] Fixed warning message when `ksh_arrays` is set (#4084)
|
||||||
|
|
||||||
|
0.56.1
|
||||||
|
------
|
||||||
|
- Bug fixes and improvements
|
||||||
|
- Fixed a race condition which would cause fzf to present stale results after `reload` (#4070)
|
||||||
|
- `page-up` and `page-down` actions now work correctly with multi-line items (#4069)
|
||||||
|
- `{n}` is allowed in `SCROLL` expression in `--preview-window` (#4079)
|
||||||
|
- [zsh] Fixed regression in history loading with shared option (#4071)
|
||||||
|
- [zsh] Better command extraction in zsh completion (#4082)
|
||||||
|
- Thanks to @LangLangBart, @jaydee-coder, @alex-huff, and @vejkse for the contributions
|
||||||
|
|
||||||
|
0.56.0
|
||||||
|
------
|
||||||
|
- Added `--gap[=N]` option to display empty lines between items.
|
||||||
|
- This can be useful to visually separate adjacent multi-line items.
|
||||||
|
```sh
|
||||||
|
# All bash functions, highlighted
|
||||||
|
declare -f | perl -0777 -pe 's/^}\n/}\0/gm' |
|
||||||
|
bat --plain --language bash --color always |
|
||||||
|
fzf --read0 --ansi --reverse --multi --highlight-line --gap
|
||||||
|
```
|
||||||
|
- Or just to make the list easier to read. For single-line items, you probably want to set `--color gutter:-1` as well to hide the gutter.
|
||||||
|
```sh
|
||||||
|
fzf --info inline-right --gap --color gutter:-1
|
||||||
|
```
|
||||||
|
- Added `noinfo` option to `--preview-window` to hide the scroll indicator in the preview window
|
||||||
|
- Bug fixes
|
||||||
|
- Thanks to @LangLangBart, @akinomyoga, and @charlievieth for fixing the bugs
|
||||||
|
|
||||||
|
0.55.0
|
||||||
|
------
|
||||||
|
_Release highlights: https://junegunn.github.io/fzf/releases/0.55.0/_
|
||||||
|
|
||||||
|
- Added `exact-boundary-match` type to the search syntax. When a search term is single-quoted, fzf will search for the exact occurrences of the string with both ends at word boundaries.
|
||||||
|
```sh
|
||||||
|
fzf --query "'here'" << EOF
|
||||||
|
come here
|
||||||
|
not there
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
- [bash] Fuzzy path completion is enabled for all commands
|
||||||
|
- 1. If the default completion is not already set
|
||||||
|
- 2. And if the current bash supports `complete -D` option
|
||||||
|
- However, fuzzy completion for some commands can be "dynamically" disabled by the dynamic completion loader
|
||||||
|
- See the comment in `__fzf_default_completion` function for more information
|
||||||
|
- Comments are now allowed in `$FZF_DEFAULT_OPTS` and `$FZF_DEFAULT_OPTS_FILE`
|
||||||
|
```sh
|
||||||
|
export FZF_DEFAULT_OPTS='
|
||||||
|
# Layout options
|
||||||
|
--layout=reverse
|
||||||
|
--info=inline-right # Show info on the right side of the prompt line
|
||||||
|
# ...
|
||||||
|
'
|
||||||
|
```
|
||||||
|
- Hyperlinks (OSC 8) are now supported in the preview window and in the main window
|
||||||
|
```sh
|
||||||
|
printf '<< \e]8;;http://github.com/junegunn/fzf\e\\Link to \e[32mfz\e[0mf\e]8;;\e\\ >>' | fzf --ansi
|
||||||
|
|
||||||
|
fzf --preview "printf '<< \e]8;;http://github.com/junegunn/fzf\e\\Link to \e[32mfz\e[0mf\e]8;;\e\\ >>'"
|
||||||
|
```
|
||||||
|
- The default `--ellipsis` is now `··` instead of `..`.
|
||||||
|
- [vim] A spec can have `exit` callback that is called with the exit status of fzf
|
||||||
|
- This can be used to clean up temporary resources or restore the original state when fzf is closed without a selection
|
||||||
|
- Fixed `--tmux bottom` when the status line is not at the bottom
|
||||||
|
- Fixed extra scroll offset in multi-line mode (`--read0` or `--wrap`)
|
||||||
|
- Added fallback `ps` command for `kill` completion on Cygwin
|
||||||
|
|
||||||
|
0.54.3
|
||||||
|
------
|
||||||
|
- Fixed incompatibility of adaptive height specification and 'start:reload'
|
||||||
|
```sh
|
||||||
|
# A regression in 0.54.0 would cause this to fail
|
||||||
|
fzf --height '~100%' --bind 'start:reload:seq 10'
|
||||||
|
```
|
||||||
|
- Environment variables are now available to `$FZF_DEFAULT_COMMAND`
|
||||||
|
```sh
|
||||||
|
FZF_DEFAULT_COMMAND='echo $FZF_QUERY' fzf --query foo
|
||||||
|
```
|
||||||
|
|
||||||
|
0.54.2
|
||||||
|
------
|
||||||
|
- Fixed incorrect syntax highlighting of truncated multi-line entries
|
||||||
|
- Updated GoReleaser to 2.1.0 to simplify notarization of macOS binaries
|
||||||
|
- macOS archives will be in `tar.gz` format instead of `zip` format since we no longer notarize the zip files but binaries
|
||||||
|
- (Windows) Reverted a mintty fix in 0.54.0
|
||||||
|
- As a result, mouse may not work on mintty in fullscreen mode. However, fzf will correctly read non-ASCII input in fullscreen mode (`--no-height`).
|
||||||
|
- fzf unfortunately cannot read non-ASCII input when not in fullscreen mode on Windows. So if you need to input non-ASCII characters, add `--no-height` to your `$FZF_DEFAULT_OPTS`.
|
||||||
|
- Any help in fixing this issue will be appreciated (#3799, #3847).
|
||||||
|
|
||||||
0.54.1
|
0.54.1
|
||||||
------
|
------
|
||||||
- Updated [fastwalk](https://github.com/charlievieth/fastwalk) dependency for built-in directory walker
|
- Updated [fastwalk](https://github.com/charlievieth/fastwalk) dependency for built-in directory walker
|
||||||
|
@@ -8,5 +8,5 @@ RUN echo '. ~/.bashrc' >> ~/.bash_profile
|
|||||||
RUN rm -f /etc/bash.bashrc
|
RUN rm -f /etc/bash.bashrc
|
||||||
COPY . /fzf
|
COPY . /fzf
|
||||||
RUN cd /fzf && make install && ./install --all
|
RUN cd /fzf && make install && ./install --all
|
||||||
ENV LANG C.UTF-8
|
ENV LANG=C.UTF-8
|
||||||
CMD tmux new 'set -o pipefail; ruby /fzf/test/test_go.rb | tee out && touch ok' && cat out && [ -e ok ]
|
CMD ["bash", "-ic", "tmux new 'set -o pipefail; ruby /fzf/test/test_go.rb | tee out && touch ok' && cat out && [ -e ok ]"]
|
||||||
|
10
Makefile
10
Makefile
@@ -1,4 +1,3 @@
|
|||||||
SHELL := bash
|
|
||||||
GO ?= go
|
GO ?= go
|
||||||
GOOS ?= $(shell $(GO) env GOOS)
|
GOOS ?= $(shell $(GO) env GOOS)
|
||||||
|
|
||||||
@@ -14,7 +13,7 @@ endif
|
|||||||
ifeq ($(VERSION),)
|
ifeq ($(VERSION),)
|
||||||
$(error Not on git repository; cannot determine $$FZF_VERSION)
|
$(error Not on git repository; cannot determine $$FZF_VERSION)
|
||||||
endif
|
endif
|
||||||
VERSION_TRIM := $(shell sed "s/^v//; s/-.*//" <<< $(VERSION))
|
VERSION_TRIM := $(shell echo $(VERSION) | sed "s/^v//; s/-.*//")
|
||||||
VERSION_REGEX := $(subst .,\.,$(VERSION_TRIM))
|
VERSION_REGEX := $(subst .,\.,$(VERSION_TRIM))
|
||||||
|
|
||||||
ifdef FZF_REVISION
|
ifdef FZF_REVISION
|
||||||
@@ -77,7 +76,6 @@ endif
|
|||||||
all: target/$(BINARY)
|
all: target/$(BINARY)
|
||||||
|
|
||||||
test: $(SOURCES)
|
test: $(SOURCES)
|
||||||
[ -z "$$(gofmt -s -d src)" ] || (gofmt -s -d src; exit 1)
|
|
||||||
SHELL=/bin/sh GOOS= $(GO) test -v -tags "$(TAGS)" \
|
SHELL=/bin/sh GOOS= $(GO) test -v -tags "$(TAGS)" \
|
||||||
github.com/junegunn/fzf/src \
|
github.com/junegunn/fzf/src \
|
||||||
github.com/junegunn/fzf/src/algo \
|
github.com/junegunn/fzf/src/algo \
|
||||||
@@ -87,6 +85,10 @@ test: $(SOURCES)
|
|||||||
bench:
|
bench:
|
||||||
cd src && SHELL=/bin/sh GOOS= $(GO) test -v -tags "$(TAGS)" -run=Bench -bench=. -benchmem
|
cd src && SHELL=/bin/sh GOOS= $(GO) test -v -tags "$(TAGS)" -run=Bench -bench=. -benchmem
|
||||||
|
|
||||||
|
lint: $(SOURCES) test/test_go.rb
|
||||||
|
[ -z "$$(gofmt -s -d src)" ] || (gofmt -s -d src; exit 1)
|
||||||
|
rubocop --require rubocop-minitest --require rubocop-performance
|
||||||
|
|
||||||
install: bin/fzf
|
install: bin/fzf
|
||||||
|
|
||||||
generate:
|
generate:
|
||||||
@@ -184,4 +186,4 @@ update:
|
|||||||
$(GO) get -u
|
$(GO) get -u
|
||||||
$(GO) mod tidy
|
$(GO) mod tidy
|
||||||
|
|
||||||
.PHONY: all generate build release test bench install clean docker docker-test update
|
.PHONY: all generate build release test bench lint install clean docker docker-test update
|
||||||
|
@@ -289,8 +289,9 @@ The following table summarizes the available options.
|
|||||||
| `source` | string | External command to generate input to fzf (e.g. `find .`) |
|
| `source` | string | External command to generate input to fzf (e.g. `find .`) |
|
||||||
| `source` | list | Vim list as input to fzf |
|
| `source` | list | Vim list as input to fzf |
|
||||||
| `sink` | string | Vim command to handle the selected item (e.g. `e`, `tabe`) |
|
| `sink` | string | Vim command to handle the selected item (e.g. `e`, `tabe`) |
|
||||||
| `sink` | funcref | Reference to function to process each selected item |
|
| `sink` | funcref | Function to be called with each selected item |
|
||||||
| `sinklist` (or `sink*`) | funcref | Similar to `sink`, but takes the list of output lines at once |
|
| `sinklist` (or `sink*`) | funcref | Similar to `sink`, but takes the list of output lines at once |
|
||||||
|
| `exit` | funcref | Function to be called with the exit status of fzf (e.g. 0, 1, 2, 130) |
|
||||||
| `options` | string/list | Options to fzf |
|
| `options` | string/list | Options to fzf |
|
||||||
| `dir` | string | Working directory |
|
| `dir` | string | Working directory |
|
||||||
| `up`/`down`/`left`/`right` | number/string | (Layout) Window position and size (e.g. `20`, `50%`) |
|
| `up`/`down`/`left`/`right` | number/string | (Layout) Window position and size (e.g. `20`, `50%`) |
|
||||||
|
@@ -9,12 +9,24 @@
|
|||||||
# - https://iterm2.com/utilities/imgcat
|
# - https://iterm2.com/utilities/imgcat
|
||||||
|
|
||||||
if [[ $# -ne 1 ]]; then
|
if [[ $# -ne 1 ]]; then
|
||||||
>&2 echo "usage: $0 FILENAME"
|
>&2 echo "usage: $0 FILENAME[:LINENO][:IGNORED]"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
file=${1/#\~\//$HOME/}
|
file=${1/#\~\//$HOME/}
|
||||||
type=$(file --dereference --mime -- "$file")
|
|
||||||
|
center=0
|
||||||
|
if [[ ! -r $file ]]; then
|
||||||
|
if [[ $file =~ ^(.+):([0-9]+)\ *$ ]] && [[ -r ${BASH_REMATCH[1]} ]]; then
|
||||||
|
file=${BASH_REMATCH[1]}
|
||||||
|
center=${BASH_REMATCH[2]}
|
||||||
|
elif [[ $file =~ ^(.+):([0-9]+):[0-9]+\ *$ ]] && [[ -r ${BASH_REMATCH[1]} ]]; then
|
||||||
|
file=${BASH_REMATCH[1]}
|
||||||
|
center=${BASH_REMATCH[2]}
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
type=$(file --brief --dereference --mime -- "$file")
|
||||||
|
|
||||||
if [[ ! $type =~ image/ ]]; then
|
if [[ ! $type =~ image/ ]]; then
|
||||||
if [[ $type =~ =binary ]]; then
|
if [[ $type =~ =binary ]]; then
|
||||||
@@ -32,7 +44,7 @@ if [[ ! $type =~ image/ ]]; then
|
|||||||
exit
|
exit
|
||||||
fi
|
fi
|
||||||
|
|
||||||
${batname} --style="${BAT_STYLE:-numbers}" --color=always --pager=never -- "$file"
|
${batname} --style="${BAT_STYLE:-numbers}" --color=always --pager=never --highlight-line="${center:-0}" -- "$file"
|
||||||
exit
|
exit
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
24
bin/fzf-tmux
24
bin/fzf-tmux
@@ -19,6 +19,9 @@ term=""
|
|||||||
[[ -n "$LINES" ]] && lines=$LINES || lines=$(tput lines) || lines=$(tmux display-message -p "#{pane_height}")
|
[[ -n "$LINES" ]] && lines=$LINES || lines=$(tput lines) || lines=$(tmux display-message -p "#{pane_height}")
|
||||||
[[ -n "$COLUMNS" ]] && columns=$COLUMNS || columns=$(tput cols) || columns=$(tmux display-message -p "#{pane_width}")
|
[[ -n "$COLUMNS" ]] && columns=$COLUMNS || columns=$(tput cols) || columns=$(tmux display-message -p "#{pane_width}")
|
||||||
|
|
||||||
|
tmux_version=$(tmux -V | sed 's/[^0-9.]//g')
|
||||||
|
tmux_32=$(awk '{print ($1 >= 3.2)}' <<< "$tmux_version" 2> /dev/null || bc -l <<< "$tmux_version >= 3.2")
|
||||||
|
|
||||||
help() {
|
help() {
|
||||||
>&2 echo 'usage: fzf-tmux [LAYOUT OPTIONS] [--] [FZF OPTIONS]
|
>&2 echo 'usage: fzf-tmux [LAYOUT OPTIONS] [--] [FZF OPTIONS]
|
||||||
|
|
||||||
@@ -94,10 +97,18 @@ while [[ $# -gt 0 ]]; do
|
|||||||
opt="$opt ${arg:0:2}$size"
|
opt="$opt ${arg:0:2}$size"
|
||||||
elif [[ "$size" =~ %$ ]]; then
|
elif [[ "$size" =~ %$ ]]; then
|
||||||
size=${size:0:((${#size}-1))}
|
size=${size:0:((${#size}-1))}
|
||||||
if [[ -n "$swap" ]]; then
|
if [[ $tmux_32 = 1 ]]; then
|
||||||
opt="$opt -l $(( 100 - size ))%"
|
if [[ -n "$swap" ]]; then
|
||||||
|
opt="$opt -l $(( 100 - size ))%"
|
||||||
|
else
|
||||||
|
opt="$opt -l $size%"
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
opt="$opt -l $size%"
|
if [[ -n "$swap" ]]; then
|
||||||
|
opt="$opt -p $(( 100 - size ))"
|
||||||
|
else
|
||||||
|
opt="$opt -p $size"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
if [[ -n "$swap" ]]; then
|
if [[ -n "$swap" ]]; then
|
||||||
@@ -187,12 +198,11 @@ trap 'cleanup' EXIT
|
|||||||
|
|
||||||
envs="export TERM=$TERM "
|
envs="export TERM=$TERM "
|
||||||
if [[ "$opt" =~ "-E" ]]; then
|
if [[ "$opt" =~ "-E" ]]; then
|
||||||
tmux_version=$(tmux -V | sed 's/[^0-9.]//g')
|
if [[ $tmux_version = 3.2 ]]; then
|
||||||
if [[ $(awk '{print ($1 > 3.2)}' <<< "$tmux_version" 2> /dev/null || bc -l <<< "$tmux_version > 3.2") = 1 ]]; then
|
FZF_DEFAULT_OPTS="--margin 0,1 $FZF_DEFAULT_OPTS"
|
||||||
|
elif [[ $tmux_32 = 1 ]]; then
|
||||||
FZF_DEFAULT_OPTS="--border $FZF_DEFAULT_OPTS"
|
FZF_DEFAULT_OPTS="--border $FZF_DEFAULT_OPTS"
|
||||||
opt="-B $opt"
|
opt="-B $opt"
|
||||||
elif [[ $tmux_version = 3.2 ]]; then
|
|
||||||
FZF_DEFAULT_OPTS="--margin 0,1 $FZF_DEFAULT_OPTS"
|
|
||||||
else
|
else
|
||||||
echo "fzf-tmux: tmux 3.2 or above is required for popup mode" >&2
|
echo "fzf-tmux: tmux 3.2 or above is required for popup mode" >&2
|
||||||
exit 2
|
exit 2
|
||||||
|
@@ -306,8 +306,9 @@ The following table summarizes the available options.
|
|||||||
`source` | string | External command to generate input to fzf (e.g. `find .` )
|
`source` | string | External command to generate input to fzf (e.g. `find .` )
|
||||||
`source` | list | Vim list as input to fzf
|
`source` | list | Vim list as input to fzf
|
||||||
`sink` | string | Vim command to handle the selected item (e.g. `e` , `tabe` )
|
`sink` | string | Vim command to handle the selected item (e.g. `e` , `tabe` )
|
||||||
`sink` | funcref | Reference to function to process each selected item
|
`sink` | funcref | Function to be called with each selected item
|
||||||
`sinklist` (or `sink*` ) | funcref | Similar to `sink` , but takes the list of output lines at once
|
`sinklist` (or `sink*` ) | funcref | Similar to `sink` , but takes the list of output lines at once
|
||||||
|
`exit` | funcref | Function to be called with the exit status of fzf (e.g. 0, 1, 2, 130)
|
||||||
`options` | string/list | Options to fzf
|
`options` | string/list | Options to fzf
|
||||||
`dir` | string | Working directory
|
`dir` | string | Working directory
|
||||||
`up` / `down` / `left` / `right` | number/string | (Layout) Window position and size (e.g. `20` , `50%` )
|
`up` / `down` / `left` / `right` | number/string | (Layout) Window position and size (e.g. `20` , `50%` )
|
||||||
|
16
go.mod
16
go.mod
@@ -1,20 +1,20 @@
|
|||||||
module github.com/junegunn/fzf
|
module github.com/junegunn/fzf
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/charlievieth/fastwalk v1.0.8
|
github.com/charlievieth/fastwalk v1.0.9
|
||||||
github.com/gdamore/tcell/v2 v2.7.4
|
github.com/gdamore/tcell/v2 v2.8.1
|
||||||
|
github.com/junegunn/go-shellwords v0.0.0-20240813092932-a62c48c52e97
|
||||||
github.com/mattn/go-isatty v0.0.20
|
github.com/mattn/go-isatty v0.0.20
|
||||||
github.com/mattn/go-shellwords v1.0.12
|
|
||||||
github.com/rivo/uniseg v0.4.7
|
github.com/rivo/uniseg v0.4.7
|
||||||
golang.org/x/sys v0.22.0
|
golang.org/x/sys v0.29.0
|
||||||
golang.org/x/term v0.22.0
|
golang.org/x/term v0.28.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gdamore/encoding v1.0.0 // indirect
|
github.com/gdamore/encoding v1.0.1 // indirect
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||||
golang.org/x/text v0.14.0 // indirect
|
golang.org/x/text v0.21.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
go 1.20
|
go 1.20
|
||||||
|
58
go.sum
58
go.sum
@@ -1,17 +1,18 @@
|
|||||||
github.com/charlievieth/fastwalk v1.0.8 h1:uaoH6cAKSk73aK7aKXqs0+bL+J3Txzd3NGH8tRXgHko=
|
github.com/charlievieth/fastwalk v1.0.9 h1:Odb92AfoReO3oFBfDGT5J+nwgzQPF/gWAw6E6/lkor0=
|
||||||
github.com/charlievieth/fastwalk v1.0.8/go.mod h1:yGy1zbxog41ZVMcKA/i8ojXLFsuayX5VvwhQVoj9PBI=
|
github.com/charlievieth/fastwalk v1.0.9/go.mod h1:yGy1zbxog41ZVMcKA/i8ojXLFsuayX5VvwhQVoj9PBI=
|
||||||
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw=
|
||||||
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo=
|
||||||
github.com/gdamore/tcell/v2 v2.7.4 h1:sg6/UnTM9jGpZU+oFYAsDahfchWAFW8Xx2yFinNSAYU=
|
github.com/gdamore/tcell/v2 v2.8.1 h1:KPNxyqclpWpWQlPLx6Xui1pMk8S+7+R37h3g07997NU=
|
||||||
github.com/gdamore/tcell/v2 v2.7.4/go.mod h1:dSXtXTSK0VsW1biw65DZLZ2NKr7j0qP/0J7ONmsraWg=
|
github.com/gdamore/tcell/v2 v2.8.1/go.mod h1:bj8ori1BG3OYMjmb3IklZVWfZUJ1UBQt9JXrOCOhGWw=
|
||||||
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/junegunn/go-shellwords v0.0.0-20240813092932-a62c48c52e97 h1:rqzLixVo1c/GQW6px9j1xQmlvQIn+lf/V6M1UQ7IFzw=
|
||||||
|
github.com/junegunn/go-shellwords v0.0.0-20240813092932-a62c48c52e97/go.mod h1:6EILKtGpo5t+KLb85LNZLAF6P9LKp78hJI80PXMcn3c=
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
|
|
||||||
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
@@ -19,15 +20,29 @@ github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUc
|
|||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||||
|
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||||
|
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
|
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
|
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
|
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
|
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||||
|
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||||
|
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||||
|
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
@@ -35,23 +50,36 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||||
|
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
|
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||||
|
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||||
golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
|
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||||
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
|
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
|
||||||
|
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
|
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
|
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
|
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||||
|
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
|
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||||
|
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
47
install
47
install
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
set -u
|
set -u
|
||||||
|
|
||||||
version=0.54.1
|
version=0.58.0
|
||||||
auto_completion=
|
auto_completion=
|
||||||
key_bindings=
|
key_bindings=
|
||||||
update_config=2
|
update_config=2
|
||||||
@@ -83,7 +83,7 @@ ask() {
|
|||||||
check_binary() {
|
check_binary() {
|
||||||
echo -n " - Checking fzf executable ... "
|
echo -n " - Checking fzf executable ... "
|
||||||
local output
|
local output
|
||||||
output=$("$fzf_base"/bin/fzf --version 2>&1)
|
output=$(FZF_DEFAULT_OPTS= "$fzf_base"/bin/fzf --version 2>&1)
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo "Error: $output"
|
echo "Error: $output"
|
||||||
binary_error="Invalid binary"
|
binary_error="Invalid binary"
|
||||||
@@ -168,8 +168,8 @@ archi=$(uname -sm)
|
|||||||
binary_available=1
|
binary_available=1
|
||||||
binary_error=""
|
binary_error=""
|
||||||
case "$archi" in
|
case "$archi" in
|
||||||
Darwin\ arm64) download fzf-$version-darwin_arm64.zip ;;
|
Darwin\ arm64) download fzf-$version-darwin_arm64.tar.gz ;;
|
||||||
Darwin\ x86_64) download fzf-$version-darwin_amd64.zip ;;
|
Darwin\ x86_64) download fzf-$version-darwin_amd64.tar.gz ;;
|
||||||
Linux\ armv5*) download fzf-$version-linux_armv5.tar.gz ;;
|
Linux\ armv5*) download fzf-$version-linux_armv5.tar.gz ;;
|
||||||
Linux\ armv6*) download fzf-$version-linux_armv6.tar.gz ;;
|
Linux\ armv6*) download fzf-$version-linux_armv6.tar.gz ;;
|
||||||
Linux\ armv7*) download fzf-$version-linux_armv7.tar.gz ;;
|
Linux\ armv7*) download fzf-$version-linux_armv7.tar.gz ;;
|
||||||
@@ -295,35 +295,44 @@ EOF
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
append_line() {
|
append_line() {
|
||||||
set -e
|
local update line file pat lines
|
||||||
|
|
||||||
local update line file pat lno
|
|
||||||
update="$1"
|
update="$1"
|
||||||
line="$2"
|
line="$2"
|
||||||
file="$3"
|
file="$3"
|
||||||
pat="${4:-}"
|
pat="${4:-}"
|
||||||
lno=""
|
lines=""
|
||||||
|
|
||||||
echo "Update $file:"
|
echo "Update $file:"
|
||||||
echo " - $line"
|
echo " - $line"
|
||||||
if [ -f "$file" ]; then
|
if [ -f "$file" ]; then
|
||||||
if [ $# -lt 4 ]; then
|
if [ $# -lt 4 ]; then
|
||||||
lno=$(\grep -nF "$line" "$file" | sed 's/:.*//' | tr '\n' ' ')
|
lines=$(\grep -nF "$line" "$file")
|
||||||
else
|
else
|
||||||
lno=$(\grep -nF "$pat" "$file" | sed 's/:.*//' | tr '\n' ' ')
|
lines=$(\grep -nF "$pat" "$file")
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
if [ -n "$lno" ]; then
|
|
||||||
echo " - Already exists: line #$lno"
|
if [ -n "$lines" ]; then
|
||||||
|
echo " - Already exists:"
|
||||||
|
sed 's/^/ Line /' <<< "$lines"
|
||||||
|
|
||||||
|
update=0
|
||||||
|
if ! \grep -qv "^[0-9]*:[[:space:]]*#" <<< "$lines" ; then
|
||||||
|
echo " - But they all seem to be commented"
|
||||||
|
ask " - Continue modifying $file?"
|
||||||
|
update=$?
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
set -e
|
||||||
|
if [ "$update" -eq 1 ]; then
|
||||||
|
[ -f "$file" ] && echo >> "$file"
|
||||||
|
echo "$line" >> "$file"
|
||||||
|
echo " + Added"
|
||||||
else
|
else
|
||||||
if [ $update -eq 1 ]; then
|
echo " ~ Skipped"
|
||||||
[ -f "$file" ] && echo >> "$file"
|
|
||||||
echo "$line" >> "$file"
|
|
||||||
echo " + Added"
|
|
||||||
else
|
|
||||||
echo " ~ Skipped"
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo
|
echo
|
||||||
set +e
|
set +e
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
$version="0.54.1"
|
$version="0.58.0"
|
||||||
|
|
||||||
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||||
|
|
||||||
|
2
main.go
2
main.go
@@ -11,7 +11,7 @@ import (
|
|||||||
"github.com/junegunn/fzf/src/protector"
|
"github.com/junegunn/fzf/src/protector"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version = "0.54"
|
var version = "0.58"
|
||||||
var revision = "devel"
|
var revision = "devel"
|
||||||
|
|
||||||
//go:embed shell/key-bindings.bash
|
//go:embed shell/key-bindings.bash
|
||||||
|
@@ -21,13 +21,13 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
..
|
..
|
||||||
.TH fzf\-tmux 1 "Jul 2024" "fzf 0.54.1" "fzf\-tmux - open fzf in tmux split pane"
|
.TH fzf\-tmux 1 "Jan 2025" "fzf 0.58.0" "fzf\-tmux - open fzf in tmux split pane"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf\-tmux - open fzf in tmux split pane
|
fzf\-tmux - open fzf in tmux split pane
|
||||||
|
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
.B fzf\-tmux [LAYOUT OPTIONS] [\-\-] [FZF OPTIONS]
|
.B fzf\-tmux [\fILAYOUT OPTIONS\fR] [\-\-] [\fIFZF OPTIONS\fR]
|
||||||
|
|
||||||
.SH DESCRIPTION
|
.SH DESCRIPTION
|
||||||
fzf\-tmux is a wrapper script for fzf that opens fzf in a tmux split pane or in
|
fzf\-tmux is a wrapper script for fzf that opens fzf in a tmux split pane or in
|
||||||
|
792
man/man1/fzf.1
792
man/man1/fzf.1
File diff suppressed because it is too large
Load Diff
@@ -198,6 +198,7 @@ function! s:compare_binary_versions(a, b)
|
|||||||
return s:compare_versions(s:get_version(a:a), s:get_version(a:b))
|
return s:compare_versions(s:get_version(a:a), s:get_version(a:b))
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
|
let s:min_version = '0.53.0'
|
||||||
let s:checked = {}
|
let s:checked = {}
|
||||||
function! fzf#exec(...)
|
function! fzf#exec(...)
|
||||||
if !exists('s:exec')
|
if !exists('s:exec')
|
||||||
@@ -225,7 +226,11 @@ function! fzf#exec(...)
|
|||||||
let s:exec = binaries[-1]
|
let s:exec = binaries[-1]
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if a:0 && !has_key(s:checked, a:1)
|
let min_version = s:min_version
|
||||||
|
if a:0 && s:compare_versions(a:1, min_version) > 0
|
||||||
|
let min_version = a:1
|
||||||
|
endif
|
||||||
|
if !has_key(s:checked, min_version)
|
||||||
let fzf_version = s:get_version(s:exec)
|
let fzf_version = s:get_version(s:exec)
|
||||||
if empty(fzf_version)
|
if empty(fzf_version)
|
||||||
let message = printf('Failed to run "%s --version"', s:exec)
|
let message = printf('Failed to run "%s --version"', s:exec)
|
||||||
@@ -233,17 +238,17 @@ function! fzf#exec(...)
|
|||||||
throw message
|
throw message
|
||||||
end
|
end
|
||||||
|
|
||||||
if s:compare_versions(fzf_version, a:1) >= 0
|
if s:compare_versions(fzf_version, min_version) >= 0
|
||||||
let s:checked[a:1] = 1
|
let s:checked[min_version] = 1
|
||||||
return s:exec
|
return s:exec
|
||||||
elseif a:0 < 2 && input(printf('You need fzf %s or above. Found: %s. Download binary? (y/n) ', a:1, fzf_version)) =~? '^y'
|
elseif a:0 < 2 && input(printf('You need fzf %s or above. Found: %s. Download binary? (y/n) ', min_version, fzf_version)) =~? '^y'
|
||||||
let s:versions = {}
|
let s:versions = {}
|
||||||
unlet s:exec
|
unlet s:exec
|
||||||
redraw
|
redraw
|
||||||
call fzf#install()
|
call fzf#install()
|
||||||
return fzf#exec(a:1, 1)
|
return fzf#exec(min_version, 1)
|
||||||
else
|
else
|
||||||
throw printf('You need to upgrade fzf (required: %s or above)', a:1)
|
throw printf('You need to upgrade fzf (required: %s or above)', min_version)
|
||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
@@ -665,21 +670,17 @@ else
|
|||||||
let s:launcher = function('s:xterm_launcher')
|
let s:launcher = function('s:xterm_launcher')
|
||||||
endif
|
endif
|
||||||
|
|
||||||
function! s:exit_handler(code, command, ...)
|
function! s:exit_handler(dict, code, command, ...)
|
||||||
if a:code == 130
|
if has_key(a:dict, 'exit')
|
||||||
return 0
|
call a:dict.exit(a:code)
|
||||||
elseif has('nvim') && a:code == 129
|
endif
|
||||||
" When deleting the terminal buffer while fzf is still running,
|
if a:code == 2
|
||||||
" Nvim sends SIGHUP.
|
|
||||||
return 0
|
|
||||||
elseif a:code > 1
|
|
||||||
call s:error('Error running ' . a:command)
|
call s:error('Error running ' . a:command)
|
||||||
if !empty(a:000)
|
if !empty(a:000)
|
||||||
sleep
|
sleep
|
||||||
endif
|
endif
|
||||||
return 0
|
|
||||||
endif
|
endif
|
||||||
return 1
|
return a:code
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:execute(dict, command, use_height, temps) abort
|
function! s:execute(dict, command, use_height, temps) abort
|
||||||
@@ -731,7 +732,7 @@ function! s:execute(dict, command, use_height, temps) abort
|
|||||||
let exit_status = v:shell_error
|
let exit_status = v:shell_error
|
||||||
redraw!
|
redraw!
|
||||||
let lines = s:collect(a:temps)
|
let lines = s:collect(a:temps)
|
||||||
return s:exit_handler(exit_status, command) ? lines : []
|
return s:exit_handler(a:dict, exit_status, command) < 2 ? lines : []
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:execute_tmux(dict, command, temps) abort
|
function! s:execute_tmux(dict, command, temps) abort
|
||||||
@@ -746,7 +747,7 @@ function! s:execute_tmux(dict, command, temps) abort
|
|||||||
let exit_status = v:shell_error
|
let exit_status = v:shell_error
|
||||||
redraw!
|
redraw!
|
||||||
let lines = s:collect(a:temps)
|
let lines = s:collect(a:temps)
|
||||||
return s:exit_handler(exit_status, command) ? lines : []
|
return s:exit_handler(a:dict, exit_status, command) < 2 ? lines : []
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:calc_size(max, val, dict)
|
function! s:calc_size(max, val, dict)
|
||||||
@@ -912,7 +913,7 @@ function! s:execute_term(dict, command, temps) abort
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
let lines = s:collect(self.temps)
|
let lines = s:collect(self.temps)
|
||||||
if !s:exit_handler(a:code, self.command, 1)
|
if s:exit_handler(self.dict, a:code, self.command, 1) >= 2
|
||||||
return
|
return
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
@@ -264,6 +264,7 @@ _fzf_handle_dynamic_completion() {
|
|||||||
# _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)
|
||||||
|
__fzf_orig_completion_get_orig_func "$cmd" || ret=1
|
||||||
|
|
||||||
# Update orig_complete by _fzf_orig_completion entry
|
# Update orig_complete by _fzf_orig_completion entry
|
||||||
[[ $orig_complete =~ ' -F '(_fzf_[^ ]+)' ' ]] &&
|
[[ $orig_complete =~ ' -F '(_fzf_[^ ]+)' ' ]] &&
|
||||||
@@ -289,7 +290,7 @@ __fzf_generic_path_completion() {
|
|||||||
fi
|
fi
|
||||||
COMPREPLY=()
|
COMPREPLY=()
|
||||||
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
||||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
[[ $COMP_CWORD -ge 0 ]] && cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
if [[ "$cur" == *"$trigger" ]] && [[ $cur != *'$('* ]] && [[ $cur != *':='* ]] && [[ $cur != *'`'* ]]; then
|
if [[ "$cur" == *"$trigger" ]] && [[ $cur != *'$('* ]] && [[ $cur != *':='* ]] && [[ $cur != *'`'* ]]; then
|
||||||
base=${cur:0:${#cur}-${#trigger}}
|
base=${cur:0:${#cur}-${#trigger}}
|
||||||
eval "base=$base" 2> /dev/null || return
|
eval "base=$base" 2> /dev/null || return
|
||||||
@@ -376,7 +377,7 @@ _fzf_complete() {
|
|||||||
selected=$(
|
selected=$(
|
||||||
FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse" "${FZF_COMPLETION_OPTS-} $str_arg") \
|
FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse" "${FZF_COMPLETION_OPTS-} $str_arg") \
|
||||||
FZF_DEFAULT_OPTS_FILE='' \
|
FZF_DEFAULT_OPTS_FILE='' \
|
||||||
__fzf_comprun "${rest[0]}" "${args[@]}" -q "$cur" | $post | command tr '\n' ' ')
|
__fzf_comprun "${rest[0]}" "${args[@]}" -q "$cur" | eval "$post" | command 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")
|
||||||
@@ -410,7 +411,8 @@ _fzf_complete_kill() {
|
|||||||
_fzf_proc_completion() {
|
_fzf_proc_completion() {
|
||||||
_fzf_complete -m --header-lines=1 --no-preview --wrap -- "$@" < <(
|
_fzf_complete -m --header-lines=1 --no-preview --wrap -- "$@" < <(
|
||||||
command ps -eo user,pid,ppid,start,time,command 2> /dev/null ||
|
command ps -eo user,pid,ppid,start,time,command 2> /dev/null ||
|
||||||
command ps -eo user,pid,ppid,time,args # For BusyBox
|
command ps -eo user,pid,ppid,time,args 2> /dev/null || # For BusyBox
|
||||||
|
command ps --everyone --full --windows # For cygwin
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -480,10 +482,36 @@ complete -o default -F _fzf_opts_completion fzf
|
|||||||
# fzf-tmux specific options (like `-w WIDTH`) are left as a future patch.
|
# fzf-tmux specific options (like `-w WIDTH`) are left as a future patch.
|
||||||
complete -o default -F _fzf_opts_completion fzf-tmux
|
complete -o default -F _fzf_opts_completion fzf-tmux
|
||||||
|
|
||||||
|
# Default path completion
|
||||||
|
__fzf_default_completion() {
|
||||||
|
__fzf_generic_path_completion _fzf_compgen_path "-m" "" "$@"
|
||||||
|
|
||||||
|
# Dynamic completion loader has updated the completion for the command
|
||||||
|
if [[ $? -eq 124 ]]; then
|
||||||
|
# We trigger _fzf_setup_completion so that fuzzy completion for the command
|
||||||
|
# still works. However, loader can update the completion for multiple
|
||||||
|
# commands at once, and fuzzy completion will no longer work for those
|
||||||
|
# other commands. e.g. pytest -> py.test, pytest-2, pytest-3, etc
|
||||||
|
_fzf_setup_completion path "$1"
|
||||||
|
return 124
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Set fuzzy path completion as the default completion for all commands.
|
||||||
|
# We can't set up default completion,
|
||||||
|
# 1. if it's already set up by another script
|
||||||
|
# 2. or if the current version of bash doesn't support -D option
|
||||||
|
complete | command grep -q __fzf_default_completion ||
|
||||||
|
complete | command grep -- '-D$' | command grep -qv _comp_complete_load ||
|
||||||
|
complete -D -F __fzf_default_completion -o default -o bashdefault 2> /dev/null
|
||||||
|
|
||||||
d_cmds="${FZF_COMPLETION_DIR_COMMANDS-cd pushd rmdir}"
|
d_cmds="${FZF_COMPLETION_DIR_COMMANDS-cd pushd rmdir}"
|
||||||
|
|
||||||
# NOTE: $FZF_COMPLETION_PATH_COMMANDS and $FZF_COMPLETION_VAR_COMMANDS are
|
# NOTE: $FZF_COMPLETION_PATH_COMMANDS and $FZF_COMPLETION_VAR_COMMANDS are
|
||||||
# undocumented and subject to change in the future.
|
# undocumented and subject to change in the future.
|
||||||
|
#
|
||||||
|
# NOTE: Although we have default completion, we still need to set up completion
|
||||||
|
# for each command in case they already have completion set up by another script.
|
||||||
a_cmds="${FZF_COMPLETION_PATH_COMMANDS-"
|
a_cmds="${FZF_COMPLETION_PATH_COMMANDS-"
|
||||||
awk bat cat code diff diff3
|
awk bat cat code diff diff3
|
||||||
emacs emacsclient ex file ftp g++ gcc gvim head hg hx java
|
emacs emacsclient ex file ftp g++ gcc gvim head hg hx java
|
||||||
|
@@ -120,25 +120,18 @@ __fzf_comprun() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Extract the name of the command. e.g. foo=1 bar baz**<tab>
|
# Extract the name of the command. e.g. ls; foo=1 ssh **<tab>
|
||||||
__fzf_extract_command() {
|
__fzf_extract_command() {
|
||||||
local token tokens
|
# Control completion with the "compstate" parameter, insert and list nothing
|
||||||
tokens=(${(z)1})
|
compstate[insert]=
|
||||||
for token in $tokens; do
|
compstate[list]=
|
||||||
token=${(Q)token}
|
cmd_word="${(Q)words[1]}"
|
||||||
if [[ "$token" =~ [[:alnum:]] && ! "$token" =~ "=" ]]; then
|
|
||||||
echo "$token"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
echo "${tokens[1]}"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
__fzf_generic_path_completion() {
|
__fzf_generic_path_completion() {
|
||||||
local base lbuf cmd compgen fzf_opts suffix tail dir leftover matches
|
local base lbuf compgen fzf_opts suffix tail dir leftover matches
|
||||||
base=$1
|
base=$1
|
||||||
lbuf=$2
|
lbuf=$2
|
||||||
cmd=$(__fzf_extract_command "$lbuf")
|
|
||||||
compgen=$3
|
compgen=$3
|
||||||
fzf_opts=$4
|
fzf_opts=$4
|
||||||
suffix=$5
|
suffix=$5
|
||||||
@@ -161,7 +154,7 @@ __fzf_generic_path_completion() {
|
|||||||
FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse --scheme=path" "${FZF_COMPLETION_OPTS-}")
|
FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse --scheme=path" "${FZF_COMPLETION_OPTS-}")
|
||||||
unset FZF_DEFAULT_COMMAND FZF_DEFAULT_OPTS_FILE
|
unset FZF_DEFAULT_COMMAND FZF_DEFAULT_OPTS_FILE
|
||||||
if declare -f "$compgen" > /dev/null; then
|
if declare -f "$compgen" > /dev/null; then
|
||||||
eval "$compgen $(printf %q "$dir")" | __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover"
|
eval "$compgen $(printf %q "$dir")" | __fzf_comprun "$cmd_word" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover"
|
||||||
else
|
else
|
||||||
if [[ $compgen =~ dir ]]; then
|
if [[ $compgen =~ dir ]]; then
|
||||||
walker=dir,follow
|
walker=dir,follow
|
||||||
@@ -170,7 +163,7 @@ __fzf_generic_path_completion() {
|
|||||||
walker=file,dir,follow,hidden
|
walker=file,dir,follow,hidden
|
||||||
rest=${FZF_COMPLETION_PATH_OPTS-}
|
rest=${FZF_COMPLETION_PATH_OPTS-}
|
||||||
fi
|
fi
|
||||||
__fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" --walker "$walker" --walker-root="$dir" ${(Q)${(Z+n+)rest}} < /dev/tty
|
__fzf_comprun "$cmd_word" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" --walker "$walker" --walker-root="$dir" ${(Q)${(Z+n+)rest}} < /dev/tty
|
||||||
fi | while read -r item; do
|
fi | while read -r item; do
|
||||||
item="${item%$suffix}$suffix"
|
item="${item%$suffix}$suffix"
|
||||||
echo -n -E "${(q)item} "
|
echo -n -E "${(q)item} "
|
||||||
@@ -227,10 +220,9 @@ _fzf_complete() {
|
|||||||
rest=("$@")
|
rest=("$@")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local fifo lbuf cmd matches post
|
local fifo lbuf matches post
|
||||||
fifo="${TMPDIR:-/tmp}/fzf-complete-fifo-$$"
|
fifo="${TMPDIR:-/tmp}/fzf-complete-fifo-$$"
|
||||||
lbuf=${rest[0]}
|
lbuf=${rest[0]}
|
||||||
cmd=$(__fzf_extract_command "$lbuf")
|
|
||||||
post="${funcstack[1]}_post"
|
post="${funcstack[1]}_post"
|
||||||
type $post > /dev/null 2>&1 || post=cat
|
type $post > /dev/null 2>&1 || post=cat
|
||||||
|
|
||||||
@@ -238,7 +230,7 @@ _fzf_complete() {
|
|||||||
matches=$(
|
matches=$(
|
||||||
FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse" "${FZF_COMPLETION_OPTS-} $str_arg") \
|
FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse" "${FZF_COMPLETION_OPTS-} $str_arg") \
|
||||||
FZF_DEFAULT_OPTS_FILE='' \
|
FZF_DEFAULT_OPTS_FILE='' \
|
||||||
__fzf_comprun "$cmd" "${args[@]}" -q "${(Q)prefix}" < "$fifo" | $post | tr '\n' ' ')
|
__fzf_comprun "$cmd_word" "${args[@]}" -q "${(Q)prefix}" < "$fifo" | $post | tr '\n' ' ')
|
||||||
if [ -n "$matches" ]; then
|
if [ -n "$matches" ]; then
|
||||||
LBUFFER="$lbuf$matches"
|
LBUFFER="$lbuf$matches"
|
||||||
fi
|
fi
|
||||||
@@ -300,7 +292,8 @@ _fzf_complete_unalias() {
|
|||||||
_fzf_complete_kill() {
|
_fzf_complete_kill() {
|
||||||
_fzf_complete -m --header-lines=1 --no-preview --wrap -- "$@" < <(
|
_fzf_complete -m --header-lines=1 --no-preview --wrap -- "$@" < <(
|
||||||
command ps -eo user,pid,ppid,start,time,command 2> /dev/null ||
|
command ps -eo user,pid,ppid,start,time,command 2> /dev/null ||
|
||||||
command ps -eo user,pid,ppid,time,args # For BusyBox
|
command ps -eo user,pid,ppid,time,args 2> /dev/null || # For BusyBox
|
||||||
|
command ps --everyone --full --windows # For cygwin
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,7 +302,7 @@ _fzf_complete_kill_post() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fzf-completion() {
|
fzf-completion() {
|
||||||
local tokens cmd prefix trigger tail matches lbuf d_cmds
|
local tokens prefix trigger tail matches lbuf d_cmds cursor_pos cmd_word
|
||||||
setopt localoptions noshwordsplit noksh_arrays noposixbuiltins
|
setopt localoptions noshwordsplit noksh_arrays noposixbuiltins
|
||||||
|
|
||||||
# http://zsh.sourceforge.net/FAQ/zshfaq03.html
|
# http://zsh.sourceforge.net/FAQ/zshfaq03.html
|
||||||
@@ -320,11 +313,9 @@ fzf-completion() {
|
|||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cmd=$(__fzf_extract_command "$LBUFFER")
|
|
||||||
|
|
||||||
# Explicitly allow for empty trigger.
|
# Explicitly allow for empty trigger.
|
||||||
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
||||||
[ -z "$trigger" -a ${LBUFFER[-1]} = ' ' ] && tokens+=("")
|
[[ -z $trigger && ${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
|
||||||
@@ -339,16 +330,37 @@ fzf-completion() {
|
|||||||
if [ ${#tokens} -gt 1 -a "$tail" = "$trigger" ]; then
|
if [ ${#tokens} -gt 1 -a "$tail" = "$trigger" ]; then
|
||||||
d_cmds=(${=FZF_COMPLETION_DIR_COMMANDS-cd pushd rmdir})
|
d_cmds=(${=FZF_COMPLETION_DIR_COMMANDS-cd pushd rmdir})
|
||||||
|
|
||||||
|
{
|
||||||
|
cursor_pos=$CURSOR
|
||||||
|
# Move the cursor before the trigger to preserve word array elements when
|
||||||
|
# trigger chars like ';' or '`' would otherwise reset the 'words' array.
|
||||||
|
CURSOR=$((cursor_pos - ${#trigger} - 1))
|
||||||
|
# Check if at least one completion system (old or new) is active.
|
||||||
|
# If at least one user-defined completion widget is detected, nothing will
|
||||||
|
# be completed if neither the old nor the new completion system is enabled.
|
||||||
|
# In such cases, the 'zsh/compctl' module is loaded as a fallback.
|
||||||
|
if ! zmodload -F zsh/parameter p:functions 2>/dev/null || ! (( ${+functions[compdef]} )); then
|
||||||
|
zmodload -F zsh/compctl 2>/dev/null
|
||||||
|
fi
|
||||||
|
# Create a completion widget to access the 'words' array (man zshcompwid)
|
||||||
|
zle -C __fzf_extract_command .complete-word __fzf_extract_command
|
||||||
|
zle __fzf_extract_command
|
||||||
|
} always {
|
||||||
|
CURSOR=$cursor_pos
|
||||||
|
# Delete the completion widget
|
||||||
|
zle -D __fzf_extract_command 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
[ -z "$trigger" ] && prefix=${tokens[-1]} || prefix=${tokens[-1]:0:-${#trigger}}
|
[ -z "$trigger" ] && prefix=${tokens[-1]} || prefix=${tokens[-1]:0:-${#trigger}}
|
||||||
if [[ $prefix = *'$('* ]] || [[ $prefix = *'<('* ]] || [[ $prefix = *'>('* ]] || [[ $prefix = *':='* ]] || [[ $prefix = *'`'* ]]; then
|
if [[ $prefix = *'$('* ]] || [[ $prefix = *'<('* ]] || [[ $prefix = *'>('* ]] || [[ $prefix = *':='* ]] || [[ $prefix = *'`'* ]]; then
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
[ -n "${tokens[-1]}" ] && lbuf=${lbuf:0:-${#tokens[-1]}}
|
[ -n "${tokens[-1]}" ] && lbuf=${lbuf:0:-${#tokens[-1]}}
|
||||||
|
|
||||||
if eval "type _fzf_complete_${cmd} > /dev/null"; then
|
if eval "noglob type _fzf_complete_${cmd_word} >/dev/null"; then
|
||||||
prefix="$prefix" eval _fzf_complete_${cmd} ${(q)lbuf}
|
prefix="$prefix" eval _fzf_complete_${cmd_word} ${(q)lbuf}
|
||||||
zle reset-prompt
|
zle reset-prompt
|
||||||
elif [ ${d_cmds[(i)$cmd]} -le ${#d_cmds} ]; then
|
elif [ ${d_cmds[(i)$cmd_word]} -le ${#d_cmds} ]; then
|
||||||
_fzf_dir_completion "$prefix" "$lbuf"
|
_fzf_dir_completion "$prefix" "$lbuf"
|
||||||
else
|
else
|
||||||
_fzf_path_completion "$prefix" "$lbuf"
|
_fzf_path_completion "$prefix" "$lbuf"
|
||||||
@@ -365,6 +377,7 @@ fzf-completion() {
|
|||||||
unset binding
|
unset binding
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Normal widget
|
||||||
zle -N fzf-completion
|
zle -N fzf-completion
|
||||||
bindkey '^I' fzf-completion
|
bindkey '^I' fzf-completion
|
||||||
fi
|
fi
|
||||||
|
@@ -11,8 +11,6 @@
|
|||||||
# - $FZF_ALT_C_COMMAND
|
# - $FZF_ALT_C_COMMAND
|
||||||
# - $FZF_ALT_C_OPTS
|
# - $FZF_ALT_C_OPTS
|
||||||
|
|
||||||
status is-interactive; or exit 0
|
|
||||||
|
|
||||||
|
|
||||||
# Key bindings
|
# Key bindings
|
||||||
# ------------
|
# ------------
|
||||||
@@ -23,7 +21,7 @@ function fzf_key_bindings
|
|||||||
# $2: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
# $2: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
||||||
echo "--height $FZF_TMUX_HEIGHT --bind=ctrl-z:ignore" $argv[1]
|
echo "--height $FZF_TMUX_HEIGHT --bind=ctrl-z:ignore" $argv[1]
|
||||||
command cat "$FZF_DEFAULT_OPTS_FILE" 2> /dev/null
|
test -r "$FZF_DEFAULT_OPTS_FILE"; and string collect -N -- <$FZF_DEFAULT_OPTS_FILE
|
||||||
echo $FZF_DEFAULT_OPTS $argv[2]
|
echo $FZF_DEFAULT_OPTS $argv[2]
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -33,15 +31,16 @@ function fzf_key_bindings
|
|||||||
set -lx dir $commandline[1]
|
set -lx dir $commandline[1]
|
||||||
set -l fzf_query $commandline[2]
|
set -l fzf_query $commandline[2]
|
||||||
set -l prefix $commandline[3]
|
set -l prefix $commandline[3]
|
||||||
|
set -l result
|
||||||
|
|
||||||
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 (__fzf_defaults "--reverse --walker=file,dir,follow,hidden --scheme=path --walker-root='$dir'" "$FZF_CTRL_T_OPTS")
|
set -lx FZF_DEFAULT_OPTS (__fzf_defaults "--reverse --walker=file,dir,follow,hidden --scheme=path --walker-root=$dir" "$FZF_CTRL_T_OPTS")
|
||||||
set -lx FZF_DEFAULT_COMMAND "$FZF_CTRL_T_COMMAND"
|
set -lx FZF_DEFAULT_COMMAND "$FZF_CTRL_T_COMMAND"
|
||||||
set -lx FZF_DEFAULT_OPTS_FILE ''
|
set -lx FZF_DEFAULT_OPTS_FILE ''
|
||||||
eval (__fzfcmd)' -m --query "'$fzf_query'"' | while read -l r; set result $result $r; end
|
set result (eval (__fzfcmd) -m --query=$fzf_query)
|
||||||
end
|
end
|
||||||
if [ -z "$result" ]
|
if test -z "$result"
|
||||||
commandline -f repaint
|
commandline -f repaint
|
||||||
return
|
return
|
||||||
else
|
else
|
||||||
@@ -50,7 +49,7 @@ function fzf_key_bindings
|
|||||||
end
|
end
|
||||||
for i in $result
|
for i in $result
|
||||||
commandline -it -- $prefix
|
commandline -it -- $prefix
|
||||||
commandline -it -- (string escape $i)
|
commandline -it -- (string escape -- $i)
|
||||||
commandline -it -- ' '
|
commandline -it -- ' '
|
||||||
end
|
end
|
||||||
commandline -f repaint
|
commandline -f repaint
|
||||||
@@ -59,33 +58,25 @@ 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 -l FISH_MAJOR (echo $version | cut -f1 -d.)
|
|
||||||
set -l FISH_MINOR (echo $version | cut -f2 -d.)
|
|
||||||
|
|
||||||
# merge history from other sessions before searching
|
# merge history from other sessions before searching
|
||||||
if test -z "$fish_private_mode"
|
test -z "$fish_private_mode"; and builtin history merge
|
||||||
builtin history merge
|
|
||||||
end
|
|
||||||
|
|
||||||
# history's -z flag is needed for multi-line support.
|
set -lx FZF_DEFAULT_OPTS (__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --wrap-sign '"\t"↳ ' --highlight-line +m $FZF_CTRL_R_OPTS")
|
||||||
# history's -z flag was added in fish 2.4.0, so don't use it for versions
|
set -lx FZF_DEFAULT_OPTS_FILE ''
|
||||||
# before 2.4.0.
|
set -lx FZF_DEFAULT_COMMAND
|
||||||
if [ "$FISH_MAJOR" -gt 2 -o \( "$FISH_MAJOR" -eq 2 -a "$FISH_MINOR" -ge 4 \) ];
|
string match -q -r -- '/fish$' $SHELL; or set -lx SHELL (type -p fish)
|
||||||
if type -P perl > /dev/null 2>&1
|
if type -q perl
|
||||||
set -lx FZF_DEFAULT_OPTS (__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --wrap-sign '"\t"↳ ' --highlight-line $FZF_CTRL_R_OPTS +m")
|
set -a FZF_DEFAULT_OPTS '--tac'
|
||||||
set -lx FZF_DEFAULT_OPTS_FILE ''
|
set FZF_DEFAULT_COMMAND 'builtin history -z --reverse | command perl -0 -pe \'s/^/$.\t/g; s/\n/\n\t/gm\''
|
||||||
builtin history -z --reverse | command perl -0 -pe 's/^/$.\t/g; s/\n/\n\t/gm' | eval (__fzfcmd) --tac --read0 --print0 -q '(commandline)' | command perl -pe 's/^\d*\t//' | read -lz result
|
|
||||||
and commandline -- $result
|
|
||||||
else
|
|
||||||
set -lx FZF_DEFAULT_OPTS (__fzf_defaults "" "--scheme=history --bind=ctrl-r:toggle-sort --wrap-sign '"\t"↳ ' --highlight-line $FZF_CTRL_R_OPTS +m")
|
|
||||||
set -lx FZF_DEFAULT_OPTS_FILE ''
|
|
||||||
builtin history -z | eval (__fzfcmd) --read0 --print0 -q '(commandline)' | read -lz result
|
|
||||||
and commandline -- $result
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
builtin history | eval (__fzfcmd) -q '(commandline)' | read -l result
|
set FZF_DEFAULT_COMMAND \
|
||||||
and commandline -- $result
|
'set -l h (builtin history -z --reverse | string split0);' \
|
||||||
|
'for i in (seq (count $h) -1 1);' \
|
||||||
|
'string join0 -- $i\t(string replace -a -- \n \n\t $h[$i] | string collect);' \
|
||||||
|
'end'
|
||||||
end
|
end
|
||||||
|
set -l result (eval "$FZF_DEFAULT_COMMAND | $(__fzfcmd) --read0 --print0 -q (commandline) --bind='enter:become:string replace -a -- \n\t \n {2..} | string collect'")
|
||||||
|
and commandline -- $result
|
||||||
end
|
end
|
||||||
commandline -f repaint
|
commandline -f repaint
|
||||||
end
|
end
|
||||||
@@ -98,12 +89,12 @@ function fzf_key_bindings
|
|||||||
|
|
||||||
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 (__fzf_defaults "--reverse --walker=dir,follow,hidden --scheme=path --walker-root='$dir'" "$FZF_ALT_C_OPTS")
|
set -lx FZF_DEFAULT_OPTS (__fzf_defaults "--reverse --walker=dir,follow,hidden --scheme=path --walker-root=$dir" "$FZF_ALT_C_OPTS")
|
||||||
set -lx FZF_DEFAULT_OPTS_FILE ''
|
set -lx FZF_DEFAULT_OPTS_FILE ''
|
||||||
set -lx FZF_DEFAULT_COMMAND "$FZF_ALT_C_COMMAND"
|
set -lx FZF_DEFAULT_COMMAND "$FZF_ALT_C_COMMAND"
|
||||||
eval (__fzfcmd)' +m --query "'$fzf_query'"' | read -l result
|
set -l result (eval (__fzfcmd) +m --query=$fzf_query)
|
||||||
|
|
||||||
if [ -n "$result" ]
|
if test -n "$result"
|
||||||
cd -- $result
|
cd -- $result
|
||||||
|
|
||||||
# Remove last token from commandline.
|
# Remove last token from commandline.
|
||||||
@@ -118,9 +109,9 @@ function fzf_key_bindings
|
|||||||
function __fzfcmd
|
function __fzfcmd
|
||||||
test -n "$FZF_TMUX"; or set FZF_TMUX 0
|
test -n "$FZF_TMUX"; or set FZF_TMUX 0
|
||||||
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
||||||
if [ -n "$FZF_TMUX_OPTS" ]
|
if test -n "$FZF_TMUX_OPTS"
|
||||||
echo "fzf-tmux $FZF_TMUX_OPTS -- "
|
echo "fzf-tmux $FZF_TMUX_OPTS -- "
|
||||||
else if [ $FZF_TMUX -eq 1 ]
|
else if test "$FZF_TMUX" = "1"
|
||||||
echo "fzf-tmux -d$FZF_TMUX_HEIGHT -- "
|
echo "fzf-tmux -d$FZF_TMUX_HEIGHT -- "
|
||||||
else
|
else
|
||||||
echo "fzf"
|
echo "fzf"
|
||||||
@@ -135,14 +126,12 @@ function fzf_key_bindings
|
|||||||
bind \ec fzf-cd-widget
|
bind \ec fzf-cd-widget
|
||||||
end
|
end
|
||||||
|
|
||||||
if bind -M insert > /dev/null 2>&1
|
bind -M insert \cr fzf-history-widget
|
||||||
bind -M insert \cr fzf-history-widget
|
if not set -q FZF_CTRL_T_COMMAND; or test -n "$FZF_CTRL_T_COMMAND"
|
||||||
if not set -q FZF_CTRL_T_COMMAND; or test -n "$FZF_CTRL_T_COMMAND"
|
bind -M insert \ct fzf-file-widget
|
||||||
bind -M insert \ct fzf-file-widget
|
end
|
||||||
end
|
if not set -q FZF_ALT_C_COMMAND; or test -n "$FZF_ALT_C_COMMAND"
|
||||||
if not set -q FZF_ALT_C_COMMAND; or test -n "$FZF_ALT_C_COMMAND"
|
bind -M insert \ec fzf-cd-widget
|
||||||
bind -M insert \ec fzf-cd-widget
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function __fzf_parse_commandline -d 'Parse the current command line token and return split of existing filepath, fzf query, and optional -option= prefix'
|
function __fzf_parse_commandline -d 'Parse the current command line token and return split of existing filepath, fzf query, and optional -option= prefix'
|
||||||
@@ -152,40 +141,53 @@ function fzf_key_bindings
|
|||||||
set -l prefix (string match -r -- '^-[^\s=]+=' $commandline)
|
set -l prefix (string match -r -- '^-[^\s=]+=' $commandline)
|
||||||
set commandline (string replace -- "$prefix" '' $commandline)
|
set commandline (string replace -- "$prefix" '' $commandline)
|
||||||
|
|
||||||
|
# Enable home directory expansion of leading ~/
|
||||||
|
set commandline (string replace -r -- '^~/' '\$HOME/' $commandline)
|
||||||
|
|
||||||
|
# escape special characters, except for the $ sign of valid variable names,
|
||||||
|
# so that after eval, the original string is returned, but with the
|
||||||
|
# variable names replaced by their values.
|
||||||
|
set commandline (string escape -n -- $commandline)
|
||||||
|
set commandline (string replace -r -a -- '\x5c\$(?=[\w])' '\$' $commandline)
|
||||||
|
|
||||||
# eval is used to do shell expansion on paths
|
# eval is used to do shell expansion on paths
|
||||||
eval set commandline $commandline
|
eval set commandline $commandline
|
||||||
|
|
||||||
if [ -z $commandline ]
|
# Combine multiple consecutive slashes into one
|
||||||
|
set commandline (string replace -r -a -- '/+' '/' $commandline)
|
||||||
|
|
||||||
|
if test -z "$commandline"
|
||||||
# Default to current directory with no --query
|
# Default to current directory with no --query
|
||||||
set dir '.'
|
set dir '.'
|
||||||
set fzf_query ''
|
set fzf_query ''
|
||||||
else
|
else
|
||||||
set dir (__fzf_get_dir $commandline)
|
set dir (__fzf_get_dir $commandline)
|
||||||
|
|
||||||
if [ "$dir" = "." -a (string sub -l 1 -- $commandline) != '.' ]
|
# BUG: on combined expressions, if a left argument is a single `!`, the
|
||||||
|
# builtin test command of fish will treat it as the ! operator. To
|
||||||
|
# overcome this, have the variable parts on the right.
|
||||||
|
if test "." = "$dir" -a "./" != (string sub -l 2 -- $commandline)
|
||||||
# if $dir is "." but commandline is not a relative path, this means no file path found
|
# if $dir is "." but commandline is not a relative path, this means no file path found
|
||||||
set fzf_query $commandline
|
set fzf_query $commandline
|
||||||
else
|
else
|
||||||
# Also remove trailing slash after dir, to "split" input properly
|
# Also remove trailing slash after dir, to "split" input properly
|
||||||
set fzf_query (string replace -r "^$dir/?" -- '' "$commandline")
|
set fzf_query (string replace -r -- "^$dir/?" '' $commandline)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
echo $dir
|
echo (string escape -- $dir)
|
||||||
echo $fzf_query
|
echo (string escape -- $fzf_query)
|
||||||
echo $prefix
|
echo $prefix
|
||||||
end
|
end
|
||||||
|
|
||||||
function __fzf_get_dir -d 'Find the longest existing filepath from input string'
|
function __fzf_get_dir -d 'Find the longest existing filepath from input string'
|
||||||
set dir $argv
|
set dir $argv
|
||||||
|
|
||||||
# Strip all trailing slashes. Ignore if $dir is root dir (/)
|
# Strip trailing slash, unless $dir is root dir (/)
|
||||||
if [ (string length -- $dir) -gt 1 ]
|
set dir (string replace -r -- '(?<!^)/$' '' $dir)
|
||||||
set dir (string replace -r '/*$' -- '' $dir)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Iteratively check if dir exists and strip tail end of path
|
# Iteratively check if dir exists and strip tail end of path
|
||||||
while [ ! -d "$dir" ]
|
while test ! -d "$dir"
|
||||||
# If path is absolute, this can keep going until ends up at /
|
# If path is absolute, this can keep going until ends up at /
|
||||||
# If path is relative, this can keep going until entire input is consumed, dirname returns "."
|
# If path is relative, this can keep going until entire input is consumed, dirname returns "."
|
||||||
set dir (dirname -- "$dir")
|
set dir (dirname -- "$dir")
|
||||||
|
@@ -108,9 +108,10 @@ fi
|
|||||||
fzf-history-widget() {
|
fzf-history-widget() {
|
||||||
local selected
|
local selected
|
||||||
setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases noglob nobash_rematch 2> /dev/null
|
setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases noglob nobash_rematch 2> /dev/null
|
||||||
# Ensure the associative history array, which maps event numbers to the full
|
# Ensure the module is loaded if not already, and the required features, such
|
||||||
# history lines, is loaded, and that Perl is installed for multi-line output.
|
# as the associative 'history' array, which maps event numbers to full history
|
||||||
if zmodload -F zsh/parameter p:history 2>/dev/null && (( ${#commands[perl]} )); then
|
# lines, are set. Also, make sure Perl is installed for multi-line output.
|
||||||
|
if zmodload -F zsh/parameter p:{commands,history} 2>/dev/null && (( ${+commands[perl]} )); then
|
||||||
selected="$(printf '%s\t%s\000' "${(kv)history[@]}" |
|
selected="$(printf '%s\t%s\000' "${(kv)history[@]}" |
|
||||||
perl -0 -ne 'if (!$seen{(/^\s*[0-9]+\**\t(.*)/s, $1)}++) { s/\n/\n\t/g; print; }' |
|
perl -0 -ne 'if (!$seen{(/^\s*[0-9]+\**\t(.*)/s, $1)}++) { s/\n/\n\t/g; print; }' |
|
||||||
FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --wrap-sign '\t↳ ' --highlight-line ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER} +m --read0") \
|
FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --wrap-sign '\t↳ ' --highlight-line ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER} +m --read0") \
|
||||||
|
@@ -25,107 +25,116 @@ func _() {
|
|||||||
_ = x[actBackwardWord-14]
|
_ = x[actBackwardWord-14]
|
||||||
_ = x[actCancel-15]
|
_ = x[actCancel-15]
|
||||||
_ = x[actChangeBorderLabel-16]
|
_ = x[actChangeBorderLabel-16]
|
||||||
_ = x[actChangeHeader-17]
|
_ = x[actChangeListLabel-17]
|
||||||
_ = x[actChangeMulti-18]
|
_ = x[actChangeInputLabel-18]
|
||||||
_ = x[actChangePreviewLabel-19]
|
_ = x[actChangeHeader-19]
|
||||||
_ = x[actChangePrompt-20]
|
_ = x[actChangeHeaderLabel-20]
|
||||||
_ = x[actChangeQuery-21]
|
_ = x[actChangeMulti-21]
|
||||||
_ = x[actClearScreen-22]
|
_ = x[actChangePreviewLabel-22]
|
||||||
_ = x[actClearQuery-23]
|
_ = x[actChangePrompt-23]
|
||||||
_ = x[actClearSelection-24]
|
_ = x[actChangeQuery-24]
|
||||||
_ = x[actClose-25]
|
_ = x[actChangeNth-25]
|
||||||
_ = x[actDeleteChar-26]
|
_ = x[actClearScreen-26]
|
||||||
_ = x[actDeleteCharEof-27]
|
_ = x[actClearQuery-27]
|
||||||
_ = x[actEndOfLine-28]
|
_ = x[actClearSelection-28]
|
||||||
_ = x[actFatal-29]
|
_ = x[actClose-29]
|
||||||
_ = x[actForwardChar-30]
|
_ = x[actDeleteChar-30]
|
||||||
_ = x[actForwardWord-31]
|
_ = x[actDeleteCharEof-31]
|
||||||
_ = x[actKillLine-32]
|
_ = x[actEndOfLine-32]
|
||||||
_ = x[actKillWord-33]
|
_ = x[actFatal-33]
|
||||||
_ = x[actUnixLineDiscard-34]
|
_ = x[actForwardChar-34]
|
||||||
_ = x[actUnixWordRubout-35]
|
_ = x[actForwardWord-35]
|
||||||
_ = x[actYank-36]
|
_ = x[actKillLine-36]
|
||||||
_ = x[actBackwardKillWord-37]
|
_ = x[actKillWord-37]
|
||||||
_ = x[actSelectAll-38]
|
_ = x[actUnixLineDiscard-38]
|
||||||
_ = x[actDeselectAll-39]
|
_ = x[actUnixWordRubout-39]
|
||||||
_ = x[actToggle-40]
|
_ = x[actYank-40]
|
||||||
_ = x[actToggleSearch-41]
|
_ = x[actBackwardKillWord-41]
|
||||||
_ = x[actToggleAll-42]
|
_ = x[actSelectAll-42]
|
||||||
_ = x[actToggleDown-43]
|
_ = x[actDeselectAll-43]
|
||||||
_ = x[actToggleUp-44]
|
_ = x[actToggle-44]
|
||||||
_ = x[actToggleIn-45]
|
_ = x[actToggleSearch-45]
|
||||||
_ = x[actToggleOut-46]
|
_ = x[actToggleAll-46]
|
||||||
_ = x[actToggleTrack-47]
|
_ = x[actToggleDown-47]
|
||||||
_ = x[actToggleTrackCurrent-48]
|
_ = x[actToggleUp-48]
|
||||||
_ = x[actToggleHeader-49]
|
_ = x[actToggleIn-49]
|
||||||
_ = x[actToggleWrap-50]
|
_ = x[actToggleOut-50]
|
||||||
_ = x[actTrackCurrent-51]
|
_ = x[actToggleTrack-51]
|
||||||
_ = x[actUntrackCurrent-52]
|
_ = x[actToggleTrackCurrent-52]
|
||||||
_ = x[actDown-53]
|
_ = x[actToggleHeader-53]
|
||||||
_ = x[actUp-54]
|
_ = x[actToggleWrap-54]
|
||||||
_ = x[actPageUp-55]
|
_ = x[actToggleMultiLine-55]
|
||||||
_ = x[actPageDown-56]
|
_ = x[actToggleHscroll-56]
|
||||||
_ = x[actPosition-57]
|
_ = x[actTrackCurrent-57]
|
||||||
_ = x[actHalfPageUp-58]
|
_ = x[actUntrackCurrent-58]
|
||||||
_ = x[actHalfPageDown-59]
|
_ = x[actDown-59]
|
||||||
_ = x[actOffsetUp-60]
|
_ = x[actUp-60]
|
||||||
_ = x[actOffsetDown-61]
|
_ = x[actPageUp-61]
|
||||||
_ = x[actOffsetMiddle-62]
|
_ = x[actPageDown-62]
|
||||||
_ = x[actJump-63]
|
_ = x[actPosition-63]
|
||||||
_ = x[actJumpAccept-64]
|
_ = x[actHalfPageUp-64]
|
||||||
_ = x[actPrintQuery-65]
|
_ = x[actHalfPageDown-65]
|
||||||
_ = x[actRefreshPreview-66]
|
_ = x[actOffsetUp-66]
|
||||||
_ = x[actReplaceQuery-67]
|
_ = x[actOffsetDown-67]
|
||||||
_ = x[actToggleSort-68]
|
_ = x[actOffsetMiddle-68]
|
||||||
_ = x[actShowPreview-69]
|
_ = x[actJump-69]
|
||||||
_ = x[actHidePreview-70]
|
_ = x[actJumpAccept-70]
|
||||||
_ = x[actTogglePreview-71]
|
_ = x[actPrintQuery-71]
|
||||||
_ = x[actTogglePreviewWrap-72]
|
_ = x[actRefreshPreview-72]
|
||||||
_ = x[actTransform-73]
|
_ = x[actReplaceQuery-73]
|
||||||
_ = x[actTransformBorderLabel-74]
|
_ = x[actToggleSort-74]
|
||||||
_ = x[actTransformHeader-75]
|
_ = x[actShowPreview-75]
|
||||||
_ = x[actTransformPreviewLabel-76]
|
_ = x[actHidePreview-76]
|
||||||
_ = x[actTransformPrompt-77]
|
_ = x[actTogglePreview-77]
|
||||||
_ = x[actTransformQuery-78]
|
_ = x[actTogglePreviewWrap-78]
|
||||||
_ = x[actPreview-79]
|
_ = x[actTransform-79]
|
||||||
_ = x[actChangePreview-80]
|
_ = x[actTransformBorderLabel-80]
|
||||||
_ = x[actChangePreviewWindow-81]
|
_ = x[actTransformListLabel-81]
|
||||||
_ = x[actPreviewTop-82]
|
_ = x[actTransformInputLabel-82]
|
||||||
_ = x[actPreviewBottom-83]
|
_ = x[actTransformHeader-83]
|
||||||
_ = x[actPreviewUp-84]
|
_ = x[actTransformHeaderLabel-84]
|
||||||
_ = x[actPreviewDown-85]
|
_ = x[actTransformPreviewLabel-85]
|
||||||
_ = x[actPreviewPageUp-86]
|
_ = x[actTransformPrompt-86]
|
||||||
_ = x[actPreviewPageDown-87]
|
_ = x[actTransformQuery-87]
|
||||||
_ = x[actPreviewHalfPageUp-88]
|
_ = x[actPreview-88]
|
||||||
_ = x[actPreviewHalfPageDown-89]
|
_ = x[actChangePreview-89]
|
||||||
_ = x[actPrevHistory-90]
|
_ = x[actChangePreviewWindow-90]
|
||||||
_ = x[actPrevSelected-91]
|
_ = x[actPreviewTop-91]
|
||||||
_ = x[actPrint-92]
|
_ = x[actPreviewBottom-92]
|
||||||
_ = x[actPut-93]
|
_ = x[actPreviewUp-93]
|
||||||
_ = x[actNextHistory-94]
|
_ = x[actPreviewDown-94]
|
||||||
_ = x[actNextSelected-95]
|
_ = x[actPreviewPageUp-95]
|
||||||
_ = x[actExecute-96]
|
_ = x[actPreviewPageDown-96]
|
||||||
_ = x[actExecuteSilent-97]
|
_ = x[actPreviewHalfPageUp-97]
|
||||||
_ = x[actExecuteMulti-98]
|
_ = x[actPreviewHalfPageDown-98]
|
||||||
_ = x[actSigStop-99]
|
_ = x[actPrevHistory-99]
|
||||||
_ = x[actFirst-100]
|
_ = x[actPrevSelected-100]
|
||||||
_ = x[actLast-101]
|
_ = x[actPrint-101]
|
||||||
_ = x[actReload-102]
|
_ = x[actPut-102]
|
||||||
_ = x[actReloadSync-103]
|
_ = x[actNextHistory-103]
|
||||||
_ = x[actDisableSearch-104]
|
_ = x[actNextSelected-104]
|
||||||
_ = x[actEnableSearch-105]
|
_ = x[actExecute-105]
|
||||||
_ = x[actSelect-106]
|
_ = x[actExecuteSilent-106]
|
||||||
_ = x[actDeselect-107]
|
_ = x[actExecuteMulti-107]
|
||||||
_ = x[actUnbind-108]
|
_ = x[actSigStop-108]
|
||||||
_ = x[actRebind-109]
|
_ = x[actFirst-109]
|
||||||
_ = x[actBecome-110]
|
_ = x[actLast-110]
|
||||||
_ = x[actShowHeader-111]
|
_ = x[actReload-111]
|
||||||
_ = x[actHideHeader-112]
|
_ = x[actReloadSync-112]
|
||||||
|
_ = x[actDisableSearch-113]
|
||||||
|
_ = x[actEnableSearch-114]
|
||||||
|
_ = x[actSelect-115]
|
||||||
|
_ = x[actDeselect-116]
|
||||||
|
_ = x[actUnbind-117]
|
||||||
|
_ = x[actRebind-118]
|
||||||
|
_ = x[actBecome-119]
|
||||||
|
_ = x[actShowHeader-120]
|
||||||
|
_ = x[actHideHeader-121]
|
||||||
}
|
}
|
||||||
|
|
||||||
const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeHeaderactChangeMultiactChangePreviewLabelactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactToggleWrapactTrackCurrentactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformHeaderactTransformPreviewLabelactTransformPromptactTransformQueryactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactBecomeactShowHeaderactHideHeader"
|
const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeListLabelactChangeInputLabelactChangeHeaderactChangeHeaderLabelactChangeMultiactChangePreviewLabelactChangePromptactChangeQueryactChangeNthactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactToggleWrapactToggleMultiLineactToggleHscrollactTrackCurrentactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformListLabelactTransformInputLabelactTransformHeaderactTransformHeaderLabelactTransformPreviewLabelactTransformPromptactTransformQueryactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactBecomeactShowHeaderactHideHeader"
|
||||||
|
|
||||||
var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 42, 50, 68, 76, 85, 102, 123, 138, 159, 183, 198, 207, 227, 242, 256, 277, 292, 306, 320, 333, 350, 358, 371, 387, 399, 407, 421, 435, 446, 457, 475, 492, 499, 518, 530, 544, 553, 568, 580, 593, 604, 615, 627, 641, 662, 677, 690, 705, 722, 729, 734, 743, 754, 765, 778, 793, 804, 817, 832, 839, 852, 865, 882, 897, 910, 924, 938, 954, 974, 986, 1009, 1027, 1051, 1069, 1086, 1096, 1112, 1134, 1147, 1163, 1175, 1189, 1205, 1223, 1243, 1265, 1279, 1294, 1302, 1308, 1322, 1337, 1347, 1363, 1378, 1388, 1396, 1403, 1412, 1425, 1441, 1456, 1465, 1476, 1485, 1494, 1503, 1516, 1529}
|
var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 42, 50, 68, 76, 85, 102, 123, 138, 159, 183, 198, 207, 227, 245, 264, 279, 299, 313, 334, 349, 363, 375, 389, 402, 419, 427, 440, 456, 468, 476, 490, 504, 515, 526, 544, 561, 568, 587, 599, 613, 622, 637, 649, 662, 673, 684, 696, 710, 731, 746, 759, 777, 793, 808, 825, 832, 837, 846, 857, 868, 881, 896, 907, 920, 935, 942, 955, 968, 985, 1000, 1013, 1027, 1041, 1057, 1077, 1089, 1112, 1133, 1155, 1173, 1196, 1220, 1238, 1255, 1265, 1281, 1303, 1316, 1332, 1344, 1358, 1374, 1392, 1412, 1434, 1448, 1463, 1471, 1477, 1491, 1506, 1516, 1532, 1547, 1557, 1565, 1572, 1581, 1594, 1610, 1625, 1634, 1645, 1654, 1663, 1672, 1685, 1698}
|
||||||
|
|
||||||
func (i actionType) String() string {
|
func (i actionType) String() string {
|
||||||
if i < 0 || i >= actionType(len(_actionType_index)-1) {
|
if i < 0 || i >= actionType(len(_actionType_index)-1) {
|
||||||
|
@@ -401,7 +401,7 @@ func debugV2(T []rune, pattern []rune, F []int32, lastIdx int, H []int16, C []in
|
|||||||
if i == 0 {
|
if i == 0 {
|
||||||
fmt.Print(" ")
|
fmt.Print(" ")
|
||||||
for j := int(f); j <= lastIdx; j++ {
|
for j := int(f); j <= lastIdx; j++ {
|
||||||
fmt.Printf(" " + string(T[j]) + " ")
|
fmt.Print(" " + string(T[j]) + " ")
|
||||||
}
|
}
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
}
|
}
|
||||||
@@ -798,6 +798,14 @@ func FuzzyMatchV1(caseSensitive bool, normalize bool, forward bool, text *util.C
|
|||||||
// The solution is much cheaper since there is only one possible alignment of
|
// The solution is much cheaper since there is only one possible alignment of
|
||||||
// the pattern.
|
// the pattern.
|
||||||
func ExactMatchNaive(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
func ExactMatchNaive(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
||||||
|
return exactMatchNaive(caseSensitive, normalize, forward, false, text, pattern, withPos, slab)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExactMatchBoundary(caseSensitive bool, normalize bool, forward bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
||||||
|
return exactMatchNaive(caseSensitive, normalize, forward, true, text, pattern, withPos, slab)
|
||||||
|
}
|
||||||
|
|
||||||
|
func exactMatchNaive(caseSensitive bool, normalize bool, forward bool, boundaryCheck bool, text *util.Chars, pattern []rune, withPos bool, slab *util.Slab) (Result, *[]int) {
|
||||||
if len(pattern) == 0 {
|
if len(pattern) == 0 {
|
||||||
return Result{0, 0, 0}, nil
|
return Result{0, 0, 0}, nil
|
||||||
}
|
}
|
||||||
@@ -832,10 +840,22 @@ func ExactMatchNaive(caseSensitive bool, normalize bool, forward bool, text *uti
|
|||||||
}
|
}
|
||||||
pidx_ := indexAt(pidx, lenPattern, forward)
|
pidx_ := indexAt(pidx, lenPattern, forward)
|
||||||
pchar := pattern[pidx_]
|
pchar := pattern[pidx_]
|
||||||
if pchar == char {
|
ok := pchar == char
|
||||||
|
if ok {
|
||||||
if pidx_ == 0 {
|
if pidx_ == 0 {
|
||||||
bonus = bonusAt(text, index_)
|
bonus = bonusAt(text, index_)
|
||||||
}
|
}
|
||||||
|
if boundaryCheck {
|
||||||
|
ok = bonus >= bonusBoundary
|
||||||
|
if ok && pidx_ == 0 {
|
||||||
|
ok = index_ == 0 || charClassOf(text.Get(index_-1)) <= charDelimiter
|
||||||
|
}
|
||||||
|
if ok && pidx_ == len(pattern)-1 {
|
||||||
|
ok = index_ == lenRunes-1 || charClassOf(text.Get(index_+1)) <= charDelimiter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
pidx++
|
pidx++
|
||||||
if pidx == lenPattern {
|
if pidx == lenPattern {
|
||||||
if bonus > bestBonus {
|
if bonus > bestBonus {
|
||||||
@@ -861,7 +881,23 @@ func ExactMatchNaive(caseSensitive bool, normalize bool, forward bool, text *uti
|
|||||||
sidx = lenRunes - (bestPos + 1)
|
sidx = lenRunes - (bestPos + 1)
|
||||||
eidx = lenRunes - (bestPos - lenPattern + 1)
|
eidx = lenRunes - (bestPos - lenPattern + 1)
|
||||||
}
|
}
|
||||||
score, _ := calculateScore(caseSensitive, normalize, text, pattern, sidx, eidx, false)
|
var score int
|
||||||
|
if boundaryCheck {
|
||||||
|
// Underscore boundaries should be ranked lower than the other types of boundaries
|
||||||
|
score = int(bonus)
|
||||||
|
deduct := int(bonus-bonusBoundary) + 1
|
||||||
|
if sidx > 0 && text.Get(sidx-1) == '_' {
|
||||||
|
score -= deduct + 1
|
||||||
|
deduct = 1
|
||||||
|
}
|
||||||
|
if eidx < lenRunes && text.Get(eidx) == '_' {
|
||||||
|
score -= deduct
|
||||||
|
}
|
||||||
|
// Add base score so that this can compete with other match types e.g. 'foo' | bar
|
||||||
|
score += scoreMatch*lenPattern + int(bonusBoundaryWhite)*(lenPattern+1)
|
||||||
|
} else {
|
||||||
|
score, _ = calculateScore(caseSensitive, normalize, text, pattern, sidx, eidx, false)
|
||||||
|
}
|
||||||
return Result{sidx, eidx, score}, nil
|
return Result{sidx, eidx, score}, nil
|
||||||
}
|
}
|
||||||
return Result{-1, -1, 0}, nil
|
return Result{-1, -1, 0}, nil
|
||||||
|
90
src/ansi.go
90
src/ansi.go
@@ -1,6 +1,7 @@
|
|||||||
package fzf
|
package fzf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
@@ -13,22 +14,28 @@ type ansiOffset struct {
|
|||||||
color ansiState
|
color ansiState
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type url struct {
|
||||||
|
uri string
|
||||||
|
params string
|
||||||
|
}
|
||||||
|
|
||||||
type ansiState struct {
|
type ansiState struct {
|
||||||
fg tui.Color
|
fg tui.Color
|
||||||
bg tui.Color
|
bg tui.Color
|
||||||
attr tui.Attr
|
attr tui.Attr
|
||||||
lbg tui.Color
|
lbg tui.Color
|
||||||
|
url *url
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ansiState) colored() bool {
|
func (s *ansiState) colored() bool {
|
||||||
return s.fg != -1 || s.bg != -1 || s.attr > 0 || s.lbg >= 0
|
return s.fg != -1 || s.bg != -1 || s.attr > 0 || s.lbg >= 0 || s.url != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ansiState) equals(t *ansiState) bool {
|
func (s *ansiState) equals(t *ansiState) bool {
|
||||||
if t == nil {
|
if t == nil {
|
||||||
return !s.colored()
|
return !s.colored()
|
||||||
}
|
}
|
||||||
return s.fg == t.fg && s.bg == t.bg && s.attr == t.attr && s.lbg == t.lbg
|
return s.fg == t.fg && s.bg == t.bg && s.attr == t.attr && s.lbg == t.lbg && s.url == t.url
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ansiState) ToString() string {
|
func (s *ansiState) ToString() string {
|
||||||
@@ -37,7 +44,7 @@ func (s *ansiState) ToString() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ret := ""
|
ret := ""
|
||||||
if s.attr&tui.Bold > 0 {
|
if s.attr&tui.Bold > 0 || s.attr&tui.BoldForce > 0 {
|
||||||
ret += "1;"
|
ret += "1;"
|
||||||
}
|
}
|
||||||
if s.attr&tui.Dim > 0 {
|
if s.attr&tui.Dim > 0 {
|
||||||
@@ -60,7 +67,11 @@ func (s *ansiState) ToString() string {
|
|||||||
}
|
}
|
||||||
ret += toAnsiString(s.fg, 30) + toAnsiString(s.bg, 40)
|
ret += toAnsiString(s.fg, 30) + toAnsiString(s.bg, 40)
|
||||||
|
|
||||||
return "\x1b[" + strings.TrimSuffix(ret, ";") + "m"
|
ret = "\x1b[" + strings.TrimSuffix(ret, ";") + "m"
|
||||||
|
if s.url != nil {
|
||||||
|
ret = fmt.Sprintf("\x1b]8;%s;%s\x1b\\%s\x1b]8;;\x1b", s.url.params, s.url.uri, ret)
|
||||||
|
}
|
||||||
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func toAnsiString(color tui.Color, offset int) string {
|
func toAnsiString(color tui.Color, offset int) string {
|
||||||
@@ -87,21 +98,30 @@ func isPrint(c uint8) bool {
|
|||||||
return '\x20' <= c && c <= '\x7e'
|
return '\x20' <= c && c <= '\x7e'
|
||||||
}
|
}
|
||||||
|
|
||||||
func matchOperatingSystemCommand(s string) int {
|
func matchOperatingSystemCommand(s string, start int) int {
|
||||||
// `\x1b][0-9][;:][[:print:]]+(?:\x1b\\\\|\x07)`
|
// `\x1b][0-9][;:][[:print:]]+(?:\x1b\\\\|\x07)`
|
||||||
// ^ match starting here
|
// ^ match starting here after the first printable character
|
||||||
//
|
//
|
||||||
i := 5 // prefix matched in nextAnsiEscapeSequence()
|
i := start // prefix matched in nextAnsiEscapeSequence()
|
||||||
for ; i < len(s) && isPrint(s[i]); i++ {
|
for ; i < len(s) && isPrint(s[i]); i++ {
|
||||||
}
|
}
|
||||||
if i < len(s) {
|
if i < len(s) {
|
||||||
if s[i] == '\x07' {
|
if s[i] == '\x07' {
|
||||||
return i + 1
|
return i + 1
|
||||||
}
|
}
|
||||||
|
// `\x1b]8;PARAMS;URI\x1b\\TITLE\x1b]8;;\x1b`
|
||||||
|
// ------
|
||||||
if s[i] == '\x1b' && i < len(s)-1 && s[i+1] == '\\' {
|
if s[i] == '\x1b' && i < len(s)-1 && s[i+1] == '\\' {
|
||||||
return i + 2
|
return i + 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// `\x1b]8;PARAMS;URI\x1b\\TITLE\x1b]8;;\x1b`
|
||||||
|
// ------------
|
||||||
|
if i < len(s) && s[:i+1] == "\x1b]8;;\x1b" {
|
||||||
|
return i + 1
|
||||||
|
}
|
||||||
|
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,7 +156,7 @@ func isCtrlSeqStart(c uint8) bool {
|
|||||||
// 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
|
||||||
@@ -171,12 +191,20 @@ Loop:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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] == ']' {
|
||||||
(s[i+3] == ';' || s[i+3] == ':') && isPrint(s[i+4]) {
|
j := 2
|
||||||
|
// \x1b][0-9]+[;:][[:print:]]+(?:\x1b\\\\|\x07)
|
||||||
|
// ------
|
||||||
|
for ; i+j < len(s) && isNumeric(s[i+j]); j++ {
|
||||||
|
}
|
||||||
|
|
||||||
if j := matchOperatingSystemCommand(s[i:]); j != -1 {
|
// \x1b][0-9]+[;:][[:print:]]+(?:\x1b\\\\|\x07)
|
||||||
return i, i + j
|
// ---------------
|
||||||
|
if j > 2 && i+j+1 < len(s) && (s[i+j] == ';' || s[i+j] == ':') && isPrint(s[i+j+1]) {
|
||||||
|
if k := matchOperatingSystemCommand(s[i:], j+2); k != -1 {
|
||||||
|
return i, i + k
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,20 +318,15 @@ func extractColor(str string, state *ansiState, proc func(string, *ansiState) bo
|
|||||||
return trimmed, nil, state
|
return trimmed, nil, state
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseAnsiCode(s string, delimiter byte) (int, byte, string) {
|
func parseAnsiCode(s string) (int, string) {
|
||||||
var remaining string
|
var remaining string
|
||||||
var i int
|
var i int
|
||||||
if delimiter == 0 {
|
// Faster than strings.IndexAny(";:")
|
||||||
// Faster than strings.IndexAny(";:")
|
i = strings.IndexByte(s, ';')
|
||||||
i = strings.IndexByte(s, ';')
|
if i < 0 {
|
||||||
if i < 0 {
|
i = strings.IndexByte(s, ':')
|
||||||
i = strings.IndexByte(s, ':')
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
i = strings.IndexByte(s, delimiter)
|
|
||||||
}
|
}
|
||||||
if i >= 0 {
|
if i >= 0 {
|
||||||
delimiter = s[i]
|
|
||||||
remaining = s[i+1:]
|
remaining = s[i+1:]
|
||||||
s = s[:i]
|
s = s[:i]
|
||||||
}
|
}
|
||||||
@@ -315,26 +338,34 @@ func parseAnsiCode(s string, delimiter byte) (int, byte, string) {
|
|||||||
for _, ch := range stringBytes(s) {
|
for _, ch := range stringBytes(s) {
|
||||||
ch -= '0'
|
ch -= '0'
|
||||||
if ch > 9 {
|
if ch > 9 {
|
||||||
return -1, delimiter, remaining
|
return -1, remaining
|
||||||
}
|
}
|
||||||
code = code*10 + int(ch)
|
code = code*10 + int(ch)
|
||||||
}
|
}
|
||||||
return code, delimiter, remaining
|
return code, remaining
|
||||||
}
|
}
|
||||||
|
|
||||||
return -1, delimiter, remaining
|
return -1, remaining
|
||||||
}
|
}
|
||||||
|
|
||||||
func interpretCode(ansiCode string, prevState *ansiState) ansiState {
|
func interpretCode(ansiCode string, prevState *ansiState) ansiState {
|
||||||
var state ansiState
|
var state ansiState
|
||||||
if prevState == nil {
|
if prevState == nil {
|
||||||
state = ansiState{-1, -1, 0, -1}
|
state = ansiState{-1, -1, 0, -1, nil}
|
||||||
} else {
|
} else {
|
||||||
state = ansiState{prevState.fg, prevState.bg, prevState.attr, prevState.lbg}
|
state = ansiState{prevState.fg, prevState.bg, prevState.attr, prevState.lbg, prevState.url}
|
||||||
}
|
}
|
||||||
if ansiCode[0] != '\x1b' || ansiCode[1] != '[' || ansiCode[len(ansiCode)-1] != 'm' {
|
if ansiCode[0] != '\x1b' || ansiCode[1] != '[' || ansiCode[len(ansiCode)-1] != 'm' {
|
||||||
if prevState != nil && strings.HasSuffix(ansiCode, "0K") {
|
if prevState != nil && strings.HasSuffix(ansiCode, "0K") {
|
||||||
state.lbg = prevState.bg
|
state.lbg = prevState.bg
|
||||||
|
} else if ansiCode == "\x1b]8;;\x1b\\" { // End of a hyperlink
|
||||||
|
state.url = nil
|
||||||
|
} else if strings.HasPrefix(ansiCode, "\x1b]8;") && strings.HasSuffix(ansiCode, "\x1b\\") {
|
||||||
|
if paramsEnd := strings.IndexRune(ansiCode[4:], ';'); paramsEnd >= 0 {
|
||||||
|
params := ansiCode[4 : 4+paramsEnd]
|
||||||
|
uri := ansiCode[5+paramsEnd : len(ansiCode)-2]
|
||||||
|
state.url = &url{uri: uri, params: params}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
@@ -350,11 +381,10 @@ func interpretCode(ansiCode string, prevState *ansiState) ansiState {
|
|||||||
state256 := 0
|
state256 := 0
|
||||||
ptr := &state.fg
|
ptr := &state.fg
|
||||||
|
|
||||||
var delimiter byte
|
|
||||||
count := 0
|
count := 0
|
||||||
for len(ansiCode) != 0 {
|
for len(ansiCode) != 0 {
|
||||||
var num int
|
var num int
|
||||||
if num, delimiter, ansiCode = parseAnsiCode(ansiCode, delimiter); num != -1 {
|
if num, ansiCode = parseAnsiCode(ansiCode); num != -1 {
|
||||||
count++
|
count++
|
||||||
switch state256 {
|
switch state256 {
|
||||||
case 0:
|
case 0:
|
||||||
|
@@ -335,6 +335,28 @@ func TestExtractColor(t *testing.T) {
|
|||||||
assert((*offsets)[0], 0, 6, 2, -1, true)
|
assert((*offsets)[0], 0, 6, 2, -1, true)
|
||||||
assert((*offsets)[1], 6, 11, 200, 100, false)
|
assert((*offsets)[1], 6, 11, 200, 100, false)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
state = nil
|
||||||
|
var color24 tui.Color = (1 << 24) + (180 << 16) + (190 << 8) + 254
|
||||||
|
src = "\x1b[1mhello \x1b[22;1;38:2:180:190:254mworld"
|
||||||
|
check(func(offsets *[]ansiOffset, state *ansiState) {
|
||||||
|
if len(*offsets) != 2 {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
if state.fg != color24 || state.attr != 1 {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
assert((*offsets)[0], 0, 6, -1, -1, true)
|
||||||
|
assert((*offsets)[1], 6, 11, color24, -1, true)
|
||||||
|
})
|
||||||
|
|
||||||
|
src = "\x1b]133;A\x1b\\hello \x1b]133;C\x1b\\world"
|
||||||
|
check(func(offsets *[]ansiOffset, state *ansiState) {
|
||||||
|
if len(*offsets) != 1 {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
assert((*offsets)[0], 0, 11, color24, -1, true)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAnsiCodeStringConversion(t *testing.T) {
|
func TestAnsiCodeStringConversion(t *testing.T) {
|
||||||
@@ -381,7 +403,7 @@ func TestParseAnsiCode(t *testing.T) {
|
|||||||
{"-2", "", -1},
|
{"-2", "", -1},
|
||||||
}
|
}
|
||||||
for _, x := range tests {
|
for _, x := range tests {
|
||||||
n, _, s := parseAnsiCode(x.In, 0)
|
n, s := parseAnsiCode(x.In)
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
@@ -58,7 +58,6 @@ const (
|
|||||||
const (
|
const (
|
||||||
EvtReadNew util.EventType = iota
|
EvtReadNew util.EventType = iota
|
||||||
EvtReadFin
|
EvtReadFin
|
||||||
EvtReadNone
|
|
||||||
EvtSearchNew
|
EvtSearchNew
|
||||||
EvtSearchProgress
|
EvtSearchProgress
|
||||||
EvtSearchFin
|
EvtSearchFin
|
||||||
|
61
src/core.go
61
src/core.go
@@ -146,8 +146,25 @@ func Run(opts *Options) (int, error) {
|
|||||||
// Process executor
|
// Process executor
|
||||||
executor := util.NewExecutor(opts.WithShell)
|
executor := util.NewExecutor(opts.WithShell)
|
||||||
|
|
||||||
|
// Terminal I/O
|
||||||
|
var terminal *Terminal
|
||||||
|
var err error
|
||||||
|
var initialEnv []string
|
||||||
|
initialReload := opts.extractReloadOnStart()
|
||||||
|
if opts.Filter == nil {
|
||||||
|
terminal, err = NewTerminal(opts, eventBox, executor)
|
||||||
|
if err != nil {
|
||||||
|
return ExitError, err
|
||||||
|
}
|
||||||
|
if len(initialReload) > 0 {
|
||||||
|
var temps []string
|
||||||
|
initialReload, temps = terminal.replacePlaceholderInInitialCommand(initialReload)
|
||||||
|
initialEnv = terminal.environ()
|
||||||
|
defer removeFiles(temps)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Reader
|
// Reader
|
||||||
reloadOnStart := opts.reloadOnStart()
|
|
||||||
streamingFilter := opts.Filter != nil && !sort && !opts.Tac && !opts.Sync
|
streamingFilter := opts.Filter != nil && !sort && !opts.Tac && !opts.Sync
|
||||||
var reader *Reader
|
var reader *Reader
|
||||||
if !streamingFilter {
|
if !streamingFilter {
|
||||||
@@ -155,12 +172,9 @@ func Run(opts *Options) (int, error) {
|
|||||||
return chunkList.Push(data)
|
return chunkList.Push(data)
|
||||||
}, eventBox, executor, opts.ReadZero, opts.Filter == nil)
|
}, eventBox, executor, opts.ReadZero, opts.Filter == nil)
|
||||||
|
|
||||||
if reloadOnStart {
|
readyChan := make(chan bool)
|
||||||
// reload or reload-sync action is bound to 'start' event, no need to start the reader
|
go reader.ReadSource(opts.Input, opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip, initialReload, initialEnv, readyChan)
|
||||||
eventBox.Set(EvtReadNone, nil)
|
<-readyChan
|
||||||
} else {
|
|
||||||
go reader.ReadSource(opts.Input, opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Matcher
|
// Matcher
|
||||||
@@ -176,11 +190,14 @@ func Run(opts *Options) (int, error) {
|
|||||||
forward = true
|
forward = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nth := opts.Nth
|
||||||
|
nthRevision := 0
|
||||||
patternCache := make(map[string]*Pattern)
|
patternCache := make(map[string]*Pattern)
|
||||||
patternBuilder := func(runes []rune) *Pattern {
|
patternBuilder := func(runes []rune) *Pattern {
|
||||||
return BuildPattern(cache, patternCache,
|
return BuildPattern(cache, patternCache,
|
||||||
opts.Fuzzy, opts.FuzzyAlgo, opts.Extended, opts.Case, opts.Normalize, forward, withPos,
|
opts.Fuzzy, opts.FuzzyAlgo, opts.Extended, opts.Case, opts.Normalize, forward, withPos,
|
||||||
opts.Filter == nil, opts.Nth, opts.Delimiter, runes)
|
opts.Filter == nil, nth, opts.Delimiter, nthRevision, runes)
|
||||||
}
|
}
|
||||||
inputRevision := revision{}
|
inputRevision := revision{}
|
||||||
snapshotRevision := revision{}
|
snapshotRevision := revision{}
|
||||||
@@ -212,7 +229,7 @@ func Run(opts *Options) (int, error) {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}, eventBox, executor, opts.ReadZero, false)
|
}, eventBox, executor, opts.ReadZero, false)
|
||||||
reader.ReadSource(opts.Input, opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip)
|
reader.ReadSource(opts.Input, opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip, initialReload, initialEnv, nil)
|
||||||
} else {
|
} else {
|
||||||
eventBox.Unwatch(EvtReadNew)
|
eventBox.Unwatch(EvtReadNew)
|
||||||
eventBox.WaitFor(EvtReadFin)
|
eventBox.WaitFor(EvtReadFin)
|
||||||
@@ -234,8 +251,7 @@ func Run(opts *Options) (int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Synchronous search
|
// Synchronous search
|
||||||
sync := opts.Sync && !reloadOnStart
|
if opts.Sync {
|
||||||
if sync {
|
|
||||||
eventBox.Unwatch(EvtReadNew)
|
eventBox.Unwatch(EvtReadNew)
|
||||||
eventBox.WaitFor(EvtReadFin)
|
eventBox.WaitFor(EvtReadFin)
|
||||||
}
|
}
|
||||||
@@ -244,18 +260,14 @@ func Run(opts *Options) (int, error) {
|
|||||||
go matcher.Loop()
|
go matcher.Loop()
|
||||||
defer matcher.Stop()
|
defer matcher.Stop()
|
||||||
|
|
||||||
// Terminal I/O
|
// Handling adaptive height
|
||||||
terminal, err := NewTerminal(opts, eventBox, executor)
|
|
||||||
if err != nil {
|
|
||||||
return ExitError, err
|
|
||||||
}
|
|
||||||
maxFit := 0 // Maximum number of items that can fit on screen
|
maxFit := 0 // Maximum number of items that can fit on screen
|
||||||
padHeight := 0
|
padHeight := 0
|
||||||
heightUnknown := opts.Height.auto
|
heightUnknown := opts.Height.auto
|
||||||
if heightUnknown {
|
if heightUnknown {
|
||||||
maxFit, padHeight = terminal.MaxFitAndPad()
|
maxFit, padHeight = terminal.MaxFitAndPad()
|
||||||
}
|
}
|
||||||
deferred := opts.Select1 || opts.Exit0 || sync
|
deferred := opts.Select1 || opts.Exit0 || opts.Sync
|
||||||
go terminal.Loop()
|
go terminal.Loop()
|
||||||
if !deferred && !heightUnknown {
|
if !deferred && !heightUnknown {
|
||||||
// Start right away
|
// Start right away
|
||||||
@@ -292,7 +304,9 @@ func Run(opts *Options) (int, error) {
|
|||||||
itemIndex = 0
|
itemIndex = 0
|
||||||
inputRevision.bumpMajor()
|
inputRevision.bumpMajor()
|
||||||
header = make([]string, 0, opts.HeaderLines)
|
header = make([]string, 0, opts.HeaderLines)
|
||||||
go reader.restart(command, environ)
|
readyChan := make(chan bool)
|
||||||
|
go reader.restart(command, environ, readyChan)
|
||||||
|
<-readyChan
|
||||||
}
|
}
|
||||||
|
|
||||||
exitCode := ExitOk
|
exitCode := ExitOk
|
||||||
@@ -322,9 +336,6 @@ func Run(opts *Options) (int, error) {
|
|||||||
err = quitSignal.err
|
err = quitSignal.err
|
||||||
stop = true
|
stop = true
|
||||||
return
|
return
|
||||||
case EvtReadNone:
|
|
||||||
reading = false
|
|
||||||
terminal.UpdateCount(0, false, nil)
|
|
||||||
case EvtReadNew, EvtReadFin:
|
case EvtReadNew, EvtReadFin:
|
||||||
if evt == EvtReadFin && nextCommand != nil {
|
if evt == EvtReadFin && nextCommand != nil {
|
||||||
restart(*nextCommand, nextEnviron)
|
restart(*nextCommand, nextEnviron)
|
||||||
@@ -365,6 +376,14 @@ func Run(opts *Options) (int, error) {
|
|||||||
command = val.command
|
command = val.command
|
||||||
environ = val.environ
|
environ = val.environ
|
||||||
changed = val.changed
|
changed = val.changed
|
||||||
|
if val.nth != nil {
|
||||||
|
// Change nth and clear caches
|
||||||
|
nth = *val.nth
|
||||||
|
nthRevision++
|
||||||
|
patternCache = make(map[string]*Pattern)
|
||||||
|
cache.Clear()
|
||||||
|
inputRevision.bumpMinor()
|
||||||
|
}
|
||||||
if command != nil {
|
if command != nil {
|
||||||
useSnapshot = val.sync
|
useSnapshot = val.sync
|
||||||
}
|
}
|
||||||
|
@@ -6,10 +6,17 @@ import (
|
|||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type transformed struct {
|
||||||
|
// Because nth can be changed dynamically by change-nth action, we need to
|
||||||
|
// keep the revision number at the time of transformation.
|
||||||
|
revision int
|
||||||
|
tokens []Token
|
||||||
|
}
|
||||||
|
|
||||||
// Item represents each input line. 56 bytes.
|
// Item represents each input line. 56 bytes.
|
||||||
type Item struct {
|
type Item struct {
|
||||||
text util.Chars // 32 = 24 + 1 + 1 + 2 + 4
|
text util.Chars // 32 = 24 + 1 + 1 + 2 + 4
|
||||||
transformed *[]Token // 8
|
transformed *transformed // 8
|
||||||
origText *[]byte // 8
|
origText *[]byte // 8
|
||||||
colors *[]ansiOffset // 8
|
colors *[]ansiOffset // 8
|
||||||
}
|
}
|
||||||
|
@@ -102,7 +102,7 @@ func (m *Matcher) Loop() {
|
|||||||
if !cacheCleared {
|
if !cacheCleared {
|
||||||
if count == prevCount {
|
if count == prevCount {
|
||||||
// Look up mergerCache
|
// Look up mergerCache
|
||||||
if cached, found := m.mergerCache[patternString]; found {
|
if cached, found := m.mergerCache[patternString]; found && cached.final == request.final {
|
||||||
merger = cached
|
merger = cached
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
1333
src/options.go
1333
src/options.go
File diff suppressed because it is too large
Load Diff
@@ -9,9 +9,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestDelimiterRegex(t *testing.T) {
|
func TestDelimiterRegex(t *testing.T) {
|
||||||
// Valid regex
|
// Valid regex, but a single character -> string
|
||||||
delim := delimiterRegexp(".")
|
delim := delimiterRegexp(".")
|
||||||
if delim.regex == nil || delim.str != nil {
|
if delim.regex != nil || *delim.str != "." {
|
||||||
|
t.Error(delim)
|
||||||
|
}
|
||||||
|
delim = delimiterRegexp("|")
|
||||||
|
if delim.regex != nil || *delim.str != "|" {
|
||||||
t.Error(delim)
|
t.Error(delim)
|
||||||
}
|
}
|
||||||
// Broken regex -> string
|
// Broken regex -> string
|
||||||
|
@@ -23,6 +23,7 @@ type termType int
|
|||||||
const (
|
const (
|
||||||
termFuzzy termType = iota
|
termFuzzy termType = iota
|
||||||
termExact
|
termExact
|
||||||
|
termExactBoundary
|
||||||
termPrefix
|
termPrefix
|
||||||
termSuffix
|
termSuffix
|
||||||
termEqual
|
termEqual
|
||||||
@@ -59,6 +60,7 @@ type Pattern struct {
|
|||||||
cacheKey string
|
cacheKey string
|
||||||
delimiter Delimiter
|
delimiter Delimiter
|
||||||
nth []Range
|
nth []Range
|
||||||
|
revision int
|
||||||
procFun map[termType]algo.Algo
|
procFun map[termType]algo.Algo
|
||||||
cache *ChunkCache
|
cache *ChunkCache
|
||||||
}
|
}
|
||||||
@@ -71,7 +73,7 @@ func init() {
|
|||||||
|
|
||||||
// BuildPattern builds Pattern object from the given arguments
|
// BuildPattern builds Pattern object from the given arguments
|
||||||
func BuildPattern(cache *ChunkCache, patternCache map[string]*Pattern, fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case, normalize bool, forward bool,
|
func BuildPattern(cache *ChunkCache, patternCache map[string]*Pattern, fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case, normalize bool, forward bool,
|
||||||
withPos bool, cacheable bool, nth []Range, delimiter Delimiter, runes []rune) *Pattern {
|
withPos bool, cacheable bool, nth []Range, delimiter Delimiter, revision int, runes []rune) *Pattern {
|
||||||
|
|
||||||
var asString string
|
var asString string
|
||||||
if extended {
|
if extended {
|
||||||
@@ -139,6 +141,7 @@ func BuildPattern(cache *ChunkCache, patternCache map[string]*Pattern, fuzzy boo
|
|||||||
sortable: sortable,
|
sortable: sortable,
|
||||||
cacheable: cacheable,
|
cacheable: cacheable,
|
||||||
nth: nth,
|
nth: nth,
|
||||||
|
revision: revision,
|
||||||
delimiter: delimiter,
|
delimiter: delimiter,
|
||||||
cache: cache,
|
cache: cache,
|
||||||
procFun: make(map[termType]algo.Algo)}
|
procFun: make(map[termType]algo.Algo)}
|
||||||
@@ -147,6 +150,7 @@ func BuildPattern(cache *ChunkCache, patternCache map[string]*Pattern, fuzzy boo
|
|||||||
ptr.procFun[termFuzzy] = fuzzyAlgo
|
ptr.procFun[termFuzzy] = fuzzyAlgo
|
||||||
ptr.procFun[termEqual] = algo.EqualMatch
|
ptr.procFun[termEqual] = algo.EqualMatch
|
||||||
ptr.procFun[termExact] = algo.ExactMatchNaive
|
ptr.procFun[termExact] = algo.ExactMatchNaive
|
||||||
|
ptr.procFun[termExactBoundary] = algo.ExactMatchBoundary
|
||||||
ptr.procFun[termPrefix] = algo.PrefixMatch
|
ptr.procFun[termPrefix] = algo.PrefixMatch
|
||||||
ptr.procFun[termSuffix] = algo.SuffixMatch
|
ptr.procFun[termSuffix] = algo.SuffixMatch
|
||||||
|
|
||||||
@@ -193,7 +197,10 @@ func parseTerms(fuzzy bool, caseMode Case, normalize bool, str string) []termSet
|
|||||||
text = text[:len(text)-1]
|
text = text[:len(text)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(text, "'") {
|
if len(text) > 2 && strings.HasPrefix(text, "'") && strings.HasSuffix(text, "'") {
|
||||||
|
typ = termExactBoundary
|
||||||
|
text = text[1 : len(text)-1]
|
||||||
|
} else if strings.HasPrefix(text, "'") {
|
||||||
// Flip exactness
|
// Flip exactness
|
||||||
if fuzzy && !inv {
|
if fuzzy && !inv {
|
||||||
typ = termExact
|
typ = termExact
|
||||||
@@ -388,12 +395,15 @@ func (p *Pattern) extendedMatch(item *Item, withPos bool, slab *util.Slab) ([]Of
|
|||||||
|
|
||||||
func (p *Pattern) transformInput(item *Item) []Token {
|
func (p *Pattern) transformInput(item *Item) []Token {
|
||||||
if item.transformed != nil {
|
if item.transformed != nil {
|
||||||
return *item.transformed
|
transformed := *item.transformed
|
||||||
|
if transformed.revision == p.revision {
|
||||||
|
return transformed.tokens
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tokens := Tokenize(item.text.ToString(), p.delimiter)
|
tokens := Tokenize(item.text.ToString(), p.delimiter)
|
||||||
ret := Transform(tokens, p.nth)
|
ret := Transform(tokens, p.nth)
|
||||||
item.transformed = &ret
|
item.transformed = &transformed{p.revision, ret}
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -68,7 +68,7 @@ func buildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case,
|
|||||||
withPos bool, cacheable bool, nth []Range, delimiter Delimiter, runes []rune) *Pattern {
|
withPos bool, cacheable bool, nth []Range, delimiter Delimiter, runes []rune) *Pattern {
|
||||||
return BuildPattern(NewChunkCache(), make(map[string]*Pattern),
|
return BuildPattern(NewChunkCache(), make(map[string]*Pattern),
|
||||||
fuzzy, fuzzyAlgo, extended, caseMode, normalize, forward,
|
fuzzy, fuzzyAlgo, extended, caseMode, normalize, forward,
|
||||||
withPos, cacheable, nth, delimiter, runes)
|
withPos, cacheable, nth, delimiter, 0, runes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestExact(t *testing.T) {
|
func TestExact(t *testing.T) {
|
||||||
@@ -135,12 +135,12 @@ func TestOrigTextAndTransformed(t *testing.T) {
|
|||||||
chunk.items[0] = Item{
|
chunk.items[0] = Item{
|
||||||
text: util.ToChars([]byte("junegunn")),
|
text: util.ToChars([]byte("junegunn")),
|
||||||
origText: &origBytes,
|
origText: &origBytes,
|
||||||
transformed: &trans}
|
transformed: &transformed{pattern.revision, trans}}
|
||||||
pattern.extended = extended
|
pattern.extended = extended
|
||||||
matches := pattern.matchChunk(&chunk, nil, slab) // No cache
|
matches := pattern.matchChunk(&chunk, nil, slab) // No cache
|
||||||
if !(matches[0].item.text.ToString() == "junegunn" &&
|
if !(matches[0].item.text.ToString() == "junegunn" &&
|
||||||
string(*matches[0].item.origText) == "junegunn.choi" &&
|
string(*matches[0].item.origText) == "junegunn.choi" &&
|
||||||
reflect.DeepEqual(*matches[0].item.transformed, trans)) {
|
reflect.DeepEqual((*matches[0].item.transformed).tokens, trans)) {
|
||||||
t.Error("Invalid match result", matches)
|
t.Error("Invalid match result", matches)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,7 +148,7 @@ func TestOrigTextAndTransformed(t *testing.T) {
|
|||||||
if !(match.item.text.ToString() == "junegunn" &&
|
if !(match.item.text.ToString() == "junegunn" &&
|
||||||
string(*match.item.origText) == "junegunn.choi" &&
|
string(*match.item.origText) == "junegunn.choi" &&
|
||||||
offsets[0][0] == 0 && offsets[0][1] == 5 &&
|
offsets[0][0] == 0 && offsets[0][1] == 5 &&
|
||||||
reflect.DeepEqual(*match.item.transformed, trans)) {
|
reflect.DeepEqual((*match.item.transformed).tokens, trans)) {
|
||||||
t.Error("Invalid match result", match, offsets, extended)
|
t.Error("Invalid match result", match, offsets, extended)
|
||||||
}
|
}
|
||||||
if !((*pos)[0] == 4 && (*pos)[1] == 0) {
|
if !((*pos)[0] == 4 && (*pos)[1] == 0) {
|
||||||
|
22
src/proxy.go
22
src/proxy.go
@@ -9,6 +9,7 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -32,7 +33,7 @@ func fifo(name string) (string, error) {
|
|||||||
return output, nil
|
return output, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runProxy(commandPrefix string, cmdBuilder func(temp string) *exec.Cmd, opts *Options, withExports bool) (int, error) {
|
func runProxy(commandPrefix string, cmdBuilder func(temp string, needBash bool) (*exec.Cmd, error), opts *Options, withExports bool) (int, error) {
|
||||||
output, err := fifo("proxy-output")
|
output, err := fifo("proxy-output")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ExitError, err
|
return ExitError, err
|
||||||
@@ -92,17 +93,28 @@ func runProxy(commandPrefix string, cmdBuilder func(temp string) *exec.Cmd, opts
|
|||||||
// To ensure that the options are processed by a POSIX-compliant shell,
|
// To ensure that the options are processed by a POSIX-compliant shell,
|
||||||
// we need to write the command to a temporary file and execute it with sh.
|
// we need to write the command to a temporary file and execute it with sh.
|
||||||
var exports []string
|
var exports []string
|
||||||
|
needBash := false
|
||||||
if withExports {
|
if withExports {
|
||||||
exports = os.Environ()
|
validIdentifier := regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*$`)
|
||||||
for idx, pairStr := range exports {
|
for _, pairStr := range os.Environ() {
|
||||||
pair := strings.SplitN(pairStr, "=", 2)
|
pair := strings.SplitN(pairStr, "=", 2)
|
||||||
exports[idx] = fmt.Sprintf("export %s=%s", pair[0], escapeSingleQuote(pair[1]))
|
if validIdentifier.MatchString(pair[0]) {
|
||||||
|
exports = append(exports, fmt.Sprintf("export %s=%s", pair[0], escapeSingleQuote(pair[1])))
|
||||||
|
} else if strings.HasPrefix(pair[0], "BASH_FUNC_") && strings.HasSuffix(pair[0], "%%") {
|
||||||
|
name := pair[0][10 : len(pair[0])-2]
|
||||||
|
exports = append(exports, name+pair[1])
|
||||||
|
exports = append(exports, "export -f "+name)
|
||||||
|
needBash = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
temp := WriteTemporaryFile(append(exports, command), "\n")
|
temp := WriteTemporaryFile(append(exports, command), "\n")
|
||||||
defer os.Remove(temp)
|
defer os.Remove(temp)
|
||||||
|
|
||||||
cmd := cmdBuilder(temp)
|
cmd, err := cmdBuilder(temp, needBash)
|
||||||
|
if err != nil {
|
||||||
|
return ExitError, err
|
||||||
|
}
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
intChan := make(chan os.Signal, 1)
|
intChan := make(chan os.Signal, 1)
|
||||||
defer close(intChan)
|
defer close(intChan)
|
||||||
|
@@ -9,7 +9,10 @@ import (
|
|||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
func sh() (string, error) {
|
func sh(bash bool) (string, error) {
|
||||||
|
if bash {
|
||||||
|
return "bash", nil
|
||||||
|
}
|
||||||
return "sh", nil
|
return "sh", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -13,12 +13,16 @@ import (
|
|||||||
|
|
||||||
var shPath atomic.Value
|
var shPath atomic.Value
|
||||||
|
|
||||||
func sh() (string, error) {
|
func sh(bash bool) (string, error) {
|
||||||
if cached := shPath.Load(); cached != nil {
|
if cached := shPath.Load(); cached != nil {
|
||||||
return cached.(string), nil
|
return cached.(string), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command("cygpath", "-w", "/usr/bin/sh")
|
name := "sh"
|
||||||
|
if bash {
|
||||||
|
name = "bash"
|
||||||
|
}
|
||||||
|
cmd := exec.Command("cygpath", "-w", "/usr/bin/"+name)
|
||||||
bytes, err := cmd.Output()
|
bytes, err := cmd.Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -31,7 +35,7 @@ func sh() (string, error) {
|
|||||||
|
|
||||||
func mkfifo(path string, mode uint32) (string, error) {
|
func mkfifo(path string, mode uint32) (string, error) {
|
||||||
m := strconv.FormatUint(uint64(mode), 8)
|
m := strconv.FormatUint(uint64(mode), 8)
|
||||||
sh, err := sh()
|
sh, err := sh(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return path, err
|
return path, err
|
||||||
}
|
}
|
||||||
@@ -43,7 +47,7 @@ func mkfifo(path string, mode uint32) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func withOutputPipe(output string, task func(io.ReadCloser)) error {
|
func withOutputPipe(output string, task func(io.ReadCloser)) error {
|
||||||
sh, err := sh()
|
sh, err := sh(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -62,7 +66,7 @@ func withOutputPipe(output string, task func(io.ReadCloser)) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func withInputPipe(input string, task func(io.WriteCloser)) error {
|
func withInputPipe(input string, task func(io.WriteCloser)) error {
|
||||||
sh, err := sh()
|
sh, err := sh(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
125
src/reader.go
125
src/reader.go
@@ -6,8 +6,8 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
@@ -25,16 +25,26 @@ type Reader struct {
|
|||||||
event int32
|
event int32
|
||||||
finChan chan bool
|
finChan chan bool
|
||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
exec *exec.Cmd
|
|
||||||
execOut io.ReadCloser
|
|
||||||
command *string
|
|
||||||
killed bool
|
killed bool
|
||||||
|
termFunc func()
|
||||||
|
command *string
|
||||||
wait bool
|
wait bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewReader returns new Reader object
|
// NewReader returns new Reader object
|
||||||
func NewReader(pusher func([]byte) bool, eventBox *util.EventBox, executor *util.Executor, delimNil bool, wait bool) *Reader {
|
func NewReader(pusher func([]byte) bool, eventBox *util.EventBox, executor *util.Executor, delimNil bool, wait bool) *Reader {
|
||||||
return &Reader{pusher, executor, eventBox, delimNil, int32(EvtReady), make(chan bool, 1), sync.Mutex{}, nil, nil, nil, false, wait}
|
return &Reader{
|
||||||
|
pusher,
|
||||||
|
executor,
|
||||||
|
eventBox,
|
||||||
|
delimNil,
|
||||||
|
int32(EvtReady),
|
||||||
|
make(chan bool, 1),
|
||||||
|
sync.Mutex{},
|
||||||
|
false,
|
||||||
|
func() { os.Stdin.Close() },
|
||||||
|
nil,
|
||||||
|
wait}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) startEventPoller() {
|
func (r *Reader) startEventPoller() {
|
||||||
@@ -80,19 +90,19 @@ func (r *Reader) fin(success bool) {
|
|||||||
func (r *Reader) terminate() {
|
func (r *Reader) terminate() {
|
||||||
r.mutex.Lock()
|
r.mutex.Lock()
|
||||||
r.killed = true
|
r.killed = true
|
||||||
if r.exec != nil && r.exec.Process != nil {
|
if r.termFunc != nil {
|
||||||
r.execOut.Close()
|
r.termFunc()
|
||||||
util.KillCommand(r.exec)
|
r.termFunc = nil
|
||||||
} else {
|
|
||||||
os.Stdin.Close()
|
|
||||||
}
|
}
|
||||||
r.mutex.Unlock()
|
r.mutex.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) restart(command commandSpec, environ []string) {
|
func (r *Reader) restart(command commandSpec, environ []string, readyChan chan bool) {
|
||||||
r.event = int32(EvtReady)
|
r.event = int32(EvtReady)
|
||||||
r.startEventPoller()
|
r.startEventPoller()
|
||||||
success := r.readFromCommand(command.command, environ)
|
success := r.readFromCommand(command.command, environ, func() {
|
||||||
|
readyChan <- true
|
||||||
|
})
|
||||||
r.fin(success)
|
r.fin(success)
|
||||||
removeFiles(command.tempFiles)
|
removeFiles(command.tempFiles)
|
||||||
}
|
}
|
||||||
@@ -111,20 +121,29 @@ func (r *Reader) readChannel(inputChan chan string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ReadSource reads data from the default command or from standard input
|
// ReadSource reads data from the default command or from standard input
|
||||||
func (r *Reader) ReadSource(inputChan chan string, root string, opts walkerOpts, ignores []string) {
|
func (r *Reader) ReadSource(inputChan chan string, roots []string, opts walkerOpts, ignores []string, initCmd string, initEnv []string, readyChan chan bool) {
|
||||||
r.startEventPoller()
|
r.startEventPoller()
|
||||||
var success bool
|
var success bool
|
||||||
|
signalReady := func() {
|
||||||
|
if readyChan != nil {
|
||||||
|
readyChan <- true
|
||||||
|
}
|
||||||
|
}
|
||||||
if inputChan != nil {
|
if inputChan != nil {
|
||||||
|
signalReady()
|
||||||
success = r.readChannel(inputChan)
|
success = r.readChannel(inputChan)
|
||||||
|
} else if len(initCmd) > 0 {
|
||||||
|
success = r.readFromCommand(initCmd, initEnv, signalReady)
|
||||||
} else if util.IsTty(os.Stdin) {
|
} else if util.IsTty(os.Stdin) {
|
||||||
cmd := os.Getenv("FZF_DEFAULT_COMMAND")
|
cmd := os.Getenv("FZF_DEFAULT_COMMAND")
|
||||||
if len(cmd) == 0 {
|
if len(cmd) == 0 {
|
||||||
success = r.readFiles(root, opts, ignores)
|
signalReady()
|
||||||
|
success = r.readFiles(roots, opts, ignores)
|
||||||
} else {
|
} else {
|
||||||
// We can't export FZF_* environment variables to the default command
|
success = r.readFromCommand(cmd, initEnv, signalReady)
|
||||||
success = r.readFromCommand(cmd, nil)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
signalReady()
|
||||||
success = r.readFromStdin()
|
success = r.readFromStdin()
|
||||||
}
|
}
|
||||||
r.fin(success)
|
r.fin(success)
|
||||||
@@ -247,14 +266,33 @@ func trimPath(path string) string {
|
|||||||
return byteString(bytes)
|
return byteString(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) readFiles(root string, opts walkerOpts, ignores []string) bool {
|
func (r *Reader) readFiles(roots []string, opts walkerOpts, ignores []string) bool {
|
||||||
r.killed = false
|
|
||||||
conf := fastwalk.Config{
|
conf := fastwalk.Config{
|
||||||
Follow: opts.follow,
|
Follow: opts.follow,
|
||||||
// Use forward slashes when running a Windows binary under WSL or MSYS
|
// Use forward slashes when running a Windows binary under WSL or MSYS
|
||||||
ToSlash: fastwalk.DefaultToSlash(),
|
ToSlash: fastwalk.DefaultToSlash(),
|
||||||
Sort: fastwalk.SortFilesFirst,
|
Sort: fastwalk.SortFilesFirst,
|
||||||
}
|
}
|
||||||
|
ignoresBase := []string{}
|
||||||
|
ignoresFull := []string{}
|
||||||
|
ignoresSuffix := []string{}
|
||||||
|
sep := string(os.PathSeparator)
|
||||||
|
for _, ignore := range ignores {
|
||||||
|
if strings.ContainsRune(ignore, os.PathSeparator) {
|
||||||
|
if strings.HasPrefix(ignore, sep) {
|
||||||
|
ignoresSuffix = append(ignoresSuffix, ignore)
|
||||||
|
} else {
|
||||||
|
// 'foo/bar' should match match
|
||||||
|
// * 'foo/bar'
|
||||||
|
// * 'baz/foo/bar'
|
||||||
|
// * but NOT 'bazfoo/bar'
|
||||||
|
ignoresFull = append(ignoresFull, ignore)
|
||||||
|
ignoresSuffix = append(ignoresSuffix, sep+ignore)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ignoresBase = append(ignoresBase, ignore)
|
||||||
|
}
|
||||||
|
}
|
||||||
fn := func(path string, de os.DirEntry, err error) error {
|
fn := func(path string, de os.DirEntry, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
@@ -264,14 +302,24 @@ func (r *Reader) readFiles(root string, opts walkerOpts, ignores []string) bool
|
|||||||
isDir := de.IsDir()
|
isDir := de.IsDir()
|
||||||
if isDir || opts.follow && isSymlinkToDir(path, de) {
|
if isDir || opts.follow && isSymlinkToDir(path, de) {
|
||||||
base := filepath.Base(path)
|
base := filepath.Base(path)
|
||||||
if !opts.hidden && base[0] == '.' {
|
if !opts.hidden && base[0] == '.' && base != ".." {
|
||||||
return filepath.SkipDir
|
return filepath.SkipDir
|
||||||
}
|
}
|
||||||
for _, ignore := range ignores {
|
for _, ignore := range ignoresBase {
|
||||||
if ignore == base {
|
if ignore == base {
|
||||||
return filepath.SkipDir
|
return filepath.SkipDir
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for _, ignore := range ignoresFull {
|
||||||
|
if ignore == path {
|
||||||
|
return filepath.SkipDir
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, ignore := range ignoresSuffix {
|
||||||
|
if strings.HasSuffix(path, ignore) {
|
||||||
|
return filepath.SkipDir
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if ((opts.file && !isDir) || (opts.dir && isDir)) && r.pusher(stringBytes(path)) {
|
if ((opts.file && !isDir) || (opts.dir && isDir)) && r.pusher(stringBytes(path)) {
|
||||||
atomic.StoreInt32(&r.event, int32(EvtReadNew))
|
atomic.StoreInt32(&r.event, int32(EvtReadNew))
|
||||||
@@ -284,34 +332,39 @@ func (r *Reader) readFiles(root string, opts walkerOpts, ignores []string) bool
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return fastwalk.Walk(&conf, root, fn) == nil
|
noerr := true
|
||||||
|
for _, root := range roots {
|
||||||
|
noerr = noerr && (fastwalk.Walk(&conf, root, fn) == nil)
|
||||||
|
}
|
||||||
|
return noerr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) readFromCommand(command string, environ []string) bool {
|
func (r *Reader) readFromCommand(command string, environ []string, signalReady func()) bool {
|
||||||
r.mutex.Lock()
|
r.mutex.Lock()
|
||||||
|
|
||||||
r.killed = false
|
r.killed = false
|
||||||
|
r.termFunc = nil
|
||||||
r.command = &command
|
r.command = &command
|
||||||
r.exec = r.executor.ExecCommand(command, true)
|
exec := r.executor.ExecCommand(command, true)
|
||||||
if environ != nil {
|
if environ != nil {
|
||||||
r.exec.Env = environ
|
exec.Env = environ
|
||||||
}
|
}
|
||||||
|
execOut, err := exec.StdoutPipe()
|
||||||
var err error
|
if err != nil || exec.Start() != nil {
|
||||||
r.execOut, err = r.exec.StdoutPipe()
|
signalReady()
|
||||||
if err != nil {
|
|
||||||
r.exec = nil
|
|
||||||
r.mutex.Unlock()
|
r.mutex.Unlock()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
err = r.exec.Start()
|
// Function to call to terminate the running command
|
||||||
if err != nil {
|
r.termFunc = func() {
|
||||||
r.exec = nil
|
execOut.Close()
|
||||||
r.mutex.Unlock()
|
util.KillCommand(exec)
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
signalReady()
|
||||||
r.mutex.Unlock()
|
r.mutex.Unlock()
|
||||||
r.feed(r.execOut)
|
|
||||||
return r.exec.Wait() == nil
|
r.feed(execOut)
|
||||||
|
return exec.Wait() == nil
|
||||||
}
|
}
|
||||||
|
@@ -23,8 +23,12 @@ func TestReadFromCommand(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Normal command
|
// Normal command
|
||||||
reader.fin(reader.readFromCommand(`echo abc&&echo def`, nil))
|
counter := 0
|
||||||
if len(strs) != 2 || strs[0] != "abc" || strs[1] != "def" {
|
ready := func() {
|
||||||
|
counter++
|
||||||
|
}
|
||||||
|
reader.fin(reader.readFromCommand(`echo abc&&echo def`, nil, ready))
|
||||||
|
if len(strs) != 2 || strs[0] != "abc" || strs[1] != "def" || counter != 1 {
|
||||||
t.Errorf("%s", strs)
|
t.Errorf("%s", strs)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,9 +52,9 @@ func TestReadFromCommand(t *testing.T) {
|
|||||||
reader.startEventPoller()
|
reader.startEventPoller()
|
||||||
|
|
||||||
// Failing command
|
// Failing command
|
||||||
reader.fin(reader.readFromCommand(`no-such-command`, nil))
|
reader.fin(reader.readFromCommand(`no-such-command`, nil, ready))
|
||||||
strs = []string{}
|
strs = []string{}
|
||||||
if len(strs) > 0 {
|
if len(strs) > 0 || counter != 2 {
|
||||||
t.Errorf("%s", strs)
|
t.Errorf("%s", strs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -16,6 +16,7 @@ type colorOffset struct {
|
|||||||
offset [2]int32
|
offset [2]int32
|
||||||
color tui.ColorPair
|
color tui.ColorPair
|
||||||
match bool
|
match bool
|
||||||
|
url *url
|
||||||
}
|
}
|
||||||
|
|
||||||
type Result struct {
|
type Result struct {
|
||||||
@@ -103,11 +104,11 @@ func minRank() Result {
|
|||||||
return Result{item: &minItem, points: [4]uint16{math.MaxUint16, 0, 0, 0}}
|
return Result{item: &minItem, points: [4]uint16{math.MaxUint16, 0, 0, 0}}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme, colBase tui.ColorPair, colMatch tui.ColorPair, current bool) []colorOffset {
|
func (result *Result) colorOffsets(matchOffsets []Offset, nthOffsets []Offset, theme *tui.ColorTheme, colBase tui.ColorPair, colMatch tui.ColorPair, attrNth tui.Attr, current bool) []colorOffset {
|
||||||
itemColors := result.item.Colors()
|
itemColors := result.item.Colors()
|
||||||
|
|
||||||
// No ANSI codes
|
// No ANSI codes
|
||||||
if len(itemColors) == 0 {
|
if len(itemColors) == 0 && len(nthOffsets) == 0 {
|
||||||
var offsets []colorOffset
|
var offsets []colorOffset
|
||||||
for _, off := range matchOffsets {
|
for _, off := range matchOffsets {
|
||||||
offsets = append(offsets, colorOffset{offset: [2]int32{off[0], off[1]}, color: colMatch, match: true})
|
offsets = append(offsets, colorOffset{offset: [2]int32{off[0], off[1]}, color: colMatch, match: true})
|
||||||
@@ -117,7 +118,7 @@ func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme,
|
|||||||
|
|
||||||
// Find max column
|
// Find max column
|
||||||
var maxCol int32
|
var maxCol int32
|
||||||
for _, off := range matchOffsets {
|
for _, off := range append(matchOffsets, nthOffsets...) {
|
||||||
if off[1] > maxCol {
|
if off[1] > maxCol {
|
||||||
maxCol = off[1]
|
maxCol = off[1]
|
||||||
}
|
}
|
||||||
@@ -128,20 +129,29 @@ func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cols := make([]int, maxCol)
|
type cellInfo struct {
|
||||||
|
index int
|
||||||
|
color bool
|
||||||
|
match bool
|
||||||
|
nth bool
|
||||||
|
}
|
||||||
|
|
||||||
|
cols := make([]cellInfo, maxCol)
|
||||||
for colorIndex, ansi := range itemColors {
|
for colorIndex, ansi := range itemColors {
|
||||||
for i := ansi.offset[0]; i < ansi.offset[1]; i++ {
|
for i := ansi.offset[0]; i < ansi.offset[1]; i++ {
|
||||||
cols[i] = colorIndex + 1 // 1-based index of itemColors
|
cols[i] = cellInfo{colorIndex, true, false, false}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, off := range matchOffsets {
|
for _, off := range matchOffsets {
|
||||||
for i := off[0]; i < off[1]; i++ {
|
for i := off[0]; i < off[1]; i++ {
|
||||||
// Negative of 1-based index of itemColors
|
cols[i].match = true
|
||||||
// - The extra -1 means highlighted
|
}
|
||||||
if cols[i] >= 0 {
|
}
|
||||||
cols[i] = cols[i]*-1 - 1
|
|
||||||
}
|
for _, off := range nthOffsets {
|
||||||
|
for i := off[0]; i < off[1]; i++ {
|
||||||
|
cols[i].nth = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,7 +161,7 @@ func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme,
|
|||||||
// ------------ ---- -- ----
|
// ------------ ---- -- ----
|
||||||
// ++++++++ ++++++++++
|
// ++++++++ ++++++++++
|
||||||
// --++++++++-- --++++++++++---
|
// --++++++++-- --++++++++++---
|
||||||
curr := 0
|
var curr cellInfo = cellInfo{0, false, false, false}
|
||||||
start := 0
|
start := 0
|
||||||
ansiToColorPair := func(ansi ansiOffset, base tui.ColorPair) tui.ColorPair {
|
ansiToColorPair := func(ansi ansiOffset, base tui.ColorPair) tui.ColorPair {
|
||||||
fg := ansi.color.fg
|
fg := ansi.color.fg
|
||||||
@@ -174,11 +184,19 @@ func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme,
|
|||||||
}
|
}
|
||||||
var colors []colorOffset
|
var colors []colorOffset
|
||||||
add := func(idx int) {
|
add := func(idx int) {
|
||||||
if curr != 0 && idx > start {
|
if (curr.color || curr.nth || curr.match) && idx > start {
|
||||||
if curr < 0 {
|
if curr.match {
|
||||||
color := colMatch
|
var color tui.ColorPair
|
||||||
if curr < -1 && theme.Colored {
|
if curr.nth {
|
||||||
origColor := ansiToColorPair(itemColors[-curr-2], colMatch)
|
color = colBase.WithAttr(attrNth).Merge(colMatch)
|
||||||
|
} else {
|
||||||
|
color = colBase.Merge(colMatch)
|
||||||
|
}
|
||||||
|
var url *url
|
||||||
|
if curr.color && theme.Colored {
|
||||||
|
ansi := itemColors[curr.index]
|
||||||
|
url = ansi.color.url
|
||||||
|
origColor := ansiToColorPair(ansi, colMatch)
|
||||||
// hl or hl+ only sets the foreground color, so colMatch is the
|
// hl or hl+ only sets the foreground color, so colMatch is the
|
||||||
// combination of either [hl and bg] or [hl+ and bg+].
|
// combination of either [hl and bg] or [hl+ and bg+].
|
||||||
//
|
//
|
||||||
@@ -189,18 +207,32 @@ func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme,
|
|||||||
// echo -e "\x1b[42mfoo\x1b[mbar" | fzf --ansi --color bg+:1,hl+:-1:underline
|
// echo -e "\x1b[42mfoo\x1b[mbar" | fzf --ansi --color bg+:1,hl+:-1:underline
|
||||||
if color.Fg().IsDefault() && origColor.HasBg() {
|
if color.Fg().IsDefault() && origColor.HasBg() {
|
||||||
color = origColor
|
color = origColor
|
||||||
|
if curr.nth {
|
||||||
|
color = color.WithAttr(attrNth)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
color = origColor.MergeNonDefault(color)
|
color = origColor.MergeNonDefault(color)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
colors = append(colors, colorOffset{
|
colors = append(colors, colorOffset{
|
||||||
offset: [2]int32{int32(start), int32(idx)}, color: color, match: true})
|
offset: [2]int32{int32(start), int32(idx)}, color: color, match: true, url: url})
|
||||||
} else {
|
} else if curr.color {
|
||||||
ansi := itemColors[curr-1]
|
ansi := itemColors[curr.index]
|
||||||
|
color := ansiToColorPair(ansi, colBase)
|
||||||
|
if curr.nth {
|
||||||
|
color = color.WithAttr(attrNth)
|
||||||
|
}
|
||||||
colors = append(colors, colorOffset{
|
colors = append(colors, colorOffset{
|
||||||
offset: [2]int32{int32(start), int32(idx)},
|
offset: [2]int32{int32(start), int32(idx)},
|
||||||
color: ansiToColorPair(ansi, colBase),
|
color: color,
|
||||||
match: false})
|
match: false,
|
||||||
|
url: ansi.color.url})
|
||||||
|
} else {
|
||||||
|
colors = append(colors, colorOffset{
|
||||||
|
offset: [2]int32{int32(start), int32(idx)},
|
||||||
|
color: colBase.WithAttr(attrNth),
|
||||||
|
match: false,
|
||||||
|
url: nil})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -124,14 +124,14 @@ func TestColorOffset(t *testing.T) {
|
|||||||
item := Result{
|
item := Result{
|
||||||
item: &Item{
|
item: &Item{
|
||||||
colors: &[]ansiOffset{
|
colors: &[]ansiOffset{
|
||||||
{[2]int32{0, 20}, ansiState{1, 5, 0, -1}},
|
{[2]int32{0, 20}, ansiState{1, 5, 0, -1, nil}},
|
||||||
{[2]int32{22, 27}, ansiState{2, 6, tui.Bold, -1}},
|
{[2]int32{22, 27}, ansiState{2, 6, tui.Bold, -1, nil}},
|
||||||
{[2]int32{30, 32}, ansiState{3, 7, 0, -1}},
|
{[2]int32{30, 32}, ansiState{3, 7, 0, -1, nil}},
|
||||||
{[2]int32{33, 40}, ansiState{4, 8, tui.Bold, -1}}}}}
|
{[2]int32{33, 40}, ansiState{4, 8, tui.Bold, -1, nil}}}}}
|
||||||
|
|
||||||
colBase := tui.NewColorPair(89, 189, tui.AttrUndefined)
|
colBase := tui.NewColorPair(89, 189, tui.AttrUndefined)
|
||||||
colMatch := tui.NewColorPair(99, 199, tui.AttrUndefined)
|
colMatch := tui.NewColorPair(99, 199, tui.AttrUndefined)
|
||||||
colors := item.colorOffsets(offsets, tui.Dark256, colBase, colMatch, true)
|
colors := item.colorOffsets(offsets, nil, tui.Dark256, colBase, colMatch, tui.AttrUndefined, true)
|
||||||
assert := func(idx int, b int32, e int32, c tui.ColorPair) {
|
assert := func(idx int, b int32, e int32, c tui.ColorPair) {
|
||||||
o := colors[idx]
|
o := colors[idx]
|
||||||
if o.offset[0] != b || o.offset[1] != e || o.color != c {
|
if o.offset[0] != b || o.offset[1] != e || o.color != c {
|
||||||
@@ -155,20 +155,30 @@ func TestColorOffset(t *testing.T) {
|
|||||||
|
|
||||||
colRegular := tui.NewColorPair(-1, -1, tui.AttrUndefined)
|
colRegular := tui.NewColorPair(-1, -1, tui.AttrUndefined)
|
||||||
colUnderline := tui.NewColorPair(-1, -1, tui.Underline)
|
colUnderline := tui.NewColorPair(-1, -1, tui.Underline)
|
||||||
colors = item.colorOffsets(offsets, tui.Dark256, colRegular, colUnderline, true)
|
|
||||||
|
|
||||||
// [{[0 5] {1 5 0}} {[5 15] {1 5 8}} {[15 20] {1 5 0}}
|
nthOffsets := []Offset{{37, 39}, {42, 45}}
|
||||||
// {[22 25] {2 6 1}} {[25 27] {2 6 9}} {[27 30] {-1 -1 8}}
|
for _, attr := range []tui.Attr{tui.AttrRegular, tui.StrikeThrough} {
|
||||||
// {[30 32] {3 7 8}} {[32 33] {-1 -1 8}} {[33 35] {4 8 9}}
|
colors = item.colorOffsets(offsets, nthOffsets, tui.Dark256, colRegular, colUnderline, attr, true)
|
||||||
// {[35 40] {4 8 1}}]
|
|
||||||
assert(0, 0, 5, tui.NewColorPair(1, 5, tui.AttrUndefined))
|
// [{[0 5] {1 5 0}} {[5 15] {1 5 8}} {[15 20] {1 5 0}}
|
||||||
assert(1, 5, 15, tui.NewColorPair(1, 5, tui.Underline))
|
// {[22 25] {2 6 1}} {[25 27] {2 6 9}} {[27 30] {-1 -1 8}}
|
||||||
assert(2, 15, 20, tui.NewColorPair(1, 5, tui.AttrUndefined))
|
// {[30 32] {3 7 8}} {[32 33] {-1 -1 8}} {[33 35] {4 8 9}}
|
||||||
assert(3, 22, 25, tui.NewColorPair(2, 6, tui.Bold))
|
// {[35 37] {4 8 1}} {[37 39] {4 8 x|1}} {[39 40] {4 8 x|1}}]
|
||||||
assert(4, 25, 27, tui.NewColorPair(2, 6, tui.Bold|tui.Underline))
|
assert(0, 0, 5, tui.NewColorPair(1, 5, tui.AttrUndefined))
|
||||||
assert(5, 27, 30, colUnderline)
|
assert(1, 5, 15, tui.NewColorPair(1, 5, tui.Underline))
|
||||||
assert(6, 30, 32, tui.NewColorPair(3, 7, tui.Underline))
|
assert(2, 15, 20, tui.NewColorPair(1, 5, tui.AttrUndefined))
|
||||||
assert(7, 32, 33, colUnderline)
|
assert(3, 22, 25, tui.NewColorPair(2, 6, tui.Bold))
|
||||||
assert(8, 33, 35, tui.NewColorPair(4, 8, tui.Bold|tui.Underline))
|
assert(4, 25, 27, tui.NewColorPair(2, 6, tui.Bold|tui.Underline))
|
||||||
assert(9, 35, 40, tui.NewColorPair(4, 8, tui.Bold))
|
assert(5, 27, 30, colUnderline)
|
||||||
|
assert(6, 30, 32, tui.NewColorPair(3, 7, tui.Underline))
|
||||||
|
assert(7, 32, 33, colUnderline)
|
||||||
|
assert(8, 33, 35, tui.NewColorPair(4, 8, tui.Bold|tui.Underline))
|
||||||
|
assert(9, 35, 37, tui.NewColorPair(4, 8, tui.Bold))
|
||||||
|
expected := tui.Bold | attr
|
||||||
|
if attr == tui.AttrRegular {
|
||||||
|
expected = tui.AttrRegular
|
||||||
|
}
|
||||||
|
assert(10, 37, 39, tui.NewColorPair(4, 8, expected))
|
||||||
|
assert(11, 39, 40, tui.NewColorPair(4, 8, tui.Bold))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
1399
src/terminal.go
1399
src/terminal.go
File diff suppressed because it is too large
Load Diff
@@ -507,6 +507,34 @@ func TestParsePlaceholder(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestExtractPassthroughs(t *testing.T) {
|
||||||
|
for _, middle := range []string{
|
||||||
|
"\x1bPtmux;\x1b\x1bbar\x1b\\",
|
||||||
|
"\x1bPtmux;\x1b\x1bbar\x1bbar\x1b\\",
|
||||||
|
"\x1b]1337;bar\x1b\\",
|
||||||
|
"\x1b]1337;bar\x1bbar\x1b\\",
|
||||||
|
"\x1b]1337;bar\a",
|
||||||
|
"\x1b_Ga=T,f=32,s=1258,v=1295,c=74,r=35,m=1\x1b\\",
|
||||||
|
"\x1b_Ga=T,f=32,s=1258,v=1295,c=74,r=35,m=1\x1b\\\r",
|
||||||
|
"\x1b_Ga=T,f=32,s=1258,v=1295,c=74,r=35,m=1\x1bbar\x1b\\\r",
|
||||||
|
"\x1b_Gm=1;AAAAAAAAA=\x1b\\",
|
||||||
|
"\x1b_Gm=1;AAAAAAAAA=\x1b\\\r",
|
||||||
|
"\x1b_Gm=1;\x1bAAAAAAAAA=\x1b\\\r",
|
||||||
|
} {
|
||||||
|
line := "foo" + middle + "baz"
|
||||||
|
loc := findPassThrough(line)
|
||||||
|
if loc == nil || line[0:loc[0]] != "foo" || line[loc[1]:] != "baz" {
|
||||||
|
t.Error("failed to find passthrough")
|
||||||
|
}
|
||||||
|
garbage := "\x1bPtmux;\x1b]1337;\x1b_Ga=\x1b]1337;bar\x1b."
|
||||||
|
line = strings.Repeat("foo"+middle+middle+"baz", 3) + garbage
|
||||||
|
passthroughs, result := extractPassThroughs(line)
|
||||||
|
if result != "foobazfoobazfoobaz"+garbage || len(passthroughs) != 6 {
|
||||||
|
t.Error("failed to extract passthroughs")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* utilities section */
|
/* utilities section */
|
||||||
|
|
||||||
// Item represents one line in fzf UI. Usually it is relative path to files and folders.
|
// Item represents one line in fzf UI. Usually it is relative path to files and folders.
|
||||||
|
@@ -20,9 +20,5 @@ func notifyStop(p *os.Process) {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
pid = pgid * -1
|
pid = pgid * -1
|
||||||
}
|
}
|
||||||
unix.Kill(pid, syscall.SIGSTOP)
|
unix.Kill(pid, syscall.SIGTSTP)
|
||||||
}
|
|
||||||
|
|
||||||
func notifyOnCont(resizeChan chan<- os.Signal) {
|
|
||||||
signal.Notify(resizeChan, syscall.SIGCONT)
|
|
||||||
}
|
}
|
||||||
|
@@ -13,7 +13,3 @@ func notifyOnResize(resizeChan chan<- os.Signal) {
|
|||||||
func notifyStop(p *os.Process) {
|
func notifyStop(p *os.Process) {
|
||||||
// NOOP
|
// NOOP
|
||||||
}
|
}
|
||||||
|
|
||||||
func notifyOnCont(resizeChan chan<- os.Signal) {
|
|
||||||
// NOOP
|
|
||||||
}
|
|
||||||
|
31
src/tmux.go
31
src/tmux.go
@@ -9,13 +9,18 @@ import (
|
|||||||
|
|
||||||
func runTmux(args []string, opts *Options) (int, error) {
|
func runTmux(args []string, opts *Options) (int, error) {
|
||||||
// Prepare arguments
|
// Prepare arguments
|
||||||
fzf := args[0]
|
fzf, rest := args[0], args[1:]
|
||||||
args = append([]string{"--bind=ctrl-z:ignore"}, args[1:]...)
|
args = []string{"--bind=ctrl-z:ignore"}
|
||||||
if opts.BorderShape == tui.BorderUndefined {
|
if !opts.Tmux.border && opts.BorderShape == tui.BorderUndefined {
|
||||||
args = append(args, "--border")
|
// We append --border option at the end, because `--style=full:STYLE`
|
||||||
|
// may have changed the default border style.
|
||||||
|
rest = append(rest, "--border")
|
||||||
|
}
|
||||||
|
if opts.Tmux.border && opts.Margin == defaultMargin() {
|
||||||
|
args = append(args, "--margin=0,1")
|
||||||
}
|
}
|
||||||
argStr := escapeSingleQuote(fzf)
|
argStr := escapeSingleQuote(fzf)
|
||||||
for _, arg := range args {
|
for _, arg := range append(args, rest...) {
|
||||||
argStr += " " + escapeSingleQuote(arg)
|
argStr += " " + escapeSingleQuote(arg)
|
||||||
}
|
}
|
||||||
argStr += ` --no-tmux --no-height`
|
argStr += ` --no-tmux --no-height`
|
||||||
@@ -33,12 +38,15 @@ func runTmux(args []string, opts *Options) (int, error) {
|
|||||||
// M Both The mouse position
|
// M Both The mouse position
|
||||||
// W Both The window position on the status line
|
// W Both The window position on the status line
|
||||||
// S -y The line above or below the status line
|
// S -y The line above or below the status line
|
||||||
tmuxArgs := []string{"display-popup", "-E", "-B", "-d", dir}
|
tmuxArgs := []string{"display-popup", "-E", "-d", dir}
|
||||||
|
if !opts.Tmux.border {
|
||||||
|
tmuxArgs = append(tmuxArgs, "-B")
|
||||||
|
}
|
||||||
switch opts.Tmux.position {
|
switch opts.Tmux.position {
|
||||||
case posUp:
|
case posUp:
|
||||||
tmuxArgs = append(tmuxArgs, "-xC", "-y0")
|
tmuxArgs = append(tmuxArgs, "-xC", "-y0")
|
||||||
case posDown:
|
case posDown:
|
||||||
tmuxArgs = append(tmuxArgs, "-xC", "-yS")
|
tmuxArgs = append(tmuxArgs, "-xC", "-y9999")
|
||||||
case posLeft:
|
case posLeft:
|
||||||
tmuxArgs = append(tmuxArgs, "-x0", "-yC")
|
tmuxArgs = append(tmuxArgs, "-x0", "-yC")
|
||||||
case posRight:
|
case posRight:
|
||||||
@@ -49,9 +57,12 @@ func runTmux(args []string, opts *Options) (int, error) {
|
|||||||
tmuxArgs = append(tmuxArgs, "-w"+opts.Tmux.width.String())
|
tmuxArgs = append(tmuxArgs, "-w"+opts.Tmux.width.String())
|
||||||
tmuxArgs = append(tmuxArgs, "-h"+opts.Tmux.height.String())
|
tmuxArgs = append(tmuxArgs, "-h"+opts.Tmux.height.String())
|
||||||
|
|
||||||
return runProxy(argStr, func(temp string) *exec.Cmd {
|
return runProxy(argStr, func(temp string, needBash bool) (*exec.Cmd, error) {
|
||||||
sh, _ := sh()
|
sh, err := sh(needBash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
tmuxArgs = append(tmuxArgs, sh, temp)
|
tmuxArgs = append(tmuxArgs, sh, temp)
|
||||||
return exec.Command("tmux", tmuxArgs...)
|
return exec.Command("tmux", tmuxArgs...), nil
|
||||||
}, opts, true)
|
}, opts, true)
|
||||||
}
|
}
|
||||||
|
@@ -18,6 +18,36 @@ type Range struct {
|
|||||||
end int
|
end int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r Range) IsFull() bool {
|
||||||
|
return r.begin == rangeEllipsis && r.end == rangeEllipsis
|
||||||
|
}
|
||||||
|
|
||||||
|
func RangesToString(ranges []Range) string {
|
||||||
|
strs := []string{}
|
||||||
|
for _, r := range ranges {
|
||||||
|
s := ""
|
||||||
|
if r.begin == rangeEllipsis && r.end == rangeEllipsis {
|
||||||
|
s = ".."
|
||||||
|
} else if r.begin == r.end {
|
||||||
|
s = strconv.Itoa(r.begin)
|
||||||
|
} else {
|
||||||
|
if r.begin != rangeEllipsis {
|
||||||
|
s += strconv.Itoa(r.begin)
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.begin != -1 {
|
||||||
|
s += ".."
|
||||||
|
if r.end != rangeEllipsis {
|
||||||
|
s += strconv.Itoa(r.end)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
strs = append(strs, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(strs, ",")
|
||||||
|
}
|
||||||
|
|
||||||
// Token contains the tokenized part of the strings and its prefix length
|
// Token contains the tokenized part of the strings and its prefix length
|
||||||
type Token struct {
|
type Token struct {
|
||||||
text *util.Chars
|
text *util.Chars
|
||||||
@@ -41,7 +71,7 @@ func (d Delimiter) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func newRange(begin int, end int) Range {
|
func newRange(begin int, end int) Range {
|
||||||
if begin == 1 {
|
if begin == 1 && end != 1 {
|
||||||
begin = rangeEllipsis
|
begin = rangeEllipsis
|
||||||
}
|
}
|
||||||
if end == -1 {
|
if end == -1 {
|
||||||
@@ -73,7 +103,7 @@ func ParseRange(str *string) (Range, bool) {
|
|||||||
}
|
}
|
||||||
begin, err1 := strconv.Atoi(ns[0])
|
begin, err1 := strconv.Atoi(ns[0])
|
||||||
end, err2 := strconv.Atoi(ns[1])
|
end, err2 := strconv.Atoi(ns[1])
|
||||||
if err1 != nil || err2 != nil || begin == 0 || end == 0 {
|
if err1 != nil || err2 != nil || begin == 0 || end == 0 || begin < 0 && end > 0 {
|
||||||
return Range{}, false
|
return Range{}, false
|
||||||
}
|
}
|
||||||
return newRange(begin, end), true
|
return newRange(begin, end), true
|
||||||
|
@@ -40,6 +40,18 @@ func TestParseRange(t *testing.T) {
|
|||||||
t.Errorf("%v", r)
|
t.Errorf("%v", r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
i := "1..3..5"
|
||||||
|
if r, ok := ParseRange(&i); ok {
|
||||||
|
t.Errorf("%v", r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
i := "-3..3"
|
||||||
|
if r, ok := ParseRange(&i); ok {
|
||||||
|
t.Errorf("%v", r)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTokenize(t *testing.T) {
|
func TestTokenize(t *testing.T) {
|
||||||
|
@@ -11,6 +11,11 @@ func HasFullscreenRenderer() bool {
|
|||||||
var DefaultBorderShape = BorderRounded
|
var DefaultBorderShape = BorderRounded
|
||||||
|
|
||||||
func (a Attr) Merge(b Attr) Attr {
|
func (a Attr) Merge(b Attr) Attr {
|
||||||
|
if b&AttrRegular > 0 {
|
||||||
|
// Only keep bold attribute set by the system
|
||||||
|
return b | (a & BoldForce)
|
||||||
|
}
|
||||||
|
|
||||||
return a | b
|
return a | b
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -18,6 +23,7 @@ const (
|
|||||||
AttrUndefined = Attr(0)
|
AttrUndefined = Attr(0)
|
||||||
AttrRegular = Attr(1 << 8)
|
AttrRegular = Attr(1 << 8)
|
||||||
AttrClear = Attr(1 << 9)
|
AttrClear = Attr(1 << 9)
|
||||||
|
BoldForce = Attr(1 << 10)
|
||||||
|
|
||||||
Bold = Attr(1)
|
Bold = Attr(1)
|
||||||
Dim = Attr(1 << 1)
|
Dim = Attr(1 << 1)
|
||||||
@@ -30,6 +36,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (r *FullscreenRenderer) Init() error { return nil }
|
func (r *FullscreenRenderer) Init() error { return nil }
|
||||||
|
func (r *FullscreenRenderer) DefaultTheme() *ColorTheme { return nil }
|
||||||
func (r *FullscreenRenderer) Resize(maxHeightFunc func(int) int) {}
|
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) {}
|
||||||
@@ -48,6 +55,6 @@ func (r *FullscreenRenderer) MaxY() int { return 0 }
|
|||||||
|
|
||||||
func (r *FullscreenRenderer) RefreshWindows(windows []Window) {}
|
func (r *FullscreenRenderer) RefreshWindows(windows []Window) {}
|
||||||
|
|
||||||
func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, preview bool, borderStyle BorderStyle) Window {
|
func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, windowType WindowType, borderStyle BorderStyle, erase bool) Window {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
148
src/tui/light.go
148
src/tui/light.go
@@ -73,11 +73,15 @@ func (r *LightRenderer) csi(code string) string {
|
|||||||
|
|
||||||
func (r *LightRenderer) flush() {
|
func (r *LightRenderer) flush() {
|
||||||
if r.queued.Len() > 0 {
|
if r.queued.Len() > 0 {
|
||||||
fmt.Fprint(r.ttyout, "\x1b[?7l\x1b[?25l"+r.queued.String()+"\x1b[?25h\x1b[?7h")
|
r.flushRaw("\x1b[?7l\x1b[?25l" + r.queued.String() + "\x1b[?25h\x1b[?7h")
|
||||||
r.queued.Reset()
|
r.queued.Reset()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) flushRaw(sequence string) {
|
||||||
|
fmt.Fprint(r.ttyout, sequence)
|
||||||
|
}
|
||||||
|
|
||||||
// Light renderer
|
// Light renderer
|
||||||
type LightRenderer struct {
|
type LightRenderer struct {
|
||||||
closed *util.AtomicBool
|
closed *util.AtomicBool
|
||||||
@@ -112,19 +116,19 @@ type LightRenderer struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type LightWindow struct {
|
type LightWindow struct {
|
||||||
renderer *LightRenderer
|
renderer *LightRenderer
|
||||||
colored bool
|
colored bool
|
||||||
preview bool
|
windowType WindowType
|
||||||
border BorderStyle
|
border BorderStyle
|
||||||
top int
|
top int
|
||||||
left int
|
left int
|
||||||
width int
|
width int
|
||||||
height int
|
height int
|
||||||
posx int
|
posx int
|
||||||
posy int
|
posy int
|
||||||
tabstop int
|
tabstop int
|
||||||
fg Color
|
fg Color
|
||||||
bg Color
|
bg Color
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLightRenderer(ttyin *os.File, theme *ColorTheme, forceBlack bool, mouse bool, tabstop int, clearOnExit bool, fullscreen bool, maxHeightFunc func(int) int) (Renderer, error) {
|
func NewLightRenderer(ttyin *os.File, theme *ColorTheme, forceBlack bool, mouse bool, tabstop int, clearOnExit bool, fullscreen bool, maxHeightFunc func(int) int) (Renderer, error) {
|
||||||
@@ -170,7 +174,6 @@ func (r *LightRenderer) Init() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
r.updateTerminalSize()
|
r.updateTerminalSize()
|
||||||
initTheme(r.theme, r.defaultTheme(), r.forceBlack)
|
|
||||||
|
|
||||||
if r.fullscreen {
|
if r.fullscreen {
|
||||||
r.smcup()
|
r.smcup()
|
||||||
@@ -655,11 +658,13 @@ func (r *LightRenderer) mouseSequence(sz *int) Event {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) smcup() {
|
func (r *LightRenderer) smcup() {
|
||||||
r.csi("?1049h")
|
r.flush()
|
||||||
|
r.flushRaw("\x1b[?1049h")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) rmcup() {
|
func (r *LightRenderer) rmcup() {
|
||||||
r.csi("?1049l")
|
r.flush()
|
||||||
|
r.flushRaw("\x1b[?1049l")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) Pause(clear bool) {
|
func (r *LightRenderer) Pause(clear bool) {
|
||||||
@@ -774,27 +779,40 @@ func (r *LightRenderer) MaxY() int {
|
|||||||
return r.height
|
return r.height
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) NewWindow(top int, left int, width int, height int, preview bool, borderStyle BorderStyle) Window {
|
func (r *LightRenderer) NewWindow(top int, left int, width int, height int, windowType WindowType, borderStyle BorderStyle, erase bool) Window {
|
||||||
|
width = util.Max(0, width)
|
||||||
|
height = util.Max(0, height)
|
||||||
w := &LightWindow{
|
w := &LightWindow{
|
||||||
renderer: r,
|
renderer: r,
|
||||||
colored: r.theme.Colored,
|
colored: r.theme.Colored,
|
||||||
preview: preview,
|
windowType: windowType,
|
||||||
border: borderStyle,
|
border: borderStyle,
|
||||||
top: top,
|
top: top,
|
||||||
left: left,
|
left: left,
|
||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
tabstop: r.tabstop,
|
tabstop: r.tabstop,
|
||||||
fg: colDefault,
|
fg: colDefault,
|
||||||
bg: colDefault}
|
bg: colDefault}
|
||||||
if preview {
|
switch windowType {
|
||||||
w.fg = r.theme.PreviewFg.Color
|
case WindowBase:
|
||||||
w.bg = r.theme.PreviewBg.Color
|
|
||||||
} else {
|
|
||||||
w.fg = r.theme.Fg.Color
|
w.fg = r.theme.Fg.Color
|
||||||
w.bg = r.theme.Bg.Color
|
w.bg = r.theme.Bg.Color
|
||||||
|
case WindowList:
|
||||||
|
w.fg = r.theme.ListFg.Color
|
||||||
|
w.bg = r.theme.ListBg.Color
|
||||||
|
case WindowInput:
|
||||||
|
w.fg = r.theme.Input.Color
|
||||||
|
w.bg = r.theme.InputBg.Color
|
||||||
|
case WindowHeader:
|
||||||
|
w.fg = r.theme.Header.Color
|
||||||
|
w.bg = r.theme.HeaderBg.Color
|
||||||
|
case WindowPreview:
|
||||||
|
w.fg = r.theme.PreviewFg.Color
|
||||||
|
w.bg = r.theme.PreviewBg.Color
|
||||||
}
|
}
|
||||||
if !w.bg.IsDefault() && w.border.shape != BorderNone {
|
if erase && !w.bg.IsDefault() && w.border.shape != BorderNone {
|
||||||
|
// fzf --color bg:blue --border --padding 1,2
|
||||||
w.Erase()
|
w.Erase()
|
||||||
}
|
}
|
||||||
w.drawBorder(false)
|
w.drawBorder(false)
|
||||||
@@ -810,6 +828,9 @@ func (w *LightWindow) DrawHBorder() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *LightWindow) drawBorder(onlyHorizontal bool) {
|
func (w *LightWindow) drawBorder(onlyHorizontal bool) {
|
||||||
|
if w.height == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
switch w.border.shape {
|
switch w.border.shape {
|
||||||
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble:
|
case BorderRounded, BorderSharp, BorderBold, BorderBlock, BorderThinBlock, BorderDouble:
|
||||||
w.drawBorderAround(onlyHorizontal)
|
w.drawBorderAround(onlyHorizontal)
|
||||||
@@ -839,7 +860,14 @@ func (w *LightWindow) drawBorder(onlyHorizontal bool) {
|
|||||||
|
|
||||||
func (w *LightWindow) drawBorderHorizontal(top, bottom bool) {
|
func (w *LightWindow) drawBorderHorizontal(top, bottom bool) {
|
||||||
color := ColBorder
|
color := ColBorder
|
||||||
if w.preview {
|
switch w.windowType {
|
||||||
|
case WindowList:
|
||||||
|
color = ColListBorder
|
||||||
|
case WindowInput:
|
||||||
|
color = ColInputBorder
|
||||||
|
case WindowHeader:
|
||||||
|
color = ColHeaderBorder
|
||||||
|
case WindowPreview:
|
||||||
color = ColPreviewBorder
|
color = ColPreviewBorder
|
||||||
}
|
}
|
||||||
hw := runeWidth(w.border.top)
|
hw := runeWidth(w.border.top)
|
||||||
@@ -857,7 +885,14 @@ func (w *LightWindow) drawBorderHorizontal(top, bottom bool) {
|
|||||||
func (w *LightWindow) drawBorderVertical(left, right bool) {
|
func (w *LightWindow) drawBorderVertical(left, right bool) {
|
||||||
vw := runeWidth(w.border.left)
|
vw := runeWidth(w.border.left)
|
||||||
color := ColBorder
|
color := ColBorder
|
||||||
if w.preview {
|
switch w.windowType {
|
||||||
|
case WindowList:
|
||||||
|
color = ColListBorder
|
||||||
|
case WindowInput:
|
||||||
|
color = ColInputBorder
|
||||||
|
case WindowHeader:
|
||||||
|
color = ColHeaderBorder
|
||||||
|
case WindowPreview:
|
||||||
color = ColPreviewBorder
|
color = ColPreviewBorder
|
||||||
}
|
}
|
||||||
for y := 0; y < w.height; y++ {
|
for y := 0; y < w.height; y++ {
|
||||||
@@ -877,7 +912,14 @@ func (w *LightWindow) drawBorderVertical(left, right bool) {
|
|||||||
func (w *LightWindow) drawBorderAround(onlyHorizontal bool) {
|
func (w *LightWindow) drawBorderAround(onlyHorizontal bool) {
|
||||||
w.Move(0, 0)
|
w.Move(0, 0)
|
||||||
color := ColBorder
|
color := ColBorder
|
||||||
if w.preview {
|
switch w.windowType {
|
||||||
|
case WindowList:
|
||||||
|
color = ColListBorder
|
||||||
|
case WindowInput:
|
||||||
|
color = ColInputBorder
|
||||||
|
case WindowHeader:
|
||||||
|
color = ColHeaderBorder
|
||||||
|
case WindowPreview:
|
||||||
color = ColPreviewBorder
|
color = ColPreviewBorder
|
||||||
}
|
}
|
||||||
hw := runeWidth(w.border.top)
|
hw := runeWidth(w.border.top)
|
||||||
@@ -929,9 +971,6 @@ func (w *LightWindow) Height() int {
|
|||||||
func (w *LightWindow) Refresh() {
|
func (w *LightWindow) Refresh() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *LightWindow) Close() {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *LightWindow) X() int {
|
func (w *LightWindow) X() int {
|
||||||
return w.posx
|
return w.posx
|
||||||
}
|
}
|
||||||
@@ -940,9 +979,16 @@ func (w *LightWindow) Y() int {
|
|||||||
return w.posy
|
return w.posy
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *LightWindow) EncloseX(x int) bool {
|
||||||
|
return x >= w.left && x < (w.left+w.width)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *LightWindow) EncloseY(y int) bool {
|
||||||
|
return y >= w.top && y < (w.top+w.height)
|
||||||
|
}
|
||||||
|
|
||||||
func (w *LightWindow) Enclose(y int, x int) bool {
|
func (w *LightWindow) Enclose(y int, x int) bool {
|
||||||
return x >= w.left && x < (w.left+w.width) &&
|
return w.EncloseX(x) && w.EncloseY(y)
|
||||||
y >= w.top && y < (w.top+w.height)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *LightWindow) Move(y int, x int) {
|
func (w *LightWindow) Move(y int, x int) {
|
||||||
@@ -965,7 +1011,7 @@ func attrCodes(attr Attr) []string {
|
|||||||
if (attr & AttrClear) > 0 {
|
if (attr & AttrClear) > 0 {
|
||||||
return codes
|
return codes
|
||||||
}
|
}
|
||||||
if (attr & Bold) > 0 {
|
if (attr&Bold) > 0 || (attr&BoldForce) > 0 {
|
||||||
codes = append(codes, "1")
|
codes = append(codes, "1")
|
||||||
}
|
}
|
||||||
if (attr & Dim) > 0 {
|
if (attr & Dim) > 0 {
|
||||||
@@ -1030,13 +1076,13 @@ func cleanse(str string) string {
|
|||||||
func (w *LightWindow) CPrint(pair ColorPair, text string) {
|
func (w *LightWindow) CPrint(pair ColorPair, text string) {
|
||||||
_, code := w.csiColor(pair.Fg(), pair.Bg(), pair.Attr())
|
_, code := w.csiColor(pair.Fg(), pair.Bg(), pair.Attr())
|
||||||
w.stderrInternal(cleanse(text), false, code)
|
w.stderrInternal(cleanse(text), false, code)
|
||||||
w.csi("m")
|
w.csi("0m")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *LightWindow) cprint2(fg Color, bg Color, attr Attr, text string) {
|
func (w *LightWindow) cprint2(fg Color, bg Color, attr Attr, text string) {
|
||||||
hasColors, code := w.csiColor(fg, bg, attr)
|
hasColors, code := w.csiColor(fg, bg, attr)
|
||||||
if hasColors {
|
if hasColors {
|
||||||
defer w.csi("m")
|
defer w.csi("0m")
|
||||||
}
|
}
|
||||||
w.stderrInternal(cleanse(text), false, code)
|
w.stderrInternal(cleanse(text), false, code)
|
||||||
}
|
}
|
||||||
@@ -1097,7 +1143,7 @@ func (w *LightWindow) fill(str string, resetCode string) FillReturn {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if w.posx+1 >= w.Width() {
|
if w.posx >= w.Width() {
|
||||||
if w.posy+1 >= w.height {
|
if w.posy+1 >= w.height {
|
||||||
return FillSuspend
|
return FillSuspend
|
||||||
}
|
}
|
||||||
@@ -1118,6 +1164,14 @@ func (w *LightWindow) setBg() string {
|
|||||||
return "\x1b[m"
|
return "\x1b[m"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *LightWindow) LinkBegin(uri string, params string) {
|
||||||
|
w.renderer.queued.WriteString("\x1b]8;" + params + ";" + uri + "\x1b\\")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *LightWindow) LinkEnd() {
|
||||||
|
w.renderer.queued.WriteString("\x1b]8;;\x1b\\")
|
||||||
|
}
|
||||||
|
|
||||||
func (w *LightWindow) Fill(text string) FillReturn {
|
func (w *LightWindow) Fill(text string) FillReturn {
|
||||||
w.Move(w.posy, w.posx)
|
w.Move(w.posy, w.posx)
|
||||||
code := w.setBg()
|
code := w.setBg()
|
||||||
@@ -1133,7 +1187,7 @@ func (w *LightWindow) CFill(fg Color, bg Color, attr Attr, text string) FillRetu
|
|||||||
bg = w.bg
|
bg = w.bg
|
||||||
}
|
}
|
||||||
if hasColors, resetCode := w.csiColor(fg, bg, attr); hasColors {
|
if hasColors, resetCode := w.csiColor(fg, bg, attr); hasColors {
|
||||||
defer w.csi("m")
|
defer w.csi("0m")
|
||||||
return w.fill(text, resetCode)
|
return w.fill(text, resetCode)
|
||||||
}
|
}
|
||||||
return w.fill(text, w.setBg())
|
return w.fill(text, w.setBg())
|
||||||
|
@@ -18,7 +18,7 @@ func IsLightRendererSupported() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) defaultTheme() *ColorTheme {
|
func (r *LightRenderer) DefaultTheme() *ColorTheme {
|
||||||
if strings.Contains(os.Getenv("TERM"), "256") {
|
if strings.Contains(os.Getenv("TERM"), "256") {
|
||||||
return Dark256
|
return Dark256
|
||||||
}
|
}
|
||||||
|
@@ -39,7 +39,7 @@ func IsLightRendererSupported() bool {
|
|||||||
return canSetVt100
|
return canSetVt100
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) defaultTheme() *ColorTheme {
|
func (r *LightRenderer) DefaultTheme() *ColorTheme {
|
||||||
// the getenv check is borrowed from here: https://github.com/gdamore/tcell/commit/0c473b86d82f68226a142e96cc5a34c5a29b3690#diff-b008fcd5e6934bf31bc3d33bf49f47d8R178:
|
// the getenv check is borrowed from here: https://github.com/gdamore/tcell/commit/0c473b86d82f68226a142e96cc5a34c5a29b3690#diff-b008fcd5e6934bf31bc3d33bf49f47d8R178:
|
||||||
if !IsLightRendererSupported() || os.Getenv("ConEmuPID") != "" || os.Getenv("TCELL_TRUECOLOR") == "disable" {
|
if !IsLightRendererSupported() || os.Getenv("ConEmuPID") != "" || os.Getenv("TCELL_TRUECOLOR") == "disable" {
|
||||||
return Default16
|
return Default16
|
||||||
|
112
src/tui/tcell.go
112
src/tui/tcell.go
@@ -4,6 +4,7 @@ package tui
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"regexp"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
@@ -39,7 +40,7 @@ type Attr int32
|
|||||||
|
|
||||||
type TcellWindow struct {
|
type TcellWindow struct {
|
||||||
color bool
|
color bool
|
||||||
preview bool
|
windowType WindowType
|
||||||
top int
|
top int
|
||||||
left int
|
left int
|
||||||
width int
|
width int
|
||||||
@@ -49,6 +50,8 @@ type TcellWindow struct {
|
|||||||
lastY int
|
lastY int
|
||||||
moveCursor bool
|
moveCursor bool
|
||||||
borderStyle BorderStyle
|
borderStyle BorderStyle
|
||||||
|
uri *string
|
||||||
|
params *string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) Top() int {
|
func (w *TcellWindow) Top() int {
|
||||||
@@ -94,6 +97,7 @@ const (
|
|||||||
AttrUndefined = Attr(0)
|
AttrUndefined = Attr(0)
|
||||||
AttrRegular = Attr(1 << 7)
|
AttrRegular = Attr(1 << 7)
|
||||||
AttrClear = Attr(1 << 8)
|
AttrClear = Attr(1 << 8)
|
||||||
|
BoldForce = Attr(1 << 10)
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *FullscreenRenderer) PassThrough(str string) {
|
func (r *FullscreenRenderer) PassThrough(str string) {
|
||||||
@@ -103,8 +107,12 @@ func (r *FullscreenRenderer) PassThrough(str string) {
|
|||||||
|
|
||||||
func (r *FullscreenRenderer) Resize(maxHeightFunc func(int) int) {}
|
func (r *FullscreenRenderer) Resize(maxHeightFunc func(int) int) {}
|
||||||
|
|
||||||
func (r *FullscreenRenderer) defaultTheme() *ColorTheme {
|
func (r *FullscreenRenderer) DefaultTheme() *ColorTheme {
|
||||||
if _screen.Colors() >= 256 {
|
s, e := r.getScreen()
|
||||||
|
if e != nil {
|
||||||
|
return Default16
|
||||||
|
}
|
||||||
|
if s.Colors() >= 256 {
|
||||||
return Dark256
|
return Dark256
|
||||||
}
|
}
|
||||||
return Default16
|
return Default16
|
||||||
@@ -134,6 +142,11 @@ func (c Color) Style() tcell.Color {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a Attr) Merge(b Attr) Attr {
|
func (a Attr) Merge(b Attr) Attr {
|
||||||
|
if b&AttrRegular > 0 {
|
||||||
|
// Only keep bold attribute set by the system
|
||||||
|
return b | (a & BoldForce)
|
||||||
|
}
|
||||||
|
|
||||||
return a | b
|
return a | b
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,8 +158,19 @@ var (
|
|||||||
_initialResize bool = true
|
_initialResize bool = true
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (r *FullscreenRenderer) getScreen() (tcell.Screen, error) {
|
||||||
|
if _screen == nil {
|
||||||
|
s, e := tcell.NewScreen()
|
||||||
|
if e != nil {
|
||||||
|
return nil, e
|
||||||
|
}
|
||||||
|
_screen = s
|
||||||
|
}
|
||||||
|
return _screen, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *FullscreenRenderer) initScreen() error {
|
func (r *FullscreenRenderer) initScreen() error {
|
||||||
s, e := tcell.NewScreen()
|
s, e := r.getScreen()
|
||||||
if e != nil {
|
if e != nil {
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
@@ -158,7 +182,6 @@ func (r *FullscreenRenderer) initScreen() error {
|
|||||||
} else {
|
} else {
|
||||||
s.DisableMouse()
|
s.DisableMouse()
|
||||||
}
|
}
|
||||||
_screen = s
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -171,7 +194,6 @@ func (r *FullscreenRenderer) Init() error {
|
|||||||
if err := r.initScreen(); err != nil {
|
if err := r.initScreen(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
initTheme(r.theme, r.defaultTheme(), r.forceBlack)
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -534,14 +556,23 @@ func (r *FullscreenRenderer) RefreshWindows(windows []Window) {
|
|||||||
_screen.Show()
|
_screen.Show()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, preview bool, borderStyle BorderStyle) Window {
|
func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, windowType WindowType, borderStyle BorderStyle, erase bool) Window {
|
||||||
normal := ColNormal
|
width = util.Max(0, width)
|
||||||
if preview {
|
height = util.Max(0, height)
|
||||||
|
normal := ColBorder
|
||||||
|
switch windowType {
|
||||||
|
case WindowList:
|
||||||
|
normal = ColNormal
|
||||||
|
case WindowHeader:
|
||||||
|
normal = ColHeader
|
||||||
|
case WindowInput:
|
||||||
|
normal = ColInput
|
||||||
|
case WindowPreview:
|
||||||
normal = ColPreview
|
normal = ColPreview
|
||||||
}
|
}
|
||||||
w := &TcellWindow{
|
w := &TcellWindow{
|
||||||
color: r.theme.Colored,
|
color: r.theme.Colored,
|
||||||
preview: preview,
|
windowType: windowType,
|
||||||
top: top,
|
top: top,
|
||||||
left: left,
|
left: left,
|
||||||
width: width,
|
width: width,
|
||||||
@@ -552,10 +583,6 @@ func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int,
|
|||||||
return w
|
return w
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) Close() {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
func fill(x, y, w, h int, n ColorPair, r rune) {
|
func fill(x, y, w, h int, n ColorPair, r rune) {
|
||||||
for ly := 0; ly <= h; ly++ {
|
for ly := 0; ly <= h; ly++ {
|
||||||
for lx := 0; lx <= w; lx++ {
|
for lx := 0; lx <= w; lx++ {
|
||||||
@@ -565,11 +592,7 @@ func fill(x, y, w, h int, n ColorPair, r rune) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) Erase() {
|
func (w *TcellWindow) Erase() {
|
||||||
if w.borderStyle.shape.HasLeft() {
|
fill(w.left, w.top, w.width-1, w.height-1, w.normal, ' ')
|
||||||
fill(w.left-1, w.top, w.width, w.height-1, w.normal, ' ')
|
|
||||||
} else {
|
|
||||||
fill(w.left, w.top, w.width-1, w.height-1, w.normal, ' ')
|
|
||||||
}
|
|
||||||
w.drawBorder(false)
|
w.drawBorder(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -578,9 +601,16 @@ func (w *TcellWindow) EraseMaybe() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *TcellWindow) EncloseX(x int) bool {
|
||||||
|
return x >= w.left && x < (w.left+w.width)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *TcellWindow) EncloseY(y int) bool {
|
||||||
|
return y >= w.top && y < (w.top+w.height)
|
||||||
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) Enclose(y int, x int) bool {
|
func (w *TcellWindow) Enclose(y int, x int) bool {
|
||||||
return x >= w.left && x < (w.left+w.width) &&
|
return w.EncloseX(x) && w.EncloseY(y)
|
||||||
y >= w.top && y < (w.top+w.height)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) Move(y int, x int) {
|
func (w *TcellWindow) Move(y int, x int) {
|
||||||
@@ -601,6 +631,16 @@ func (w *TcellWindow) Print(text string) {
|
|||||||
w.printString(text, w.normal)
|
w.printString(text, w.normal)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *TcellWindow) withUrl(style tcell.Style) tcell.Style {
|
||||||
|
if w.uri != nil {
|
||||||
|
style = style.Url(*w.uri)
|
||||||
|
if md := regexp.MustCompile(`id=([^:]+)`).FindStringSubmatch(*w.params); len(md) > 1 {
|
||||||
|
style = style.UrlId(md[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return style
|
||||||
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) printString(text string, pair ColorPair) {
|
func (w *TcellWindow) printString(text string, pair ColorPair) {
|
||||||
lx := 0
|
lx := 0
|
||||||
a := pair.Attr()
|
a := pair.Attr()
|
||||||
@@ -615,6 +655,7 @@ func (w *TcellWindow) printString(text string, pair ColorPair) {
|
|||||||
Blink(a&Attr(tcell.AttrBlink) != 0).
|
Blink(a&Attr(tcell.AttrBlink) != 0).
|
||||||
Dim(a&Attr(tcell.AttrDim) != 0)
|
Dim(a&Attr(tcell.AttrDim) != 0)
|
||||||
}
|
}
|
||||||
|
style = w.withUrl(style)
|
||||||
|
|
||||||
gr := uniseg.NewGraphemes(text)
|
gr := uniseg.NewGraphemes(text)
|
||||||
for gr.Next() {
|
for gr.Next() {
|
||||||
@@ -659,12 +700,13 @@ func (w *TcellWindow) fillString(text string, pair ColorPair) FillReturn {
|
|||||||
}
|
}
|
||||||
style = style.
|
style = style.
|
||||||
Blink(a&Attr(tcell.AttrBlink) != 0).
|
Blink(a&Attr(tcell.AttrBlink) != 0).
|
||||||
Bold(a&Attr(tcell.AttrBold) != 0).
|
Bold(a&Attr(tcell.AttrBold) != 0 || a&BoldForce != 0).
|
||||||
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).
|
StrikeThrough(a&Attr(tcell.AttrStrikeThrough) != 0).
|
||||||
Italic(a&Attr(tcell.AttrItalic) != 0)
|
Italic(a&Attr(tcell.AttrItalic) != 0)
|
||||||
|
style = w.withUrl(style)
|
||||||
|
|
||||||
gr := uniseg.NewGraphemes(text)
|
gr := uniseg.NewGraphemes(text)
|
||||||
Loop:
|
Loop:
|
||||||
@@ -716,6 +758,16 @@ func (w *TcellWindow) Fill(str string) FillReturn {
|
|||||||
return w.fillString(str, w.normal)
|
return w.fillString(str, w.normal)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *TcellWindow) LinkBegin(uri string, params string) {
|
||||||
|
w.uri = &uri
|
||||||
|
w.params = ¶ms
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *TcellWindow) LinkEnd() {
|
||||||
|
w.uri = nil
|
||||||
|
w.params = nil
|
||||||
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) CFill(fg Color, bg Color, a Attr, str string) FillReturn {
|
func (w *TcellWindow) CFill(fg Color, bg Color, a Attr, str string) FillReturn {
|
||||||
if fg == colDefault {
|
if fg == colDefault {
|
||||||
fg = w.normal.Fg()
|
fg = w.normal.Fg()
|
||||||
@@ -735,6 +787,9 @@ func (w *TcellWindow) DrawHBorder() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) drawBorder(onlyHorizontal bool) {
|
func (w *TcellWindow) drawBorder(onlyHorizontal bool) {
|
||||||
|
if w.height == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
shape := w.borderStyle.shape
|
shape := w.borderStyle.shape
|
||||||
if shape == BorderNone {
|
if shape == BorderNone {
|
||||||
return
|
return
|
||||||
@@ -747,10 +802,17 @@ func (w *TcellWindow) drawBorder(onlyHorizontal bool) {
|
|||||||
|
|
||||||
var style tcell.Style
|
var style tcell.Style
|
||||||
if w.color {
|
if w.color {
|
||||||
if w.preview {
|
switch w.windowType {
|
||||||
style = ColPreviewBorder.style()
|
case WindowBase:
|
||||||
} else {
|
|
||||||
style = ColBorder.style()
|
style = ColBorder.style()
|
||||||
|
case WindowList:
|
||||||
|
style = ColListBorder.style()
|
||||||
|
case WindowHeader:
|
||||||
|
style = ColHeaderBorder.style()
|
||||||
|
case WindowInput:
|
||||||
|
style = ColInputBorder.style()
|
||||||
|
case WindowPreview:
|
||||||
|
style = ColPreviewBorder.style()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
style = w.normal.style()
|
style = w.normal.style()
|
||||||
|
@@ -15,7 +15,7 @@ func TtyIn() (*os.File, error) {
|
|||||||
return os.Stdin, nil
|
return os.Stdin, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TtyIn on Windows returns nil
|
// TtyOut on Windows returns nil
|
||||||
func TtyOut() (*os.File, error) {
|
func TtyOut() (*os.File, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
233
src/tui/tui.go
233
src/tui/tui.go
@@ -205,10 +205,24 @@ type ColorAttr struct {
|
|||||||
Attr Attr
|
Attr Attr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a ColorAttr) IsColorDefined() bool {
|
||||||
|
return a.Color != colUndefined
|
||||||
|
}
|
||||||
|
|
||||||
func NewColorAttr() ColorAttr {
|
func NewColorAttr() ColorAttr {
|
||||||
return ColorAttr{Color: colUndefined, Attr: AttrUndefined}
|
return ColorAttr{Color: colUndefined, Attr: AttrUndefined}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a ColorAttr) Merge(other ColorAttr) ColorAttr {
|
||||||
|
if other.Color != colUndefined {
|
||||||
|
a.Color = other.Color
|
||||||
|
}
|
||||||
|
if other.Attr != AttrUndefined {
|
||||||
|
a.Attr = a.Attr.Merge(other.Attr)
|
||||||
|
}
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
colUndefined Color = -2
|
colUndefined Color = -2
|
||||||
colDefault Color = -1
|
colDefault Color = -1
|
||||||
@@ -303,6 +317,9 @@ type ColorTheme struct {
|
|||||||
Disabled ColorAttr
|
Disabled ColorAttr
|
||||||
Fg ColorAttr
|
Fg ColorAttr
|
||||||
Bg ColorAttr
|
Bg ColorAttr
|
||||||
|
ListFg ColorAttr
|
||||||
|
ListBg ColorAttr
|
||||||
|
Nth ColorAttr
|
||||||
SelectedFg ColorAttr
|
SelectedFg ColorAttr
|
||||||
SelectedBg ColorAttr
|
SelectedBg ColorAttr
|
||||||
SelectedMatch ColorAttr
|
SelectedMatch ColorAttr
|
||||||
@@ -311,6 +328,9 @@ type ColorTheme struct {
|
|||||||
DarkBg ColorAttr
|
DarkBg ColorAttr
|
||||||
Gutter ColorAttr
|
Gutter ColorAttr
|
||||||
Prompt ColorAttr
|
Prompt ColorAttr
|
||||||
|
InputBg ColorAttr
|
||||||
|
InputBorder ColorAttr
|
||||||
|
InputLabel ColorAttr
|
||||||
Match ColorAttr
|
Match ColorAttr
|
||||||
Current ColorAttr
|
Current ColorAttr
|
||||||
CurrentMatch ColorAttr
|
CurrentMatch ColorAttr
|
||||||
@@ -319,13 +339,19 @@ type ColorTheme struct {
|
|||||||
Cursor ColorAttr
|
Cursor ColorAttr
|
||||||
Marker ColorAttr
|
Marker ColorAttr
|
||||||
Header ColorAttr
|
Header ColorAttr
|
||||||
|
HeaderBg ColorAttr
|
||||||
|
HeaderBorder ColorAttr
|
||||||
|
HeaderLabel ColorAttr
|
||||||
Separator ColorAttr
|
Separator ColorAttr
|
||||||
Scrollbar ColorAttr
|
Scrollbar ColorAttr
|
||||||
Border ColorAttr
|
Border ColorAttr
|
||||||
PreviewBorder ColorAttr
|
PreviewBorder ColorAttr
|
||||||
|
PreviewLabel ColorAttr
|
||||||
PreviewScrollbar ColorAttr
|
PreviewScrollbar ColorAttr
|
||||||
BorderLabel ColorAttr
|
BorderLabel ColorAttr
|
||||||
PreviewLabel ColorAttr
|
ListLabel ColorAttr
|
||||||
|
ListBorder ColorAttr
|
||||||
|
GapLine ColorAttr
|
||||||
}
|
}
|
||||||
|
|
||||||
type Event struct {
|
type Event struct {
|
||||||
@@ -348,6 +374,7 @@ type BorderShape int
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
BorderUndefined BorderShape = iota
|
BorderUndefined BorderShape = iota
|
||||||
|
BorderLine
|
||||||
BorderNone
|
BorderNone
|
||||||
BorderRounded
|
BorderRounded
|
||||||
BorderSharp
|
BorderSharp
|
||||||
@@ -365,7 +392,7 @@ const (
|
|||||||
|
|
||||||
func (s BorderShape) HasLeft() bool {
|
func (s BorderShape) HasLeft() bool {
|
||||||
switch s {
|
switch s {
|
||||||
case BorderNone, BorderRight, BorderTop, BorderBottom, BorderHorizontal: // No Left
|
case BorderNone, BorderLine, BorderRight, BorderTop, BorderBottom, BorderHorizontal: // No Left
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@@ -373,7 +400,7 @@ func (s BorderShape) HasLeft() bool {
|
|||||||
|
|
||||||
func (s BorderShape) HasRight() bool {
|
func (s BorderShape) HasRight() bool {
|
||||||
switch s {
|
switch s {
|
||||||
case BorderNone, BorderLeft, BorderTop, BorderBottom, BorderHorizontal: // No right
|
case BorderNone, BorderLine, BorderLeft, BorderTop, BorderBottom, BorderHorizontal: // No right
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@@ -381,12 +408,24 @@ func (s BorderShape) HasRight() bool {
|
|||||||
|
|
||||||
func (s BorderShape) HasTop() bool {
|
func (s BorderShape) HasTop() bool {
|
||||||
switch s {
|
switch s {
|
||||||
case BorderNone, BorderLeft, BorderRight, BorderBottom, BorderVertical: // No top
|
case BorderNone, BorderLine, BorderLeft, BorderRight, BorderBottom, BorderVertical: // No top
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s BorderShape) HasBottom() bool {
|
||||||
|
switch s {
|
||||||
|
case BorderNone, BorderLine, BorderLeft, BorderRight, BorderTop, BorderVertical: // No bottom
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s BorderShape) Visible() bool {
|
||||||
|
return s != BorderNone
|
||||||
|
}
|
||||||
|
|
||||||
type BorderStyle struct {
|
type BorderStyle struct {
|
||||||
shape BorderShape
|
shape BorderShape
|
||||||
top rune
|
top rune
|
||||||
@@ -402,6 +441,18 @@ type BorderStyle struct {
|
|||||||
type BorderCharacter int
|
type BorderCharacter int
|
||||||
|
|
||||||
func MakeBorderStyle(shape BorderShape, unicode bool) BorderStyle {
|
func MakeBorderStyle(shape BorderShape, unicode bool) BorderStyle {
|
||||||
|
if shape == BorderNone {
|
||||||
|
return BorderStyle{
|
||||||
|
shape: shape,
|
||||||
|
top: ' ',
|
||||||
|
bottom: ' ',
|
||||||
|
left: ' ',
|
||||||
|
right: ' ',
|
||||||
|
topLeft: ' ',
|
||||||
|
topRight: ' ',
|
||||||
|
bottomLeft: ' ',
|
||||||
|
bottomRight: ' '}
|
||||||
|
}
|
||||||
if !unicode {
|
if !unicode {
|
||||||
return BorderStyle{
|
return BorderStyle{
|
||||||
shape: shape,
|
shape: shape,
|
||||||
@@ -498,19 +549,6 @@ func MakeBorderStyle(shape BorderShape, unicode bool) BorderStyle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func MakeTransparentBorder() BorderStyle {
|
|
||||||
return BorderStyle{
|
|
||||||
shape: BorderRounded,
|
|
||||||
top: ' ',
|
|
||||||
bottom: ' ',
|
|
||||||
left: ' ',
|
|
||||||
right: ' ',
|
|
||||||
topLeft: ' ',
|
|
||||||
topRight: ' ',
|
|
||||||
bottomLeft: ' ',
|
|
||||||
bottomRight: ' '}
|
|
||||||
}
|
|
||||||
|
|
||||||
type TermSize struct {
|
type TermSize struct {
|
||||||
Lines int
|
Lines int
|
||||||
Columns int
|
Columns int
|
||||||
@@ -518,7 +556,18 @@ type TermSize struct {
|
|||||||
PxHeight int
|
PxHeight int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type WindowType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
WindowBase WindowType = iota
|
||||||
|
WindowList
|
||||||
|
WindowPreview
|
||||||
|
WindowInput
|
||||||
|
WindowHeader
|
||||||
|
)
|
||||||
|
|
||||||
type Renderer interface {
|
type Renderer interface {
|
||||||
|
DefaultTheme() *ColorTheme
|
||||||
Init() error
|
Init() error
|
||||||
Resize(maxHeightFunc func(int) int)
|
Resize(maxHeightFunc func(int) int)
|
||||||
Pause(clear bool)
|
Pause(clear bool)
|
||||||
@@ -539,7 +588,7 @@ type Renderer interface {
|
|||||||
|
|
||||||
Size() TermSize
|
Size() TermSize
|
||||||
|
|
||||||
NewWindow(top int, left int, width int, height int, preview bool, borderStyle BorderStyle) Window
|
NewWindow(top int, left int, width int, height int, windowType WindowType, borderStyle BorderStyle, erase bool) Window
|
||||||
}
|
}
|
||||||
|
|
||||||
type Window interface {
|
type Window interface {
|
||||||
@@ -552,10 +601,11 @@ type Window interface {
|
|||||||
DrawHBorder()
|
DrawHBorder()
|
||||||
Refresh()
|
Refresh()
|
||||||
FinishFill()
|
FinishFill()
|
||||||
Close()
|
|
||||||
|
|
||||||
X() int
|
X() int
|
||||||
Y() int
|
Y() int
|
||||||
|
EncloseX(x int) bool
|
||||||
|
EncloseY(y int) bool
|
||||||
Enclose(y int, x int) bool
|
Enclose(y int, x int) bool
|
||||||
|
|
||||||
Move(y int, x int)
|
Move(y int, x int)
|
||||||
@@ -564,6 +614,8 @@ type Window interface {
|
|||||||
CPrint(color ColorPair, text string)
|
CPrint(color ColorPair, text string)
|
||||||
Fill(text string) FillReturn
|
Fill(text string) FillReturn
|
||||||
CFill(fg Color, bg Color, attr Attr, text string) FillReturn
|
CFill(fg Color, bg Color, attr Attr, text string) FillReturn
|
||||||
|
LinkBegin(uri string, params string)
|
||||||
|
LinkEnd()
|
||||||
Erase()
|
Erase()
|
||||||
EraseMaybe() bool
|
EraseMaybe() bool
|
||||||
}
|
}
|
||||||
@@ -610,8 +662,11 @@ var (
|
|||||||
ColSpinner ColorPair
|
ColSpinner ColorPair
|
||||||
ColInfo ColorPair
|
ColInfo ColorPair
|
||||||
ColHeader ColorPair
|
ColHeader ColorPair
|
||||||
|
ColHeaderBorder ColorPair
|
||||||
|
ColHeaderLabel ColorPair
|
||||||
ColSeparator ColorPair
|
ColSeparator ColorPair
|
||||||
ColScrollbar ColorPair
|
ColScrollbar ColorPair
|
||||||
|
ColGapLine ColorPair
|
||||||
ColBorder ColorPair
|
ColBorder ColorPair
|
||||||
ColPreview ColorPair
|
ColPreview ColorPair
|
||||||
ColPreviewBorder ColorPair
|
ColPreviewBorder ColorPair
|
||||||
@@ -619,6 +674,10 @@ var (
|
|||||||
ColPreviewLabel ColorPair
|
ColPreviewLabel ColorPair
|
||||||
ColPreviewScrollbar ColorPair
|
ColPreviewScrollbar ColorPair
|
||||||
ColPreviewSpinner ColorPair
|
ColPreviewSpinner ColorPair
|
||||||
|
ColListBorder ColorPair
|
||||||
|
ColListLabel ColorPair
|
||||||
|
ColInputBorder ColorPair
|
||||||
|
ColInputLabel ColorPair
|
||||||
)
|
)
|
||||||
|
|
||||||
func EmptyTheme() *ColorTheme {
|
func EmptyTheme() *ColorTheme {
|
||||||
@@ -627,6 +686,8 @@ func EmptyTheme() *ColorTheme {
|
|||||||
Input: ColorAttr{colUndefined, AttrUndefined},
|
Input: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Fg: ColorAttr{colUndefined, AttrUndefined},
|
Fg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Bg: ColorAttr{colUndefined, AttrUndefined},
|
Bg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
ListFg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
ListBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
SelectedFg: ColorAttr{colUndefined, AttrUndefined},
|
SelectedFg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
SelectedBg: ColorAttr{colUndefined, AttrUndefined},
|
SelectedBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
SelectedMatch: ColorAttr{colUndefined, AttrUndefined},
|
SelectedMatch: ColorAttr{colUndefined, AttrUndefined},
|
||||||
@@ -642,6 +703,8 @@ func EmptyTheme() *ColorTheme {
|
|||||||
Header: ColorAttr{colUndefined, AttrUndefined},
|
Header: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Border: ColorAttr{colUndefined, AttrUndefined},
|
Border: ColorAttr{colUndefined, AttrUndefined},
|
||||||
BorderLabel: ColorAttr{colUndefined, AttrUndefined},
|
BorderLabel: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
ListLabel: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
ListBorder: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Disabled: ColorAttr{colUndefined, AttrUndefined},
|
Disabled: ColorAttr{colUndefined, AttrUndefined},
|
||||||
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
|
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
PreviewBg: ColorAttr{colUndefined, AttrUndefined},
|
PreviewBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
@@ -651,6 +714,14 @@ func EmptyTheme() *ColorTheme {
|
|||||||
PreviewLabel: ColorAttr{colUndefined, AttrUndefined},
|
PreviewLabel: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Separator: ColorAttr{colUndefined, AttrUndefined},
|
Separator: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Scrollbar: ColorAttr{colUndefined, AttrUndefined},
|
Scrollbar: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
InputBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
InputBorder: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
InputLabel: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
HeaderBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
HeaderBorder: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
HeaderLabel: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
GapLine: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
Nth: ColorAttr{colUndefined, AttrUndefined},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -660,6 +731,8 @@ func NoColorTheme() *ColorTheme {
|
|||||||
Input: ColorAttr{colDefault, AttrUndefined},
|
Input: ColorAttr{colDefault, AttrUndefined},
|
||||||
Fg: ColorAttr{colDefault, AttrUndefined},
|
Fg: ColorAttr{colDefault, AttrUndefined},
|
||||||
Bg: ColorAttr{colDefault, AttrUndefined},
|
Bg: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
ListFg: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
ListBg: ColorAttr{colDefault, AttrUndefined},
|
||||||
SelectedFg: ColorAttr{colDefault, AttrUndefined},
|
SelectedFg: ColorAttr{colDefault, AttrUndefined},
|
||||||
SelectedBg: ColorAttr{colDefault, AttrUndefined},
|
SelectedBg: ColorAttr{colDefault, AttrUndefined},
|
||||||
SelectedMatch: ColorAttr{colDefault, AttrUndefined},
|
SelectedMatch: ColorAttr{colDefault, AttrUndefined},
|
||||||
@@ -682,8 +755,18 @@ func NoColorTheme() *ColorTheme {
|
|||||||
PreviewBorder: ColorAttr{colDefault, AttrUndefined},
|
PreviewBorder: ColorAttr{colDefault, AttrUndefined},
|
||||||
PreviewScrollbar: ColorAttr{colDefault, AttrUndefined},
|
PreviewScrollbar: ColorAttr{colDefault, AttrUndefined},
|
||||||
PreviewLabel: ColorAttr{colDefault, AttrUndefined},
|
PreviewLabel: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
ListLabel: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
ListBorder: ColorAttr{colDefault, AttrUndefined},
|
||||||
Separator: ColorAttr{colDefault, AttrUndefined},
|
Separator: ColorAttr{colDefault, AttrUndefined},
|
||||||
Scrollbar: ColorAttr{colDefault, AttrUndefined},
|
Scrollbar: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
InputBg: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
InputBorder: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
InputLabel: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
HeaderBg: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
HeaderBorder: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
HeaderLabel: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
GapLine: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
Nth: ColorAttr{colUndefined, AttrUndefined},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -693,6 +776,8 @@ func init() {
|
|||||||
Input: ColorAttr{colDefault, AttrUndefined},
|
Input: ColorAttr{colDefault, AttrUndefined},
|
||||||
Fg: ColorAttr{colDefault, AttrUndefined},
|
Fg: ColorAttr{colDefault, AttrUndefined},
|
||||||
Bg: ColorAttr{colDefault, AttrUndefined},
|
Bg: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
ListFg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
ListBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
SelectedFg: ColorAttr{colUndefined, AttrUndefined},
|
SelectedFg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
SelectedBg: ColorAttr{colUndefined, AttrUndefined},
|
SelectedBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
SelectedMatch: ColorAttr{colUndefined, AttrUndefined},
|
SelectedMatch: ColorAttr{colUndefined, AttrUndefined},
|
||||||
@@ -715,14 +800,23 @@ func init() {
|
|||||||
PreviewBorder: ColorAttr{colUndefined, AttrUndefined},
|
PreviewBorder: ColorAttr{colUndefined, AttrUndefined},
|
||||||
PreviewScrollbar: ColorAttr{colUndefined, AttrUndefined},
|
PreviewScrollbar: ColorAttr{colUndefined, AttrUndefined},
|
||||||
PreviewLabel: ColorAttr{colUndefined, AttrUndefined},
|
PreviewLabel: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
ListLabel: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
ListBorder: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Separator: ColorAttr{colUndefined, AttrUndefined},
|
Separator: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Scrollbar: ColorAttr{colUndefined, AttrUndefined},
|
Scrollbar: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
InputBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
InputBorder: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
InputLabel: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
GapLine: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
Nth: ColorAttr{colUndefined, AttrUndefined},
|
||||||
}
|
}
|
||||||
Dark256 = &ColorTheme{
|
Dark256 = &ColorTheme{
|
||||||
Colored: true,
|
Colored: true,
|
||||||
Input: ColorAttr{colDefault, AttrUndefined},
|
Input: ColorAttr{colDefault, AttrUndefined},
|
||||||
Fg: ColorAttr{colDefault, AttrUndefined},
|
Fg: ColorAttr{colDefault, AttrUndefined},
|
||||||
Bg: ColorAttr{colDefault, AttrUndefined},
|
Bg: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
ListFg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
ListBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
SelectedFg: ColorAttr{colUndefined, AttrUndefined},
|
SelectedFg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
SelectedBg: ColorAttr{colUndefined, AttrUndefined},
|
SelectedBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
SelectedMatch: ColorAttr{colUndefined, AttrUndefined},
|
SelectedMatch: ColorAttr{colUndefined, AttrUndefined},
|
||||||
@@ -745,14 +839,23 @@ func init() {
|
|||||||
PreviewBorder: ColorAttr{colUndefined, AttrUndefined},
|
PreviewBorder: ColorAttr{colUndefined, AttrUndefined},
|
||||||
PreviewScrollbar: ColorAttr{colUndefined, AttrUndefined},
|
PreviewScrollbar: ColorAttr{colUndefined, AttrUndefined},
|
||||||
PreviewLabel: ColorAttr{colUndefined, AttrUndefined},
|
PreviewLabel: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
ListLabel: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
ListBorder: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Separator: ColorAttr{colUndefined, AttrUndefined},
|
Separator: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Scrollbar: ColorAttr{colUndefined, AttrUndefined},
|
Scrollbar: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
InputBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
InputBorder: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
InputLabel: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
GapLine: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
Nth: ColorAttr{colUndefined, AttrUndefined},
|
||||||
}
|
}
|
||||||
Light256 = &ColorTheme{
|
Light256 = &ColorTheme{
|
||||||
Colored: true,
|
Colored: true,
|
||||||
Input: ColorAttr{colDefault, AttrUndefined},
|
Input: ColorAttr{colDefault, AttrUndefined},
|
||||||
Fg: ColorAttr{colDefault, AttrUndefined},
|
Fg: ColorAttr{colDefault, AttrUndefined},
|
||||||
Bg: ColorAttr{colDefault, AttrUndefined},
|
Bg: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
ListFg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
ListBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
SelectedFg: ColorAttr{colUndefined, AttrUndefined},
|
SelectedFg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
SelectedBg: ColorAttr{colUndefined, AttrUndefined},
|
SelectedBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
SelectedMatch: ColorAttr{colUndefined, AttrUndefined},
|
SelectedMatch: ColorAttr{colUndefined, AttrUndefined},
|
||||||
@@ -775,12 +878,22 @@ func init() {
|
|||||||
PreviewBorder: ColorAttr{colUndefined, AttrUndefined},
|
PreviewBorder: ColorAttr{colUndefined, AttrUndefined},
|
||||||
PreviewScrollbar: ColorAttr{colUndefined, AttrUndefined},
|
PreviewScrollbar: ColorAttr{colUndefined, AttrUndefined},
|
||||||
PreviewLabel: ColorAttr{colUndefined, AttrUndefined},
|
PreviewLabel: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
ListLabel: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
ListBorder: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Separator: ColorAttr{colUndefined, AttrUndefined},
|
Separator: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Scrollbar: ColorAttr{colUndefined, AttrUndefined},
|
Scrollbar: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
InputBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
InputBorder: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
InputLabel: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
HeaderBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
HeaderBorder: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
HeaderLabel: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
GapLine: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
Nth: ColorAttr{colUndefined, AttrUndefined},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
|
func InitTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool, hasInputWindow bool, hasHeaderWindow bool) {
|
||||||
if forceBlack {
|
if forceBlack {
|
||||||
theme.Bg = ColorAttr{colBlack, AttrUndefined}
|
theme.Bg = ColorAttr{colBlack, AttrUndefined}
|
||||||
}
|
}
|
||||||
@@ -801,7 +914,9 @@ func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
|
|||||||
theme.DarkBg = o(baseTheme.DarkBg, theme.DarkBg)
|
theme.DarkBg = o(baseTheme.DarkBg, theme.DarkBg)
|
||||||
theme.Prompt = o(baseTheme.Prompt, theme.Prompt)
|
theme.Prompt = o(baseTheme.Prompt, theme.Prompt)
|
||||||
theme.Match = o(baseTheme.Match, theme.Match)
|
theme.Match = o(baseTheme.Match, theme.Match)
|
||||||
theme.Current = o(baseTheme.Current, theme.Current)
|
// Inherit from 'fg', so that we don't have to write 'current-fg:dim'
|
||||||
|
// e.g. fzf --delimiter / --nth -1 --color fg:dim,nth:regular
|
||||||
|
theme.Current = theme.Fg.Merge(o(baseTheme.Current, theme.Current))
|
||||||
theme.CurrentMatch = o(baseTheme.CurrentMatch, theme.CurrentMatch)
|
theme.CurrentMatch = o(baseTheme.CurrentMatch, theme.CurrentMatch)
|
||||||
theme.Spinner = o(baseTheme.Spinner, theme.Spinner)
|
theme.Spinner = o(baseTheme.Spinner, theme.Spinner)
|
||||||
theme.Info = o(baseTheme.Info, theme.Info)
|
theme.Info = o(baseTheme.Info, theme.Info)
|
||||||
@@ -811,9 +926,15 @@ func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
|
|||||||
theme.Border = o(baseTheme.Border, theme.Border)
|
theme.Border = o(baseTheme.Border, theme.Border)
|
||||||
theme.BorderLabel = o(baseTheme.BorderLabel, theme.BorderLabel)
|
theme.BorderLabel = o(baseTheme.BorderLabel, theme.BorderLabel)
|
||||||
|
|
||||||
|
undefined := NewColorAttr()
|
||||||
|
scrollbarDefined := theme.Scrollbar != undefined
|
||||||
|
previewBorderDefined := theme.PreviewBorder != undefined
|
||||||
|
|
||||||
// These colors are not defined in the base themes
|
// These colors are not defined in the base themes
|
||||||
theme.SelectedFg = o(theme.Fg, theme.SelectedFg)
|
theme.ListFg = o(theme.Fg, theme.ListFg)
|
||||||
theme.SelectedBg = o(theme.Bg, theme.SelectedBg)
|
theme.ListBg = o(theme.Bg, theme.ListBg)
|
||||||
|
theme.SelectedFg = o(theme.ListFg, theme.SelectedFg)
|
||||||
|
theme.SelectedBg = o(theme.ListBg, theme.SelectedBg)
|
||||||
theme.SelectedMatch = o(theme.Match, theme.SelectedMatch)
|
theme.SelectedMatch = o(theme.Match, theme.SelectedMatch)
|
||||||
theme.Disabled = o(theme.Input, theme.Disabled)
|
theme.Disabled = o(theme.Input, theme.Disabled)
|
||||||
theme.Gutter = o(theme.DarkBg, theme.Gutter)
|
theme.Gutter = o(theme.DarkBg, theme.Gutter)
|
||||||
@@ -821,9 +942,38 @@ func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
|
|||||||
theme.PreviewBg = o(theme.Bg, theme.PreviewBg)
|
theme.PreviewBg = o(theme.Bg, theme.PreviewBg)
|
||||||
theme.PreviewLabel = o(theme.BorderLabel, theme.PreviewLabel)
|
theme.PreviewLabel = o(theme.BorderLabel, theme.PreviewLabel)
|
||||||
theme.PreviewBorder = o(theme.Border, theme.PreviewBorder)
|
theme.PreviewBorder = o(theme.Border, theme.PreviewBorder)
|
||||||
theme.Separator = o(theme.Border, theme.Separator)
|
theme.ListLabel = o(theme.BorderLabel, theme.ListLabel)
|
||||||
theme.Scrollbar = o(theme.Border, theme.Scrollbar)
|
theme.ListBorder = o(theme.Border, theme.ListBorder)
|
||||||
theme.PreviewScrollbar = o(theme.PreviewBorder, theme.PreviewScrollbar)
|
theme.Separator = o(theme.ListBorder, theme.Separator)
|
||||||
|
theme.Scrollbar = o(theme.ListBorder, theme.Scrollbar)
|
||||||
|
theme.GapLine = o(theme.ListBorder, theme.GapLine)
|
||||||
|
/*
|
||||||
|
--color list-border:green
|
||||||
|
--color scrollbar:red
|
||||||
|
--color scrollbar:red,list-border:green
|
||||||
|
--color scrollbar:red,preview-border:green
|
||||||
|
*/
|
||||||
|
if scrollbarDefined && !previewBorderDefined {
|
||||||
|
theme.PreviewScrollbar = o(theme.Scrollbar, theme.PreviewScrollbar)
|
||||||
|
} else {
|
||||||
|
theme.PreviewScrollbar = o(theme.PreviewBorder, theme.PreviewScrollbar)
|
||||||
|
}
|
||||||
|
if hasInputWindow {
|
||||||
|
theme.InputBg = o(theme.Bg, theme.InputBg)
|
||||||
|
} else {
|
||||||
|
// We shouldn't use input-bg if there's no separate input window
|
||||||
|
// e.g. fzf --color 'list-bg:green,input-bg:red' --no-input-border
|
||||||
|
theme.InputBg = o(theme.Bg, theme.ListBg)
|
||||||
|
}
|
||||||
|
theme.InputBorder = o(theme.Border, theme.InputBorder)
|
||||||
|
theme.InputLabel = o(theme.BorderLabel, theme.InputLabel)
|
||||||
|
if hasHeaderWindow {
|
||||||
|
theme.HeaderBg = o(theme.Bg, theme.HeaderBg)
|
||||||
|
} else {
|
||||||
|
theme.HeaderBg = o(theme.Bg, theme.ListBg)
|
||||||
|
}
|
||||||
|
theme.HeaderBorder = o(theme.Border, theme.HeaderBorder)
|
||||||
|
theme.HeaderLabel = o(theme.BorderLabel, theme.HeaderLabel)
|
||||||
|
|
||||||
initPalette(theme)
|
initPalette(theme)
|
||||||
}
|
}
|
||||||
@@ -835,19 +985,19 @@ func initPalette(theme *ColorTheme) {
|
|||||||
}
|
}
|
||||||
return ColorPair{fg.Color, bg.Color, fg.Attr}
|
return ColorPair{fg.Color, bg.Color, fg.Attr}
|
||||||
}
|
}
|
||||||
blank := theme.Fg
|
blank := theme.ListFg
|
||||||
blank.Attr = AttrRegular
|
blank.Attr = AttrRegular
|
||||||
|
|
||||||
ColPrompt = pair(theme.Prompt, theme.Bg)
|
ColPrompt = pair(theme.Prompt, theme.InputBg)
|
||||||
ColNormal = pair(theme.Fg, theme.Bg)
|
ColNormal = pair(theme.ListFg, theme.ListBg)
|
||||||
ColSelected = pair(theme.SelectedFg, theme.SelectedBg)
|
ColSelected = pair(theme.SelectedFg, theme.SelectedBg)
|
||||||
ColInput = pair(theme.Input, theme.Bg)
|
ColInput = pair(theme.Input, theme.InputBg)
|
||||||
ColDisabled = pair(theme.Disabled, theme.Bg)
|
ColDisabled = pair(theme.Disabled, theme.ListBg)
|
||||||
ColMatch = pair(theme.Match, theme.Bg)
|
ColMatch = pair(theme.Match, theme.ListBg)
|
||||||
ColSelectedMatch = pair(theme.SelectedMatch, theme.SelectedBg)
|
ColSelectedMatch = pair(theme.SelectedMatch, theme.SelectedBg)
|
||||||
ColCursor = pair(theme.Cursor, theme.Gutter)
|
ColCursor = pair(theme.Cursor, theme.Gutter)
|
||||||
ColCursorEmpty = pair(blank, theme.Gutter)
|
ColCursorEmpty = pair(blank, theme.Gutter)
|
||||||
if theme.SelectedBg.Color != theme.Bg.Color {
|
if theme.SelectedBg.Color != theme.ListBg.Color {
|
||||||
ColMarker = pair(theme.Marker, theme.SelectedBg)
|
ColMarker = pair(theme.Marker, theme.SelectedBg)
|
||||||
} else {
|
} else {
|
||||||
ColMarker = pair(theme.Marker, theme.Gutter)
|
ColMarker = pair(theme.Marker, theme.Gutter)
|
||||||
@@ -858,11 +1008,11 @@ func initPalette(theme *ColorTheme) {
|
|||||||
ColCurrentCursorEmpty = pair(blank, theme.DarkBg)
|
ColCurrentCursorEmpty = pair(blank, theme.DarkBg)
|
||||||
ColCurrentMarker = pair(theme.Marker, theme.DarkBg)
|
ColCurrentMarker = pair(theme.Marker, theme.DarkBg)
|
||||||
ColCurrentSelectedEmpty = pair(blank, theme.DarkBg)
|
ColCurrentSelectedEmpty = pair(blank, theme.DarkBg)
|
||||||
ColSpinner = pair(theme.Spinner, theme.Bg)
|
ColSpinner = pair(theme.Spinner, theme.InputBg)
|
||||||
ColInfo = pair(theme.Info, theme.Bg)
|
ColInfo = pair(theme.Info, theme.InputBg)
|
||||||
ColHeader = pair(theme.Header, theme.Bg)
|
ColSeparator = pair(theme.Separator, theme.InputBg)
|
||||||
ColSeparator = pair(theme.Separator, theme.Bg)
|
ColScrollbar = pair(theme.Scrollbar, theme.ListBg)
|
||||||
ColScrollbar = pair(theme.Scrollbar, theme.Bg)
|
ColGapLine = pair(theme.GapLine, theme.ListBg)
|
||||||
ColBorder = pair(theme.Border, theme.Bg)
|
ColBorder = pair(theme.Border, theme.Bg)
|
||||||
ColBorderLabel = pair(theme.BorderLabel, theme.Bg)
|
ColBorderLabel = pair(theme.BorderLabel, theme.Bg)
|
||||||
ColPreviewLabel = pair(theme.PreviewLabel, theme.PreviewBg)
|
ColPreviewLabel = pair(theme.PreviewLabel, theme.PreviewBg)
|
||||||
@@ -870,6 +1020,13 @@ func initPalette(theme *ColorTheme) {
|
|||||||
ColPreviewBorder = pair(theme.PreviewBorder, theme.PreviewBg)
|
ColPreviewBorder = pair(theme.PreviewBorder, theme.PreviewBg)
|
||||||
ColPreviewScrollbar = pair(theme.PreviewScrollbar, theme.PreviewBg)
|
ColPreviewScrollbar = pair(theme.PreviewScrollbar, theme.PreviewBg)
|
||||||
ColPreviewSpinner = pair(theme.Spinner, theme.PreviewBg)
|
ColPreviewSpinner = pair(theme.Spinner, theme.PreviewBg)
|
||||||
|
ColListLabel = pair(theme.ListLabel, theme.ListBg)
|
||||||
|
ColListBorder = pair(theme.ListBorder, theme.ListBg)
|
||||||
|
ColInputBorder = pair(theme.InputBorder, theme.InputBg)
|
||||||
|
ColInputLabel = pair(theme.InputLabel, theme.InputBg)
|
||||||
|
ColHeader = pair(theme.Header, theme.HeaderBg)
|
||||||
|
ColHeaderBorder = pair(theme.HeaderBorder, theme.HeaderBg)
|
||||||
|
ColHeaderLabel = pair(theme.HeaderLabel, theme.HeaderBg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runeWidth(r rune) int {
|
func runeWidth(r rune) int {
|
||||||
|
@@ -306,5 +306,5 @@ func (chars *Chars) Lines(multiLine bool, maxLines int, wrapCols int, wrapSignWi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return wrapped, false
|
return wrapped, overflow
|
||||||
}
|
}
|
||||||
|
@@ -44,11 +44,6 @@ func needWinpty(opts *Options) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func runWinpty(args []string, opts *Options) (int, error) {
|
func runWinpty(args []string, opts *Options) (int, error) {
|
||||||
sh, err := sh()
|
|
||||||
if err != nil {
|
|
||||||
return ExitError, err
|
|
||||||
}
|
|
||||||
|
|
||||||
argStr := escapeSingleQuote(args[0])
|
argStr := escapeSingleQuote(args[0])
|
||||||
for _, arg := range args[1:] {
|
for _, arg := range args[1:] {
|
||||||
argStr += " " + escapeSingleQuote(arg)
|
argStr += " " + escapeSingleQuote(arg)
|
||||||
@@ -56,20 +51,30 @@ func runWinpty(args []string, opts *Options) (int, error) {
|
|||||||
argStr += ` --no-winpty`
|
argStr += ` --no-winpty`
|
||||||
|
|
||||||
if isMintty345() {
|
if isMintty345() {
|
||||||
return runProxy(argStr, func(temp string) *exec.Cmd {
|
return runProxy(argStr, func(temp string, needBash bool) (*exec.Cmd, error) {
|
||||||
|
sh, err := sh(needBash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
cmd := exec.Command(sh, temp)
|
cmd := exec.Command(sh, temp)
|
||||||
cmd.Env = append(os.Environ(), "MSYS=enable_pcon")
|
cmd.Env = append(os.Environ(), "MSYS=enable_pcon")
|
||||||
cmd.Stdin = os.Stdin
|
cmd.Stdin = os.Stdin
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
return cmd
|
return cmd, nil
|
||||||
}, opts, false)
|
}, opts, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
return runProxy(argStr, func(temp string) *exec.Cmd {
|
return runProxy(argStr, func(temp string, needBash bool) (*exec.Cmd, error) {
|
||||||
|
sh, err := sh(needBash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
cmd := exec.Command(sh, "-c", fmt.Sprintf(`winpty < /dev/tty > /dev/tty -- sh %q`, temp))
|
cmd := exec.Command(sh, "-c", fmt.Sprintf(`winpty < /dev/tty > /dev/tty -- sh %q`, temp))
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
return cmd
|
return cmd, nil
|
||||||
}, opts, false)
|
}, opts, false)
|
||||||
}
|
}
|
||||||
|
453
test/test_go.rb
453
test/test_go.rb
@@ -1442,10 +1442,14 @@ class TestGoFZF < TestBase
|
|||||||
writelines(['=' * 10_000 + '0123456789'])
|
writelines(['=' * 10_000 + '0123456789'])
|
||||||
[0, 3, 6].each do |off|
|
[0, 3, 6].each do |off|
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
tmux.send_keys "#{FZF} --hscroll-off=#{off} -q 0 < #{tempname}", :Enter
|
tmux.send_keys "#{FZF} --hscroll-off=#{off} -q 0 --bind space:toggle-hscroll < #{tempname}", :Enter
|
||||||
tmux.until { |lines| assert lines[-3]&.end_with?((0..off).to_a.join + '..') }
|
tmux.until { |lines| assert lines[-3]&.end_with?((0..off).to_a.join + '··') }
|
||||||
tmux.send_keys '9'
|
tmux.send_keys '9'
|
||||||
tmux.until { |lines| assert lines[-3]&.end_with?('789') }
|
tmux.until { |lines| assert lines[-3]&.end_with?('789') }
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until { |lines| assert lines[-3]&.end_with?('=··') }
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until { |lines| assert lines[-3]&.end_with?('789') }
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -2133,7 +2137,11 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_keep_right
|
def test_keep_right
|
||||||
tmux.send_keys "seq 10000 | #{FZF} --read0 --keep-right --no-multi-line", :Enter
|
tmux.send_keys "seq 10000 | #{FZF} --read0 --keep-right --no-multi-line --bind space:toggle-multi-line", :Enter
|
||||||
|
tmux.until { |lines| assert lines.any_include?('9999␊10000') }
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until { |lines| assert lines.any_include?('> 1') }
|
||||||
|
tmux.send_keys :Space
|
||||||
tmux.until { |lines| assert lines.any_include?('9999␊10000') }
|
tmux.until { |lines| assert lines.any_include?('9999␊10000') }
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -2638,7 +2646,7 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_change_preview_window
|
def test_change_preview_window
|
||||||
tmux.send_keys "seq 1000 | #{FZF} --preview 'echo [[{}]]' --preview-window border-none --bind '" \
|
tmux.send_keys "seq 1000 | #{FZF} --preview 'echo [[{}]]' --no-preview-border --bind '" \
|
||||||
'a:change-preview(echo __{}__),' \
|
'a:change-preview(echo __{}__),' \
|
||||||
'b:change-preview-window(down)+change-preview(echo =={}==)+change-preview-window(up),' \
|
'b:change-preview-window(down)+change-preview(echo =={}==)+change-preview-window(up),' \
|
||||||
'c:change-preview(),d:change-preview-window(hidden),' \
|
'c:change-preview(),d:change-preview-window(hidden),' \
|
||||||
@@ -2814,13 +2822,13 @@ class TestGoFZF < TestBase
|
|||||||
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
|
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
|
expected = <<~OUTPUT
|
||||||
│
|
│
|
||||||
│ 1 │ > 3
|
│ 1 │ > 3
|
||||||
│ 2 │ 2
|
│ 2 │ 2
|
||||||
│ 3 │ 1
|
│ 3 │ 1
|
||||||
│ │ hello
|
│ │ hello
|
||||||
│ │ world
|
│ │ world
|
||||||
│ │ 1/1 ─
|
│ │ 1/1 ─
|
||||||
│ │ >
|
│ │ >
|
||||||
│
|
│
|
||||||
OUTPUT
|
OUTPUT
|
||||||
tmux.until { assert_block(expected, _1) }
|
tmux.until { assert_block(expected, _1) }
|
||||||
@@ -3073,6 +3081,21 @@ class TestGoFZF < TestBase
|
|||||||
tmux.until { |lines| assert_includes lines, '/1/1/' }
|
tmux.until { |lines| assert_includes lines, '/1/1/' }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_alternative_preview_window_opts
|
||||||
|
tmux.send_keys "seq 10 | #{FZF} --preview-window '~5,2,+0,<100000(~0,+100,wrap,noinfo)' --preview 'seq 1000'", :Enter
|
||||||
|
tmux.until { |lines| assert_equal 10, lines.item_count }
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal ['╭────╮', '│ 10 │', '│ 0 │', '│ 10 │', '│ 1 │'], lines.take(5).map(&:strip)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_preview_window_width_exception
|
||||||
|
tmux.send_keys "seq 10 | #{FZF} --scrollbar --preview-window border-left --border --preview 'seq 1000'", :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert lines[1]&.end_with?(' 1/1000││')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_become
|
def test_become
|
||||||
tmux.send_keys "seq 100 | #{FZF} --bind 'enter:become:seq {} | #{FZF}'", :Enter
|
tmux.send_keys "seq 100 | #{FZF} --bind 'enter:become:seq {} | #{FZF}'", :Enter
|
||||||
tmux.until { |lines| assert_equal 100, lines.item_count }
|
tmux.until { |lines| assert_equal 100, lines.item_count }
|
||||||
@@ -3366,6 +3389,397 @@ class TestGoFZF < TestBase
|
|||||||
tmux.send_keys :Space
|
tmux.send_keys :Space
|
||||||
tmux.until { |lines| assert_includes lines[-3], 'bar' }
|
tmux.until { |lines| assert_includes lines[-3], 'bar' }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_boundary_match
|
||||||
|
# Underscore boundaries should be ranked lower
|
||||||
|
{
|
||||||
|
default: [' x '] + %w[/x/ [x] -x- -x_ _x- _x_],
|
||||||
|
path: ['/x/', ' x '] + %w[[x] -x- -x_ _x- _x_],
|
||||||
|
history: ['[x]', '-x-', ' x '] + %w[/x/ -x_ _x- _x_]
|
||||||
|
}.each do |scheme, expected|
|
||||||
|
result = `printf -- 'xxx\n-xx\nxx-\n_x_\n_x-\n-x_\n[x]\n-x-\n x \n/x/\n' | #{FZF} -f"'x'" --scheme=#{scheme}`.lines(chomp: true)
|
||||||
|
assert_equal expected, result
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_preview_window_noinfo
|
||||||
|
# │ 1 ││
|
||||||
|
tmux.send_keys %(#{FZF} --preview 'seq 1000' --preview-window top,noinfo --scrollbar --bind space:change-preview-window:info), :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert lines[1]&.start_with?('│ 1')
|
||||||
|
assert lines[1]&.end_with?(' ││')
|
||||||
|
end
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert lines[1]&.start_with?('│ 1')
|
||||||
|
assert lines[1]&.end_with?('1000││')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_gap
|
||||||
|
tmux.send_keys %(seq 100 | #{FZF} --gap --border --reverse), :Enter
|
||||||
|
block = <<~BLOCK
|
||||||
|
╭─────────────────
|
||||||
|
│ >
|
||||||
|
│ 100/100 ──────
|
||||||
|
│ > 1
|
||||||
|
│ ┈┈┈┈┈┈┈┈┈┈┈┈┈┈
|
||||||
|
│ 2
|
||||||
|
│ ┈┈┈┈┈┈┈┈┈┈┈┈┈┈
|
||||||
|
│ 3
|
||||||
|
│ ┈┈┈┈┈┈┈┈┈┈┈┈┈┈
|
||||||
|
│ 4
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, _1) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_gap_2
|
||||||
|
tmux.send_keys %(seq 100 | #{FZF} --gap=2 --gap-line xyz --border --reverse), :Enter
|
||||||
|
block = <<~BLOCK
|
||||||
|
╭─────────────────
|
||||||
|
│ >
|
||||||
|
│ 100/100 ──────
|
||||||
|
│ > 1
|
||||||
|
│
|
||||||
|
│ xyzxyzxyzxyzxy
|
||||||
|
│ 2
|
||||||
|
│
|
||||||
|
│ xyzxyzxyzxyzxy
|
||||||
|
│ 3
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, _1) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_list_border_and_label
|
||||||
|
tmux.send_keys %(seq 100 | #{FZF} --border rounded --list-border double --list-label list --list-label-pos 2:bottom --header-lines 3 --query 1 --padding 1,2), :Enter
|
||||||
|
block = <<~BLOCK
|
||||||
|
│ ║ 11
|
||||||
|
│ ║ > 10
|
||||||
|
│ ║ 3
|
||||||
|
│ ║ 2
|
||||||
|
│ ║ 1
|
||||||
|
│ ║ 19/97 ─
|
||||||
|
│ ║ > 1
|
||||||
|
│ ╚list══════
|
||||||
|
│
|
||||||
|
╰──────────────
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, _1) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_input_border_and_label
|
||||||
|
tmux.send_keys %(seq 100 | #{FZF} --border rounded --input-border bold --input-label input --input-label-pos 2 --header-lines 3 --query 1 --padding 1,2), :Enter
|
||||||
|
block = <<~BLOCK
|
||||||
|
│ 11
|
||||||
|
│ > 10
|
||||||
|
│ 3
|
||||||
|
│ 2
|
||||||
|
│ 1
|
||||||
|
│ ┏input━━━━━
|
||||||
|
│ ┃ 19/97
|
||||||
|
│ ┃ > 1
|
||||||
|
│ ┗━━━━━━━━━━
|
||||||
|
│
|
||||||
|
╰──────────────
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, _1) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_input_border_and_label_header_first
|
||||||
|
tmux.send_keys %(seq 100 | #{FZF} --border rounded --input-border bold --input-label input --input-label-pos 2 --header-lines 3 --query 1 --padding 1,2 --header-first), :Enter
|
||||||
|
block = <<~BLOCK
|
||||||
|
│ 11
|
||||||
|
│ > 10
|
||||||
|
│ ┏input━━━━━
|
||||||
|
│ ┃ 19/97
|
||||||
|
│ ┃ > 1
|
||||||
|
│ ┗━━━━━━━━━━
|
||||||
|
│ 3
|
||||||
|
│ 2
|
||||||
|
│ 1
|
||||||
|
│
|
||||||
|
╰──────────────
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, _1) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_list_input_border_and_label
|
||||||
|
tmux.send_keys %(
|
||||||
|
seq 100 | #{FZF} --border rounded --list-border double --input-border bold --list-label-pos 2:bottom --input-label-pos 2 --header-lines 3 --query 1 --padding 1,2 \
|
||||||
|
--bind 'start:transform-input-label(echo INPUT)+transform-list-label(echo LIST)' \
|
||||||
|
--bind 'space:change-input-label( input )+change-list-label( list )'
|
||||||
|
).strip, :Enter
|
||||||
|
block = <<~BLOCK
|
||||||
|
│ ║ 11
|
||||||
|
│ ║ > 10
|
||||||
|
│ ╚LIST══════
|
||||||
|
│ 3
|
||||||
|
│ 2
|
||||||
|
│ 1
|
||||||
|
│ ┏INPUT━━━━━
|
||||||
|
│ ┃ 19/97
|
||||||
|
│ ┃ > 1
|
||||||
|
│ ┗━━━━━━━━━━
|
||||||
|
│
|
||||||
|
╰──────────────
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, _1) }
|
||||||
|
tmux.send_keys :Space
|
||||||
|
block = <<~BLOCK
|
||||||
|
│ ║ 11
|
||||||
|
│ ║ > 10
|
||||||
|
│ ╚ list ════
|
||||||
|
│ 3
|
||||||
|
│ 2
|
||||||
|
│ 1
|
||||||
|
│ ┏ input ━━━
|
||||||
|
│ ┃ 19/97
|
||||||
|
│ ┃ > 1
|
||||||
|
│ ┗━━━━━━━━━━
|
||||||
|
│
|
||||||
|
╰──────────────
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, _1) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_list_input_border_and_label_header_first
|
||||||
|
tmux.send_keys %(
|
||||||
|
seq 100 | #{FZF} --border rounded --list-border double --input-border bold --list-label-pos 2:bottom --input-label-pos 2 --header-lines 3 --query 1 --padding 1,2 \
|
||||||
|
--bind 'start:transform-input-label(echo INPUT)+transform-list-label(echo LIST)' \
|
||||||
|
--bind 'space:change-input-label( input )+change-list-label( list )' --header-first
|
||||||
|
).strip, :Enter
|
||||||
|
block = <<~BLOCK
|
||||||
|
│ ║ 11
|
||||||
|
│ ║ > 10
|
||||||
|
│ ╚LIST══════
|
||||||
|
│ ┏INPUT━━━━━
|
||||||
|
│ ┃ 19/97
|
||||||
|
│ ┃ > 1
|
||||||
|
│ ┗━━━━━━━━━━
|
||||||
|
│ 3
|
||||||
|
│ 2
|
||||||
|
│ 1
|
||||||
|
│
|
||||||
|
╰──────────────
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, _1) }
|
||||||
|
tmux.send_keys :Space
|
||||||
|
block = <<~BLOCK
|
||||||
|
│ ║ 11
|
||||||
|
│ ║ > 10
|
||||||
|
│ ╚ list ════
|
||||||
|
│ ┏ input ━━━
|
||||||
|
│ ┃ 19/97
|
||||||
|
│ ┃ > 1
|
||||||
|
│ ┗━━━━━━━━━━
|
||||||
|
│ 3
|
||||||
|
│ 2
|
||||||
|
│ 1
|
||||||
|
│
|
||||||
|
╰──────────────
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, _1) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_header_border_and_label
|
||||||
|
tmux.send_keys %(seq 100 | #{FZF} --border rounded --header-lines 3 --header-border sharp --header-label header --header-label-pos 2:bottom --query 1 --padding 1,2), :Enter
|
||||||
|
block = <<~BLOCK
|
||||||
|
│ 12
|
||||||
|
│ 11
|
||||||
|
│ > 10
|
||||||
|
│ ┌────────
|
||||||
|
│ │ 3
|
||||||
|
│ │ 2
|
||||||
|
│ │ 1
|
||||||
|
│ └header──
|
||||||
|
│ 19/97 ─
|
||||||
|
│ > 1
|
||||||
|
│
|
||||||
|
╰────────────
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, _1) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_header_border_and_label_header_first
|
||||||
|
tmux.send_keys %(seq 100 | #{FZF} --border rounded --header-lines 3 --header-border sharp --header-label header --header-label-pos 2:bottom --query 1 --padding 1,2 --header-first), :Enter
|
||||||
|
block = <<~BLOCK
|
||||||
|
│ 12
|
||||||
|
│ 11
|
||||||
|
│ > 10
|
||||||
|
│ 19/97 ─
|
||||||
|
│ > 1
|
||||||
|
│ ┌────────
|
||||||
|
│ │ 3
|
||||||
|
│ │ 2
|
||||||
|
│ │ 1
|
||||||
|
│ └header──
|
||||||
|
│
|
||||||
|
╰────────────
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, _1) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_header_border_and_label_with_list_border
|
||||||
|
tmux.send_keys %(seq 100 | #{FZF} --border rounded --list-border double --list-label list --list-label-pos 2:bottom --header-lines 3 --header-border sharp --header-label header --header-label-pos 2:bottom --query 1 --padding 1,2), :Enter
|
||||||
|
block = <<~BLOCK
|
||||||
|
│ ║ 12
|
||||||
|
│ ║ 11
|
||||||
|
│ ║ > 10
|
||||||
|
│ ╚list════
|
||||||
|
│ ┌────────
|
||||||
|
│ │ 3
|
||||||
|
│ │ 2
|
||||||
|
│ │ 1
|
||||||
|
│ └header──
|
||||||
|
│ 19/97 ─
|
||||||
|
│ > 1
|
||||||
|
│
|
||||||
|
╰────────────
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, _1) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_header_border_and_label_with_list_border_header_first
|
||||||
|
tmux.send_keys %(seq 100 | #{FZF} --border rounded --list-border double --list-label list --list-label-pos 2:bottom --header-lines 3 --header-border sharp --header-label header --header-label-pos 2:bottom --query 1 --padding 1,2 --header-first), :Enter
|
||||||
|
block = <<~BLOCK
|
||||||
|
│ ║ 12
|
||||||
|
│ ║ 11
|
||||||
|
│ ║ > 10
|
||||||
|
│ ╚list════
|
||||||
|
│ 19/97 ─
|
||||||
|
│ > 1
|
||||||
|
│ ┌────────
|
||||||
|
│ │ 3
|
||||||
|
│ │ 2
|
||||||
|
│ │ 1
|
||||||
|
│ └header──
|
||||||
|
│
|
||||||
|
╰────────────
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, _1) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_all_borders
|
||||||
|
tmux.send_keys %(seq 100 | #{FZF} --border rounded --list-border double --list-label list --list-label-pos 2:bottom --header-lines 3 --header-border sharp --header-label header --header-label-pos 2:bottom --query 1 --padding 1,2 --input-border bold --input-label input --input-label-pos 2:bottom), :Enter
|
||||||
|
block = <<~BLOCK
|
||||||
|
│ ║ 12
|
||||||
|
│ ║ 11
|
||||||
|
│ ║ > 10
|
||||||
|
│ ╚list══════
|
||||||
|
│ ┌──────────
|
||||||
|
│ │ 3
|
||||||
|
│ │ 2
|
||||||
|
│ │ 1
|
||||||
|
│ └header────
|
||||||
|
│ ┏━━━━━━━━━━
|
||||||
|
│ ┃ 19/97
|
||||||
|
│ ┃ > 1
|
||||||
|
│ ┗input━━━━━
|
||||||
|
│
|
||||||
|
╰──────────────
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, _1) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_all_borders_header_first
|
||||||
|
tmux.send_keys %(seq 100 | #{FZF} --border rounded --list-border double --list-label list --list-label-pos 2:bottom --header-lines 3 --header-border sharp --header-label header --header-label-pos 2:bottom --query 1 --padding 1,2 --input-border bold --input-label input --input-label-pos 2:bottom --header-first), :Enter
|
||||||
|
block = <<~BLOCK
|
||||||
|
│ ║ 12
|
||||||
|
│ ║ 11
|
||||||
|
│ ║ > 10
|
||||||
|
│ ╚list══════
|
||||||
|
│ ┏━━━━━━━━━━
|
||||||
|
│ ┃ 19/97
|
||||||
|
│ ┃ > 1
|
||||||
|
│ ┗input━━━━━
|
||||||
|
│ ┌──────────
|
||||||
|
│ │ 3
|
||||||
|
│ │ 2
|
||||||
|
│ │ 1
|
||||||
|
│ └header────
|
||||||
|
│
|
||||||
|
╰──────────────
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, _1) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_style_full_adaptive_height
|
||||||
|
tmux.send_keys %(seq 1| #{FZF} --style=full --height=~100% --header-lines=1 --info=default), :Enter
|
||||||
|
block = <<~BLOCK
|
||||||
|
╭────────
|
||||||
|
╰────────
|
||||||
|
╭────────
|
||||||
|
│ 1
|
||||||
|
╰────────
|
||||||
|
╭────────
|
||||||
|
│ 0/0
|
||||||
|
│ >
|
||||||
|
╰────────
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, _1) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_style_full_adaptive_height_double
|
||||||
|
tmux.send_keys %(seq 1| #{FZF} --style=full:double --border --height=~100% --header-lines=1 --info=default), :Enter
|
||||||
|
block = <<~BLOCK
|
||||||
|
╔══════════
|
||||||
|
║ ╔════════
|
||||||
|
║ ╚════════
|
||||||
|
║ ╔════════
|
||||||
|
║ ║ 1
|
||||||
|
║ ╚════════
|
||||||
|
║ ╔════════
|
||||||
|
║ ║ 0/0
|
||||||
|
║ ║ >
|
||||||
|
║ ╚════════
|
||||||
|
╚══════════
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, _1) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_change_nth
|
||||||
|
input = [
|
||||||
|
*[''] * 1000,
|
||||||
|
'foo bar bar bar bar',
|
||||||
|
'foo foo bar bar bar',
|
||||||
|
'foo foo foo bar bar',
|
||||||
|
'foo foo foo foo bar',
|
||||||
|
*[''] * 1000
|
||||||
|
]
|
||||||
|
writelines(input)
|
||||||
|
nths = '1,2..4,-1,-3..,..2'
|
||||||
|
tmux.send_keys %(#{FZF} -qfoo -n#{nths} --bind 'space:change-nth(2|3|4|5|),result:transform-prompt:echo "[$FZF_NTH] "' < #{tempname}), :Enter
|
||||||
|
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert lines.any_include?("[#{nths}] foo")
|
||||||
|
assert_equal 4, lines.match_count
|
||||||
|
end
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert lines.any_include?('[2] foo')
|
||||||
|
assert_equal 3, lines.match_count
|
||||||
|
end
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert lines.any_include?('[3] foo')
|
||||||
|
assert_equal 2, lines.match_count
|
||||||
|
end
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert lines.any_include?('[4] foo')
|
||||||
|
assert_equal 1, lines.match_count
|
||||||
|
end
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert lines.any_include?('[5] foo')
|
||||||
|
assert_equal 0, lines.match_count
|
||||||
|
end
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert lines.any_include?("[#{nths}] foo")
|
||||||
|
assert_equal 4, lines.match_count
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
module TestShell
|
module TestShell
|
||||||
@@ -3677,6 +4091,23 @@ module CompletionTest
|
|||||||
tmux.until { |lines| assert_equal 'unset FZFFOOBAR', lines[-1] }
|
tmux.until { |lines| assert_equal 'unset FZFFOOBAR', lines[-1] }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_completion_in_command_sequence
|
||||||
|
tmux.send_keys 'export FZFFOOBAR=BAZ', :Enter
|
||||||
|
tmux.prepare
|
||||||
|
|
||||||
|
triggers = ['**', '~~', '++', 'ff', '/']
|
||||||
|
triggers.concat(['&', '[', ';', '`']) if instance_of?(TestZsh)
|
||||||
|
|
||||||
|
triggers.each do |trigger|
|
||||||
|
set_var('FZF_COMPLETION_TRIGGER', trigger)
|
||||||
|
command = "echo foo; QUX=THUD unset FZFFOOBR#{trigger}"
|
||||||
|
tmux.send_keys command.sub(/(;|`)$/, '\\\\\1'), :Tab
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { |lines| assert_equal 'echo foo; QUX=THUD unset FZFFOOBAR', lines[-1] }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_file_completion_unicode
|
def test_file_completion_unicode
|
||||||
FileUtils.mkdir_p('/tmp/fzf-test')
|
FileUtils.mkdir_p('/tmp/fzf-test')
|
||||||
tmux.paste "cd /tmp/fzf-test; echo test3 > $'fzf-unicode \\355\\205\\214\\354\\212\\244\\355\\212\\2701'; echo test4 > $'fzf-unicode \\355\\205\\214\\354\\212\\244\\355\\212\\2702'"
|
tmux.paste "cd /tmp/fzf-test; echo test3 > $'fzf-unicode \\355\\205\\214\\354\\212\\244\\355\\212\\2701'; echo test4 > $'fzf-unicode \\355\\205\\214\\354\\212\\244\\355\\212\\2702'"
|
||||||
|
Reference in New Issue
Block a user