mirror of
https://github.com/junegunn/fzf.git
synced 2025-08-13 11:15:47 -07:00
Compare commits
191 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
1afd143810 | ||
|
e5cd7f0a3a | ||
|
51d3940c63 | ||
|
179aec1578 | ||
|
af0014aba8 | ||
|
da3d995709 | ||
|
04c4269db3 | ||
|
78f238294f | ||
|
354d0468c1 | ||
|
4efcc344c3 | ||
|
5818b58350 | ||
|
7941129cc4 | ||
|
069d71a840 | ||
|
08027e7a79 | ||
|
ead302981c | ||
|
fe0ffa14ff | ||
|
821b8e70a8 | ||
|
8ceda54c7d | ||
|
84e515bd6e | ||
|
dea1df6878 | ||
|
0076ec2e8d | ||
|
82c9671f79 | ||
|
d364a1122e | ||
|
fb570e94e7 | ||
|
6e3c830cd2 | ||
|
d7db7fc132 | ||
|
ff1550bb38 | ||
|
976001e474 | ||
|
531dd6fb4f | ||
|
ba035f2a76 | ||
|
d34675d3c9 | ||
|
ce95adc66c | ||
|
397fe8e395 | ||
|
111266d832 | ||
|
19d858f9b6 | ||
|
79690724d8 | ||
|
5ed87ffcb9 | ||
|
b99cb6323f | ||
|
debf3d8a8a | ||
|
4811e52af3 | ||
|
8d81730ec2 | ||
|
330a85c25c | ||
|
3a21116307 | ||
|
247d168af6 | ||
|
b2a8a283c7 | ||
|
c36ddce36f | ||
|
c35d9cff7d | ||
|
549ce3cf6c | ||
|
575bc0768c | ||
|
89334e881e | ||
|
dcec6354f5 | ||
|
16d338da84 | ||
|
27258f7207 | ||
|
4d2d6a5ced | ||
|
0c00b203e6 | ||
|
3b68dcdd81 | ||
|
39db026161 | ||
|
f6c589c606 | ||
|
2bd29c3172 | ||
|
4a61f53b85 | ||
|
adc9ad28da | ||
|
585cfaef8b | ||
|
b5cd8880b1 | ||
|
44ddab881e | ||
|
bfa287b66d | ||
|
243e52fa11 | ||
|
c166eaba6d | ||
|
09194c24f2 | ||
|
ec521e47aa | ||
|
e3f4a51c18 | ||
|
0a06fd6f63 | ||
|
70eace5290 | ||
|
40f9f254a9 | ||
|
15d6c17390 | ||
|
a9d1d42436 | ||
|
1ecfa38eee | ||
|
54fd92b7dd | ||
|
835906d392 | ||
|
1721e6a1ed | ||
|
c7ee3b833f | ||
|
ffb6e28ca7 | ||
|
a4c6846851 | ||
|
d18c0bf694 | ||
|
4e3f9854e6 | ||
|
b27943423e | ||
|
894a1016bc | ||
|
efe6cddd34 | ||
|
f1c6bdf3e8 | ||
|
710659bcf5 | ||
|
be67775da4 | ||
|
2c6381499c | ||
|
4df842e78c | ||
|
b81696fb64 | ||
|
d226d841a1 | ||
|
c6d83047e5 | ||
|
46dabccdf1 | ||
|
cd9517b679 | ||
|
cd6677ba1d | ||
|
9c1a47acf7 | ||
|
0c280a3ce1 | ||
|
53e8b6e705 | ||
|
ad33165fa7 | ||
|
2055db61c8 | ||
|
d2c662e54f | ||
|
d24b58ef3f | ||
|
06ae9b0f3b | ||
|
2a9c1c06a4 | ||
|
90ad1b7f22 | ||
|
f22fbcd1af | ||
|
1d761684c5 | ||
|
e491770f1c | ||
|
a41be61506 | ||
|
1a8f633611 | ||
|
af8fe918d8 | ||
|
8ef9dfd9a2 | ||
|
66df24040f | ||
|
ed4442d9ea | ||
|
0edb5d5ebb | ||
|
9ffc2c7ca3 | ||
|
93cb3758b5 | ||
|
d22e75dcdd | ||
|
a1b2a6fe2c | ||
|
e15cba0c8c | ||
|
31fd207ba2 | ||
|
ba6d1b8772 | ||
|
0dce561ec9 | ||
|
376142eb0d | ||
|
664ee1f483 | ||
|
dac5b6fde1 | ||
|
998c57442b | ||
|
4a0ab6c926 | ||
|
f43e82f17f | ||
|
62238620a5 | ||
|
200745011a | ||
|
82fd88339b | ||
|
de0f2efbfb | ||
|
29cf28d845 | ||
|
7e4dbb5f3b | ||
|
923c3a814d | ||
|
779e3cc5b5 | ||
|
3f3d1ef8f5 | ||
|
f92f9f137a | ||
|
87f7f436e8 | ||
|
4298c0b1eb | ||
|
6c104d771e | ||
|
aefb9a5bc4 | ||
|
8868d7cbb8 | ||
|
10cbac20f9 | ||
|
26bcd0c90d | ||
|
fbece2bb67 | ||
|
0012183ede | ||
|
8916cbc6ab | ||
|
21ce70054f | ||
|
3ba82b6d87 | ||
|
e771c5d057 | ||
|
4e5e925e39 | ||
|
b7248d4115 | ||
|
639253840f | ||
|
710ebdf9c1 | ||
|
bb64d84ce4 | ||
|
cd1da27ff2 | ||
|
c1accc2e5b | ||
|
e4489dcbc1 | ||
|
c0d407f7ce | ||
|
461115afde | ||
|
bae1965231 | ||
|
b89c77ec9a | ||
|
1ca5f09d7b | ||
|
d79902ae59 | ||
|
77568e114f | ||
|
a24d274a3c | ||
|
dac81432d6 | ||
|
309b5081ef | ||
|
91bc4f2671 | ||
|
4c9d37d919 | ||
|
7e9566f66a | ||
|
3f7e8a475d | ||
|
1cf7c0f334 | ||
|
ff8ee9ee4e | ||
|
cbbd939a94 | ||
|
f232df2887 | ||
|
16bfb2c80c | ||
|
0ba066123e | ||
|
81c51c26cc | ||
|
6fa8295ac5 | ||
|
f975b40236 | ||
|
01d9d9c8c8 | ||
|
1eafc4e5d9 | ||
|
38e4020aa8 | ||
|
ac32fbb3b2 | ||
|
7d26eca5cc |
2
.github/workflows/linux.yml
vendored
2
.github/workflows/linux.yml
vendored
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
name: Test fzf on Linux
|
name: build
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -11,3 +11,4 @@ gopath
|
|||||||
fzf
|
fzf
|
||||||
tmp
|
tmp
|
||||||
*.patch
|
*.patch
|
||||||
|
.idea
|
||||||
|
@@ -14,6 +14,7 @@ builds:
|
|||||||
- windows
|
- windows
|
||||||
- freebsd
|
- freebsd
|
||||||
- openbsd
|
- openbsd
|
||||||
|
- android
|
||||||
goarch:
|
goarch:
|
||||||
- amd64
|
- amd64
|
||||||
- arm
|
- arm
|
||||||
@@ -22,9 +23,9 @@ builds:
|
|||||||
- ppc64le
|
- ppc64le
|
||||||
- s390x
|
- s390x
|
||||||
goarm:
|
goarm:
|
||||||
- 5
|
- "5"
|
||||||
- 6
|
- "6"
|
||||||
- 7
|
- "7"
|
||||||
flags:
|
flags:
|
||||||
- -trimpath
|
- -trimpath
|
||||||
ldflags:
|
ldflags:
|
||||||
@@ -38,6 +39,10 @@ builds:
|
|||||||
goarch: arm64
|
goarch: arm64
|
||||||
- goos: openbsd
|
- goos: openbsd
|
||||||
goarch: arm64
|
goarch: arm64
|
||||||
|
- goos: android
|
||||||
|
goarch: amd64
|
||||||
|
- goos: android
|
||||||
|
goarch: arm
|
||||||
|
|
||||||
# .goreleaser.yaml
|
# .goreleaser.yaml
|
||||||
notarize:
|
notarize:
|
||||||
|
@@ -1,2 +1,2 @@
|
|||||||
golang 1.20.13
|
golang 1.20.14
|
||||||
ruby 3.4.1
|
ruby 3.4.1
|
||||||
|
222
CHANGELOG.md
222
CHANGELOG.md
@@ -1,6 +1,228 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
0.65.1
|
||||||
|
------
|
||||||
|
- Fixed incorrect `$FZF_CLICK_HEADER_WORD` and `$FZF_CLICK_FOOTER_WORD` when the header or footer contains ANSI escape sequences and tab characters.
|
||||||
|
- Fixed a bug where you cannot unset the default `--nth` using `change-nth` action.
|
||||||
|
- Fixed a highlighting bug when using `--color fg:dim,nth:regular` pattern over ANSI-colored items.
|
||||||
|
|
||||||
|
0.65.0
|
||||||
|
------
|
||||||
|
- Added `click-footer` event that is triggered when the footer section is clicked. When the event is triggered, the following environment variables are set:
|
||||||
|
- `$FZF_CLICK_FOOTER_COLUMN` - clicked column (1-based)
|
||||||
|
- `$FZF_CLICK_FOOTER_LINE` - clicked line (1-based)
|
||||||
|
- `$FZF_CLICK_FOOTER_WORD` - the word under the cursor
|
||||||
|
```sh
|
||||||
|
fzf --footer $'[Edit] [View]\n[Copy to clipboard]' \
|
||||||
|
--with-shell 'bash -c' \
|
||||||
|
--bind 'click-footer:transform:
|
||||||
|
[[ $FZF_CLICK_FOOTER_WORD =~ Edit ]] && echo "execute:vim \{}"
|
||||||
|
[[ $FZF_CLICK_FOOTER_WORD =~ View ]] && echo "execute:view \{}"
|
||||||
|
(( FZF_CLICK_FOOTER_LINE == 2 )) && (( FZF_CLICK_FOOTER_COLUMN < 20 )) &&
|
||||||
|
echo "execute-silent(echo -n \{} | pbcopy)+bell"
|
||||||
|
'
|
||||||
|
```
|
||||||
|
- Added `trigger(...)` action that triggers events bound to another key or event.
|
||||||
|
```sh
|
||||||
|
# You can click on each key name to trigger the actions bound to that key
|
||||||
|
fzf --footer 'Ctrl-E: Edit / Ctrl-V: View / Ctrl-Y: Copy to clipboard' \
|
||||||
|
--with-shell 'bash -c' \
|
||||||
|
--bind 'ctrl-e:execute:vim {}' \
|
||||||
|
--bind 'ctrl-v:execute:view {}' \
|
||||||
|
--bind 'ctrl-y:execute-silent(echo -n {} | pbcopy)+bell' \
|
||||||
|
--bind 'click-footer:transform:
|
||||||
|
[[ $FZF_CLICK_FOOTER_WORD =~ Ctrl ]] && echo "trigger(${FZF_CLICK_FOOTER_WORD%:})"
|
||||||
|
'
|
||||||
|
```
|
||||||
|
- You can specify a series of keys and events
|
||||||
|
```sh
|
||||||
|
fzf --bind 'a:up,b:trigger(a,a,a)'
|
||||||
|
```
|
||||||
|
- Added support for `{*n}` and `{*nf}` placeholder.
|
||||||
|
- `{*n}` evaluates to the zero-based ordinal index of all matched items.
|
||||||
|
- `{*nf}` evaluates to the temporary file containing that.
|
||||||
|
- Bug fixes and improvements
|
||||||
|
- [neovim] Fixed margin background color when `&winborder` is used (#4453)
|
||||||
|
- Fixed rendering error when hiding a preview window without border (#4465)
|
||||||
|
- fix(shell): check for mawk existence before version check (#4468)
|
||||||
|
- Thanks to @LangLangBart and @akinomyoga
|
||||||
|
- Fixed `--no-header-lines-border` behavior (08027e7a)
|
||||||
|
|
||||||
|
0.64.0
|
||||||
|
------
|
||||||
|
- Added `multi` event that is triggered when the multi-selection has changed.
|
||||||
|
```sh
|
||||||
|
fzf --multi \
|
||||||
|
--bind 'ctrl-a:select-all,ctrl-d:deselect-all' \
|
||||||
|
--bind 'multi:transform-footer:(( FZF_SELECT_COUNT )) && echo "Selected $FZF_SELECT_COUNT item(s)"'
|
||||||
|
```
|
||||||
|
- [Halfwidth and fullwidth alphanumeric and punctuation characters](https://en.wikipedia.org/wiki/Halfwidth_and_Fullwidth_Forms_(Unicode_block)) are now internally normalized to their ASCII equivalents to allow matching with ASCII queries.
|
||||||
|
```sh
|
||||||
|
echo ABC| fzf -q abc
|
||||||
|
```
|
||||||
|
- Renamed `clear-selection` action to `clear-multi` for consistency.
|
||||||
|
- `clear-selection` remains supported as an alias for backward compatibility.
|
||||||
|
- Bug fixes
|
||||||
|
- Fixed a bug that could cause fzf to abort due to incorrect update ordering.
|
||||||
|
- Fixed a bug where some multi-selections were lost when using `exclude` or `change-nth`.
|
||||||
|
|
||||||
|
0.63.0
|
||||||
|
------
|
||||||
|
_Release highlights: https://junegunn.github.io/fzf/releases/0.63.0/_
|
||||||
|
|
||||||
|
- Added footer. The default border style for footer is `line`, which draws a single separator line.
|
||||||
|
```sh
|
||||||
|
fzf --reverse --footer "fzf: friend zone forever"
|
||||||
|
```
|
||||||
|
- Options
|
||||||
|
- `--footer[=STRING]`
|
||||||
|
- `--footer-border[=STYLE]`
|
||||||
|
- `--footer-label=LABEL`
|
||||||
|
- `--footer-label-pos=COL[:bottom]`
|
||||||
|
- Colors
|
||||||
|
- `footer`
|
||||||
|
- `footer-bg`
|
||||||
|
- `footer-border`
|
||||||
|
- `footer-label`
|
||||||
|
- Actions
|
||||||
|
- `change-footer`
|
||||||
|
- `transform-footer`
|
||||||
|
- `bg-transform-footer`
|
||||||
|
- `change-footer-label`
|
||||||
|
- `transform-footer-label`
|
||||||
|
- `bg-transform-footer-label`
|
||||||
|
- `line` border style is now allowed for all types of border except for `--list-border`.
|
||||||
|
```sh
|
||||||
|
fzf --height 50% --style full:line --preview 'cat {}' \
|
||||||
|
--bind 'focus:bg-transform-header(file {})+bg-transform-footer(wc {})'
|
||||||
|
```
|
||||||
|
- Added `{*}` placeholder flag that evaluates to all matched items.
|
||||||
|
```bash
|
||||||
|
seq 10000 | fzf --preview "awk '{sum += \$1} END {print sum}' {*f}"
|
||||||
|
```
|
||||||
|
- Use this with caution, as it can make fzf sluggish for large lists.
|
||||||
|
- Added asynchronous transform actions with `bg-` prefix that run asynchronously in the background, along with `bg-cancel` action to cancel currently running `bg-transform` actions.
|
||||||
|
```sh
|
||||||
|
# Implement popup that disappears after 1 second
|
||||||
|
# * Use footer as the popup
|
||||||
|
# * Use `bell` to ring the terminal bell
|
||||||
|
# * Use `bg-transform-footer` to clear the footer after 1 second
|
||||||
|
# * Use `bg-cancel` to cancel currently running background transform actions
|
||||||
|
fzf --multi --list-border \
|
||||||
|
--bind 'enter:execute-silent(echo -n {+} | pbcopy)+bell' \
|
||||||
|
--bind 'enter:+transform-footer(echo Copied {} to clipboard)' \
|
||||||
|
--bind 'enter:+bg-cancel+bg-transform-footer(sleep 1)'
|
||||||
|
|
||||||
|
# It's okay for the commands to take a little while because they run in the background
|
||||||
|
GETTER='curl -s http://metaphorpsum.com/sentences/1'
|
||||||
|
fzf --style full --border --preview : \
|
||||||
|
--bind "focus:bg-transform-header:$GETTER" \
|
||||||
|
--bind "focus:+bg-transform-footer:$GETTER" \
|
||||||
|
--bind "focus:+bg-transform-border-label:$GETTER" \
|
||||||
|
--bind "focus:+bg-transform-preview-label:$GETTER" \
|
||||||
|
--bind "focus:+bg-transform-input-label:$GETTER" \
|
||||||
|
--bind "focus:+bg-transform-list-label:$GETTER" \
|
||||||
|
--bind "focus:+bg-transform-header-label:$GETTER" \
|
||||||
|
--bind "focus:+bg-transform-footer-label:$GETTER" \
|
||||||
|
--bind "focus:+bg-transform-ghost:$GETTER" \
|
||||||
|
--bind "focus:+bg-transform-prompt:$GETTER"
|
||||||
|
```
|
||||||
|
- Added support for full-line background color in the list section
|
||||||
|
```sh
|
||||||
|
for i in $(seq 16 255); do
|
||||||
|
echo -e "\x1b[48;5;${i}m\x1b[0Khello"
|
||||||
|
done | fzf --ansi
|
||||||
|
```
|
||||||
|
- SSH completion enhancements by @akinomyoga
|
||||||
|
- Bug fixes and improvements
|
||||||
|
|
||||||
|
0.62.0
|
||||||
|
------
|
||||||
|
- Relaxed the `--color` option syntax to allow whitespace-separated entries (in addition to commas), making multi-line definitions easier to write and read
|
||||||
|
```sh
|
||||||
|
# seoul256-light
|
||||||
|
fzf --style full --color='
|
||||||
|
fg:#616161 fg+:#616161
|
||||||
|
bg:#ffffff bg+:#e9e9e9 alt-bg:#f1f1f1
|
||||||
|
hl:#719872 hl+:#719899
|
||||||
|
pointer:#e12672 marker:#e17899
|
||||||
|
header:#719872
|
||||||
|
spinner:#719899 info:#727100
|
||||||
|
prompt:#0099bd query:#616161
|
||||||
|
border:#e1e1e1
|
||||||
|
'
|
||||||
|
```
|
||||||
|
- Added `alt-bg` color to create striped lines to visually separate rows
|
||||||
|
```sh
|
||||||
|
fzf --color bg:237,alt-bg:238,current-bg:236 --highlight-line
|
||||||
|
|
||||||
|
declare -f | perl -0777 -pe 's/^}\n/}\0/gm' |
|
||||||
|
bat --plain --language bash --color always |
|
||||||
|
fzf --read0 --ansi --reverse --multi \
|
||||||
|
--color bg:237,alt-bg:238,current-bg:236 --highlight-line
|
||||||
|
```
|
||||||
|
- [fish] Improvements in CTRL-R binding (@bitraid)
|
||||||
|
- You can trigger CTRL-R in the middle of a command to insert the selected item
|
||||||
|
- You can delete history items with SHIFT-DEL
|
||||||
|
- Bug fixes and improvements
|
||||||
|
- Fixed unnecessary 100ms delay after `reload` (#4364)
|
||||||
|
- Fixed `selected-bg` not applied to colored items (#4372)
|
||||||
|
|
||||||
|
0.61.3
|
||||||
|
------
|
||||||
|
- Reverted #4351 as it caused `tmux run-shell 'fzf --tmux'` to fail (#4559 #4560)
|
||||||
|
- More environment variables for child processes (#4356)
|
||||||
|
|
||||||
|
0.61.2
|
||||||
|
------
|
||||||
|
- Fixed panic when using header border without pointer/marker (@phanen)
|
||||||
|
- Fixed `--tmux` option when already inside a tmux popup (@peikk0)
|
||||||
|
- Bug fixes and improvements in CTRL-T binding of fish (#4334) (@bitraid)
|
||||||
|
- Added `--no-tty-default` option to make fzf search for the current TTY device instead of defaulting to `/dev/tty` (#4242)
|
||||||
|
|
||||||
|
0.61.1
|
||||||
|
------
|
||||||
|
- Disable bracketed-paste mode on exit. This fixes issue where pasting breaks after running fzf on old bash versions that don't support the mode.
|
||||||
|
|
||||||
|
0.61.0
|
||||||
|
------
|
||||||
|
- Added `--ghost=TEXT` to display a ghost text when the input is empty
|
||||||
|
```sh
|
||||||
|
# Display "Type to search" when the input is empty
|
||||||
|
fzf --ghost "Type to search"
|
||||||
|
```
|
||||||
|
- Added `change-ghost` and `transform-ghost` actions for dynamically changing the ghost text
|
||||||
|
- Added `change-pointer` and `transform-pointer` actions for dynamically changing the pointer sign
|
||||||
|
- Added `r` flag for placeholder expression (raw mode) for unquoted output
|
||||||
|
- Bug fixes and improvements
|
||||||
|
|
||||||
|
0.60.3
|
||||||
|
------
|
||||||
|
- Bug fixes and improvements
|
||||||
|
- [fish] Enable multiple history commands insertion (#4280) (@bitraid)
|
||||||
|
- [walker] Append '/' to directory entries on MSYS2 (#4281)
|
||||||
|
- Trim trailing whitespaces after processing ANSI sequences (#4282)
|
||||||
|
- Remove temp files before `become` when using `--tmux` option (#4283)
|
||||||
|
- Fix condition for using item numlines cache (#4285) (@alex-huff)
|
||||||
|
- Make `--accept-nth` compatible with `--select-1` (#4287)
|
||||||
|
- Increase the query length limit from 300 to 1000 (#4292)
|
||||||
|
- [windows] Prevent fzf from consuming user input while paused (#4260)
|
||||||
|
|
||||||
|
0.60.2
|
||||||
|
------
|
||||||
|
- Template for `--with-nth` and `--accept-nth` now supports `{n}` which evaluates to the zero-based ordinal index of the item
|
||||||
|
- Fixed a regression that caused the last field in the "nth" expression to be trimmed when a regular expression delimiter is used
|
||||||
|
- Thanks to @phanen for the fix
|
||||||
|
- Fixed 'jump' action when the pointer is an empty string
|
||||||
|
|
||||||
|
0.60.1
|
||||||
|
------
|
||||||
|
- Bug fixes and minor improvements
|
||||||
|
- Built-in walker now prints directory entries with a trailing slash
|
||||||
|
- Fixed a bug causing unexpected behavior with [fzf-tab](https://github.com/Aloxaf/fzf-tab). Please upgrade if you use it.
|
||||||
|
- Thanks to @alexeisersun, @bitraid, @Lompik, and @fsc0 for the contributions
|
||||||
|
|
||||||
0.60.0
|
0.60.0
|
||||||
------
|
------
|
||||||
_Release highlights: https://junegunn.github.io/fzf/releases/0.60.0/_
|
_Release highlights: https://junegunn.github.io/fzf/releases/0.60.0/_
|
||||||
|
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2013-2024 Junegunn Choi
|
Copyright (c) 2013-2025 Junegunn Choi
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
@@ -155,6 +155,7 @@ let g:fzf_layout = { 'window': '10new' }
|
|||||||
let g:fzf_colors =
|
let g:fzf_colors =
|
||||||
\ { 'fg': ['fg', 'Normal'],
|
\ { 'fg': ['fg', 'Normal'],
|
||||||
\ 'bg': ['bg', 'Normal'],
|
\ 'bg': ['bg', 'Normal'],
|
||||||
|
\ 'query': ['fg', 'Normal'],
|
||||||
\ 'hl': ['fg', 'Comment'],
|
\ 'hl': ['fg', 'Comment'],
|
||||||
\ 'fg+': ['fg', 'CursorLine', 'CursorColumn', 'Normal'],
|
\ 'fg+': ['fg', 'CursorLine', 'CursorColumn', 'Normal'],
|
||||||
\ 'bg+': ['bg', 'CursorLine', 'CursorColumn'],
|
\ 'bg+': ['bg', 'CursorLine', 'CursorColumn'],
|
||||||
@@ -492,4 +493,4 @@ autocmd FileType fzf set laststatus=0 noshowmode noruler
|
|||||||
|
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2013-2024 Junegunn Choi
|
Copyright (c) 2013-2025 Junegunn Choi
|
||||||
|
33
SECURITY.md
Normal file
33
SECURITY.md
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# Security Reporting
|
||||||
|
|
||||||
|
If you wish to report a security vulnerability privately, we appreciate your diligence. Please follow the guidelines below to submit your report.
|
||||||
|
|
||||||
|
## Reporting
|
||||||
|
|
||||||
|
To report a security vulnerability, please provide the following information:
|
||||||
|
|
||||||
|
1. **PROJECT**
|
||||||
|
- https://github.com/junegunn/fzf
|
||||||
|
|
||||||
|
2. **PUBLIC**
|
||||||
|
- Indicate whether this vulnerability has already been publicly discussed or disclosed.
|
||||||
|
- If so, provide relevant links.
|
||||||
|
|
||||||
|
3. **DESCRIPTION**
|
||||||
|
- Provide a detailed description of the security vulnerability.
|
||||||
|
- Include as much information as possible to help us understand and address the issue.
|
||||||
|
|
||||||
|
Send this information, along with any additional relevant details, to <junegunn.c AT gmail DOT com>.
|
||||||
|
|
||||||
|
## Confidentiality
|
||||||
|
|
||||||
|
We kindly ask you to keep the report confidential until a public announcement is made.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Vulnerabilities will be handled on a best-effort basis.
|
||||||
|
- You may request an advance copy of the patched release, but we cannot guarantee early access before the public release.
|
||||||
|
- You will be notified via email simultaneously with the public announcement.
|
||||||
|
- We will respond within a few weeks to confirm whether your report has been accepted or rejected.
|
||||||
|
|
||||||
|
Thank you for helping to improve the security of our project!
|
@@ -503,7 +503,7 @@ LICENSE *fzf-license*
|
|||||||
|
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2013-2024 Junegunn Choi
|
Copyright (c) 2013-2025 Junegunn Choi
|
||||||
|
|
||||||
==============================================================================
|
==============================================================================
|
||||||
vim:tw=78:sw=2:ts=2:ft=help:norl:nowrap:
|
vim:tw=78:sw=2:ts=2:ft=help:norl:nowrap:
|
||||||
|
2
go.mod
2
go.mod
@@ -1,7 +1,7 @@
|
|||||||
module github.com/junegunn/fzf
|
module github.com/junegunn/fzf
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/charlievieth/fastwalk v1.0.9
|
github.com/charlievieth/fastwalk v1.0.12
|
||||||
github.com/gdamore/tcell/v2 v2.8.1
|
github.com/gdamore/tcell/v2 v2.8.1
|
||||||
github.com/junegunn/go-shellwords v0.0.0-20250127100254-2aa3b3277741
|
github.com/junegunn/go-shellwords v0.0.0-20250127100254-2aa3b3277741
|
||||||
github.com/mattn/go-isatty v0.0.20
|
github.com/mattn/go-isatty v0.0.20
|
||||||
|
4
go.sum
4
go.sum
@@ -1,5 +1,5 @@
|
|||||||
github.com/charlievieth/fastwalk v1.0.9 h1:Odb92AfoReO3oFBfDGT5J+nwgzQPF/gWAw6E6/lkor0=
|
github.com/charlievieth/fastwalk v1.0.12 h1:pwfxe1LajixViQqo7EFLXU2+mQxb6OaO0CeNdVwRKTg=
|
||||||
github.com/charlievieth/fastwalk v1.0.9/go.mod h1:yGy1zbxog41ZVMcKA/i8ojXLFsuayX5VvwhQVoj9PBI=
|
github.com/charlievieth/fastwalk v1.0.12/go.mod h1:yGy1zbxog41ZVMcKA/i8ojXLFsuayX5VvwhQVoj9PBI=
|
||||||
github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw=
|
github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw=
|
||||||
github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo=
|
github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo=
|
||||||
github.com/gdamore/tcell/v2 v2.8.1 h1:KPNxyqclpWpWQlPLx6Xui1pMk8S+7+R37h3g07997NU=
|
github.com/gdamore/tcell/v2 v2.8.1 h1:KPNxyqclpWpWQlPLx6Xui1pMk8S+7+R37h3g07997NU=
|
||||||
|
41
install
41
install
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
set -u
|
set -u
|
||||||
|
|
||||||
version=0.60.0
|
version=0.65.1
|
||||||
auto_completion=
|
auto_completion=
|
||||||
key_bindings=
|
key_bindings=
|
||||||
update_config=2
|
update_config=2
|
||||||
@@ -164,28 +164,29 @@ download() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Try to download binary executable
|
# Try to download binary executable
|
||||||
archi=$(uname -sm)
|
archi=$(uname -smo)
|
||||||
binary_available=1
|
binary_available=1
|
||||||
binary_error=""
|
binary_error=""
|
||||||
case "$archi" in
|
case "$archi" in
|
||||||
Darwin\ arm64) download fzf-$version-darwin_arm64.tar.gz ;;
|
Darwin\ arm64*) download fzf-$version-darwin_arm64.tar.gz ;;
|
||||||
Darwin\ x86_64) download fzf-$version-darwin_amd64.tar.gz ;;
|
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 ;;
|
||||||
Linux\ armv8*) download fzf-$version-linux_arm64.tar.gz ;;
|
Linux\ armv8*) download fzf-$version-linux_arm64.tar.gz ;;
|
||||||
Linux\ aarch64*) download fzf-$version-linux_arm64.tar.gz ;;
|
Linux\ aarch64\ Android) download fzf-$version-android_arm64.tar.gz ;;
|
||||||
Linux\ loongarch64) download fzf-$version-linux_loong64.tar.gz ;;
|
Linux\ aarch64*) download fzf-$version-linux_arm64.tar.gz ;;
|
||||||
Linux\ ppc64le) download fzf-$version-linux_ppc64le.tar.gz ;;
|
Linux\ loongarch64*) download fzf-$version-linux_loong64.tar.gz ;;
|
||||||
Linux\ *64) download fzf-$version-linux_amd64.tar.gz ;;
|
Linux\ ppc64le*) download fzf-$version-linux_ppc64le.tar.gz ;;
|
||||||
Linux\ s390x) download fzf-$version-linux_s390x.tar.gz ;;
|
Linux\ *64*) download fzf-$version-linux_amd64.tar.gz ;;
|
||||||
FreeBSD\ *64) download fzf-$version-freebsd_amd64.tar.gz ;;
|
Linux\ s390x*) download fzf-$version-linux_s390x.tar.gz ;;
|
||||||
OpenBSD\ *64) download fzf-$version-openbsd_amd64.tar.gz ;;
|
FreeBSD\ *64*) download fzf-$version-freebsd_amd64.tar.gz ;;
|
||||||
CYGWIN*\ *64) download fzf-$version-windows_amd64.zip ;;
|
OpenBSD\ *64*) download fzf-$version-openbsd_amd64.tar.gz ;;
|
||||||
MINGW*\ *64) download fzf-$version-windows_amd64.zip ;;
|
CYGWIN*\ *64*) download fzf-$version-windows_amd64.zip ;;
|
||||||
MSYS*\ *64) download fzf-$version-windows_amd64.zip ;;
|
MINGW*\ *64*) download fzf-$version-windows_amd64.zip ;;
|
||||||
Windows*\ *64) download fzf-$version-windows_amd64.zip ;;
|
MSYS*\ *64*) download fzf-$version-windows_amd64.zip ;;
|
||||||
*) binary_available=0 binary_error=1 ;;
|
Windows*\ *64*) download fzf-$version-windows_amd64.zip ;;
|
||||||
|
*) binary_available=0 binary_error=1 ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
cd "$fzf_base"
|
cd "$fzf_base"
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
$version="0.60.0"
|
$version="0.65.1"
|
||||||
|
|
||||||
$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.60"
|
var version = "0.65"
|
||||||
var revision = "devel"
|
var revision = "devel"
|
||||||
|
|
||||||
//go:embed shell/key-bindings.bash
|
//go:embed shell/key-bindings.bash
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
.ig
|
.ig
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2013-2024 Junegunn Choi
|
Copyright (c) 2013-2025 Junegunn Choi
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
..
|
..
|
||||||
.TH fzf\-tmux 1 "Feb 2025" "fzf 0.60.0" "fzf\-tmux - open fzf in tmux split pane"
|
.TH fzf\-tmux 1 "Aug 2025" "fzf 0.65.1" "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
|
||||||
|
202
man/man1/fzf.1
202
man/man1/fzf.1
@@ -1,7 +1,7 @@
|
|||||||
.ig
|
.ig
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2013-2024 Junegunn Choi
|
Copyright (c) 2013-2025 Junegunn Choi
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
..
|
..
|
||||||
.TH fzf 1 "Feb 2025" "fzf 0.60.0" "fzf - a command-line fuzzy finder"
|
.TH fzf 1 "Aug 2025" "fzf 0.65.1" "fzf - a command-line fuzzy finder"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf - a command-line fuzzy finder
|
fzf - a command-line fuzzy finder
|
||||||
@@ -56,7 +56,9 @@ Case-insensitive match (default: smart-case match)
|
|||||||
Case-sensitive match
|
Case-sensitive match
|
||||||
.TP
|
.TP
|
||||||
.B "\-\-smart\-case"
|
.B "\-\-smart\-case"
|
||||||
Smart-case match (default)
|
Smart-case match (default). In this mode, the search is case-insensitive by
|
||||||
|
default, but it becomes case-sensitive if the query contains any uppercase
|
||||||
|
letters.
|
||||||
.TP
|
.TP
|
||||||
.B "\-\-literal"
|
.B "\-\-literal"
|
||||||
Do not normalize latin script letters for matching.
|
Do not normalize latin script letters for matching.
|
||||||
@@ -120,7 +122,9 @@ fields.
|
|||||||
.BI "\-\-with\-nth=" "N[,..] or TEMPLATE"
|
.BI "\-\-with\-nth=" "N[,..] or TEMPLATE"
|
||||||
Transform the presentation of each line using the field index expressions.
|
Transform the presentation of each line using the field index expressions.
|
||||||
For advanced transformation, you can provide a template containing field index
|
For advanced transformation, you can provide a template containing field index
|
||||||
expressions in curly braces.
|
expressions in curly braces. When you use a template, the trailing delimiter is
|
||||||
|
stripped from each expression, giving you more control over the output.
|
||||||
|
\fB{n}\fR in template evaluates to the zero-based ordinal index of the line.
|
||||||
|
|
||||||
.RS
|
.RS
|
||||||
e.g.
|
e.g.
|
||||||
@@ -128,13 +132,16 @@ e.g.
|
|||||||
echo foo bar baz | fzf --with-nth 2..
|
echo foo bar baz | fzf --with-nth 2..
|
||||||
|
|
||||||
# Use template to rearrange fields
|
# Use template to rearrange fields
|
||||||
echo foo,bar,baz | fzf --delimiter , --with-nth '{1},{3},{2},{1..2}'
|
echo foo,bar,baz | fzf --delimiter , --with-nth '{n},{1},{3},{2},{1..2}'
|
||||||
.RE
|
.RE
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-accept\-nth=" "N[,..] or TEMPLATE"
|
.BI "\-\-accept\-nth=" "N[,..] or TEMPLATE"
|
||||||
Define which fields to print on accept. The last delimiter is stripped from the
|
Define which fields to print on accept. The last delimiter is stripped from the
|
||||||
output. For advanced transformation, you can provide a template containing
|
output. For advanced transformation, you can provide a template containing
|
||||||
field index expressions in curly braces.
|
field index expressions in curly braces. When you use a template, the trailing
|
||||||
|
delimiter is stripped from each expression, giving you more control over the
|
||||||
|
output. \fB{n}\fR in template evaluates to the zero-based ordinal index of the
|
||||||
|
line.
|
||||||
|
|
||||||
.RS
|
.RS
|
||||||
e.g.
|
e.g.
|
||||||
@@ -142,7 +149,7 @@ e.g.
|
|||||||
echo foo bar baz | fzf --accept-nth 2
|
echo foo bar baz | fzf --accept-nth 2
|
||||||
|
|
||||||
# Template
|
# Template
|
||||||
echo foo bar baz | fzf --accept-nth '1st: {1}, 2nd: {2}, 3rd: {3}'
|
echo foo bar baz | fzf --accept-nth 'Index: {n}, 1st: {1}, 2nd: {2}, 3rd: {3}'
|
||||||
.RE
|
.RE
|
||||||
.TP
|
.TP
|
||||||
.B "+s, \-\-no\-sort"
|
.B "+s, \-\-no\-sort"
|
||||||
@@ -221,6 +228,13 @@ e.g. \fB# Avoid rendering both fzf instances at the same time
|
|||||||
(sleep 1; seq 1000000; sleep 1) |
|
(sleep 1; seq 1000000; sleep 1) |
|
||||||
fzf \-\-sync \-\-query 5 \-\-listen \-\-bind start:up,load:up,result:up,focus:change\-header:Ready\fR
|
fzf \-\-sync \-\-query 5 \-\-listen \-\-bind start:up,load:up,result:up,focus:change\-header:Ready\fR
|
||||||
.RE
|
.RE
|
||||||
|
.TP
|
||||||
|
.B "\-\-no\-tty\-default"
|
||||||
|
Make fzf search for the current TTY device via standard error instead of
|
||||||
|
defaulting to \fB/dev/tty\fR. This option avoids issues when launching
|
||||||
|
emacsclient from within fzf. Alternatively, you can change the default TTY
|
||||||
|
device by setting \fB--tty-default=DEVICE_NAME\fR.
|
||||||
|
|
||||||
.SS GLOBAL STYLE
|
.SS GLOBAL STYLE
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-style=" "PRESET"
|
.BI "\-\-style=" "PRESET"
|
||||||
@@ -228,7 +242,7 @@ Apply a style preset [default|minimal|full[:BORDER_STYLE]]
|
|||||||
.TP
|
.TP
|
||||||
.BI "\-\-color=" "[BASE_SCHEME][,COLOR_NAME[:ANSI_COLOR][:ANSI_ATTRIBUTES]]..."
|
.BI "\-\-color=" "[BASE_SCHEME][,COLOR_NAME[:ANSI_COLOR][:ANSI_ATTRIBUTES]]..."
|
||||||
Color configuration. The name of the base color scheme is followed by custom
|
Color configuration. The name of the base color scheme is followed by custom
|
||||||
color mappings.
|
color mappings. Each entry is separated by a comma and/or whitespaces.
|
||||||
|
|
||||||
.RS
|
.RS
|
||||||
.B BASE SCHEME:
|
.B BASE SCHEME:
|
||||||
@@ -248,15 +262,18 @@ color mappings.
|
|||||||
\fBlist\-bg \fRList section background
|
\fBlist\-bg \fRList section background
|
||||||
\fBselected\-bg \fRSelected line background
|
\fBselected\-bg \fRSelected line background
|
||||||
\fBpreview\-bg \fRPreview window background
|
\fBpreview\-bg \fRPreview window background
|
||||||
\fBinput\-bg \fRInput window background (\fB\-\-input\-border\fR)
|
\fBinput\-bg \fRInput window background
|
||||||
\fBheader\-bg \fRHeader window background (\fB\-\-header\-border\fR)
|
\fBheader\-bg \fRHeader window background
|
||||||
|
\fBfooter\-bg \fRFooter window background
|
||||||
\fBhl \fRHighlighted substrings
|
\fBhl \fRHighlighted substrings
|
||||||
\fBselected\-hl \fRHighlighted substrings in the selected line
|
\fBselected\-hl \fRHighlighted substrings in the selected line
|
||||||
\fBcurrent\-fg (fg+) \fRText (current line)
|
\fBcurrent\-fg (fg+) \fRText (current line)
|
||||||
\fBcurrent\-bg (bg+) \fRBackground (current line)
|
\fBcurrent\-bg (bg+) \fRBackground (current line)
|
||||||
\fBgutter \fRGutter on the left
|
\fBgutter \fRGutter on the left
|
||||||
\fBcurrent\-hl (hl+) \fRHighlighted substrings (current line)
|
\fBcurrent\-hl (hl+) \fRHighlighted substrings (current line)
|
||||||
|
\fBalt\-bg \fRAlternate background color to create striped lines
|
||||||
\fBquery (input\-fg) \fRQuery string
|
\fBquery (input\-fg) \fRQuery string
|
||||||
|
\fBghost \fRGhost text (\fB\-\-ghost\fR, \fBdim\fR applied by default)
|
||||||
\fBdisabled \fRQuery string when search is disabled (\fB\-\-disabled\fR)
|
\fBdisabled \fRQuery string when search is disabled (\fB\-\-disabled\fR)
|
||||||
\fBinfo \fRInfo line (match counters)
|
\fBinfo \fRInfo line (match counters)
|
||||||
\fBborder \fRBorder around the window (\fB\-\-border\fR and \fB\-\-preview\fR)
|
\fBborder \fRBorder around the window (\fB\-\-border\fR and \fB\-\-preview\fR)
|
||||||
@@ -268,16 +285,19 @@ color mappings.
|
|||||||
\fBpreview\-scrollbar \fRScrollbar
|
\fBpreview\-scrollbar \fRScrollbar
|
||||||
\fBinput\-border \fRBorder around the input window (\fB\-\-input\-border\fR)
|
\fBinput\-border \fRBorder around the input window (\fB\-\-input\-border\fR)
|
||||||
\fBheader\-border \fRBorder around the header window (\fB\-\-header\-border\fR)
|
\fBheader\-border \fRBorder around the header window (\fB\-\-header\-border\fR)
|
||||||
|
\fBfooter\-border \fRBorder around the footer window (\fB\-\-footer\-border\fR)
|
||||||
\fBlabel \fRBorder label (\fB\-\-border\-label\fR, \fB\-\-list\-label\fR, \fB\-\-input\-label\fR, and \fB\-\-preview\-label\fR)
|
\fBlabel \fRBorder label (\fB\-\-border\-label\fR, \fB\-\-list\-label\fR, \fB\-\-input\-label\fR, and \fB\-\-preview\-label\fR)
|
||||||
\fBlist\-label \fRBorder label of the list section (\fB\-\-list\-label\fR)
|
\fBlist\-label \fRBorder label of the list section (\fB\-\-list\-label\fR)
|
||||||
\fBpreview\-label \fRBorder label of the preview window (\fB\-\-preview\-label\fR)
|
\fBpreview\-label \fRBorder label of the preview window (\fB\-\-preview\-label\fR)
|
||||||
\fBinput\-label \fRBorder label of the input window (\fB\-\-input\-label\fR)
|
\fBinput\-label \fRBorder label of the input window (\fB\-\-input\-label\fR)
|
||||||
\fBheader\-label \fRBorder label of the header window (\fB\-\-header\-label\fR)
|
\fBheader\-label \fRBorder label of the header window (\fB\-\-header\-label\fR)
|
||||||
|
\fBfooter\-label \fRBorder label of the footer window (\fB\-\-footer\-label\fR)
|
||||||
\fBprompt \fRPrompt
|
\fBprompt \fRPrompt
|
||||||
\fBpointer \fRPointer to the current line
|
\fBpointer \fRPointer to the current line
|
||||||
\fBmarker \fRMulti\-select marker
|
\fBmarker \fRMulti\-select marker
|
||||||
\fBspinner \fRStreaming input indicator
|
\fBspinner \fRStreaming input indicator
|
||||||
\fBheader (header\-fg) \fRHeader
|
\fBheader (header\-fg) \fRHeader
|
||||||
|
\fBfooter (footer\-fg) \fRFooter
|
||||||
\fBnth \fRParts of the line specified by \fB\-\-nth\fR (only supports attributes)
|
\fBnth \fRParts of the line specified by \fB\-\-nth\fR (only supports attributes)
|
||||||
|
|
||||||
.B ANSI COLORS:
|
.B ANSI COLORS:
|
||||||
@@ -323,7 +343,19 @@ color mappings.
|
|||||||
# Seoul256 theme with 24-bit colors
|
# Seoul256 theme with 24-bit colors
|
||||||
fzf \-\-color='bg:#4B4B4B,bg+:#3F3F3F,info:#BDBB72,border:#6B6B6B,spinner:#98BC99' \\
|
fzf \-\-color='bg:#4B4B4B,bg+:#3F3F3F,info:#BDBB72,border:#6B6B6B,spinner:#98BC99' \\
|
||||||
\-\-color='hl:#719872,fg:#D9D9D9,header:#719872,fg+:#D9D9D9' \\
|
\-\-color='hl:#719872,fg:#D9D9D9,header:#719872,fg+:#D9D9D9' \\
|
||||||
\-\-color='pointer:#E12672,marker:#E17899,prompt:#98BEDE,hl+:#98BC99'\fR
|
\-\-color='pointer:#E12672,marker:#E17899,prompt:#98BEDE,hl+:#98BC99'
|
||||||
|
|
||||||
|
# Seoul256 light theme with 24-bit colors, each entry separated by whitespaces
|
||||||
|
fzf \-\-style full \-\-color='
|
||||||
|
fg:#616161 fg+:#616161
|
||||||
|
bg:#ffffff bg+:#e9e9e9 alt-bg:#f1f1f1
|
||||||
|
hl:#719872 hl+:#719899
|
||||||
|
pointer:#e12672 marker:#e17899
|
||||||
|
header:#719872
|
||||||
|
spinner:#719899 info:#727100
|
||||||
|
prompt:#0099bd query:#616161
|
||||||
|
border:#e1e1e1
|
||||||
|
'\fR
|
||||||
.RE
|
.RE
|
||||||
.TP
|
.TP
|
||||||
.B "\-\-no\-color"
|
.B "\-\-no\-color"
|
||||||
@@ -475,6 +507,8 @@ Draw border around the finder
|
|||||||
.br
|
.br
|
||||||
.BR vertical " Vertical lines on each side of the finder"
|
.BR vertical " Vertical lines on each side of the finder"
|
||||||
.br
|
.br
|
||||||
|
.BR line " Single line border (position automatically determined)"
|
||||||
|
.br
|
||||||
.BR top " (up)"
|
.BR top " (up)"
|
||||||
.br
|
.br
|
||||||
.BR bottom " (down)"
|
.BR bottom " (down)"
|
||||||
@@ -490,6 +524,9 @@ If you use a terminal emulator where each box-drawing character takes
|
|||||||
2 columns, try setting \fB\-\-ambidouble\fR. If the border is still not properly
|
2 columns, try setting \fB\-\-ambidouble\fR. If the border is still not properly
|
||||||
rendered, set \fB\-\-no\-unicode\fR.
|
rendered, set \fB\-\-no\-unicode\fR.
|
||||||
|
|
||||||
|
\fBline\fR style draws a single separator line at the top when \fB\-\-height\fR
|
||||||
|
is used.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-border\-label" [=LABEL]
|
.BI "\-\-border\-label" [=LABEL]
|
||||||
Label to print on the horizontal border line. Should be used with one of the
|
Label to print on the horizontal border line. Should be used with one of the
|
||||||
@@ -633,7 +670,8 @@ Do not display scrollbar. A synonym for \fB\-\-scrollbar=''\fB
|
|||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-list\-border" [=STYLE]
|
.BI "\-\-list\-border" [=STYLE]
|
||||||
Draw border around the list section
|
Draw border around the list section. \fBline\fR style is not supported for
|
||||||
|
this border.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-list\-label" [=LABEL]
|
.BI "\-\-list\-label" [=LABEL]
|
||||||
@@ -702,6 +740,10 @@ ANSI color codes are supported.
|
|||||||
Do not display horizontal separator on the info line. A synonym for
|
Do not display horizontal separator on the info line. A synonym for
|
||||||
\fB\-\-separator=''\fB
|
\fB\-\-separator=''\fB
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BI "\-\-ghost=" "TEXT"
|
||||||
|
Ghost text to display when the input is empty
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.B "\-\-filepath\-word"
|
.B "\-\-filepath\-word"
|
||||||
Make word-wise movements and actions respect path separators. The following
|
Make word-wise movements and actions respect path separators. The following
|
||||||
@@ -716,7 +758,8 @@ actions are affected:
|
|||||||
\fBkill\-word\fR
|
\fBkill\-word\fR
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-input\-border" [=STYLE]
|
.BI "\-\-input\-border" [=STYLE]
|
||||||
Draw border around the input section
|
Draw border around the input section. \fBline\fR style draws a single separator
|
||||||
|
line between the input section and the list section.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-input\-label" [=LABEL]
|
.BI "\-\-input\-label" [=LABEL]
|
||||||
@@ -750,26 +793,34 @@ fzf also exports \fB$FZF_PREVIEW_TOP\fR and \fB$FZF_PREVIEW_LEFT\fR so that
|
|||||||
the preview command can determine the position of the preview window.
|
the preview command can determine the position of the preview window.
|
||||||
|
|
||||||
A placeholder expression starting with \fB+\fR flag will be replaced to the
|
A placeholder expression starting with \fB+\fR flag will be replaced to the
|
||||||
space-separated list of the selected lines (or the current line if no selection
|
space-separated list of the selected items (or the current item if no selection
|
||||||
was made) individually quoted.
|
was made) individually quoted.
|
||||||
|
|
||||||
e.g.
|
e.g.
|
||||||
\fBfzf \-\-multi \-\-preview='head \-10 {+}'
|
\fBfzf \-\-multi \-\-preview='head \-10 {+}'
|
||||||
git log \-\-oneline | fzf \-\-multi \-\-preview 'git show {+1}'\fR
|
git log \-\-oneline | fzf \-\-multi \-\-preview 'git show {+1}'\fR
|
||||||
|
|
||||||
|
Similarly, a placeholder expression starting with \fB*\fR flag will be replaced
|
||||||
|
to the space-separated list of all matched items individually quoted.
|
||||||
|
|
||||||
|
Each expression expands to a quoted string, so that it's safe to pass it as an
|
||||||
|
argument to an external command. So you should not manually add quotes around
|
||||||
|
the curly braces. But if you don't want this behavior, you can put
|
||||||
|
\fBr\fR flag (raw) in the expression (e.g. \fB{r}\fR, \fB{r1}\fR, etc).
|
||||||
|
Use it with caution as unquoted output can lead to broken commands.
|
||||||
|
|
||||||
When using a field index expression, leading and trailing whitespace is stripped
|
When using a field index expression, leading and trailing whitespace is stripped
|
||||||
from the replacement string. To preserve the whitespace, use the \fBs\fR flag.
|
from the replacement string. To preserve the whitespace, use the \fBs\fR flag.
|
||||||
|
|
||||||
A placeholder expression with \fBf\fR flag is replaced to the path of
|
A placeholder expression with \fBf\fR flag is replaced to the path of
|
||||||
a temporary file that holds the evaluated list. This is useful when you
|
a temporary file that holds the evaluated list. This is useful when you
|
||||||
multi-select a large number of items and the length of the evaluated string may
|
pass a large number of items and the length of the evaluated string may
|
||||||
exceed \fBARG_MAX\fR.
|
exceed \fBARG_MAX\fR.
|
||||||
|
|
||||||
e.g.
|
e.g.
|
||||||
\fB# Press CTRL\-A to select 100K items and see the sum of all the numbers.
|
\fB# See the sum of all the matched numbers
|
||||||
# This won't work properly without 'f' flag due to ARG_MAX limit.
|
# This won't work properly without 'f' flag due to ARG_MAX limit.
|
||||||
seq 100000 | fzf \-\-multi \-\-bind ctrl\-a:select\-all \\
|
seq 100000 | fzf \-\-preview "awk '{sum+=\\$1} END {print sum}' {*f}"\fR
|
||||||
\-\-preview "awk '{sum+=\\$1} END {print sum}' {+f}"\fR
|
|
||||||
|
|
||||||
Also,
|
Also,
|
||||||
|
|
||||||
@@ -810,8 +861,7 @@ e.g.
|
|||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-preview\-border" [=STYLE]
|
.BI "\-\-preview\-border" [=STYLE]
|
||||||
Short for \fB\-\-preview\-window=border\-STYLE\fR. In addition to the other
|
Short for \fB\-\-preview\-window=border\-STYLE\fR. \fBline\fR style draws
|
||||||
styles, \fBline\fR style is also supported for preview border, which draws
|
|
||||||
a single separator line between the preview window and the rest of the
|
a single separator line between the preview window and the rest of the
|
||||||
interface.
|
interface.
|
||||||
|
|
||||||
@@ -963,10 +1013,12 @@ The first N lines of the input are treated as the sticky header. When
|
|||||||
lines that follow.
|
lines that follow.
|
||||||
.TP
|
.TP
|
||||||
.B "\-\-header\-first"
|
.B "\-\-header\-first"
|
||||||
Print header before the prompt line
|
Print header before the prompt line. When both normal header and header lines
|
||||||
|
(\fB\-\-header\-lines\fR) are present, this applies only to the normal header.
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-header\-border" [=STYLE]
|
.BI "\-\-header\-border" [=STYLE]
|
||||||
Draw border around the header section
|
Draw border around the header section. \fBline\fR style draws a single
|
||||||
|
separator line between the header window and the list section.
|
||||||
|
|
||||||
.TP
|
.TP
|
||||||
.BI "\-\-header\-label" [=LABEL]
|
.BI "\-\-header\-label" [=LABEL]
|
||||||
@@ -980,7 +1032,30 @@ Position of the header label
|
|||||||
.BI "\-\-header\-lines\-border" [=STYLE]
|
.BI "\-\-header\-lines\-border" [=STYLE]
|
||||||
Display header from \fB--header\-lines\fR with a separate border. Pass
|
Display header from \fB--header\-lines\fR with a separate border. Pass
|
||||||
\fBnone\fR to still separate the header lines but without a border. To combine
|
\fBnone\fR to still separate the header lines but without a border. To combine
|
||||||
two headers, use \fB\-\-no\-header\-lines\-border\fR.
|
two headers, use \fB\-\-no\-header\-lines\-border\fR. \fBline\fR style draws
|
||||||
|
a single separator line between the header lines and the list section.
|
||||||
|
|
||||||
|
.SS FOOTER
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BI "\-\-footer=" "STR"
|
||||||
|
The given string will be printed as the sticky footer. The lines are displayed
|
||||||
|
in the given order from top to bottom regardless of \fB\-\-layout\fR option, and
|
||||||
|
are not affected by \fB\-\-with\-nth\fR. ANSI color codes are processed even when
|
||||||
|
\fB\-\-ansi\fR is not set.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BI "\-\-footer\-border" [=STYLE]
|
||||||
|
Draw border around the header section. \fBline\fR style draws a single
|
||||||
|
separator line between the footer and the list section.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BI "\-\-footer\-label" [=LABEL]
|
||||||
|
Label to print on the footer border
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BI "\-\-footer\-label\-pos" [=N[:top|bottom]]
|
||||||
|
Position of the footer label
|
||||||
|
|
||||||
.SS SCRIPTING
|
.SS SCRIPTING
|
||||||
.TP
|
.TP
|
||||||
@@ -1071,11 +1146,6 @@ e.g.
|
|||||||
# Send action to the server
|
# Send action to the server
|
||||||
curl \-XPOST localhost:6266 \-d 'reload(seq 100)+change\-prompt(hundred> )'
|
curl \-XPOST localhost:6266 \-d 'reload(seq 100)+change\-prompt(hundred> )'
|
||||||
|
|
||||||
# Get program state in JSON format (experimental)
|
|
||||||
# * Make sure NOT to access this endpoint from execute/transform actions
|
|
||||||
# as it will result in a timeout
|
|
||||||
curl localhost:6266
|
|
||||||
|
|
||||||
# Start HTTP server on port 6266 with remote connections allowed
|
# Start HTTP server on port 6266 with remote connections allowed
|
||||||
# * Listening on non-localhost address requires using an API key
|
# * Listening on non-localhost address requires using an API key
|
||||||
export FZF_API_KEY="$(head \-c 32 /dev/urandom | base64)"
|
export FZF_API_KEY="$(head \-c 32 /dev/urandom | base64)"
|
||||||
@@ -1086,6 +1156,24 @@ e.g.
|
|||||||
|
|
||||||
# Choose port automatically and export it as $FZF_PORT to the child process
|
# Choose port automatically and export it as $FZF_PORT to the child process
|
||||||
fzf \-\-listen \-\-bind 'start:execute\-silent:echo $FZF_PORT > /tmp/fzf\-port'
|
fzf \-\-listen \-\-bind 'start:execute\-silent:echo $FZF_PORT > /tmp/fzf\-port'
|
||||||
|
|
||||||
|
# Get program state in JSON format (experimental)
|
||||||
|
# - GET Parameters:
|
||||||
|
# - limit: number of items to return (default: 100)
|
||||||
|
# - offset: number of items to skip (default: 0)
|
||||||
|
curl localhost:6266
|
||||||
|
|
||||||
|
# Automatically select items with .txt extension
|
||||||
|
fzf \-\-multi \-\-sync \-\-listen \-\-bind 'load:transform:
|
||||||
|
pos=1
|
||||||
|
curl \-s localhost:$FZF_PORT?limit=1000 | jq \-r .matches[].text | while read \-r text; do
|
||||||
|
if [[ $text =~ \\.txt$ ]]; then
|
||||||
|
echo \-n "+pos($pos)+select"
|
||||||
|
fi
|
||||||
|
pos=$((pos + 1))
|
||||||
|
done
|
||||||
|
echo +first
|
||||||
|
'
|
||||||
\fR
|
\fR
|
||||||
|
|
||||||
.SS DIRECTORY TRAVERSAL
|
.SS DIRECTORY TRAVERSAL
|
||||||
@@ -1255,10 +1343,20 @@ fzf exports the following environment variables to its child processes.
|
|||||||
.br
|
.br
|
||||||
.BR FZF_PROMPT " Prompt string"
|
.BR FZF_PROMPT " Prompt string"
|
||||||
.br
|
.br
|
||||||
|
.BR FZF_GHOST " Ghost string"
|
||||||
|
.br
|
||||||
|
.BR FZF_POINTER " Pointer string"
|
||||||
|
.br
|
||||||
.BR FZF_PREVIEW_LABEL " Preview label string"
|
.BR FZF_PREVIEW_LABEL " Preview label string"
|
||||||
.br
|
.br
|
||||||
.BR FZF_BORDER_LABEL " Border label string"
|
.BR FZF_BORDER_LABEL " Border label string"
|
||||||
.br
|
.br
|
||||||
|
.BR FZF_LIST_LABEL " List label string"
|
||||||
|
.br
|
||||||
|
.BR FZF_INPUT_LABEL " Input label string"
|
||||||
|
.br
|
||||||
|
.BR FZF_HEADER_LABEL " Header label string"
|
||||||
|
.br
|
||||||
.BR FZF_ACTION " The name of the last action performed"
|
.BR FZF_ACTION " The name of the last action performed"
|
||||||
.br
|
.br
|
||||||
.BR FZF_KEY " The name of the last key pressed"
|
.BR FZF_KEY " The name of the last key pressed"
|
||||||
@@ -1511,6 +1609,10 @@ e.g.
|
|||||||
# Beware not to introduce an infinite loop
|
# Beware not to introduce an infinite loop
|
||||||
seq 10 | fzf \-\-bind 'focus:up' \-\-cycle\fR
|
seq 10 | fzf \-\-bind 'focus:up' \-\-cycle\fR
|
||||||
.RE
|
.RE
|
||||||
|
\fImulti\fR
|
||||||
|
.RS
|
||||||
|
Triggered when the multi\-selection has changed.
|
||||||
|
.RE
|
||||||
|
|
||||||
\fIone\fR
|
\fIone\fR
|
||||||
.RS
|
.RS
|
||||||
@@ -1580,6 +1682,14 @@ e.g.
|
|||||||
)'\fR
|
)'\fR
|
||||||
.RE
|
.RE
|
||||||
|
|
||||||
|
\fIclick\-footer\fR
|
||||||
|
.RS
|
||||||
|
Triggered when a mouse click occurs within the footer. Sets
|
||||||
|
\fBFZF_CLICK_FOOTER_LINE\fR and \fBFZF_CLICK_FOOTER_COLUMN\fR environment
|
||||||
|
variables starting from 1. It optionally sets \fBFZF_CLICK_FOOTER_WORD\fR
|
||||||
|
if clicked on a word.
|
||||||
|
.RE
|
||||||
|
|
||||||
.SS AVAILABLE ACTIONS:
|
.SS AVAILABLE ACTIONS:
|
||||||
A key or an event can be bound to one or more of the following actions.
|
A key or an event can be bound to one or more of the following actions.
|
||||||
|
|
||||||
@@ -1596,8 +1706,10 @@ A key or an event can be bound to one or more of the following actions.
|
|||||||
\fBbecome(...)\fR (replace fzf process with the specified command; see below for the details)
|
\fBbecome(...)\fR (replace fzf process with the specified command; see below for the details)
|
||||||
\fBbeginning\-of\-line\fR \fIctrl\-a home\fR
|
\fBbeginning\-of\-line\fR \fIctrl\-a home\fR
|
||||||
\fBbell\fR (ring the terminal bell)
|
\fBbell\fR (ring the terminal bell)
|
||||||
|
\fBbg\-cancel\fR (cancel background transform processes)
|
||||||
\fBcancel\fR (clear query string if not empty, abort fzf otherwise)
|
\fBcancel\fR (clear query string if not empty, abort fzf otherwise)
|
||||||
\fBchange\-border\-label(...)\fR (change \fB\-\-border\-label\fR to the given string)
|
\fBchange\-border\-label(...)\fR (change \fB\-\-border\-label\fR to the given string)
|
||||||
|
\fBchange\-ghost(...)\fR (change ghost text to the given string)
|
||||||
\fBchange\-header(...)\fR (change header to the given string; doesn't affect \fB\-\-header\-lines\fR)
|
\fBchange\-header(...)\fR (change header to the given string; doesn't affect \fB\-\-header\-lines\fR)
|
||||||
\fBchange\-header\-label(...)\fR (change \fB\-\-header\-label\fR to the given string)
|
\fBchange\-header\-label(...)\fR (change \fB\-\-header\-label\fR to the given string)
|
||||||
\fBchange\-input\-label(...)\fR (change \fB\-\-input\-label\fR to the given string)
|
\fBchange\-input\-label(...)\fR (change \fB\-\-input\-label\fR to the given string)
|
||||||
@@ -1605,19 +1717,20 @@ A key or an event can be bound to one or more of the following actions.
|
|||||||
\fBchange\-multi\fR (enable multi-select mode with no limit)
|
\fBchange\-multi\fR (enable multi-select mode with no limit)
|
||||||
\fBchange\-multi(...)\fR (enable multi-select mode with a limit or disable it with 0)
|
\fBchange\-multi(...)\fR (enable multi-select mode with a limit or disable it with 0)
|
||||||
\fBchange\-nth(...)\fR (change \fB\-\-nth\fR option; rotate through the multiple options separated by '|')
|
\fBchange\-nth(...)\fR (change \fB\-\-nth\fR option; rotate through the multiple options separated by '|')
|
||||||
|
\fBchange\-pointer(...)\fR (change \fB\-\-pointer\fR option)
|
||||||
\fBchange\-preview(...)\fR (change \fB\-\-preview\fR option)
|
\fBchange\-preview(...)\fR (change \fB\-\-preview\fR option)
|
||||||
\fBchange\-preview\-label(...)\fR (change \fB\-\-preview\-label\fR to the given string)
|
\fBchange\-preview\-label(...)\fR (change \fB\-\-preview\-label\fR to the given string)
|
||||||
\fBchange\-preview\-window(...)\fR (change \fB\-\-preview\-window\fR option; rotate through the multiple option sets separated by '|')
|
\fBchange\-preview\-window(...)\fR (change \fB\-\-preview\-window\fR option; rotate through the multiple option sets separated by '|')
|
||||||
\fBchange\-prompt(...)\fR (change prompt to the given string)
|
\fBchange\-prompt(...)\fR (change prompt to the given string)
|
||||||
\fBchange\-query(...)\fR (change query string to the given string)
|
\fBchange\-query(...)\fR (change query string to the given string)
|
||||||
\fBclear\-screen\fR \fIctrl\-l\fR
|
\fBclear\-screen\fR \fIctrl\-l\fR
|
||||||
\fBclear\-selection\fR (clear multi\-selection)
|
\fBclear\-multi\fR (clear multi\-selection)
|
||||||
\fBclose\fR (close preview window if open, abort fzf otherwise)
|
\fBclose\fR (close preview window if open, abort fzf otherwise)
|
||||||
\fBclear\-query\fR (clear query string)
|
\fBclear\-query\fR (clear query string)
|
||||||
\fBdelete\-char\fR \fIdel\fR
|
\fBdelete\-char\fR \fIdel\fR
|
||||||
\fBdelete\-char/eof\fR \fIctrl\-d\fR (same as \fBdelete\-char\fR except aborts fzf if query is empty)
|
\fBdelete\-char/eof\fR \fIctrl\-d\fR (same as \fBdelete\-char\fR except aborts fzf if query is empty)
|
||||||
\fBdeselect\fR
|
\fBdeselect\fR
|
||||||
\fBdeselect\-all\fR (deselect all matches)
|
\fBdeselect\-all\fR (deselect all matches; to also clear non-matched selections, use \fBclear\-multi\fR)
|
||||||
\fBdisable\-search\fR (disable search functionality)
|
\fBdisable\-search\fR (disable search functionality)
|
||||||
\fBdown\fR \fIctrl\-j ctrl\-n down\fR
|
\fBdown\fR \fIctrl\-j ctrl\-n down\fR
|
||||||
\fBenable\-search\fR (enable search functionality)
|
\fBenable\-search\fR (enable search functionality)
|
||||||
@@ -1693,15 +1806,18 @@ A key or an event can be bound to one or more of the following actions.
|
|||||||
\fBtrack\-current\fR (track the current item; automatically disabled if focus changes)
|
\fBtrack\-current\fR (track the current item; automatically disabled if focus changes)
|
||||||
\fBtransform(...)\fR (transform states using the output of an external command)
|
\fBtransform(...)\fR (transform states using the output of an external command)
|
||||||
\fBtransform\-border\-label(...)\fR (transform border label using an external command)
|
\fBtransform\-border\-label(...)\fR (transform border label using an external command)
|
||||||
|
\fBtransform\-ghost(...)\fR (transform ghost text using an external command)
|
||||||
\fBtransform\-header(...)\fR (transform header using an external command)
|
\fBtransform\-header(...)\fR (transform header using an external command)
|
||||||
\fBtransform\-header\-label(...)\fR (transform header label using an external command)
|
\fBtransform\-header\-label(...)\fR (transform header label using an external command)
|
||||||
\fBtransform\-input\-label(...)\fR (transform input label using an external command)
|
\fBtransform\-input\-label(...)\fR (transform input label using an external command)
|
||||||
\fBtransform\-list\-label(...)\fR (transform list label using an external command)
|
\fBtransform\-list\-label(...)\fR (transform list label using an external command)
|
||||||
\fBtransform\-nth(...)\fR (transform nth using an external command)
|
\fBtransform\-nth(...)\fR (transform nth using an external command)
|
||||||
|
\fBtransform\-pointer(...)\fR (transform pointer using an external command)
|
||||||
\fBtransform\-preview\-label(...)\fR (transform preview label using an external command)
|
\fBtransform\-preview\-label(...)\fR (transform preview label using an external command)
|
||||||
\fBtransform\-prompt(...)\fR (transform prompt string using an external command)
|
\fBtransform\-prompt(...)\fR (transform prompt string using an external command)
|
||||||
\fBtransform\-query(...)\fR (transform query string using an external command)
|
\fBtransform\-query(...)\fR (transform query string using an external command)
|
||||||
\fBtransform\-search(...)\fR (trigger fzf search with the output of an external command)
|
\fBtransform\-search(...)\fR (trigger fzf search with the output of an external command)
|
||||||
|
\fBtrigger(...)\fR (trigger actions bound to a comma-separated list of keys and events)
|
||||||
\fBunbind(...)\fR (unbind bindings)
|
\fBunbind(...)\fR (unbind bindings)
|
||||||
\fBunix\-line\-discard\fR \fIctrl\-u\fR
|
\fBunix\-line\-discard\fR \fIctrl\-u\fR
|
||||||
\fBunix\-word\-rubout\fR \fIctrl\-w\fR
|
\fBunix\-word\-rubout\fR \fIctrl\-w\fR
|
||||||
@@ -1709,6 +1825,9 @@ A key or an event can be bound to one or more of the following actions.
|
|||||||
\fBup\fR \fIctrl\-k ctrl\-p up\fR
|
\fBup\fR \fIctrl\-k ctrl\-p up\fR
|
||||||
\fByank\fR \fIctrl\-y\fR
|
\fByank\fR \fIctrl\-y\fR
|
||||||
|
|
||||||
|
Each \fBtransform*\fR action has a corresponding \fBbg\-transform*\fR
|
||||||
|
variant that runs the command in the background.
|
||||||
|
|
||||||
.SS ACTION COMPOSITION
|
.SS ACTION COMPOSITION
|
||||||
|
|
||||||
Multiple actions can be chained using \fB+\fR separator.
|
Multiple actions can be chained using \fB+\fR separator.
|
||||||
@@ -1717,6 +1836,9 @@ e.g.
|
|||||||
\fBfzf \-\-multi \-\-bind 'ctrl\-a:select\-all+accept'\fR
|
\fBfzf \-\-multi \-\-bind 'ctrl\-a:select\-all+accept'\fR
|
||||||
\fBfzf \-\-multi \-\-bind 'ctrl\-a:select\-all' \-\-bind 'ctrl\-a:+accept'\fR
|
\fBfzf \-\-multi \-\-bind 'ctrl\-a:select\-all' \-\-bind 'ctrl\-a:+accept'\fR
|
||||||
|
|
||||||
|
Any action after a terminal action that exits fzf, such as \fBaccept\fR or
|
||||||
|
\fBabort\fR, is ignored.
|
||||||
|
|
||||||
.SS ACTION ARGUMENT
|
.SS ACTION ARGUMENT
|
||||||
|
|
||||||
An action denoted with \fB(...)\fR suffix takes an argument.
|
An action denoted with \fB(...)\fR suffix takes an argument.
|
||||||
@@ -1830,6 +1952,26 @@ e.g.
|
|||||||
echo "change\-header:Invalid selection"'
|
echo "change\-header:Invalid selection"'
|
||||||
\fR
|
\fR
|
||||||
|
|
||||||
|
A common mistake when writing a \fBtransform\fR action is not escaping
|
||||||
|
placeholder expressions when passing them back to fzf. In the following
|
||||||
|
example, if you don't escape \fB{}\fR, fzf will immediately replace it with the
|
||||||
|
single-quoted string of the current item. This causes single quotes to appear
|
||||||
|
in the header and footer, and the script will break if any item contains
|
||||||
|
double-quote characters.
|
||||||
|
|
||||||
|
\fBfzf \-\-bind 'focus:transform:[[ $FZF_ACTION =~ up ]] &&
|
||||||
|
echo "change\-header()+transform\-footer:echo \\{}" ||
|
||||||
|
echo "change\-footer()+transform\-header:echo \\{}"'\fR
|
||||||
|
|
||||||
|
.SS TRANSFORM IN THE BACKGROUND
|
||||||
|
|
||||||
|
Transform actions are synchronous, meaning fzf becomes unresponsive while the
|
||||||
|
command runs. To avoid this, each \fBtransform*\fR action has a corresponding
|
||||||
|
\fBbg\-transform*\fR variant that runs in the background. Unless you need to
|
||||||
|
chain multiple transform actions where later ones depend on earlier results,
|
||||||
|
prefer using the \fBbg\fR variant. To cancel currently running background
|
||||||
|
transform processes, use \fBbg\-cancel\fR action.
|
||||||
|
|
||||||
.SS PREVIEW BINDING
|
.SS PREVIEW BINDING
|
||||||
|
|
||||||
With \fBpreview(...)\fR action, you can specify multiple different preview
|
With \fBpreview(...)\fR action, you can specify multiple different preview
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
" Copyright (c) 2013-2024 Junegunn Choi
|
" Copyright (c) 2013-2025 Junegunn Choi
|
||||||
"
|
"
|
||||||
" MIT License
|
" MIT License
|
||||||
"
|
"
|
||||||
@@ -358,7 +358,7 @@ endfunction
|
|||||||
|
|
||||||
function! s:get_color(attr, ...)
|
function! s:get_color(attr, ...)
|
||||||
" Force 24 bit colors: g:fzf_force_termguicolors (temporary workaround for https://github.com/junegunn/fzf.vim/issues/1152)
|
" Force 24 bit colors: g:fzf_force_termguicolors (temporary workaround for https://github.com/junegunn/fzf.vim/issues/1152)
|
||||||
let gui = get(g:, 'fzf_force_termguicolors', 0) || (!s:is_win && !has('win32unix') && has('termguicolors') && &termguicolors)
|
let gui = get(g:, 'fzf_force_termguicolors', 0) || (!s:is_win && !has('win32unix') && (has('gui_running') || has('termguicolors') && &termguicolors))
|
||||||
let fam = gui ? 'gui' : 'cterm'
|
let fam = gui ? 'gui' : 'cterm'
|
||||||
let pat = gui ? '^#[a-f0-9]\+' : '^[0-9]\+$'
|
let pat = gui ? '^#[a-f0-9]\+' : '^[0-9]\+$'
|
||||||
for group in a:000
|
for group in a:000
|
||||||
@@ -553,8 +553,15 @@ try
|
|||||||
let height = s:calc_size(&lines, dict.down, dict)
|
let height = s:calc_size(&lines, dict.down, dict)
|
||||||
let optstr .= ' --no-tmux --height='.height
|
let optstr .= ' --no-tmux --height='.height
|
||||||
endif
|
endif
|
||||||
" Respect --border option given in $FZF_DEFAULT_OPTS and 'options'
|
|
||||||
let optstr = join([s:border_opt(get(dict, 'window', 0)), s:extract_option($FZF_DEFAULT_OPTS, 'border'), optstr])
|
if exists('&winborder') && &winborder !=# '' && &winborder !=# 'none'
|
||||||
|
" Add 1-column horizontal margin
|
||||||
|
let optstr = join(['--margin 0,1', optstr])
|
||||||
|
else
|
||||||
|
" Respect --border option given in $FZF_DEFAULT_OPTS and 'options'
|
||||||
|
let optstr = join([s:border_opt(get(dict, 'window', 0)), s:extract_option($FZF_DEFAULT_OPTS, 'border'), optstr])
|
||||||
|
endif
|
||||||
|
|
||||||
let command = prefix.(use_tmux ? s:fzf_tmux(dict) : fzf_exec).' '.optstr.' > '.temps.result
|
let command = prefix.(use_tmux ? s:fzf_tmux(dict) : fzf_exec).' '.optstr.' > '.temps.result
|
||||||
|
|
||||||
if use_term
|
if use_term
|
||||||
@@ -1020,8 +1027,23 @@ if has('nvim')
|
|||||||
let buf = nvim_create_buf(v:false, v:true)
|
let buf = nvim_create_buf(v:false, v:true)
|
||||||
let opts = extend({'relative': 'editor', 'style': 'minimal'}, a:opts)
|
let opts = extend({'relative': 'editor', 'style': 'minimal'}, a:opts)
|
||||||
let win = nvim_open_win(buf, v:true, opts)
|
let win = nvim_open_win(buf, v:true, opts)
|
||||||
silent! call setwinvar(win, '&winhighlight', 'Pmenu:,Normal:Normal')
|
|
||||||
call setwinvar(win, '&colorcolumn', '')
|
call setwinvar(win, '&colorcolumn', '')
|
||||||
|
|
||||||
|
" Colors
|
||||||
|
try
|
||||||
|
call setwinvar(win, '&winhighlight', 'Pmenu:,Normal:Normal')
|
||||||
|
let rules = get(g:, 'fzf_colors', {})
|
||||||
|
if has_key(rules, 'bg')
|
||||||
|
let color = call('s:get_color', rules.bg)
|
||||||
|
if len(color)
|
||||||
|
let ns = nvim_create_namespace('fzf_popup')
|
||||||
|
let hl = nvim_set_hl(ns, 'Normal',
|
||||||
|
\ &termguicolors ? { 'bg': color } : { 'ctermbg': str2nr(color) })
|
||||||
|
call nvim_win_set_hl_ns(win, ns)
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
catch
|
||||||
|
endtry
|
||||||
return buf
|
return buf
|
||||||
endfunction
|
endfunction
|
||||||
else
|
else
|
||||||
|
38
shell/common.sh
Normal file
38
shell/common.sh
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
|
||||||
|
__fzf_defaults() {
|
||||||
|
# $1: Prepend to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
|
# $2: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
|
printf '%s\n' "--height ${FZF_TMUX_HEIGHT:-40%} --min-height 20+ --bind=ctrl-z:ignore $1"
|
||||||
|
command cat "${FZF_DEFAULT_OPTS_FILE-}" 2> /dev/null
|
||||||
|
printf '%s\n' "${FZF_DEFAULT_OPTS-} $2"
|
||||||
|
}
|
||||||
|
|
||||||
|
__fzf_exec_awk() {
|
||||||
|
# This function performs `exec awk "$@"` safely by working around awk
|
||||||
|
# compatibility issues.
|
||||||
|
#
|
||||||
|
# To reduce an extra fork, this function performs "exec" so is expected to be
|
||||||
|
# run as the last command in a subshell.
|
||||||
|
if [[ -z ${__fzf_awk-} ]]; then
|
||||||
|
__fzf_awk=awk
|
||||||
|
if [[ $OSTYPE == solaris* && -x /usr/xpg4/bin/awk ]]; then
|
||||||
|
# Note: Solaris awk at /usr/bin/awk is meant for backward compatibility
|
||||||
|
# with an ancient implementation of 1977 awk in the original UNIX. It
|
||||||
|
# lacks many features of POSIX awk, so it is essentially useless in the
|
||||||
|
# modern point of view. To use a standard-conforming version in Solaris,
|
||||||
|
# one needs to explicitly use /usr/xpg4/bin/awk.
|
||||||
|
__fzf_awk=/usr/xpg4/bin/awk
|
||||||
|
elif command -v mawk >/dev/null 2>&1; then
|
||||||
|
# choose the faster mawk if: it's installed && build date >= 20230322 &&
|
||||||
|
# version >= 1.3.4
|
||||||
|
local n x y z d
|
||||||
|
IFS=' .' read -r n x y z d <<< $(command mawk -W version 2> /dev/null)
|
||||||
|
[[ $n == mawk ]] && (( d >= 20230302 && (x * 1000 + y) * 1000 + z >= 1003004 )) && __fzf_awk=mawk
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
# Note: macOS awk has a quirk that it stops processing at all when it sees
|
||||||
|
# any data not following UTF-8 in the input stream when the current LC_CTYPE
|
||||||
|
# specifies the UTF-8 encoding. To work around this quirk, one needs to
|
||||||
|
# specify LC_ALL=C to change the current encoding to the plain one.
|
||||||
|
LC_ALL=C exec "$__fzf_awk" "$@"
|
||||||
|
}
|
@@ -31,17 +31,32 @@ if [[ $- =~ i ]]; then
|
|||||||
|
|
||||||
###########################################################
|
###########################################################
|
||||||
|
|
||||||
# To redraw line after fzf closes (printf '\e[5n')
|
#----BEGIN INCLUDE common.sh
|
||||||
bind '"\e[0n": redraw-current-line' 2> /dev/null
|
# NOTE: Do not directly edit this section, which is copied from "common.sh".
|
||||||
|
# To modify it, one can edit "common.sh" and run "./update-common.sh" to apply
|
||||||
|
# the changes. See code comments in "common.sh" for the implementation details.
|
||||||
|
|
||||||
__fzf_defaults() {
|
__fzf_defaults() {
|
||||||
# $1: Prepend to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
printf '%s\n' "--height ${FZF_TMUX_HEIGHT:-40%} --min-height 20+ --bind=ctrl-z:ignore $1"
|
||||||
# $2: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
|
||||||
echo "--height ${FZF_TMUX_HEIGHT:-40%} --min-height 20+ --bind=ctrl-z:ignore $1"
|
|
||||||
command cat "${FZF_DEFAULT_OPTS_FILE-}" 2> /dev/null
|
command cat "${FZF_DEFAULT_OPTS_FILE-}" 2> /dev/null
|
||||||
echo "${FZF_DEFAULT_OPTS-} $2"
|
printf '%s\n' "${FZF_DEFAULT_OPTS-} $2"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
__fzf_exec_awk() {
|
||||||
|
if [[ -z ${__fzf_awk-} ]]; then
|
||||||
|
__fzf_awk=awk
|
||||||
|
if [[ $OSTYPE == solaris* && -x /usr/xpg4/bin/awk ]]; then
|
||||||
|
__fzf_awk=/usr/xpg4/bin/awk
|
||||||
|
elif command -v mawk >/dev/null 2>&1; then
|
||||||
|
local n x y z d
|
||||||
|
IFS=' .' read -r n x y z d <<< $(command mawk -W version 2> /dev/null)
|
||||||
|
[[ $n == mawk ]] && (( d >= 20230302 && (x * 1000 + y) * 1000 + z >= 1003004 )) && __fzf_awk=mawk
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
LC_ALL=C exec "$__fzf_awk" "$@"
|
||||||
|
}
|
||||||
|
#----END INCLUDE
|
||||||
|
|
||||||
__fzf_comprun() {
|
__fzf_comprun() {
|
||||||
if [[ "$(type -t _fzf_comprun 2>&1)" = function ]]; then
|
if [[ "$(type -t _fzf_comprun 2>&1)" = function ]]; then
|
||||||
_fzf_comprun "$@"
|
_fzf_comprun "$@"
|
||||||
@@ -311,12 +326,12 @@ __fzf_generic_path_completion() {
|
|||||||
else
|
else
|
||||||
if [[ $1 =~ dir ]]; then
|
if [[ $1 =~ dir ]]; then
|
||||||
walker=dir,follow
|
walker=dir,follow
|
||||||
rest=${FZF_COMPLETION_DIR_OPTS-}
|
eval "rest=(${FZF_COMPLETION_DIR_OPTS-})"
|
||||||
else
|
else
|
||||||
walker=file,dir,follow,hidden
|
walker=file,dir,follow,hidden
|
||||||
rest=${FZF_COMPLETION_PATH_OPTS-}
|
eval "rest=(${FZF_COMPLETION_PATH_OPTS-})"
|
||||||
fi
|
fi
|
||||||
__fzf_comprun "$4" -q "$leftover" --walker "$walker" --walker-root="$dir" $rest
|
__fzf_comprun "$4" -q "$leftover" --walker "$walker" --walker-root="$dir" "${rest[@]}"
|
||||||
fi | while read -r item; do
|
fi | while read -r item; do
|
||||||
printf "%q " "${item%$3}$3"
|
printf "%q " "${item%$3}$3"
|
||||||
done
|
done
|
||||||
@@ -328,6 +343,8 @@ __fzf_generic_path_completion() {
|
|||||||
else
|
else
|
||||||
COMPREPLY=( "$cur" )
|
COMPREPLY=( "$cur" )
|
||||||
fi
|
fi
|
||||||
|
# To redraw line after fzf closes (printf '\e[5n')
|
||||||
|
bind '"\e[0n": redraw-current-line' 2> /dev/null
|
||||||
printf '\e[5n'
|
printf '\e[5n'
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
@@ -365,7 +382,7 @@ _fzf_complete() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
local cur selected trigger cmd post
|
local cur selected trigger cmd post
|
||||||
post="$(caller 0 | command awk '{print $2}')_post"
|
post="$(caller 0 | __fzf_exec_awk '{print $2}')_post"
|
||||||
type -t "$post" > /dev/null 2>&1 || post='command cat'
|
type -t "$post" > /dev/null 2>&1 || post='command cat'
|
||||||
|
|
||||||
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
trigger=${FZF_COMPLETION_TRIGGER-'**'}
|
||||||
@@ -384,6 +401,7 @@ _fzf_complete() {
|
|||||||
else
|
else
|
||||||
COMPREPLY=("$cur")
|
COMPREPLY=("$cur")
|
||||||
fi
|
fi
|
||||||
|
bind '"\e[0n": redraw-current-line' 2> /dev/null
|
||||||
printf '\e[5n'
|
printf '\e[5n'
|
||||||
return 0
|
return 0
|
||||||
else
|
else
|
||||||
@@ -443,7 +461,7 @@ _fzf_proc_completion() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_fzf_proc_completion_post() {
|
_fzf_proc_completion_post() {
|
||||||
command awk '{print $2}'
|
__fzf_exec_awk '{print $2}'
|
||||||
}
|
}
|
||||||
|
|
||||||
# To use custom hostname lists, override __fzf_list_hosts.
|
# To use custom hostname lists, override __fzf_list_hosts.
|
||||||
@@ -460,10 +478,54 @@ _fzf_proc_completion_post() {
|
|||||||
# }
|
# }
|
||||||
if ! declare -F __fzf_list_hosts > /dev/null; then
|
if ! declare -F __fzf_list_hosts > /dev/null; then
|
||||||
__fzf_list_hosts() {
|
__fzf_list_hosts() {
|
||||||
command cat <(command tail -n +1 ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^\s*host\(name\)\? ' | command awk '{for (i = 2; i <= NF; i++) print $1 " " $i}' | command grep -v '[*?%]') \
|
command sort -u \
|
||||||
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts 2> /dev/null | command tr ',' '\n' | command tr -d '[' | command awk '{ print $1 " " $1 }') \
|
<(
|
||||||
<(command grep -v '^\s*\(#\|$\)' /etc/hosts 2> /dev/null | command grep -Fv '0.0.0.0' | command sed 's/#.*//') |
|
# Note: To make the pathname expansion of "~/.ssh/config.d/*" work
|
||||||
command awk '{for (i = 2; i <= NF; i++) print $i}' | command sort -u
|
# properly, we need to adjust the related shell options. We need to
|
||||||
|
# unset "set -f" and "GLOBIGNORE", which disable the pathname expansion
|
||||||
|
# totally or partially. We need to unset "dotglob" and "nocaseglob" to
|
||||||
|
# avoid matching unwanted files. We need to unset "failglob" to avoid
|
||||||
|
# outputting the error messages to the terminal when no matching is
|
||||||
|
# found. We need to set "nullglob" to avoid attempting to read the
|
||||||
|
# literal filename '~/.ssh/config.d/*' when no matching is found.
|
||||||
|
set +f
|
||||||
|
GLOBIGNORE=
|
||||||
|
shopt -u dotglob nocaseglob failglob
|
||||||
|
shopt -s nullglob
|
||||||
|
|
||||||
|
__fzf_exec_awk '
|
||||||
|
# Note: mawk <= 1.3.3-20090705 does not support the POSIX brackets of
|
||||||
|
# the form [[:blank:]], and Ubuntu 18.04 LTS still uses this
|
||||||
|
# 16-year-old mawk unfortunately. We need to use [ \t] instead.
|
||||||
|
match(tolower($0), /^[ \t]*host(name)?[ \t]*[ \t=]/) {
|
||||||
|
$0 = substr($0, RLENGTH + 1) # Remove "Host(name)?=?"
|
||||||
|
sub(/#.*/, "")
|
||||||
|
for (i = 1; i <= NF; i++)
|
||||||
|
if ($i !~ /[*?%]/)
|
||||||
|
print $i
|
||||||
|
}
|
||||||
|
' ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null
|
||||||
|
) \
|
||||||
|
<(
|
||||||
|
__fzf_exec_awk -F ',' '
|
||||||
|
match($0, /^[][a-zA-Z0-9.,:-]+/) {
|
||||||
|
$0 = substr($0, 1, RLENGTH)
|
||||||
|
gsub(/[][]|:[^,]*/, "")
|
||||||
|
for (i = 1; i <= NF; i++)
|
||||||
|
print $i
|
||||||
|
}
|
||||||
|
' ~/.ssh/known_hosts 2> /dev/null
|
||||||
|
) \
|
||||||
|
<(
|
||||||
|
__fzf_exec_awk '
|
||||||
|
{
|
||||||
|
sub(/#.*/, "")
|
||||||
|
for (i = 2; i <= NF; i++)
|
||||||
|
if ($i != "0.0.0.0")
|
||||||
|
print $i
|
||||||
|
}
|
||||||
|
' /etc/hosts 2> /dev/null
|
||||||
|
)
|
||||||
}
|
}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -484,7 +546,7 @@ _fzf_complete_ssh() {
|
|||||||
*)
|
*)
|
||||||
local user=
|
local user=
|
||||||
[[ "$2" =~ '@' ]] && user="${2%%@*}@"
|
[[ "$2" =~ '@' ]] && user="${2%%@*}@"
|
||||||
_fzf_complete +m -- "$@" < <(__fzf_list_hosts | command awk -v user="$user" '{print user $0}')
|
_fzf_complete +m -- "$@" < <(__fzf_list_hosts | __fzf_exec_awk -v user="$user" '{print user $0}')
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
@@ -572,7 +634,7 @@ __fzf_defc() {
|
|||||||
if __fzf_orig_completion_instantiate "$cmd" "$func"; then
|
if __fzf_orig_completion_instantiate "$cmd" "$func"; then
|
||||||
eval "$REPLY"
|
eval "$REPLY"
|
||||||
else
|
else
|
||||||
complete -F "$func" $opts "$cmd"
|
eval "complete -F \"$func\" $opts \"$cmd\""
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -96,14 +96,32 @@ if [[ -o interactive ]]; then
|
|||||||
|
|
||||||
###########################################################
|
###########################################################
|
||||||
|
|
||||||
|
#----BEGIN INCLUDE common.sh
|
||||||
|
# NOTE: Do not directly edit this section, which is copied from "common.sh".
|
||||||
|
# To modify it, one can edit "common.sh" and run "./update-common.sh" to apply
|
||||||
|
# the changes. See code comments in "common.sh" for the implementation details.
|
||||||
|
|
||||||
__fzf_defaults() {
|
__fzf_defaults() {
|
||||||
# $1: Prepend to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
printf '%s\n' "--height ${FZF_TMUX_HEIGHT:-40%} --min-height 20+ --bind=ctrl-z:ignore $1"
|
||||||
# $2: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
|
||||||
echo "--height ${FZF_TMUX_HEIGHT:-40%} --min-height 20+ --bind=ctrl-z:ignore $1"
|
|
||||||
command cat "${FZF_DEFAULT_OPTS_FILE-}" 2> /dev/null
|
command cat "${FZF_DEFAULT_OPTS_FILE-}" 2> /dev/null
|
||||||
echo "${FZF_DEFAULT_OPTS-} $2"
|
printf '%s\n' "${FZF_DEFAULT_OPTS-} $2"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
__fzf_exec_awk() {
|
||||||
|
if [[ -z ${__fzf_awk-} ]]; then
|
||||||
|
__fzf_awk=awk
|
||||||
|
if [[ $OSTYPE == solaris* && -x /usr/xpg4/bin/awk ]]; then
|
||||||
|
__fzf_awk=/usr/xpg4/bin/awk
|
||||||
|
elif command -v mawk >/dev/null 2>&1; then
|
||||||
|
local n x y z d
|
||||||
|
IFS=' .' read -r n x y z d <<< $(command mawk -W version 2> /dev/null)
|
||||||
|
[[ $n == mawk ]] && (( d >= 20230302 && (x * 1000 + y) * 1000 + z >= 1003004 )) && __fzf_awk=mawk
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
LC_ALL=C exec "$__fzf_awk" "$@"
|
||||||
|
}
|
||||||
|
#----END INCLUDE
|
||||||
|
|
||||||
__fzf_comprun() {
|
__fzf_comprun() {
|
||||||
if [[ "$(type _fzf_comprun 2>&1)" =~ function ]]; then
|
if [[ "$(type _fzf_comprun 2>&1)" =~ function ]]; then
|
||||||
_fzf_comprun "$@"
|
_fzf_comprun "$@"
|
||||||
@@ -242,11 +260,50 @@ _fzf_complete() {
|
|||||||
# desired sorting and with any duplicates removed, to standard output.
|
# desired sorting and with any duplicates removed, to standard output.
|
||||||
if ! declare -f __fzf_list_hosts > /dev/null; then
|
if ! declare -f __fzf_list_hosts > /dev/null; then
|
||||||
__fzf_list_hosts() {
|
__fzf_list_hosts() {
|
||||||
setopt localoptions nonomatch
|
command sort -u \
|
||||||
command cat <(command tail -n +1 ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null | command grep -i '^\s*host\(name\)\? ' | awk '{for (i = 2; i <= NF; i++) print $1 " " $i}' | command grep -v '[*?%]') \
|
<(
|
||||||
<(command grep -oE '^[[a-z0-9.,:-]+' ~/.ssh/known_hosts 2> /dev/null | tr ',' '\n' | tr -d '[' | awk '{ print $1 " " $1 }') \
|
# Note: To make the pathname expansion of "~/.ssh/config.d/*" work
|
||||||
<(command grep -v '^\s*\(#\|$\)' /etc/hosts 2> /dev/null | command grep -Fv '0.0.0.0' | command sed 's/#.*//') |
|
# properly, we need to adjust the related shell options. We need to
|
||||||
awk '{for (i = 2; i <= NF; i++) print $i}' | sort -u
|
# unset "NO_GLOB" (or reset "GLOB"), which disable the pathname
|
||||||
|
# expansion totally. We need to unset "DOT_GLOB" and set "CASE_GLOB"
|
||||||
|
# to avoid matching unwanted files. We need to set "NULL_GLOB" to
|
||||||
|
# avoid attempting to read the literal filename '~/.ssh/config.d/*'
|
||||||
|
# when no matching is found.
|
||||||
|
setopt GLOB NO_DOT_GLOB CASE_GLOB NO_NOMATCH NULL_GLOB
|
||||||
|
|
||||||
|
__fzf_exec_awk '
|
||||||
|
# Note: mawk <= 1.3.3-20090705 does not support the POSIX brackets of
|
||||||
|
# the form [[:blank:]], and Ubuntu 18.04 LTS still uses this
|
||||||
|
# 16-year-old mawk unfortunately. We need to use [ \t] instead.
|
||||||
|
match(tolower($0), /^[ \t]*host(name)?[ \t]*[ \t=]/) {
|
||||||
|
$0 = substr($0, RLENGTH + 1) # Remove "Host(name)?=?"
|
||||||
|
sub(/#.*/, "")
|
||||||
|
for (i = 1; i <= NF; i++)
|
||||||
|
if ($i !~ /[*?%]/)
|
||||||
|
print $i
|
||||||
|
}
|
||||||
|
' ~/.ssh/config ~/.ssh/config.d/* /etc/ssh/ssh_config 2> /dev/null
|
||||||
|
) \
|
||||||
|
<(
|
||||||
|
__fzf_exec_awk -F ',' '
|
||||||
|
match($0, /^[][a-zA-Z0-9.,:-]+/) {
|
||||||
|
$0 = substr($0, 1, RLENGTH)
|
||||||
|
gsub(/[][]|:[^,]*/, "")
|
||||||
|
for (i = 1; i <= NF; i++)
|
||||||
|
print $i
|
||||||
|
}
|
||||||
|
' ~/.ssh/known_hosts 2> /dev/null
|
||||||
|
) \
|
||||||
|
<(
|
||||||
|
__fzf_exec_awk '
|
||||||
|
{
|
||||||
|
sub(/#.*/, "")
|
||||||
|
for (i = 2; i <= NF; i++)
|
||||||
|
if ($i != "0.0.0.0")
|
||||||
|
print $i
|
||||||
|
}
|
||||||
|
' /etc/hosts 2> /dev/null
|
||||||
|
)
|
||||||
}
|
}
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -266,7 +323,7 @@ _fzf_complete_ssh() {
|
|||||||
*)
|
*)
|
||||||
local user
|
local user
|
||||||
[[ $prefix =~ @ ]] && user="${prefix%%@*}@"
|
[[ $prefix =~ @ ]] && user="${prefix%%@*}@"
|
||||||
_fzf_complete +m -- "$@" < <(__fzf_list_hosts | awk -v user="$user" '{print user $0}')
|
_fzf_complete +m -- "$@" < <(__fzf_list_hosts | __fzf_exec_awk -v user="$user" '{print user $0}')
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
@@ -324,7 +381,7 @@ _fzf_complete_kill() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_fzf_complete_kill_post() {
|
_fzf_complete_kill_post() {
|
||||||
awk '{print $2}'
|
__fzf_exec_awk '{print $2}'
|
||||||
}
|
}
|
||||||
|
|
||||||
fzf-completion() {
|
fzf-completion() {
|
||||||
|
@@ -17,14 +17,32 @@ if [[ $- =~ i ]]; then
|
|||||||
# Key bindings
|
# Key bindings
|
||||||
# ------------
|
# ------------
|
||||||
|
|
||||||
|
#----BEGIN INCLUDE common.sh
|
||||||
|
# NOTE: Do not directly edit this section, which is copied from "common.sh".
|
||||||
|
# To modify it, one can edit "common.sh" and run "./update-common.sh" to apply
|
||||||
|
# the changes. See code comments in "common.sh" for the implementation details.
|
||||||
|
|
||||||
__fzf_defaults() {
|
__fzf_defaults() {
|
||||||
# $1: Prepend to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
printf '%s\n' "--height ${FZF_TMUX_HEIGHT:-40%} --min-height 20+ --bind=ctrl-z:ignore $1"
|
||||||
# $2: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
|
||||||
echo "--height ${FZF_TMUX_HEIGHT:-40%} --min-height 20+ --bind=ctrl-z:ignore $1"
|
|
||||||
command cat "${FZF_DEFAULT_OPTS_FILE-}" 2> /dev/null
|
command cat "${FZF_DEFAULT_OPTS_FILE-}" 2> /dev/null
|
||||||
echo "${FZF_DEFAULT_OPTS-} $2"
|
printf '%s\n' "${FZF_DEFAULT_OPTS-} $2"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
__fzf_exec_awk() {
|
||||||
|
if [[ -z ${__fzf_awk-} ]]; then
|
||||||
|
__fzf_awk=awk
|
||||||
|
if [[ $OSTYPE == solaris* && -x /usr/xpg4/bin/awk ]]; then
|
||||||
|
__fzf_awk=/usr/xpg4/bin/awk
|
||||||
|
elif command -v mawk >/dev/null 2>&1; then
|
||||||
|
local n x y z d
|
||||||
|
IFS=' .' read -r n x y z d <<< $(command mawk -W version 2> /dev/null)
|
||||||
|
[[ $n == mawk ]] && (( d >= 20230302 && (x * 1000 + y) * 1000 + z >= 1003004 )) && __fzf_awk=mawk
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
LC_ALL=C exec "$__fzf_awk" "$@"
|
||||||
|
}
|
||||||
|
#----END INCLUDE
|
||||||
|
|
||||||
__fzf_select__() {
|
__fzf_select__() {
|
||||||
FZF_DEFAULT_COMMAND=${FZF_CTRL_T_COMMAND:-} \
|
FZF_DEFAULT_COMMAND=${FZF_CTRL_T_COMMAND:-} \
|
||||||
FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse --walker=file,dir,follow,hidden --scheme=path" "${FZF_CTRL_T_OPTS-} -m") \
|
FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse --walker=file,dir,follow,hidden --scheme=path" "${FZF_CTRL_T_OPTS-} -m") \
|
||||||
@@ -74,13 +92,7 @@ if command -v perl > /dev/null; then
|
|||||||
}
|
}
|
||||||
else # awk - fallback for POSIX systems
|
else # awk - fallback for POSIX systems
|
||||||
__fzf_history__() {
|
__fzf_history__() {
|
||||||
local output script n x y z d
|
local output script
|
||||||
if [[ -z $__fzf_awk ]]; then
|
|
||||||
__fzf_awk=awk
|
|
||||||
# choose the faster mawk if: it's installed && build date >= 20230322 && version >= 1.3.4
|
|
||||||
IFS=' .' read n x y z d <<< $(command mawk -W version 2> /dev/null)
|
|
||||||
[[ $n == mawk ]] && (( d >= 20230302 && (x *1000 +y) *1000 +z >= 1003004 )) && __fzf_awk=mawk
|
|
||||||
fi
|
|
||||||
[[ $(HISTTIMEFORMAT='' builtin history 1) =~ [[:digit:]]+ ]] # how many history entries
|
[[ $(HISTTIMEFORMAT='' builtin history 1) =~ [[:digit:]]+ ]] # how many history entries
|
||||||
script='function P(b) { ++n; sub(/^[ *]/, "", b); if (!seen[b]++) { printf "%d\t%s%c", '$((BASH_REMATCH + 1))' - n, b, 0 } }
|
script='function P(b) { ++n; sub(/^[ *]/, "", b); if (!seen[b]++) { printf "%d\t%s%c", '$((BASH_REMATCH + 1))' - n, b, 0 } }
|
||||||
NR==1 { b = substr($0, 2); next }
|
NR==1 { b = substr($0, 2); next }
|
||||||
@@ -90,7 +102,7 @@ else # awk - fallback for POSIX systems
|
|||||||
output=$(
|
output=$(
|
||||||
set +o pipefail
|
set +o pipefail
|
||||||
builtin fc -lnr -2147483648 2> /dev/null | # ( $'\t '<lines>$'\n' )* ; <lines> ::= [^\n]* ( $'\n'<lines> )*
|
builtin fc -lnr -2147483648 2> /dev/null | # ( $'\t '<lines>$'\n' )* ; <lines> ::= [^\n]* ( $'\n'<lines> )*
|
||||||
command $__fzf_awk "$script" | # ( <counter>$'\t'<lines>$'\000' )*
|
__fzf_exec_awk "$script" | # ( <counter>$'\t'<lines>$'\000' )*
|
||||||
FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --wrap-sign '"$'\t'"↳ ' --highlight-line ${FZF_CTRL_R_OPTS-} +m --read0") \
|
FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --wrap-sign '"$'\t'"↳ ' --highlight-line ${FZF_CTRL_R_OPTS-} +m --read0") \
|
||||||
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd) --query "$READLINE_LINE"
|
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd) --query "$READLINE_LINE"
|
||||||
) || return
|
) || return
|
||||||
|
@@ -14,102 +14,33 @@
|
|||||||
|
|
||||||
# Key bindings
|
# Key bindings
|
||||||
# ------------
|
# ------------
|
||||||
|
# The oldest supported fish version is 3.1b1. To maintain compatibility, the
|
||||||
|
# command substitution syntax $(cmd) should never be used, even behind a version
|
||||||
|
# check, otherwise the source command will fail on fish versions older than 3.4.0.
|
||||||
function fzf_key_bindings
|
function fzf_key_bindings
|
||||||
|
|
||||||
|
# Check fish version
|
||||||
|
set -l fish_ver (string match -r '^(\d+).(\d+)' $version 2> /dev/null; or echo 0\n0\n0)
|
||||||
|
if test \( "$fish_ver[2]" -lt 3 \) -o \( "$fish_ver[2]" -eq 3 -a "$fish_ver[3]" -lt 1 \)
|
||||||
|
echo "This script requires fish version 3.1b1 or newer." >&2
|
||||||
|
return 1
|
||||||
|
else if not type -q fzf
|
||||||
|
echo "fzf was not found in path." >&2
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
|
||||||
function __fzf_defaults
|
function __fzf_defaults
|
||||||
# $1: Prepend to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
# $argv[1]: Prepend to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
# $2: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
# $argv[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 -l FZF_TMUX_HEIGHT 40%
|
||||||
echo "--height $FZF_TMUX_HEIGHT --min-height 20+ --bind=ctrl-z:ignore" $argv[1]
|
string join ' ' -- \
|
||||||
test -r "$FZF_DEFAULT_OPTS_FILE"; and string collect -N -- <$FZF_DEFAULT_OPTS_FILE
|
"--height $FZF_TMUX_HEIGHT --min-height=20+ --bind=ctrl-z:ignore" $argv[1] \
|
||||||
echo $FZF_DEFAULT_OPTS $argv[2]
|
(test -r "$FZF_DEFAULT_OPTS_FILE"; and string join -- ' ' <$FZF_DEFAULT_OPTS_FILE) \
|
||||||
end
|
$FZF_DEFAULT_OPTS $argv[2..-1]
|
||||||
|
|
||||||
# Store current token in $dir as root for the 'find' command
|
|
||||||
function fzf-file-widget -d "List files and folders"
|
|
||||||
set -l commandline (__fzf_parse_commandline)
|
|
||||||
set -lx dir $commandline[1]
|
|
||||||
set -l fzf_query $commandline[2]
|
|
||||||
set -l prefix $commandline[3]
|
|
||||||
set -l result
|
|
||||||
|
|
||||||
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
|
||||||
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_COMMAND "$FZF_CTRL_T_COMMAND"
|
|
||||||
set -lx FZF_DEFAULT_OPTS_FILE ''
|
|
||||||
set result (eval (__fzfcmd) -m --query=$fzf_query)
|
|
||||||
end
|
|
||||||
if test -z "$result"
|
|
||||||
commandline -f repaint
|
|
||||||
return
|
|
||||||
else
|
|
||||||
# Remove last token from commandline.
|
|
||||||
commandline -t ""
|
|
||||||
end
|
|
||||||
for i in $result
|
|
||||||
commandline -it -- $prefix
|
|
||||||
commandline -it -- (string escape -- $i)
|
|
||||||
commandline -it -- ' '
|
|
||||||
end
|
|
||||||
commandline -f repaint
|
|
||||||
end
|
|
||||||
|
|
||||||
function fzf-history-widget -d "Show command history"
|
|
||||||
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
|
||||||
begin
|
|
||||||
# merge history from other sessions before searching
|
|
||||||
test -z "$fish_private_mode"; and builtin history merge
|
|
||||||
|
|
||||||
set -lx FZF_DEFAULT_OPTS (__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort --wrap-sign '"\t"↳ ' --highlight-line +m $FZF_CTRL_R_OPTS")
|
|
||||||
set -lx FZF_DEFAULT_OPTS_FILE ''
|
|
||||||
set -lx FZF_DEFAULT_COMMAND
|
|
||||||
set -a -- FZF_DEFAULT_OPTS --with-shell=(status fish-path)\\ -c
|
|
||||||
|
|
||||||
if type -q perl
|
|
||||||
set -a FZF_DEFAULT_OPTS '--tac'
|
|
||||||
set FZF_DEFAULT_COMMAND 'builtin history -z --reverse | command perl -0 -pe \'s/^/$.\t/g; s/\n/\n\t/gm\''
|
|
||||||
else
|
|
||||||
set FZF_DEFAULT_COMMAND \
|
|
||||||
'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
|
|
||||||
set -l result (eval $FZF_DEFAULT_COMMAND \| (__fzfcmd) --read0 --print0 -q (commandline | string escape) "--bind=enter:become:'string replace -a -- \n\t \n {2..} | string collect'")
|
|
||||||
and commandline -- $result
|
|
||||||
end
|
|
||||||
commandline -f repaint
|
|
||||||
end
|
|
||||||
|
|
||||||
function fzf-cd-widget -d "Change directory"
|
|
||||||
set -l commandline (__fzf_parse_commandline)
|
|
||||||
set -lx dir $commandline[1]
|
|
||||||
set -l fzf_query $commandline[2]
|
|
||||||
set -l prefix $commandline[3]
|
|
||||||
|
|
||||||
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
|
||||||
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_FILE ''
|
|
||||||
set -lx FZF_DEFAULT_COMMAND "$FZF_ALT_C_COMMAND"
|
|
||||||
set -l result (eval (__fzfcmd) +m --query=$fzf_query)
|
|
||||||
|
|
||||||
if test -n "$result"
|
|
||||||
cd -- $result
|
|
||||||
|
|
||||||
# Remove last token from commandline.
|
|
||||||
commandline -t ""
|
|
||||||
commandline -it -- $prefix
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
commandline -f repaint
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function __fzfcmd
|
function __fzfcmd
|
||||||
test -n "$FZF_TMUX"; or set FZF_TMUX 0
|
test -n "$FZF_TMUX_HEIGHT"; or set -l FZF_TMUX_HEIGHT 40%
|
||||||
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
|
||||||
if test -n "$FZF_TMUX_OPTS"
|
if test -n "$FZF_TMUX_OPTS"
|
||||||
echo "fzf-tmux $FZF_TMUX_OPTS -- "
|
echo "fzf-tmux $FZF_TMUX_OPTS -- "
|
||||||
else if test "$FZF_TMUX" = "1"
|
else if test "$FZF_TMUX" = "1"
|
||||||
@@ -119,82 +50,181 @@ function fzf_key_bindings
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
bind \cr fzf-history-widget
|
|
||||||
if not set -q FZF_CTRL_T_COMMAND; or test -n "$FZF_CTRL_T_COMMAND"
|
|
||||||
bind \ct fzf-file-widget
|
|
||||||
end
|
|
||||||
if not set -q FZF_ALT_C_COMMAND; or test -n "$FZF_ALT_C_COMMAND"
|
|
||||||
bind \ec fzf-cd-widget
|
|
||||||
end
|
|
||||||
|
|
||||||
bind -M insert \cr fzf-history-widget
|
|
||||||
if not set -q FZF_CTRL_T_COMMAND; or test -n "$FZF_CTRL_T_COMMAND"
|
|
||||||
bind -M insert \ct fzf-file-widget
|
|
||||||
end
|
|
||||||
if not set -q FZF_ALT_C_COMMAND; or test -n "$FZF_ALT_C_COMMAND"
|
|
||||||
bind -M insert \ec fzf-cd-widget
|
|
||||||
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'
|
||||||
set -l commandline (commandline -t)
|
set -l fzf_query ''
|
||||||
|
set -l prefix ''
|
||||||
|
set -l dir '.'
|
||||||
|
|
||||||
# strip -option= from token if present
|
# Set variables containing the major and minor fish version numbers, using
|
||||||
set -l prefix (string match -r -- '^-[^\s=]+=' $commandline)
|
# a method compatible with all supported fish versions.
|
||||||
set commandline (string replace -- "$prefix" '' $commandline)
|
set -l -- fish_major (string match -r -- '^\d+' $version)
|
||||||
|
set -l -- fish_minor (string match -r -- '^\d+\.(\d+)' $version)[2]
|
||||||
|
|
||||||
# Enable home directory expansion of leading ~/
|
# fish v3.3.0 and newer: Don't use option prefix if " -- " is preceded.
|
||||||
set commandline (string replace -r -- '^~/' '\$HOME/' $commandline)
|
set -l -- match_regex '(?<fzf_query>[\s\S]*?(?=\n?$)$)'
|
||||||
|
set -l -- prefix_regex '^-[^\s=]+=|^-(?!-)\S'
|
||||||
|
if test "$fish_major" -eq 3 -a "$fish_minor" -lt 3
|
||||||
|
or string match -q -v -- '* -- *' (string sub -l (commandline -Cp) -- (commandline -p))
|
||||||
|
set -- match_regex "(?<prefix>$prefix_regex)?$match_regex"
|
||||||
|
end
|
||||||
|
|
||||||
# escape special characters, except for the $ sign of valid variable names,
|
# Set $prefix and expanded $fzf_query with preserved trailing newlines.
|
||||||
# so that after eval, the original string is returned, but with the
|
if test "$fish_major" -ge 4
|
||||||
# variable names replaced by their values.
|
# fish v4.0.0 and newer
|
||||||
set commandline (string escape -n -- $commandline)
|
string match -q -r -- $match_regex (commandline --current-token --tokens-expanded | string collect -N)
|
||||||
set commandline (string replace -r -a -- '\x5c\$(?=[\w])' '\$' $commandline)
|
else if test "$fish_major" -eq 3 -a "$fish_minor" -ge 2
|
||||||
|
# fish v3.2.0 - v3.7.1 (last v3)
|
||||||
# eval is used to do shell expansion on paths
|
string match -q -r -- $match_regex (commandline --current-token --tokenize | string collect -N)
|
||||||
eval set commandline $commandline
|
eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^\\\(?=~)|\\\(?=\$\w)' '')
|
||||||
|
|
||||||
# Combine multiple consecutive slashes into one, and unescape.
|
|
||||||
set commandline (string replace -r -a -- '/+' '/' $commandline | string unescape -n)
|
|
||||||
|
|
||||||
if test -z "$commandline"
|
|
||||||
# Default to current directory with no --query
|
|
||||||
set dir '.'
|
|
||||||
set fzf_query ''
|
|
||||||
else
|
else
|
||||||
set dir (__fzf_get_dir $commandline)
|
# fish older than v3.2.0 (v3.1b1 - v3.1.2)
|
||||||
|
set -l -- cl_token (commandline --current-token --tokenize | string collect -N)
|
||||||
|
set -- prefix (string match -r -- $prefix_regex $cl_token)
|
||||||
|
set -- fzf_query (string replace -- "$prefix" '' $cl_token | string collect -N)
|
||||||
|
eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^\\\(?=~)|\\\(?=\$\w)|\\\n\\\n$' '')
|
||||||
|
end
|
||||||
|
|
||||||
# BUG: on combined expressions, if a left argument is a single `!`, the
|
if test -n "$fzf_query"
|
||||||
# builtin test command of fish will treat it as the ! operator. To
|
# Normalize path in $fzf_query, set $dir to the longest existing directory.
|
||||||
# overcome this, have the variable parts on the right.
|
if test \( "$fish_major" -ge 4 \) -o \( "$fish_major" -eq 3 -a "$fish_minor" -ge 5 \)
|
||||||
if test "." = "$dir" -a "./" != (string sub -l 2 -- $commandline)
|
# fish v3.5.0 and newer
|
||||||
# if $dir is "." but commandline is not a relative path, this means no file path found
|
set -- fzf_query (path normalize -- $fzf_query)
|
||||||
set fzf_query $commandline
|
set -- dir $fzf_query
|
||||||
|
while not path is -d $dir
|
||||||
|
set -- dir (path dirname $dir)
|
||||||
|
end
|
||||||
else
|
else
|
||||||
# Also remove trailing slash after dir, to "split" input properly
|
# fish older than v3.5.0 (v3.1b1 - v3.4.1)
|
||||||
set fzf_query (string replace -r -- "^$dir/?" '' $commandline)
|
if test "$fish_major" -eq 3 -a "$fish_minor" -ge 2
|
||||||
|
# fish v3.2.0 - v3.4.1
|
||||||
|
string match -q -r -- '(?<fzf_query>^[\s\S]*?(?=\n?$)$)' \
|
||||||
|
(string replace -r -a -- '(?<=/)/|(?<!^)/+(?!\n)$' '' $fzf_query | string collect -N)
|
||||||
|
else
|
||||||
|
# fish v3.1b1 - v3.1.2
|
||||||
|
set -- fzf_query (string replace -r -a -- '(?<=/)/|(?<!^)/+(?!\n)$' '' $fzf_query | string collect -N)
|
||||||
|
eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r '\\\n$' '')
|
||||||
|
end
|
||||||
|
set -- dir $fzf_query
|
||||||
|
while not test -d "$dir"
|
||||||
|
set -- dir (dirname -z -- "$dir" | string split0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not string match -q -- '.' $dir; or string match -q -r -- '^\./|^\.$' $fzf_query
|
||||||
|
# Strip $dir from $fzf_query - preserve trailing newlines.
|
||||||
|
if test "$fish_major" -ge 4
|
||||||
|
# fish v4.0.0 and newer
|
||||||
|
string match -q -r -- '^'(string escape --style=regex -- $dir)'/?(?<fzf_query>[\s\S]*)' $fzf_query
|
||||||
|
else if test "$fish_major" -eq 3 -a "$fish_minor" -ge 2
|
||||||
|
# fish v3.2.0 - v3.7.1 (last v3)
|
||||||
|
string match -q -r -- '^/?(?<fzf_query>[\s\S]*?(?=\n?$)$)' \
|
||||||
|
(string replace -- "$dir" '' $fzf_query | string collect -N)
|
||||||
|
else
|
||||||
|
# fish older than v3.2.0 (v3.1b1 - v3.1.2)
|
||||||
|
set -- fzf_query (string replace -- "$dir" '' $fzf_query | string collect -N)
|
||||||
|
eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^/?|\\\n$' '')
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
echo -- $dir
|
string escape -n -- "$dir" "$fzf_query" "$prefix"
|
||||||
string escape -- $fzf_query
|
|
||||||
echo -- $prefix
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function __fzf_get_dir -d 'Find the longest existing filepath from input string'
|
# Store current token in $dir as root for the 'find' command
|
||||||
set dir $argv
|
function fzf-file-widget -d "List files and folders"
|
||||||
|
set -l commandline (__fzf_parse_commandline)
|
||||||
|
set -lx dir $commandline[1]
|
||||||
|
set -l fzf_query $commandline[2]
|
||||||
|
set -l prefix $commandline[3]
|
||||||
|
|
||||||
# Strip trailing slash, unless $dir is root dir (/)
|
set -lx FZF_DEFAULT_OPTS (__fzf_defaults \
|
||||||
set dir (string replace -r -- '(?<!^)/$' '' $dir)
|
"--reverse --walker=file,dir,follow,hidden --scheme=path" \
|
||||||
|
"$FZF_CTRL_T_OPTS --multi --print0")
|
||||||
|
|
||||||
# Iteratively check if dir exists and strip tail end of path
|
set -lx FZF_DEFAULT_COMMAND "$FZF_CTRL_T_COMMAND"
|
||||||
while test ! -d "$dir"
|
set -lx FZF_DEFAULT_OPTS_FILE
|
||||||
# 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 "."
|
set -l result (eval (__fzfcmd) --walker-root=$dir --query=$fzf_query | string split0)
|
||||||
set dir (dirname -- "$dir")
|
and commandline -rt -- (string join -- ' ' $prefix(string escape -- $result))' '
|
||||||
|
|
||||||
|
commandline -f repaint
|
||||||
|
end
|
||||||
|
|
||||||
|
function fzf-history-widget -d "Show command history"
|
||||||
|
set -l -- command_line (commandline)
|
||||||
|
set -l -- current_line (commandline -L)
|
||||||
|
set -l -- total_lines (count $command_line)
|
||||||
|
set -l -- fzf_query (string escape -- $command_line[$current_line])
|
||||||
|
|
||||||
|
set -lx FZF_DEFAULT_OPTS (__fzf_defaults '' \
|
||||||
|
'--nth=2..,.. --scheme=history --multi --wrap-sign="\t↳ "' \
|
||||||
|
'--bind=\'shift-delete:execute-silent(eval history delete --exact --case-sensitive -- (string escape -n -- {+} | string replace -r -a "^\d*\\\\\\t|(?<=\\\\\\n)\\\\\\t" ""))+reload(eval $FZF_DEFAULT_COMMAND)\'' \
|
||||||
|
"--bind=ctrl-r:toggle-sort --highlight-line $FZF_CTRL_R_OPTS" \
|
||||||
|
'--accept-nth=2.. --read0 --print0 --with-shell='(status fish-path)\\ -c)
|
||||||
|
|
||||||
|
set -lx FZF_DEFAULT_OPTS_FILE
|
||||||
|
set -lx FZF_DEFAULT_COMMAND
|
||||||
|
|
||||||
|
if type -q perl
|
||||||
|
set -a FZF_DEFAULT_OPTS '--tac'
|
||||||
|
set FZF_DEFAULT_COMMAND 'builtin history -z --reverse | command perl -0 -pe \'s/^/$.\t/g; s/\n/\n\t/gm\''
|
||||||
|
else
|
||||||
|
set FZF_DEFAULT_COMMAND \
|
||||||
|
'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
|
||||||
|
|
||||||
string escape -n -- $dir
|
# Merge history from other sessions before searching
|
||||||
|
test -z "$fish_private_mode"; and builtin history merge
|
||||||
|
|
||||||
|
if set -l result (eval $FZF_DEFAULT_COMMAND \| (__fzfcmd) --query=$fzf_query | string split0)
|
||||||
|
if test "$total_lines" -eq 1
|
||||||
|
commandline -- (string replace -a -- \n\t \n $result)
|
||||||
|
else
|
||||||
|
set -l a (math $current_line - 1)
|
||||||
|
set -l b (math $current_line + 1)
|
||||||
|
commandline -- $command_line[1..$a] (string replace -a -- \n\t \n $result)
|
||||||
|
commandline -a -- '' $command_line[$b..-1]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
commandline -f repaint
|
||||||
|
end
|
||||||
|
|
||||||
|
function fzf-cd-widget -d "Change directory"
|
||||||
|
set -l commandline (__fzf_parse_commandline)
|
||||||
|
set -lx dir $commandline[1]
|
||||||
|
set -l fzf_query $commandline[2]
|
||||||
|
set -l prefix $commandline[3]
|
||||||
|
|
||||||
|
set -lx FZF_DEFAULT_OPTS (__fzf_defaults \
|
||||||
|
"--reverse --walker=dir,follow,hidden --scheme=path" \
|
||||||
|
"$FZF_ALT_C_OPTS --no-multi --print0")
|
||||||
|
|
||||||
|
set -lx FZF_DEFAULT_OPTS_FILE
|
||||||
|
set -lx FZF_DEFAULT_COMMAND "$FZF_ALT_C_COMMAND"
|
||||||
|
|
||||||
|
if set -l result (eval (__fzfcmd) --query=$fzf_query --walker-root=$dir | string split0)
|
||||||
|
cd -- $result
|
||||||
|
commandline -rt -- $prefix
|
||||||
|
end
|
||||||
|
|
||||||
|
commandline -f repaint
|
||||||
|
end
|
||||||
|
|
||||||
|
bind \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"
|
||||||
|
bind \ct fzf-file-widget
|
||||||
|
bind -M insert \ct fzf-file-widget
|
||||||
|
end
|
||||||
|
|
||||||
|
if not set -q FZF_ALT_C_COMMAND; or test -n "$FZF_ALT_C_COMMAND"
|
||||||
|
bind \ec fzf-cd-widget
|
||||||
|
bind -M insert \ec fzf-cd-widget
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
@@ -38,14 +38,32 @@ fi
|
|||||||
{
|
{
|
||||||
if [[ -o interactive ]]; then
|
if [[ -o interactive ]]; then
|
||||||
|
|
||||||
|
#----BEGIN INCLUDE common.sh
|
||||||
|
# NOTE: Do not directly edit this section, which is copied from "common.sh".
|
||||||
|
# To modify it, one can edit "common.sh" and run "./update-common.sh" to apply
|
||||||
|
# the changes. See code comments in "common.sh" for the implementation details.
|
||||||
|
|
||||||
__fzf_defaults() {
|
__fzf_defaults() {
|
||||||
# $1: Prepend to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
printf '%s\n' "--height ${FZF_TMUX_HEIGHT:-40%} --min-height 20+ --bind=ctrl-z:ignore $1"
|
||||||
# $2: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
|
||||||
echo "--height ${FZF_TMUX_HEIGHT:-40%} --min-height 20+ --bind=ctrl-z:ignore $1"
|
|
||||||
command cat "${FZF_DEFAULT_OPTS_FILE-}" 2> /dev/null
|
command cat "${FZF_DEFAULT_OPTS_FILE-}" 2> /dev/null
|
||||||
echo "${FZF_DEFAULT_OPTS-} $2"
|
printf '%s\n' "${FZF_DEFAULT_OPTS-} $2"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
__fzf_exec_awk() {
|
||||||
|
if [[ -z ${__fzf_awk-} ]]; then
|
||||||
|
__fzf_awk=awk
|
||||||
|
if [[ $OSTYPE == solaris* && -x /usr/xpg4/bin/awk ]]; then
|
||||||
|
__fzf_awk=/usr/xpg4/bin/awk
|
||||||
|
elif command -v mawk >/dev/null 2>&1; then
|
||||||
|
local n x y z d
|
||||||
|
IFS=' .' read -r n x y z d <<< $(command mawk -W version 2> /dev/null)
|
||||||
|
[[ $n == mawk ]] && (( d >= 20230302 && (x * 1000 + y) * 1000 + z >= 1003004 )) && __fzf_awk=mawk
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
LC_ALL=C exec "$__fzf_awk" "$@"
|
||||||
|
}
|
||||||
|
#----END INCLUDE
|
||||||
|
|
||||||
# CTRL-T - Paste the selected file path(s) into the command line
|
# CTRL-T - Paste the selected file path(s) into the command line
|
||||||
__fzf_select() {
|
__fzf_select() {
|
||||||
setopt localoptions pipefail no_aliases 2> /dev/null
|
setopt localoptions pipefail no_aliases 2> /dev/null
|
||||||
@@ -117,13 +135,13 @@ fzf-history-widget() {
|
|||||||
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") \
|
||||||
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd))"
|
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd))"
|
||||||
else
|
else
|
||||||
selected="$(fc -rl 1 | awk '{ cmd=$0; sub(/^[ \t]*[0-9]+\**[ \t]+/, "", cmd); if (!seen[cmd]++) print $0 }' |
|
selected="$(fc -rl 1 | __fzf_exec_awk '{ cmd=$0; sub(/^[ \t]*[0-9]+\**[ \t]+/, "", cmd); if (!seen[cmd]++) print $0 }' |
|
||||||
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") \
|
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") \
|
||||||
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd))"
|
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd))"
|
||||||
fi
|
fi
|
||||||
local ret=$?
|
local ret=$?
|
||||||
if [ -n "$selected" ]; then
|
if [ -n "$selected" ]; then
|
||||||
if [[ $(awk '{print $1; exit}' <<< "$selected") =~ ^[1-9][0-9]* ]]; then
|
if [[ $(__fzf_exec_awk '{print $1; exit}' <<< "$selected") =~ ^[1-9][0-9]* ]]; then
|
||||||
zle vi-fetch-history -n $MATCH
|
zle vi-fetch-history -n $MATCH
|
||||||
else # selected is a custom query, not from history
|
else # selected is a custom query, not from history
|
||||||
LBUFFER="$selected"
|
LBUFFER="$selected"
|
||||||
|
31
shell/update-common.sh
Executable file
31
shell/update-common.sh
Executable file
@@ -0,0 +1,31 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# This script applies the contents of "common.sh" to the other files.
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Go to the directory that contains this script
|
||||||
|
dir=${0%"${0##*/}"}
|
||||||
|
if [ -n "$dir" ]; then
|
||||||
|
cd "$dir"
|
||||||
|
fi
|
||||||
|
|
||||||
|
update() {
|
||||||
|
{
|
||||||
|
sed -n '1,/^#----BEGIN INCLUDE common\.sh/p' "$1"
|
||||||
|
cat <<EOF
|
||||||
|
# NOTE: Do not directly edit this section, which is copied from "common.sh".
|
||||||
|
# To modify it, one can edit "common.sh" and run "./update-common.sh" to apply
|
||||||
|
# the changes. See code comments in "common.sh" for the implementation details.
|
||||||
|
EOF
|
||||||
|
grep -v '^[[:blank:]]*#' common.sh # remove code comments in common.sh
|
||||||
|
sed -n '/^#----END INCLUDE/,$p' "$1"
|
||||||
|
} > "$1.part"
|
||||||
|
|
||||||
|
mv -f "$1.part" "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
update completion.bash
|
||||||
|
update completion.zsh
|
||||||
|
update key-bindings.bash
|
||||||
|
update key-bindings.zsh
|
@@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2013-2024 Junegunn Choi
|
Copyright (c) 2013-2025 Junegunn Choi
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
@@ -12,139 +12,167 @@ func _() {
|
|||||||
_ = x[actStart-1]
|
_ = x[actStart-1]
|
||||||
_ = x[actClick-2]
|
_ = x[actClick-2]
|
||||||
_ = x[actInvalid-3]
|
_ = x[actInvalid-3]
|
||||||
_ = x[actChar-4]
|
_ = x[actBracketedPasteBegin-4]
|
||||||
_ = x[actMouse-5]
|
_ = x[actBracketedPasteEnd-5]
|
||||||
_ = x[actBeginningOfLine-6]
|
_ = x[actChar-6]
|
||||||
_ = x[actAbort-7]
|
_ = x[actMouse-7]
|
||||||
_ = x[actAccept-8]
|
_ = x[actBeginningOfLine-8]
|
||||||
_ = x[actAcceptNonEmpty-9]
|
_ = x[actAbort-9]
|
||||||
_ = x[actAcceptOrPrintQuery-10]
|
_ = x[actAccept-10]
|
||||||
_ = x[actBackwardChar-11]
|
_ = x[actAcceptNonEmpty-11]
|
||||||
_ = x[actBackwardDeleteChar-12]
|
_ = x[actAcceptOrPrintQuery-12]
|
||||||
_ = x[actBackwardDeleteCharEof-13]
|
_ = x[actBackwardChar-13]
|
||||||
_ = x[actBackwardWord-14]
|
_ = x[actBackwardDeleteChar-14]
|
||||||
_ = x[actCancel-15]
|
_ = x[actBackwardDeleteCharEof-15]
|
||||||
_ = x[actChangeBorderLabel-16]
|
_ = x[actBackwardWord-16]
|
||||||
_ = x[actChangeListLabel-17]
|
_ = x[actCancel-17]
|
||||||
_ = x[actChangeInputLabel-18]
|
_ = x[actChangeBorderLabel-18]
|
||||||
_ = x[actChangeHeader-19]
|
_ = x[actChangeGhost-19]
|
||||||
_ = x[actChangeHeaderLabel-20]
|
_ = x[actChangeHeader-20]
|
||||||
_ = x[actChangeMulti-21]
|
_ = x[actChangeFooter-21]
|
||||||
_ = x[actChangePreviewLabel-22]
|
_ = x[actChangeHeaderLabel-22]
|
||||||
_ = x[actChangePrompt-23]
|
_ = x[actChangeFooterLabel-23]
|
||||||
_ = x[actChangeQuery-24]
|
_ = x[actChangeInputLabel-24]
|
||||||
_ = x[actChangeNth-25]
|
_ = x[actChangeListLabel-25]
|
||||||
_ = x[actClearScreen-26]
|
_ = x[actChangeMulti-26]
|
||||||
_ = x[actClearQuery-27]
|
_ = x[actChangeNth-27]
|
||||||
_ = x[actClearSelection-28]
|
_ = x[actChangePointer-28]
|
||||||
_ = x[actClose-29]
|
_ = x[actChangePreview-29]
|
||||||
_ = x[actDeleteChar-30]
|
_ = x[actChangePreviewLabel-30]
|
||||||
_ = x[actDeleteCharEof-31]
|
_ = x[actChangePreviewWindow-31]
|
||||||
_ = x[actEndOfLine-32]
|
_ = x[actChangePrompt-32]
|
||||||
_ = x[actFatal-33]
|
_ = x[actChangeQuery-33]
|
||||||
_ = x[actForwardChar-34]
|
_ = x[actClearScreen-34]
|
||||||
_ = x[actForwardWord-35]
|
_ = x[actClearQuery-35]
|
||||||
_ = x[actKillLine-36]
|
_ = x[actClearSelection-36]
|
||||||
_ = x[actKillWord-37]
|
_ = x[actClose-37]
|
||||||
_ = x[actUnixLineDiscard-38]
|
_ = x[actDeleteChar-38]
|
||||||
_ = x[actUnixWordRubout-39]
|
_ = x[actDeleteCharEof-39]
|
||||||
_ = x[actYank-40]
|
_ = x[actEndOfLine-40]
|
||||||
_ = x[actBackwardKillWord-41]
|
_ = x[actFatal-41]
|
||||||
_ = x[actSelectAll-42]
|
_ = x[actForwardChar-42]
|
||||||
_ = x[actDeselectAll-43]
|
_ = x[actForwardWord-43]
|
||||||
_ = x[actToggle-44]
|
_ = x[actKillLine-44]
|
||||||
_ = x[actToggleSearch-45]
|
_ = x[actKillWord-45]
|
||||||
_ = x[actToggleAll-46]
|
_ = x[actUnixLineDiscard-46]
|
||||||
_ = x[actToggleDown-47]
|
_ = x[actUnixWordRubout-47]
|
||||||
_ = x[actToggleUp-48]
|
_ = x[actYank-48]
|
||||||
_ = x[actToggleIn-49]
|
_ = x[actBackwardKillWord-49]
|
||||||
_ = x[actToggleOut-50]
|
_ = x[actSelectAll-50]
|
||||||
_ = x[actToggleTrack-51]
|
_ = x[actDeselectAll-51]
|
||||||
_ = x[actToggleTrackCurrent-52]
|
_ = x[actToggle-52]
|
||||||
_ = x[actToggleHeader-53]
|
_ = x[actToggleSearch-53]
|
||||||
_ = x[actToggleWrap-54]
|
_ = x[actToggleAll-54]
|
||||||
_ = x[actToggleMultiLine-55]
|
_ = x[actToggleDown-55]
|
||||||
_ = x[actToggleHscroll-56]
|
_ = x[actToggleUp-56]
|
||||||
_ = x[actTrackCurrent-57]
|
_ = x[actToggleIn-57]
|
||||||
_ = x[actToggleInput-58]
|
_ = x[actToggleOut-58]
|
||||||
_ = x[actHideInput-59]
|
_ = x[actToggleTrack-59]
|
||||||
_ = x[actShowInput-60]
|
_ = x[actToggleTrackCurrent-60]
|
||||||
_ = x[actUntrackCurrent-61]
|
_ = x[actToggleHeader-61]
|
||||||
_ = x[actDown-62]
|
_ = x[actToggleWrap-62]
|
||||||
_ = x[actUp-63]
|
_ = x[actToggleMultiLine-63]
|
||||||
_ = x[actPageUp-64]
|
_ = x[actToggleHscroll-64]
|
||||||
_ = x[actPageDown-65]
|
_ = x[actTrackCurrent-65]
|
||||||
_ = x[actPosition-66]
|
_ = x[actToggleInput-66]
|
||||||
_ = x[actHalfPageUp-67]
|
_ = x[actHideInput-67]
|
||||||
_ = x[actHalfPageDown-68]
|
_ = x[actShowInput-68]
|
||||||
_ = x[actOffsetUp-69]
|
_ = x[actUntrackCurrent-69]
|
||||||
_ = x[actOffsetDown-70]
|
_ = x[actDown-70]
|
||||||
_ = x[actOffsetMiddle-71]
|
_ = x[actUp-71]
|
||||||
_ = x[actJump-72]
|
_ = x[actPageUp-72]
|
||||||
_ = x[actJumpAccept-73]
|
_ = x[actPageDown-73]
|
||||||
_ = x[actPrintQuery-74]
|
_ = x[actPosition-74]
|
||||||
_ = x[actRefreshPreview-75]
|
_ = x[actHalfPageUp-75]
|
||||||
_ = x[actReplaceQuery-76]
|
_ = x[actHalfPageDown-76]
|
||||||
_ = x[actToggleSort-77]
|
_ = x[actOffsetUp-77]
|
||||||
_ = x[actShowPreview-78]
|
_ = x[actOffsetDown-78]
|
||||||
_ = x[actHidePreview-79]
|
_ = x[actOffsetMiddle-79]
|
||||||
_ = x[actTogglePreview-80]
|
_ = x[actJump-80]
|
||||||
_ = x[actTogglePreviewWrap-81]
|
_ = x[actJumpAccept-81]
|
||||||
_ = x[actTransform-82]
|
_ = x[actPrintQuery-82]
|
||||||
_ = x[actTransformBorderLabel-83]
|
_ = x[actRefreshPreview-83]
|
||||||
_ = x[actTransformListLabel-84]
|
_ = x[actReplaceQuery-84]
|
||||||
_ = x[actTransformInputLabel-85]
|
_ = x[actToggleSort-85]
|
||||||
_ = x[actTransformHeader-86]
|
_ = x[actShowPreview-86]
|
||||||
_ = x[actTransformHeaderLabel-87]
|
_ = x[actHidePreview-87]
|
||||||
_ = x[actTransformNth-88]
|
_ = x[actTogglePreview-88]
|
||||||
_ = x[actTransformPreviewLabel-89]
|
_ = x[actTogglePreviewWrap-89]
|
||||||
_ = x[actTransformPrompt-90]
|
_ = x[actTransform-90]
|
||||||
_ = x[actTransformQuery-91]
|
_ = x[actTransformBorderLabel-91]
|
||||||
_ = x[actTransformSearch-92]
|
_ = x[actTransformGhost-92]
|
||||||
_ = x[actSearch-93]
|
_ = x[actTransformHeader-93]
|
||||||
_ = x[actPreview-94]
|
_ = x[actTransformFooter-94]
|
||||||
_ = x[actChangePreview-95]
|
_ = x[actTransformHeaderLabel-95]
|
||||||
_ = x[actChangePreviewWindow-96]
|
_ = x[actTransformFooterLabel-96]
|
||||||
_ = x[actPreviewTop-97]
|
_ = x[actTransformInputLabel-97]
|
||||||
_ = x[actPreviewBottom-98]
|
_ = x[actTransformListLabel-98]
|
||||||
_ = x[actPreviewUp-99]
|
_ = x[actTransformNth-99]
|
||||||
_ = x[actPreviewDown-100]
|
_ = x[actTransformPointer-100]
|
||||||
_ = x[actPreviewPageUp-101]
|
_ = x[actTransformPreviewLabel-101]
|
||||||
_ = x[actPreviewPageDown-102]
|
_ = x[actTransformPrompt-102]
|
||||||
_ = x[actPreviewHalfPageUp-103]
|
_ = x[actTransformQuery-103]
|
||||||
_ = x[actPreviewHalfPageDown-104]
|
_ = x[actTransformSearch-104]
|
||||||
_ = x[actPrevHistory-105]
|
_ = x[actTrigger-105]
|
||||||
_ = x[actPrevSelected-106]
|
_ = x[actBgTransform-106]
|
||||||
_ = x[actPrint-107]
|
_ = x[actBgTransformBorderLabel-107]
|
||||||
_ = x[actPut-108]
|
_ = x[actBgTransformGhost-108]
|
||||||
_ = x[actNextHistory-109]
|
_ = x[actBgTransformHeader-109]
|
||||||
_ = x[actNextSelected-110]
|
_ = x[actBgTransformFooter-110]
|
||||||
_ = x[actExecute-111]
|
_ = x[actBgTransformHeaderLabel-111]
|
||||||
_ = x[actExecuteSilent-112]
|
_ = x[actBgTransformFooterLabel-112]
|
||||||
_ = x[actExecuteMulti-113]
|
_ = x[actBgTransformInputLabel-113]
|
||||||
_ = x[actSigStop-114]
|
_ = x[actBgTransformListLabel-114]
|
||||||
_ = x[actFirst-115]
|
_ = x[actBgTransformNth-115]
|
||||||
_ = x[actLast-116]
|
_ = x[actBgTransformPointer-116]
|
||||||
_ = x[actReload-117]
|
_ = x[actBgTransformPreviewLabel-117]
|
||||||
_ = x[actReloadSync-118]
|
_ = x[actBgTransformPrompt-118]
|
||||||
_ = x[actDisableSearch-119]
|
_ = x[actBgTransformQuery-119]
|
||||||
_ = x[actEnableSearch-120]
|
_ = x[actBgTransformSearch-120]
|
||||||
_ = x[actSelect-121]
|
_ = x[actBgCancel-121]
|
||||||
_ = x[actDeselect-122]
|
_ = x[actSearch-122]
|
||||||
_ = x[actUnbind-123]
|
_ = x[actPreview-123]
|
||||||
_ = x[actRebind-124]
|
_ = x[actPreviewTop-124]
|
||||||
_ = x[actToggleBind-125]
|
_ = x[actPreviewBottom-125]
|
||||||
_ = x[actBecome-126]
|
_ = x[actPreviewUp-126]
|
||||||
_ = x[actShowHeader-127]
|
_ = x[actPreviewDown-127]
|
||||||
_ = x[actHideHeader-128]
|
_ = x[actPreviewPageUp-128]
|
||||||
_ = x[actBell-129]
|
_ = x[actPreviewPageDown-129]
|
||||||
_ = x[actExclude-130]
|
_ = x[actPreviewHalfPageUp-130]
|
||||||
_ = x[actExcludeMulti-131]
|
_ = x[actPreviewHalfPageDown-131]
|
||||||
|
_ = x[actPrevHistory-132]
|
||||||
|
_ = x[actPrevSelected-133]
|
||||||
|
_ = x[actPrint-134]
|
||||||
|
_ = x[actPut-135]
|
||||||
|
_ = x[actNextHistory-136]
|
||||||
|
_ = x[actNextSelected-137]
|
||||||
|
_ = x[actExecute-138]
|
||||||
|
_ = x[actExecuteSilent-139]
|
||||||
|
_ = x[actExecuteMulti-140]
|
||||||
|
_ = x[actSigStop-141]
|
||||||
|
_ = x[actFirst-142]
|
||||||
|
_ = x[actLast-143]
|
||||||
|
_ = x[actReload-144]
|
||||||
|
_ = x[actReloadSync-145]
|
||||||
|
_ = x[actDisableSearch-146]
|
||||||
|
_ = x[actEnableSearch-147]
|
||||||
|
_ = x[actSelect-148]
|
||||||
|
_ = x[actDeselect-149]
|
||||||
|
_ = x[actUnbind-150]
|
||||||
|
_ = x[actRebind-151]
|
||||||
|
_ = x[actToggleBind-152]
|
||||||
|
_ = x[actBecome-153]
|
||||||
|
_ = x[actShowHeader-154]
|
||||||
|
_ = x[actHideHeader-155]
|
||||||
|
_ = x[actBell-156]
|
||||||
|
_ = x[actExclude-157]
|
||||||
|
_ = x[actExcludeMulti-158]
|
||||||
|
_ = x[actAsync-159]
|
||||||
}
|
}
|
||||||
|
|
||||||
const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeListLabelactChangeInputLabelactChangeHeaderactChangeHeaderLabelactChangeMultiactChangePreviewLabelactChangePromptactChangeQueryactChangeNthactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactToggleWrapactToggleMultiLineactToggleHscrollactTrackCurrentactToggleInputactHideInputactShowInputactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformListLabelactTransformInputLabelactTransformHeaderactTransformHeaderLabelactTransformNthactTransformPreviewLabelactTransformPromptactTransformQueryactTransformSearchactSearchactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactToggleBindactBecomeactShowHeaderactHideHeaderactBellactExcludeactExcludeMulti"
|
const _actionType_name = "actIgnoreactStartactClickactInvalidactBracketedPasteBeginactBracketedPasteEndactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeGhostactChangeHeaderactChangeFooteractChangeHeaderLabelactChangeFooterLabelactChangeInputLabelactChangeListLabelactChangeMultiactChangeNthactChangePointeractChangePreviewactChangePreviewLabelactChangePreviewWindowactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactToggleWrapactToggleMultiLineactToggleHscrollactTrackCurrentactToggleInputactHideInputactShowInputactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformGhostactTransformHeaderactTransformFooteractTransformHeaderLabelactTransformFooterLabelactTransformInputLabelactTransformListLabelactTransformNthactTransformPointeractTransformPreviewLabelactTransformPromptactTransformQueryactTransformSearchactTriggeractBgTransformactBgTransformBorderLabelactBgTransformGhostactBgTransformHeaderactBgTransformFooteractBgTransformHeaderLabelactBgTransformFooterLabelactBgTransformInputLabelactBgTransformListLabelactBgTransformNthactBgTransformPointeractBgTransformPreviewLabelactBgTransformPromptactBgTransformQueryactBgTransformSearchactBgCancelactSearchactPreviewactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactToggleBindactBecomeactShowHeaderactHideHeaderactBellactExcludeactExcludeMultiactAsync"
|
||||||
|
|
||||||
var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 42, 50, 68, 76, 85, 102, 123, 138, 159, 183, 198, 207, 227, 245, 264, 279, 299, 313, 334, 349, 363, 375, 389, 402, 419, 427, 440, 456, 468, 476, 490, 504, 515, 526, 544, 561, 568, 587, 599, 613, 622, 637, 649, 662, 673, 684, 696, 710, 731, 746, 759, 777, 793, 808, 822, 834, 846, 863, 870, 875, 884, 895, 906, 919, 934, 945, 958, 973, 980, 993, 1006, 1023, 1038, 1051, 1065, 1079, 1095, 1115, 1127, 1150, 1171, 1193, 1211, 1234, 1249, 1273, 1291, 1308, 1326, 1335, 1345, 1361, 1383, 1396, 1412, 1424, 1438, 1454, 1472, 1492, 1514, 1528, 1543, 1551, 1557, 1571, 1586, 1596, 1612, 1627, 1637, 1645, 1652, 1661, 1674, 1690, 1705, 1714, 1725, 1734, 1743, 1756, 1765, 1778, 1791, 1798, 1808, 1823}
|
var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 57, 77, 84, 92, 110, 118, 127, 144, 165, 180, 201, 225, 240, 249, 269, 283, 298, 313, 333, 353, 372, 390, 404, 416, 432, 448, 469, 491, 506, 520, 534, 547, 564, 572, 585, 601, 613, 621, 635, 649, 660, 671, 689, 706, 713, 732, 744, 758, 767, 782, 794, 807, 818, 829, 841, 855, 876, 891, 904, 922, 938, 953, 967, 979, 991, 1008, 1015, 1020, 1029, 1040, 1051, 1064, 1079, 1090, 1103, 1118, 1125, 1138, 1151, 1168, 1183, 1196, 1210, 1224, 1240, 1260, 1272, 1295, 1312, 1330, 1348, 1371, 1394, 1416, 1437, 1452, 1471, 1495, 1513, 1530, 1548, 1558, 1572, 1597, 1616, 1636, 1656, 1681, 1706, 1730, 1753, 1770, 1791, 1817, 1837, 1856, 1876, 1887, 1896, 1906, 1919, 1935, 1947, 1961, 1977, 1995, 2015, 2037, 2051, 2066, 2074, 2080, 2094, 2109, 2119, 2135, 2150, 2160, 2168, 2175, 2184, 2197, 2213, 2228, 2237, 2248, 2257, 2266, 2279, 2288, 2301, 2314, 2321, 2331, 2346, 2354}
|
||||||
|
|
||||||
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) {
|
||||||
|
@@ -303,7 +303,7 @@ func bonusAt(input *util.Chars, idx int) int16 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func normalizeRune(r rune) rune {
|
func normalizeRune(r rune) rune {
|
||||||
if r < 0x00C0 || r > 0x2184 {
|
if r < 0x00C0 || r > 0xFF61 {
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -767,6 +767,9 @@ func FuzzyMatchV1(caseSensitive bool, normalize bool, forward bool, text *util.C
|
|||||||
char = unicode.To(unicode.LowerCase, char)
|
char = unicode.To(unicode.LowerCase, char)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if normalize {
|
||||||
|
char = normalizeRune(char)
|
||||||
|
}
|
||||||
|
|
||||||
pidx_ := indexAt(pidx, lenPattern, forward)
|
pidx_ := indexAt(pidx, lenPattern, forward)
|
||||||
pchar := pattern[pidx_]
|
pchar := pattern[pidx_]
|
||||||
@@ -824,7 +827,7 @@ func exactMatchNaive(caseSensitive bool, normalize bool, forward bool, boundaryC
|
|||||||
|
|
||||||
// For simplicity, only look at the bonus at the first character position
|
// For simplicity, only look at the bonus at the first character position
|
||||||
pidx := 0
|
pidx := 0
|
||||||
bestPos, bonus, bestBonus := -1, int16(0), int16(-1)
|
bestPos, bonus, bbonus, bestBonus := -1, int16(0), int16(0), int16(-1)
|
||||||
for index := 0; index < lenRunes; index++ {
|
for index := 0; index < lenRunes; index++ {
|
||||||
index_ := indexAt(index, lenRunes, forward)
|
index_ := indexAt(index, lenRunes, forward)
|
||||||
char := text.Get(index_)
|
char := text.Get(index_)
|
||||||
@@ -846,7 +849,16 @@ func exactMatchNaive(caseSensitive bool, normalize bool, forward bool, boundaryC
|
|||||||
bonus = bonusAt(text, index_)
|
bonus = bonusAt(text, index_)
|
||||||
}
|
}
|
||||||
if boundaryCheck {
|
if boundaryCheck {
|
||||||
ok = bonus >= bonusBoundary
|
if forward && pidx_ == 0 {
|
||||||
|
bbonus = bonus
|
||||||
|
} else if !forward && pidx_ == lenPattern-1 {
|
||||||
|
if index_ < lenRunes-1 {
|
||||||
|
bbonus = bonusAt(text, index_+1)
|
||||||
|
} else {
|
||||||
|
bbonus = bonusBoundaryWhite
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ok = bbonus >= bonusBoundary
|
||||||
if ok && pidx_ == 0 {
|
if ok && pidx_ == 0 {
|
||||||
ok = index_ == 0 || charClassOf(text.Get(index_-1)) <= charDelimiter
|
ok = index_ == 0 || charClassOf(text.Get(index_-1)) <= charDelimiter
|
||||||
}
|
}
|
||||||
|
@@ -200,3 +200,12 @@ func TestLongString(t *testing.T) {
|
|||||||
bytes[math.MaxUint16] = 'z'
|
bytes[math.MaxUint16] = 'z'
|
||||||
assertMatch(t, FuzzyMatchV2, true, true, string(bytes), "zx", math.MaxUint16, math.MaxUint16+2, scoreMatch*2+bonusConsecutive)
|
assertMatch(t, FuzzyMatchV2, true, true, string(bytes), "zx", math.MaxUint16, math.MaxUint16+2, scoreMatch*2+bonusConsecutive)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLongStringWithNormalize(t *testing.T) {
|
||||||
|
bytes := make([]byte, 30000)
|
||||||
|
for i := range bytes {
|
||||||
|
bytes[i] = 'x'
|
||||||
|
}
|
||||||
|
unicodeString := string(bytes) + " Minímal example"
|
||||||
|
assertMatch2(t, FuzzyMatchV1, false, true, false, unicodeString, "minim", 30001, 30006, 140)
|
||||||
|
}
|
||||||
|
@@ -473,6 +473,103 @@ var normalized = map[rune]rune{
|
|||||||
'ử': 'u',
|
'ử': 'u',
|
||||||
'ữ': 'u',
|
'ữ': 'u',
|
||||||
'ự': 'u',
|
'ự': 'u',
|
||||||
|
|
||||||
|
// https://en.wikipedia.org/wiki/Halfwidth_and_Fullwidth_Forms_(Unicode_block)
|
||||||
|
0xFF01: '!', // Fullwidth exclamation
|
||||||
|
0xFF02: '"', // Fullwidth quotation mark
|
||||||
|
0xFF03: '#', // Fullwidth number sign
|
||||||
|
0xFF04: '$', // Fullwidth dollar sign
|
||||||
|
0xFF05: '%', // Fullwidth percent
|
||||||
|
0xFF06: '&', // Fullwidth ampersand
|
||||||
|
0xFF07: '\'', // Fullwidth apostrophe
|
||||||
|
0xFF08: '(', // Fullwidth left parenthesis
|
||||||
|
0xFF09: ')', // Fullwidth right parenthesis
|
||||||
|
0xFF0A: '*', // Fullwidth asterisk
|
||||||
|
0xFF0B: '+', // Fullwidth plus
|
||||||
|
0xFF0C: ',', // Fullwidth comma
|
||||||
|
0xFF0D: '-', // Fullwidth hyphen-minus
|
||||||
|
0xFF0E: '.', // Fullwidth period
|
||||||
|
0xFF0F: '/', // Fullwidth slash
|
||||||
|
0xFF10: '0',
|
||||||
|
0xFF11: '1',
|
||||||
|
0xFF12: '2',
|
||||||
|
0xFF13: '3',
|
||||||
|
0xFF14: '4',
|
||||||
|
0xFF15: '5',
|
||||||
|
0xFF16: '6',
|
||||||
|
0xFF17: '7',
|
||||||
|
0xFF18: '8',
|
||||||
|
0xFF19: '9',
|
||||||
|
0xFF1A: ':', // Fullwidth colon
|
||||||
|
0xFF1B: ';', // Fullwidth semicolon
|
||||||
|
0xFF1C: '<', // Fullwidth less-than
|
||||||
|
0xFF1D: '=', // Fullwidth equal
|
||||||
|
0xFF1E: '>', // Fullwidth greater-than
|
||||||
|
0xFF1F: '?', // Fullwidth question mark
|
||||||
|
0xFF20: '@', // Fullwidth at sign
|
||||||
|
0xFF21: 'A',
|
||||||
|
0xFF22: 'B',
|
||||||
|
0xFF23: 'C',
|
||||||
|
0xFF24: 'D',
|
||||||
|
0xFF25: 'E',
|
||||||
|
0xFF26: 'F',
|
||||||
|
0xFF27: 'G',
|
||||||
|
0xFF28: 'H',
|
||||||
|
0xFF29: 'I',
|
||||||
|
0xFF2A: 'J',
|
||||||
|
0xFF2B: 'K',
|
||||||
|
0xFF2C: 'L',
|
||||||
|
0xFF2D: 'M',
|
||||||
|
0xFF2E: 'N',
|
||||||
|
0xFF2F: 'O',
|
||||||
|
0xFF30: 'P',
|
||||||
|
0xFF31: 'Q',
|
||||||
|
0xFF32: 'R',
|
||||||
|
0xFF33: 'S',
|
||||||
|
0xFF34: 'T',
|
||||||
|
0xFF35: 'U',
|
||||||
|
0xFF36: 'V',
|
||||||
|
0xFF37: 'W',
|
||||||
|
0xFF38: 'X',
|
||||||
|
0xFF39: 'Y',
|
||||||
|
0xFF3A: 'Z',
|
||||||
|
0xFF3B: '[', // Fullwidth left bracket
|
||||||
|
0xFF3C: '\\', // Fullwidth backslash
|
||||||
|
0xFF3D: ']', // Fullwidth right bracket
|
||||||
|
0xFF3E: '^', // Fullwidth circumflex
|
||||||
|
0xFF3F: '_', // Fullwidth underscore
|
||||||
|
0xFF40: '`', // Fullwidth grave accent
|
||||||
|
0xFF41: 'a',
|
||||||
|
0xFF42: 'b',
|
||||||
|
0xFF43: 'c',
|
||||||
|
0xFF44: 'd',
|
||||||
|
0xFF45: 'e',
|
||||||
|
0xFF46: 'f',
|
||||||
|
0xFF47: 'g',
|
||||||
|
0xFF48: 'h',
|
||||||
|
0xFF49: 'i',
|
||||||
|
0xFF4A: 'j',
|
||||||
|
0xFF4B: 'k',
|
||||||
|
0xFF4C: 'l',
|
||||||
|
0xFF4D: 'm',
|
||||||
|
0xFF4E: 'n',
|
||||||
|
0xFF4F: 'o',
|
||||||
|
0xFF50: 'p',
|
||||||
|
0xFF51: 'q',
|
||||||
|
0xFF52: 'r',
|
||||||
|
0xFF53: 's',
|
||||||
|
0xFF54: 't',
|
||||||
|
0xFF55: 'u',
|
||||||
|
0xFF56: 'v',
|
||||||
|
0xFF57: 'w',
|
||||||
|
0xFF58: 'x',
|
||||||
|
0xFF59: 'y',
|
||||||
|
0xFF5A: 'z',
|
||||||
|
0xFF5B: '{', // Fullwidth left brace
|
||||||
|
0xFF5C: '|', // Fullwidth vertical bar
|
||||||
|
0xFF5D: '}', // Fullwidth right brace
|
||||||
|
0xFF5E: '~', // Fullwidth tilde
|
||||||
|
0xFF61: '.', // Halfwidth ideographic full stop
|
||||||
}
|
}
|
||||||
|
|
||||||
// NormalizeRunes normalizes latin script letters
|
// NormalizeRunes normalizes latin script letters
|
||||||
@@ -480,7 +577,7 @@ func NormalizeRunes(runes []rune) []rune {
|
|||||||
ret := make([]rune, len(runes))
|
ret := make([]rune, len(runes))
|
||||||
copy(ret, runes)
|
copy(ret, runes)
|
||||||
for idx, r := range runes {
|
for idx, r := range runes {
|
||||||
if r < 0x00C0 || r > 0x2184 {
|
if r < 0x00C0 || r > 0xFF61 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
n := normalized[r]
|
n := normalized[r]
|
||||||
|
51
src/ansi.go
51
src/ansi.go
@@ -156,13 +156,13 @@ 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|\n)"
|
||||||
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
|
||||||
for ; i < len(s); i++ {
|
for ; i < len(s); i++ {
|
||||||
switch s[i] {
|
switch s[i] {
|
||||||
case '\x0e', '\x0f', '\x1b', '\x08':
|
case '\x0e', '\x0f', '\x1b', '\x08', '\n':
|
||||||
// We ignore the fact that '\x08' cannot be the first char
|
// We ignore the fact that '\x08' cannot be the first char
|
||||||
// in the string and be an escape sequence for the sake of
|
// in the string and be an escape sequence for the sake of
|
||||||
// speed and simplicity.
|
// speed and simplicity.
|
||||||
@@ -174,6 +174,9 @@ func nextAnsiEscapeSequence(s string) (int, int) {
|
|||||||
Loop:
|
Loop:
|
||||||
for ; i < len(s); i++ {
|
for ; i < len(s); i++ {
|
||||||
switch s[i] {
|
switch s[i] {
|
||||||
|
case '\n':
|
||||||
|
// match: `\n`
|
||||||
|
return i, i + 1
|
||||||
case '\x08':
|
case '\x08':
|
||||||
// backtrack to match: `.\x08`
|
// backtrack to match: `.\x08`
|
||||||
if i > 0 && s[i-1] != '\n' {
|
if i > 0 && s[i-1] != '\n' {
|
||||||
@@ -265,13 +268,30 @@ func extractColor(str string, state *ansiState, proc func(string, *ansiState) bo
|
|||||||
output.WriteString(prev)
|
output.WriteString(prev)
|
||||||
}
|
}
|
||||||
|
|
||||||
newState := interpretCode(str[start:idx], state)
|
code := str[start:idx]
|
||||||
if !newState.equals(state) {
|
newState := interpretCode(code, state)
|
||||||
|
if code == "\n" || !newState.equals(state) {
|
||||||
if state != nil {
|
if state != nil {
|
||||||
// Update last offset
|
// Update last offset
|
||||||
(&offsets[len(offsets)-1]).offset[1] = int32(runeCount)
|
(&offsets[len(offsets)-1]).offset[1] = int32(runeCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if code == "\n" {
|
||||||
|
output.WriteRune('\n')
|
||||||
|
runeCount++
|
||||||
|
// Full-background marker
|
||||||
|
if newState.lbg >= 0 {
|
||||||
|
marker := newState
|
||||||
|
marker.attr |= tui.FullBg
|
||||||
|
offsets = append(offsets, ansiOffset{
|
||||||
|
[2]int32{int32(runeCount), int32(runeCount)},
|
||||||
|
marker,
|
||||||
|
})
|
||||||
|
// Reset the full-line background color
|
||||||
|
newState.lbg = -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if newState.colored() {
|
if newState.colored() {
|
||||||
// Append new offset
|
// Append new offset
|
||||||
if pstate == nil {
|
if pstate == nil {
|
||||||
@@ -349,6 +369,13 @@ func parseAnsiCode(s string) (int, string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func interpretCode(ansiCode string, prevState *ansiState) ansiState {
|
func interpretCode(ansiCode string, prevState *ansiState) ansiState {
|
||||||
|
if ansiCode == "\n" {
|
||||||
|
if prevState != nil {
|
||||||
|
return *prevState
|
||||||
|
}
|
||||||
|
return ansiState{-1, -1, 0, -1, nil}
|
||||||
|
}
|
||||||
|
|
||||||
var state ansiState
|
var state ansiState
|
||||||
if prevState == nil {
|
if prevState == nil {
|
||||||
state = ansiState{-1, -1, 0, -1, nil}
|
state = ansiState{-1, -1, 0, -1, nil}
|
||||||
@@ -356,7 +383,7 @@ func interpretCode(ansiCode string, prevState *ansiState) ansiState {
|
|||||||
state = ansiState{prevState.fg, prevState.bg, prevState.attr, prevState.lbg, prevState.url}
|
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") || strings.HasSuffix(ansiCode, "[K")) {
|
||||||
state.lbg = prevState.bg
|
state.lbg = prevState.bg
|
||||||
} else if strings.HasPrefix(ansiCode, "\x1b]8;") && (strings.HasSuffix(ansiCode, "\x1b\\") || strings.HasSuffix(ansiCode, "\a")) {
|
} else if strings.HasPrefix(ansiCode, "\x1b]8;") && (strings.HasSuffix(ansiCode, "\x1b\\") || strings.HasSuffix(ansiCode, "\a")) {
|
||||||
stLen := 2
|
stLen := 2
|
||||||
@@ -375,10 +402,14 @@ func interpretCode(ansiCode string, prevState *ansiState) ansiState {
|
|||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(ansiCode) <= 3 {
|
reset := func() {
|
||||||
state.fg = -1
|
state.fg = -1
|
||||||
state.bg = -1
|
state.bg = -1
|
||||||
state.attr = 0
|
state.attr = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ansiCode) <= 3 {
|
||||||
|
reset()
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
ansiCode = ansiCode[2 : len(ansiCode)-1]
|
ansiCode = ansiCode[2 : len(ansiCode)-1]
|
||||||
@@ -432,9 +463,7 @@ func interpretCode(ansiCode string, prevState *ansiState) ansiState {
|
|||||||
case 29:
|
case 29:
|
||||||
state.attr = state.attr &^ tui.StrikeThrough
|
state.attr = state.attr &^ tui.StrikeThrough
|
||||||
case 0:
|
case 0:
|
||||||
state.fg = -1
|
reset()
|
||||||
state.bg = -1
|
|
||||||
state.attr = 0
|
|
||||||
state256 = 0
|
state256 = 0
|
||||||
default:
|
default:
|
||||||
if num >= 30 && num <= 37 {
|
if num >= 30 && num <= 37 {
|
||||||
@@ -474,9 +503,7 @@ func interpretCode(ansiCode string, prevState *ansiState) ansiState {
|
|||||||
|
|
||||||
// Empty sequence: reset
|
// Empty sequence: reset
|
||||||
if count == 0 {
|
if count == 0 {
|
||||||
state.fg = -1
|
reset()
|
||||||
state.bg = -1
|
|
||||||
state.attr = 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if state256 > 0 {
|
if state256 > 0 {
|
||||||
|
@@ -22,7 +22,7 @@ import (
|
|||||||
// (archived from http://ascii-table.com/ansi-escape-sequences-vt-100.php)
|
// (archived from http://ascii-table.com/ansi-escape-sequences-vt-100.php)
|
||||||
// - http://tldp.org/HOWTO/Bash-Prompt-HOWTO/x405.html
|
// - http://tldp.org/HOWTO/Bash-Prompt-HOWTO/x405.html
|
||||||
// - https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
|
// - https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
|
||||||
var ansiRegexReference = regexp.MustCompile("(?:\x1b[\\[()][0-9;:]*[a-zA-Z@]|\x1b][0-9][;:][[:print:]]+(?:\x1b\\\\|\x07)|\x1b.|[\x0e\x0f]|.\x08)")
|
var ansiRegexReference = regexp.MustCompile("(?:\x1b[\\[()][0-9;:]*[a-zA-Z@]|\x1b][0-9][;:][[:print:]]+(?:\x1b\\\\|\x07)|\x1b.|[\x0e\x0f]|.\x08|\n)")
|
||||||
|
|
||||||
func testParserReference(t testing.TB, str string) {
|
func testParserReference(t testing.TB, str string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
@@ -41,6 +41,13 @@ func (c *Chunk) IsFull() bool {
|
|||||||
return c.count == chunkSize
|
return c.count == chunkSize
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Chunk) lastIndex(minValue int32) int32 {
|
||||||
|
if c.count == 0 {
|
||||||
|
return minValue
|
||||||
|
}
|
||||||
|
return c.items[c.count-1].Index() + 1 // Exclusive
|
||||||
|
}
|
||||||
|
|
||||||
func (cl *ChunkList) lastChunk() *Chunk {
|
func (cl *ChunkList) lastChunk() *Chunk {
|
||||||
return cl.chunks[len(cl.chunks)-1]
|
return cl.chunks[len(cl.chunks)-1]
|
||||||
}
|
}
|
||||||
|
@@ -26,9 +26,13 @@ const (
|
|||||||
previewCancelWait = 500 * time.Millisecond
|
previewCancelWait = 500 * time.Millisecond
|
||||||
previewChunkDelay = 100 * time.Millisecond
|
previewChunkDelay = 100 * time.Millisecond
|
||||||
previewDelayed = 500 * time.Millisecond
|
previewDelayed = 500 * time.Millisecond
|
||||||
maxPatternLength = 300
|
maxPatternLength = 1000
|
||||||
maxMulti = math.MaxInt32
|
maxMulti = math.MaxInt32
|
||||||
|
|
||||||
|
// Background processes
|
||||||
|
maxBgProcesses = 30
|
||||||
|
maxBgProcessesPerAction = 3
|
||||||
|
|
||||||
// Matcher
|
// Matcher
|
||||||
numPartitionsMultiplier = 8
|
numPartitionsMultiplier = 8
|
||||||
maxPartitions = 32
|
maxPartitions = 32
|
||||||
|
63
src/core.go
63
src/core.go
@@ -6,6 +6,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/junegunn/fzf/src/tui"
|
||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -39,7 +40,7 @@ func (r revision) compatible(other revision) bool {
|
|||||||
// Run starts fzf
|
// Run starts fzf
|
||||||
func Run(opts *Options) (int, error) {
|
func Run(opts *Options) (int, error) {
|
||||||
if opts.Filter == nil {
|
if opts.Filter == nil {
|
||||||
if opts.Tmux != nil && len(os.Getenv("TMUX")) > 0 && opts.Tmux.index >= opts.Height.index {
|
if opts.useTmux() {
|
||||||
return runTmux(os.Args, opts)
|
return runTmux(os.Args, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,20 +75,24 @@ func Run(opts *Options) (int, error) {
|
|||||||
|
|
||||||
var lineAnsiState, prevLineAnsiState *ansiState
|
var lineAnsiState, prevLineAnsiState *ansiState
|
||||||
if opts.Ansi {
|
if opts.Ansi {
|
||||||
if opts.Theme.Colored {
|
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
|
||||||
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
|
prevLineAnsiState = lineAnsiState
|
||||||
prevLineAnsiState = lineAnsiState
|
trimmed, offsets, newState := extractColor(byteString(data), lineAnsiState, nil)
|
||||||
trimmed, offsets, newState := extractColor(byteString(data), lineAnsiState, nil)
|
lineAnsiState = newState
|
||||||
lineAnsiState = newState
|
|
||||||
return util.ToChars(stringBytes(trimmed)), offsets
|
// Full line background is found. Add a special marker.
|
||||||
}
|
if offsets != nil && newState != nil && len(*offsets) > 0 && newState.lbg >= 0 {
|
||||||
} else {
|
marker := (*offsets)[len(*offsets)-1]
|
||||||
// When color is disabled but ansi option is given,
|
marker.offset[0] = marker.offset[1]
|
||||||
// we simply strip out ANSI codes from the input
|
marker.color.bg = newState.lbg
|
||||||
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
|
marker.color.attr = marker.color.attr | tui.FullBg
|
||||||
trimmed, _, _ := extractColor(byteString(data), nil, nil)
|
newOffsets := append(*offsets, marker)
|
||||||
return util.ToChars(stringBytes(trimmed)), nil
|
offsets = &newOffsets
|
||||||
|
|
||||||
|
// Reset the full-line background color
|
||||||
|
lineAnsiState.lbg = -1
|
||||||
}
|
}
|
||||||
|
return util.ToChars(stringBytes(trimmed)), offsets
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,7 +117,7 @@ func Run(opts *Options) (int, error) {
|
|||||||
nthTransformer := opts.WithNth(opts.Delimiter)
|
nthTransformer := opts.WithNth(opts.Delimiter)
|
||||||
chunkList = NewChunkList(cache, func(item *Item, data []byte) bool {
|
chunkList = NewChunkList(cache, func(item *Item, data []byte) bool {
|
||||||
tokens := Tokenize(byteString(data), opts.Delimiter)
|
tokens := Tokenize(byteString(data), opts.Delimiter)
|
||||||
if opts.Ansi && opts.Theme.Colored && len(tokens) > 1 {
|
if opts.Ansi && len(tokens) > 1 {
|
||||||
var ansiState *ansiState
|
var ansiState *ansiState
|
||||||
if prevLineAnsiState != nil {
|
if prevLineAnsiState != nil {
|
||||||
ansiStateDup := *prevLineAnsiState
|
ansiStateDup := *prevLineAnsiState
|
||||||
@@ -128,13 +133,24 @@ func Run(opts *Options) (int, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
transformed := nthTransformer(tokens)
|
transformed := nthTransformer(tokens, itemIndex)
|
||||||
if len(header) < opts.HeaderLines {
|
if len(header) < opts.HeaderLines {
|
||||||
header = append(header, transformed)
|
header = append(header, transformed)
|
||||||
eventBox.Set(EvtHeader, header)
|
eventBox.Set(EvtHeader, header)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
item.text, item.colors = ansiProcessor(stringBytes(transformed))
|
item.text, item.colors = ansiProcessor(stringBytes(transformed))
|
||||||
|
|
||||||
|
// We should not trim trailing whitespaces with background colors
|
||||||
|
var maxColorOffset int32
|
||||||
|
if item.colors != nil {
|
||||||
|
for _, ansi := range *item.colors {
|
||||||
|
if ansi.color.bg >= 0 {
|
||||||
|
maxColorOffset = util.Max32(maxColorOffset, ansi.offset[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item.text.TrimTrailingWhitespaces(int(maxColorOffset))
|
||||||
item.text.Index = itemIndex
|
item.text.Index = itemIndex
|
||||||
item.origText = &data
|
item.origText = &data
|
||||||
itemIndex++
|
itemIndex++
|
||||||
@@ -294,6 +310,7 @@ func Run(opts *Options) (int, error) {
|
|||||||
// Event coordination
|
// Event coordination
|
||||||
reading := true
|
reading := true
|
||||||
ticks := 0
|
ticks := 0
|
||||||
|
startTick := 0
|
||||||
var nextCommand *commandSpec
|
var nextCommand *commandSpec
|
||||||
var nextEnviron []string
|
var nextEnviron []string
|
||||||
eventBox.Watch(EvtReadNew)
|
eventBox.Watch(EvtReadNew)
|
||||||
@@ -320,6 +337,7 @@ func Run(opts *Options) (int, error) {
|
|||||||
clearDenylist()
|
clearDenylist()
|
||||||
}
|
}
|
||||||
reading = true
|
reading = true
|
||||||
|
startTick = ticks
|
||||||
chunkList.Clear()
|
chunkList.Clear()
|
||||||
itemIndex = 0
|
itemIndex = 0
|
||||||
inputRevision.bumpMajor()
|
inputRevision.bumpMajor()
|
||||||
@@ -476,8 +494,17 @@ func Run(opts *Options) (int, error) {
|
|||||||
if len(opts.Expect) > 0 {
|
if len(opts.Expect) > 0 {
|
||||||
opts.Printer("")
|
opts.Printer("")
|
||||||
}
|
}
|
||||||
|
transformer := func(item *Item) string {
|
||||||
|
return item.AsString(opts.Ansi)
|
||||||
|
}
|
||||||
|
if opts.AcceptNth != nil {
|
||||||
|
fn := opts.AcceptNth(opts.Delimiter)
|
||||||
|
transformer = func(item *Item) string {
|
||||||
|
return item.acceptNth(opts.Ansi, opts.Delimiter, fn)
|
||||||
|
}
|
||||||
|
}
|
||||||
for i := 0; i < count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
opts.Printer(val.Get(i).item.AsString(opts.Ansi))
|
opts.Printer(transformer(val.Get(i).item))
|
||||||
}
|
}
|
||||||
if count == 0 {
|
if count == 0 {
|
||||||
exitCode = ExitNoMatch
|
exitCode = ExitNoMatch
|
||||||
@@ -499,7 +526,7 @@ func Run(opts *Options) (int, error) {
|
|||||||
}
|
}
|
||||||
if delay && reading {
|
if delay && reading {
|
||||||
dur := util.DurWithin(
|
dur := util.DurWithin(
|
||||||
time.Duration(ticks)*coordinatorDelayStep,
|
time.Duration(ticks-startTick)*coordinatorDelayStep,
|
||||||
0, coordinatorDelayMax)
|
0, coordinatorDelayMax)
|
||||||
time.Sleep(dur)
|
time.Sleep(dur)
|
||||||
}
|
}
|
||||||
|
@@ -51,3 +51,9 @@ func (item *Item) AsString(stripAnsi bool) string {
|
|||||||
}
|
}
|
||||||
return item.text.ToString()
|
return item.text.ToString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (item *Item) acceptNth(stripAnsi bool, delimiter Delimiter, transformer func([]Token, int32) string) string {
|
||||||
|
tokens := Tokenize(item.AsString(stripAnsi), delimiter)
|
||||||
|
transformed := transformer(tokens, item.Index())
|
||||||
|
return StripLastDelimiter(transformed, delimiter)
|
||||||
|
}
|
||||||
|
@@ -165,6 +165,7 @@ func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
minIndex := request.chunks[0].items[0].Index()
|
minIndex := request.chunks[0].items[0].Index()
|
||||||
|
maxIndex := request.chunks[numChunks-1].lastIndex(minIndex)
|
||||||
cancelled := util.NewAtomicBool(false)
|
cancelled := util.NewAtomicBool(false)
|
||||||
|
|
||||||
slices := m.sliceChunks(request.chunks)
|
slices := m.sliceChunks(request.chunks)
|
||||||
@@ -236,7 +237,7 @@ func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
|
|||||||
partialResult := <-resultChan
|
partialResult := <-resultChan
|
||||||
partialResults[partialResult.index] = partialResult.matches
|
partialResults[partialResult.index] = partialResult.matches
|
||||||
}
|
}
|
||||||
return NewMerger(pattern, partialResults, m.sort && request.pattern.sortable, m.tac, request.revision, minIndex), false
|
return NewMerger(pattern, partialResults, m.sort && request.pattern.sortable, m.tac, request.revision, minIndex, maxIndex), false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset is called to interrupt/signal the ongoing search
|
// Reset is called to interrupt/signal the ongoing search
|
||||||
|
@@ -4,7 +4,7 @@ import "fmt"
|
|||||||
|
|
||||||
// EmptyMerger is a Merger with no data
|
// EmptyMerger is a Merger with no data
|
||||||
func EmptyMerger(revision revision) *Merger {
|
func EmptyMerger(revision revision) *Merger {
|
||||||
return NewMerger(nil, [][]Result{}, false, false, revision, 0)
|
return NewMerger(nil, [][]Result{}, false, false, revision, 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merger holds a set of locally sorted lists of items and provides the view of
|
// Merger holds a set of locally sorted lists of items and provides the view of
|
||||||
@@ -22,14 +22,16 @@ type Merger struct {
|
|||||||
pass bool
|
pass bool
|
||||||
revision revision
|
revision revision
|
||||||
minIndex int32
|
minIndex int32
|
||||||
|
maxIndex int32
|
||||||
}
|
}
|
||||||
|
|
||||||
// PassMerger returns a new Merger that simply returns the items in the
|
// PassMerger returns a new Merger that simply returns the items in the
|
||||||
// original order
|
// original order
|
||||||
func PassMerger(chunks *[]*Chunk, tac bool, revision revision) *Merger {
|
func PassMerger(chunks *[]*Chunk, tac bool, revision revision) *Merger {
|
||||||
var minIndex int32
|
var minIndex, maxIndex int32
|
||||||
if len(*chunks) > 0 {
|
if len(*chunks) > 0 {
|
||||||
minIndex = (*chunks)[0].items[0].Index()
|
minIndex = (*chunks)[0].items[0].Index()
|
||||||
|
maxIndex = (*chunks)[len(*chunks)-1].lastIndex(minIndex)
|
||||||
}
|
}
|
||||||
mg := Merger{
|
mg := Merger{
|
||||||
pattern: nil,
|
pattern: nil,
|
||||||
@@ -38,7 +40,8 @@ func PassMerger(chunks *[]*Chunk, tac bool, revision revision) *Merger {
|
|||||||
count: 0,
|
count: 0,
|
||||||
pass: true,
|
pass: true,
|
||||||
revision: revision,
|
revision: revision,
|
||||||
minIndex: minIndex}
|
minIndex: minIndex,
|
||||||
|
maxIndex: maxIndex}
|
||||||
|
|
||||||
for _, chunk := range *mg.chunks {
|
for _, chunk := range *mg.chunks {
|
||||||
mg.count += chunk.count
|
mg.count += chunk.count
|
||||||
@@ -47,7 +50,7 @@ func PassMerger(chunks *[]*Chunk, tac bool, revision revision) *Merger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewMerger returns a new Merger
|
// NewMerger returns a new Merger
|
||||||
func NewMerger(pattern *Pattern, lists [][]Result, sorted bool, tac bool, revision revision, minIndex int32) *Merger {
|
func NewMerger(pattern *Pattern, lists [][]Result, sorted bool, tac bool, revision revision, minIndex int32, maxIndex int32) *Merger {
|
||||||
mg := Merger{
|
mg := Merger{
|
||||||
pattern: pattern,
|
pattern: pattern,
|
||||||
lists: lists,
|
lists: lists,
|
||||||
@@ -59,7 +62,8 @@ func NewMerger(pattern *Pattern, lists [][]Result, sorted bool, tac bool, revisi
|
|||||||
final: false,
|
final: false,
|
||||||
count: 0,
|
count: 0,
|
||||||
revision: revision,
|
revision: revision,
|
||||||
minIndex: minIndex}
|
minIndex: minIndex,
|
||||||
|
maxIndex: maxIndex}
|
||||||
|
|
||||||
for _, list := range mg.lists {
|
for _, list := range mg.lists {
|
||||||
mg.count += len(list)
|
mg.count += len(list)
|
||||||
|
@@ -58,7 +58,7 @@ func TestMergerUnsorted(t *testing.T) {
|
|||||||
cnt := len(items)
|
cnt := len(items)
|
||||||
|
|
||||||
// Not sorted: same order
|
// Not sorted: same order
|
||||||
mg := NewMerger(nil, lists, false, false, revision{}, 0)
|
mg := NewMerger(nil, lists, false, false, revision{}, 0, 0)
|
||||||
assert(t, cnt == mg.Length(), "Invalid Length")
|
assert(t, cnt == mg.Length(), "Invalid Length")
|
||||||
for i := 0; i < cnt; i++ {
|
for i := 0; i < cnt; i++ {
|
||||||
assert(t, items[i] == mg.Get(i), "Invalid Get")
|
assert(t, items[i] == mg.Get(i), "Invalid Get")
|
||||||
@@ -70,7 +70,7 @@ func TestMergerSorted(t *testing.T) {
|
|||||||
cnt := len(items)
|
cnt := len(items)
|
||||||
|
|
||||||
// Sorted sorted order
|
// Sorted sorted order
|
||||||
mg := NewMerger(nil, lists, true, false, revision{}, 0)
|
mg := NewMerger(nil, lists, true, false, revision{}, 0, 0)
|
||||||
assert(t, cnt == mg.Length(), "Invalid Length")
|
assert(t, cnt == mg.Length(), "Invalid Length")
|
||||||
sort.Sort(ByRelevance(items))
|
sort.Sort(ByRelevance(items))
|
||||||
for i := 0; i < cnt; i++ {
|
for i := 0; i < cnt; i++ {
|
||||||
@@ -80,7 +80,7 @@ func TestMergerSorted(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Inverse order
|
// Inverse order
|
||||||
mg2 := NewMerger(nil, lists, true, false, revision{}, 0)
|
mg2 := NewMerger(nil, lists, true, false, revision{}, 0, 0)
|
||||||
for i := cnt - 1; i >= 0; i-- {
|
for i := cnt - 1; i >= 0; i-- {
|
||||||
if items[i] != mg2.Get(i) {
|
if items[i] != mg2.Get(i) {
|
||||||
t.Error("Not sorted", items[i], mg2.Get(i))
|
t.Error("Not sorted", items[i], mg2.Get(i))
|
||||||
|
275
src/options.go
275
src/options.go
@@ -83,7 +83,7 @@ Usage: fzf [options]
|
|||||||
--padding=PADDING Padding inside border (TRBL | TB,RL | T,RL,B | T,R,B,L)
|
--padding=PADDING Padding inside border (TRBL | TB,RL | T,RL,B | T,R,B,L)
|
||||||
--border[=STYLE] Draw border around the finder
|
--border[=STYLE] Draw border around the finder
|
||||||
[rounded|sharp|bold|block|thinblock|double|horizontal|vertical|
|
[rounded|sharp|bold|block|thinblock|double|horizontal|vertical|
|
||||||
top|bottom|left|right|none] (default: rounded)
|
top|bottom|left|right|line|none] (default: rounded)
|
||||||
--border-label=LABEL Label to print on the border
|
--border-label=LABEL Label to print on the border
|
||||||
--border-label-pos=COL Position of the border label
|
--border-label-pos=COL Position of the border label
|
||||||
[POSITIVE_INTEGER: columns from left|
|
[POSITIVE_INTEGER: columns from left|
|
||||||
@@ -136,10 +136,11 @@ Usage: fzf [options]
|
|||||||
--separator=STR Draw horizontal separator on info line using the string
|
--separator=STR Draw horizontal separator on info line using the string
|
||||||
(default: '─' or '-')
|
(default: '─' or '-')
|
||||||
--no-separator Hide info line separator
|
--no-separator Hide info line separator
|
||||||
|
--ghost=TEXT Ghost text to display when the input is empty
|
||||||
--filepath-word Make word-wise movements respect path separators
|
--filepath-word Make word-wise movements respect path separators
|
||||||
--input-border[=STYLE] Draw border around the input section
|
--input-border[=STYLE] Draw border around the input section
|
||||||
[rounded|sharp|bold|block|thinblock|double|horizontal|vertical|
|
[rounded|sharp|bold|block|thinblock|double|horizontal|vertical|
|
||||||
top|bottom|left|right|none] (default: rounded)
|
top|bottom|left|right|line|none] (default: rounded)
|
||||||
--input-label=LABEL Label to print on the input border
|
--input-label=LABEL Label to print on the input border
|
||||||
--input-label-pos=COL Position of the input label
|
--input-label-pos=COL Position of the input label
|
||||||
[POSITIVE_INTEGER: columns from left|
|
[POSITIVE_INTEGER: columns from left|
|
||||||
@@ -167,7 +168,7 @@ Usage: fzf [options]
|
|||||||
--header-first Print header before the prompt line
|
--header-first Print header before the prompt line
|
||||||
--header-border[=STYLE] Draw border around the header section
|
--header-border[=STYLE] Draw border around the header section
|
||||||
[rounded|sharp|bold|block|thinblock|double|horizontal|vertical|
|
[rounded|sharp|bold|block|thinblock|double|horizontal|vertical|
|
||||||
top|bottom|left|right|none] (default: rounded)
|
top|bottom|left|right|line|none] (default: rounded)
|
||||||
--header-lines-border[=STYLE]
|
--header-lines-border[=STYLE]
|
||||||
Display header from --header-lines with a separate border.
|
Display header from --header-lines with a separate border.
|
||||||
Pass 'none' to still separate it but without a border.
|
Pass 'none' to still separate it but without a border.
|
||||||
@@ -177,6 +178,17 @@ Usage: fzf [options]
|
|||||||
NEGATIVE_INTEGER: columns from right][:bottom]
|
NEGATIVE_INTEGER: columns from right][:bottom]
|
||||||
(default: 0 or center)
|
(default: 0 or center)
|
||||||
|
|
||||||
|
FOOTER
|
||||||
|
--footer=STR String to print as footer
|
||||||
|
--footer-border[=STYLE] Draw border around the footer section
|
||||||
|
[rounded|sharp|bold|block|thinblock|double|horizontal|vertical|
|
||||||
|
top|bottom|left|right|line|none] (default: line)
|
||||||
|
--footer-label=LABEL Label to print on the footer border
|
||||||
|
--footer-label-pos=COL Position of the footer label
|
||||||
|
[POSITIVE_INTEGER: columns from left|
|
||||||
|
NEGATIVE_INTEGER: columns from right][:bottom]
|
||||||
|
(default: 0 or center)
|
||||||
|
|
||||||
SCRIPTING
|
SCRIPTING
|
||||||
-q, --query=STR Start the finder with the given query
|
-q, --query=STR Start the finder with the given query
|
||||||
-1, --select-1 Automatically select the only match
|
-1, --select-1 Automatically select the only match
|
||||||
@@ -544,8 +556,8 @@ type Options struct {
|
|||||||
Case Case
|
Case Case
|
||||||
Normalize bool
|
Normalize bool
|
||||||
Nth []Range
|
Nth []Range
|
||||||
WithNth func(Delimiter) func([]Token) string
|
WithNth func(Delimiter) func([]Token, int32) string
|
||||||
AcceptNth func(Delimiter) func([]Token) string
|
AcceptNth func(Delimiter) func([]Token, int32) string
|
||||||
Delimiter Delimiter
|
Delimiter Delimiter
|
||||||
Sort int
|
Sort int
|
||||||
Track trackOption
|
Track trackOption
|
||||||
@@ -574,6 +586,7 @@ type Options struct {
|
|||||||
InfoStyle infoStyle
|
InfoStyle infoStyle
|
||||||
InfoPrefix string
|
InfoPrefix string
|
||||||
InfoCommand string
|
InfoCommand string
|
||||||
|
Ghost string
|
||||||
Separator *string
|
Separator *string
|
||||||
JumpLabels string
|
JumpLabels string
|
||||||
Prompt string
|
Prompt string
|
||||||
@@ -597,6 +610,7 @@ type Options struct {
|
|||||||
Header []string
|
Header []string
|
||||||
HeaderLines int
|
HeaderLines int
|
||||||
HeaderFirst bool
|
HeaderFirst bool
|
||||||
|
Footer []string
|
||||||
Gap int
|
Gap int
|
||||||
GapLine *string
|
GapLine *string
|
||||||
Ellipsis *string
|
Ellipsis *string
|
||||||
@@ -608,8 +622,10 @@ type Options struct {
|
|||||||
InputBorderShape tui.BorderShape
|
InputBorderShape tui.BorderShape
|
||||||
HeaderBorderShape tui.BorderShape
|
HeaderBorderShape tui.BorderShape
|
||||||
HeaderLinesShape tui.BorderShape
|
HeaderLinesShape tui.BorderShape
|
||||||
|
FooterBorderShape tui.BorderShape
|
||||||
InputLabel labelOpts
|
InputLabel labelOpts
|
||||||
HeaderLabel labelOpts
|
HeaderLabel labelOpts
|
||||||
|
FooterLabel labelOpts
|
||||||
BorderLabel labelOpts
|
BorderLabel labelOpts
|
||||||
ListLabel labelOpts
|
ListLabel labelOpts
|
||||||
PreviewLabel labelOpts
|
PreviewLabel labelOpts
|
||||||
@@ -629,6 +645,7 @@ type Options struct {
|
|||||||
MEMProfile string
|
MEMProfile string
|
||||||
BlockProfile string
|
BlockProfile string
|
||||||
MutexProfile string
|
MutexProfile string
|
||||||
|
TtyDefault string
|
||||||
}
|
}
|
||||||
|
|
||||||
func filterNonEmpty(input []string) []string {
|
func filterNonEmpty(input []string) []string {
|
||||||
@@ -689,6 +706,7 @@ func defaultOptions() *Options {
|
|||||||
ScrollOff: 3,
|
ScrollOff: 3,
|
||||||
FileWord: false,
|
FileWord: false,
|
||||||
InfoStyle: infoDefault,
|
InfoStyle: infoDefault,
|
||||||
|
Ghost: "",
|
||||||
Separator: nil,
|
Separator: nil,
|
||||||
JumpLabels: defaultJumpLabels,
|
JumpLabels: defaultJumpLabels,
|
||||||
Prompt: "> ",
|
Prompt: "> ",
|
||||||
@@ -712,6 +730,7 @@ func defaultOptions() *Options {
|
|||||||
Header: make([]string, 0),
|
Header: make([]string, 0),
|
||||||
HeaderLines: 0,
|
HeaderLines: 0,
|
||||||
HeaderFirst: false,
|
HeaderFirst: false,
|
||||||
|
Footer: make([]string, 0),
|
||||||
Gap: 0,
|
Gap: 0,
|
||||||
Ellipsis: nil,
|
Ellipsis: nil,
|
||||||
Scrollbar: nil,
|
Scrollbar: nil,
|
||||||
@@ -727,6 +746,7 @@ func defaultOptions() *Options {
|
|||||||
WalkerOpts: walkerOpts{file: true, hidden: true, follow: true},
|
WalkerOpts: walkerOpts{file: true, hidden: true, follow: true},
|
||||||
WalkerRoot: []string{"."},
|
WalkerRoot: []string{"."},
|
||||||
WalkerSkip: []string{".git", "node_modules"},
|
WalkerSkip: []string{".git", "node_modules"},
|
||||||
|
TtyDefault: tui.DefaultTtyDevice,
|
||||||
Help: false,
|
Help: false,
|
||||||
Version: false}
|
Version: false}
|
||||||
}
|
}
|
||||||
@@ -769,30 +789,31 @@ func splitNth(str string) ([]Range, error) {
|
|||||||
return ranges, nil
|
return ranges, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func nthTransformer(str string) (func(Delimiter) func([]Token) string, error) {
|
func nthTransformer(str string) (func(Delimiter) func([]Token, int32) string, error) {
|
||||||
// ^[0-9,-.]+$"
|
// ^[0-9,-.]+$"
|
||||||
if match, _ := regexp.MatchString("^[0-9,-.]+$", str); match {
|
if match, _ := regexp.MatchString("^[0-9,-.]+$", str); match {
|
||||||
nth, err := splitNth(str)
|
nth, err := splitNth(str)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return func(Delimiter) func([]Token) string {
|
return func(Delimiter) func([]Token, int32) string {
|
||||||
return func(tokens []Token) string {
|
return func(tokens []Token, index int32) string {
|
||||||
return JoinTokens(Transform(tokens, nth))
|
return JoinTokens(Transform(tokens, nth))
|
||||||
}
|
}
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// {...} {...} ...
|
// {...} {...} ...
|
||||||
placeholder := regexp.MustCompile("{[0-9,-.]+}")
|
placeholder := regexp.MustCompile("{[0-9,-.]+}|{n}")
|
||||||
indexes := placeholder.FindAllStringIndex(str, -1)
|
indexes := placeholder.FindAllStringIndex(str, -1)
|
||||||
if indexes == nil {
|
if indexes == nil {
|
||||||
return nil, errors.New("template should include at least 1 placeholder: " + str)
|
return nil, errors.New("template should include at least 1 placeholder: " + str)
|
||||||
}
|
}
|
||||||
|
|
||||||
type NthParts struct {
|
type NthParts struct {
|
||||||
str string
|
str string
|
||||||
nth []Range
|
index bool
|
||||||
|
nth []Range
|
||||||
}
|
}
|
||||||
|
|
||||||
parts := make([]NthParts, len(indexes))
|
parts := make([]NthParts, len(indexes))
|
||||||
@@ -801,7 +822,10 @@ func nthTransformer(str string) (func(Delimiter) func([]Token) string, error) {
|
|||||||
if idx < index[0] {
|
if idx < index[0] {
|
||||||
parts = append(parts, NthParts{str: str[idx:index[0]]})
|
parts = append(parts, NthParts{str: str[idx:index[0]]})
|
||||||
}
|
}
|
||||||
if nth, err := splitNth(str[index[0]+1 : index[1]-1]); err == nil {
|
expr := str[index[0]+1 : index[1]-1]
|
||||||
|
if expr == "n" {
|
||||||
|
parts = append(parts, NthParts{index: true})
|
||||||
|
} else if nth, err := splitNth(expr); err == nil {
|
||||||
parts = append(parts, NthParts{nth: nth})
|
parts = append(parts, NthParts{nth: nth})
|
||||||
}
|
}
|
||||||
idx = index[1]
|
idx = index[1]
|
||||||
@@ -810,12 +834,16 @@ func nthTransformer(str string) (func(Delimiter) func([]Token) string, error) {
|
|||||||
parts = append(parts, NthParts{str: str[idx:]})
|
parts = append(parts, NthParts{str: str[idx:]})
|
||||||
}
|
}
|
||||||
|
|
||||||
return func(delimiter Delimiter) func([]Token) string {
|
return func(delimiter Delimiter) func([]Token, int32) string {
|
||||||
return func(tokens []Token) string {
|
return func(tokens []Token, index int32) string {
|
||||||
str := ""
|
str := ""
|
||||||
for _, holder := range parts {
|
for _, holder := range parts {
|
||||||
if holder.nth != nil {
|
if holder.nth != nil {
|
||||||
str += StripLastDelimiter(JoinTokens(Transform(tokens, holder.nth)), delimiter)
|
str += StripLastDelimiter(JoinTokens(Transform(tokens, holder.nth)), delimiter)
|
||||||
|
} else if holder.index {
|
||||||
|
if index >= 0 {
|
||||||
|
str += strconv.Itoa(int(index))
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
str += holder.str
|
str += holder.str
|
||||||
}
|
}
|
||||||
@@ -867,12 +895,9 @@ func parseAlgo(str string) (algo.Algo, error) {
|
|||||||
return nil, errors.New("invalid algorithm (expected: v1 or v2)")
|
return nil, errors.New("invalid algorithm (expected: v1 or v2)")
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseBorder(str string, optional bool, allowLine bool) (tui.BorderShape, error) {
|
func parseBorder(str string, optional bool) (tui.BorderShape, error) {
|
||||||
switch str {
|
switch str {
|
||||||
case "line":
|
case "line":
|
||||||
if !allowLine {
|
|
||||||
return tui.BorderNone, errors.New("'line' is only allowed for preview border")
|
|
||||||
}
|
|
||||||
return tui.BorderLine, nil
|
return tui.BorderLine, nil
|
||||||
case "rounded":
|
case "rounded":
|
||||||
return tui.BorderRounded, nil
|
return tui.BorderRounded, nil
|
||||||
@@ -907,15 +932,12 @@ func parseBorder(str string, optional bool, allowLine bool) (tui.BorderShape, er
|
|||||||
return tui.BorderNone, errors.New("invalid border style (expected: rounded|sharp|bold|block|thinblock|double|horizontal|vertical|top|bottom|left|right|none)")
|
return tui.BorderNone, errors.New("invalid border style (expected: rounded|sharp|bold|block|thinblock|double|horizontal|vertical|top|bottom|left|right|none)")
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseKeyChords(str string, message string) (map[tui.Event]string, error) {
|
func parseKeyChords(str string, message string) (map[tui.Event]string, []tui.Event, error) {
|
||||||
return parseKeyChordsImpl(str, message)
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseKeyChordsImpl(str string, message string) (map[tui.Event]string, error) {
|
|
||||||
if len(str) == 0 {
|
if len(str) == 0 {
|
||||||
return nil, errors.New(message)
|
return nil, nil, errors.New(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
list := []tui.Event{}
|
||||||
str = regexp.MustCompile("(?i)(alt-),").ReplaceAllString(str, "$1"+string([]rune{escapedComma}))
|
str = regexp.MustCompile("(?i)(alt-),").ReplaceAllString(str, "$1"+string([]rune{escapedComma}))
|
||||||
tokens := strings.Split(str, ",")
|
tokens := strings.Split(str, ",")
|
||||||
if str == "," || strings.HasPrefix(str, ",,") || strings.HasSuffix(str, ",,") || strings.Contains(str, ",,,") {
|
if str == "," || strings.HasPrefix(str, ",,") || strings.HasSuffix(str, ",,") || strings.Contains(str, ",,,") {
|
||||||
@@ -931,6 +953,7 @@ func parseKeyChordsImpl(str string, message string) (map[tui.Event]string, error
|
|||||||
lkey := strings.ToLower(key)
|
lkey := strings.ToLower(key)
|
||||||
add := func(e tui.EventType) {
|
add := func(e tui.EventType) {
|
||||||
chords[e.AsEvent()] = key
|
chords[e.AsEvent()] = key
|
||||||
|
list = append(list, e.AsEvent())
|
||||||
}
|
}
|
||||||
switch lkey {
|
switch lkey {
|
||||||
case "up":
|
case "up":
|
||||||
@@ -944,7 +967,9 @@ func parseKeyChordsImpl(str string, message string) (map[tui.Event]string, error
|
|||||||
case "enter", "return":
|
case "enter", "return":
|
||||||
add(tui.Enter)
|
add(tui.Enter)
|
||||||
case "space":
|
case "space":
|
||||||
chords[tui.Key(' ')] = key
|
evt := tui.Key(' ')
|
||||||
|
chords[evt] = key
|
||||||
|
list = append(list, evt)
|
||||||
case "backspace", "bspace", "bs":
|
case "backspace", "bspace", "bs":
|
||||||
add(tui.Backspace)
|
add(tui.Backspace)
|
||||||
case "ctrl-space":
|
case "ctrl-space":
|
||||||
@@ -983,10 +1008,18 @@ func parseKeyChordsImpl(str string, message string) (map[tui.Event]string, error
|
|||||||
add(tui.JumpCancel)
|
add(tui.JumpCancel)
|
||||||
case "click-header":
|
case "click-header":
|
||||||
add(tui.ClickHeader)
|
add(tui.ClickHeader)
|
||||||
|
case "click-footer":
|
||||||
|
add(tui.ClickFooter)
|
||||||
|
case "multi":
|
||||||
|
add(tui.Multi)
|
||||||
case "alt-enter", "alt-return":
|
case "alt-enter", "alt-return":
|
||||||
chords[tui.CtrlAltKey('m')] = key
|
evt := tui.CtrlAltKey('m')
|
||||||
|
chords[evt] = key
|
||||||
|
list = append(list, evt)
|
||||||
case "alt-space":
|
case "alt-space":
|
||||||
chords[tui.AltKey(' ')] = key
|
evt := tui.AltKey(' ')
|
||||||
|
chords[evt] = key
|
||||||
|
list = append(list, evt)
|
||||||
case "alt-bs", "alt-bspace", "alt-backspace":
|
case "alt-bs", "alt-bspace", "alt-backspace":
|
||||||
add(tui.AltBackspace)
|
add(tui.AltBackspace)
|
||||||
case "alt-up":
|
case "alt-up":
|
||||||
@@ -1064,7 +1097,9 @@ func parseKeyChordsImpl(str string, message string) (map[tui.Event]string, error
|
|||||||
default:
|
default:
|
||||||
runes := []rune(key)
|
runes := []rune(key)
|
||||||
if len(key) == 10 && strings.HasPrefix(lkey, "ctrl-alt-") && isAlphabet(lkey[9]) {
|
if len(key) == 10 && strings.HasPrefix(lkey, "ctrl-alt-") && isAlphabet(lkey[9]) {
|
||||||
chords[tui.CtrlAltKey(rune(key[9]))] = key
|
evt := tui.CtrlAltKey(rune(key[9]))
|
||||||
|
chords[evt] = key
|
||||||
|
list = append(list, evt)
|
||||||
} else if len(key) == 6 && strings.HasPrefix(lkey, "ctrl-") && isAlphabet(lkey[5]) {
|
} else if len(key) == 6 && strings.HasPrefix(lkey, "ctrl-") && isAlphabet(lkey[5]) {
|
||||||
add(tui.EventType(tui.CtrlA.Int() + int(lkey[5]) - 'a'))
|
add(tui.EventType(tui.CtrlA.Int() + int(lkey[5]) - 'a'))
|
||||||
} else if len(runes) == 5 && strings.HasPrefix(lkey, "alt-") {
|
} else if len(runes) == 5 && strings.HasPrefix(lkey, "alt-") {
|
||||||
@@ -1077,17 +1112,21 @@ func parseKeyChordsImpl(str string, message string) (map[tui.Event]string, error
|
|||||||
case escapedPlus:
|
case escapedPlus:
|
||||||
r = '+'
|
r = '+'
|
||||||
}
|
}
|
||||||
chords[tui.AltKey(r)] = key
|
evt := tui.AltKey(r)
|
||||||
|
chords[evt] = key
|
||||||
|
list = append(list, evt)
|
||||||
} else if len(key) == 2 && strings.HasPrefix(lkey, "f") && key[1] >= '1' && key[1] <= '9' {
|
} else if len(key) == 2 && strings.HasPrefix(lkey, "f") && key[1] >= '1' && key[1] <= '9' {
|
||||||
add(tui.EventType(tui.F1.Int() + int(key[1]) - '1'))
|
add(tui.EventType(tui.F1.Int() + int(key[1]) - '1'))
|
||||||
} else if len(runes) == 1 {
|
} else if len(runes) == 1 {
|
||||||
chords[tui.Key(runes[0])] = key
|
evt := tui.Key(runes[0])
|
||||||
|
chords[evt] = key
|
||||||
|
list = append(list, evt)
|
||||||
} else {
|
} else {
|
||||||
return nil, errors.New("unsupported key: " + key)
|
return nil, list, errors.New("unsupported key: " + key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return chords, nil
|
return chords, list, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseScheme(str string) (string, []criterion, error) {
|
func parseScheme(str string) (string, []criterion, error) {
|
||||||
@@ -1171,7 +1210,12 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) (*tui.ColorTheme, erro
|
|||||||
var err error
|
var err error
|
||||||
theme := dupeTheme(defaultTheme)
|
theme := dupeTheme(defaultTheme)
|
||||||
rrggbb := regexp.MustCompile("^#[0-9a-fA-F]{6}$")
|
rrggbb := regexp.MustCompile("^#[0-9a-fA-F]{6}$")
|
||||||
for _, str := range strings.Split(strings.ToLower(str), ",") {
|
comma := regexp.MustCompile(`[\s,]+`)
|
||||||
|
for _, str := range comma.Split(strings.ToLower(str), -1) {
|
||||||
|
str = strings.TrimSpace(str)
|
||||||
|
if len(str) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
switch str {
|
switch str {
|
||||||
case "dark":
|
case "dark":
|
||||||
theme = dupeTheme(tui.Dark256)
|
theme = dupeTheme(tui.Dark256)
|
||||||
@@ -1264,6 +1308,8 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) (*tui.ColorTheme, erro
|
|||||||
switch components[0] {
|
switch components[0] {
|
||||||
case "query", "input", "input-fg":
|
case "query", "input", "input-fg":
|
||||||
mergeAttr(&theme.Input)
|
mergeAttr(&theme.Input)
|
||||||
|
case "ghost":
|
||||||
|
mergeAttr(&theme.Ghost)
|
||||||
case "disabled":
|
case "disabled":
|
||||||
mergeAttr(&theme.Disabled)
|
mergeAttr(&theme.Disabled)
|
||||||
case "fg":
|
case "fg":
|
||||||
@@ -1282,6 +1328,8 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) (*tui.ColorTheme, erro
|
|||||||
mergeAttr(&theme.Current)
|
mergeAttr(&theme.Current)
|
||||||
case "current-bg", "bg+":
|
case "current-bg", "bg+":
|
||||||
mergeAttr(&theme.DarkBg)
|
mergeAttr(&theme.DarkBg)
|
||||||
|
case "alt-bg":
|
||||||
|
mergeAttr(&theme.AltBg)
|
||||||
case "selected-fg":
|
case "selected-fg":
|
||||||
mergeAttr(&theme.SelectedFg)
|
mergeAttr(&theme.SelectedFg)
|
||||||
case "selected-bg":
|
case "selected-bg":
|
||||||
@@ -1326,6 +1374,10 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) (*tui.ColorTheme, erro
|
|||||||
mergeAttr(&theme.HeaderBorder)
|
mergeAttr(&theme.HeaderBorder)
|
||||||
case "header-label":
|
case "header-label":
|
||||||
mergeAttr(&theme.HeaderLabel)
|
mergeAttr(&theme.HeaderLabel)
|
||||||
|
case "footer-border":
|
||||||
|
mergeAttr(&theme.FooterBorder)
|
||||||
|
case "footer-label":
|
||||||
|
mergeAttr(&theme.FooterLabel)
|
||||||
case "spinner":
|
case "spinner":
|
||||||
mergeAttr(&theme.Spinner)
|
mergeAttr(&theme.Spinner)
|
||||||
case "info":
|
case "info":
|
||||||
@@ -1338,6 +1390,10 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) (*tui.ColorTheme, erro
|
|||||||
mergeAttr(&theme.Header)
|
mergeAttr(&theme.Header)
|
||||||
case "header-bg":
|
case "header-bg":
|
||||||
mergeAttr(&theme.HeaderBg)
|
mergeAttr(&theme.HeaderBg)
|
||||||
|
case "footer", "footer-fg":
|
||||||
|
mergeAttr(&theme.Footer)
|
||||||
|
case "footer-bg":
|
||||||
|
mergeAttr(&theme.FooterBg)
|
||||||
case "gap-line":
|
case "gap-line":
|
||||||
mergeAttr(&theme.GapLine)
|
mergeAttr(&theme.GapLine)
|
||||||
default:
|
default:
|
||||||
@@ -1393,7 +1449,7 @@ const (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
executeRegexp = regexp.MustCompile(
|
executeRegexp = regexp.MustCompile(
|
||||||
`(?si)[:+](become|execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|transform)-(?:query|prompt|(?:border|list|preview|input|header)-label|header|search|nth)|transform|change-(?:preview-window|preview|multi)|(?:re|un|toggle-)bind|pos|put|print|search)`)
|
`(?si)[:+](become|execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|bg-transform|transform)-(?:query|prompt|(?:border|list|preview|input|header|footer)-label|header|footer|search|nth|pointer|ghost)|bg-transform|transform|change-(?:preview-window|preview|multi)|(?:re|un|toggle-)bind|pos|put|print|search|trigger)`)
|
||||||
splitRegexp = regexp.MustCompile("[,:]+")
|
splitRegexp = regexp.MustCompile("[,:]+")
|
||||||
actionNameRegexp = regexp.MustCompile("(?i)^[a-z-]+")
|
actionNameRegexp = regexp.MustCompile("(?i)^[a-z-]+")
|
||||||
}
|
}
|
||||||
@@ -1519,7 +1575,7 @@ func parseActionList(masked string, original string, prevActions []*action, putA
|
|||||||
appendAction(actCancel)
|
appendAction(actCancel)
|
||||||
case "clear-query":
|
case "clear-query":
|
||||||
appendAction(actClearQuery)
|
appendAction(actClearQuery)
|
||||||
case "clear-selection":
|
case "clear-multi", "clear-selection":
|
||||||
appendAction(actClearSelection)
|
appendAction(actClearSelection)
|
||||||
case "forward-char":
|
case "forward-char":
|
||||||
appendAction(actForwardChar)
|
appendAction(actForwardChar)
|
||||||
@@ -1661,6 +1717,8 @@ func parseActionList(masked string, original string, prevActions []*action, putA
|
|||||||
appendAction(actExclude)
|
appendAction(actExclude)
|
||||||
case "exclude-multi":
|
case "exclude-multi":
|
||||||
appendAction(actExcludeMulti)
|
appendAction(actExcludeMulti)
|
||||||
|
case "bg-cancel":
|
||||||
|
appendAction(actBgCancel)
|
||||||
default:
|
default:
|
||||||
t := isExecuteAction(specLower)
|
t := isExecuteAction(specLower)
|
||||||
if t == actIgnore {
|
if t == actIgnore {
|
||||||
@@ -1688,7 +1746,7 @@ func parseActionList(masked string, original string, prevActions []*action, putA
|
|||||||
}
|
}
|
||||||
switch t {
|
switch t {
|
||||||
case actUnbind, actRebind, actToggleBind:
|
case actUnbind, actRebind, actToggleBind:
|
||||||
if _, err := parseKeyChordsImpl(actionArg, spec[0:offset]+" target required"); err != nil {
|
if _, _, err := parseKeyChords(actionArg, spec[0:offset]+" target required"); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
case actChangePreviewWindow:
|
case actChangePreviewWindow:
|
||||||
@@ -1733,7 +1791,7 @@ func parseKeymap(keymap map[tui.Event][]*action, str string) error {
|
|||||||
} else if len(keyName) == 1 && keyName[0] == escapedPlus {
|
} else if len(keyName) == 1 && keyName[0] == escapedPlus {
|
||||||
key = tui.Key('+')
|
key = tui.Key('+')
|
||||||
} else {
|
} else {
|
||||||
keys, err := parseKeyChordsImpl(keyName, "key name required")
|
keys, _, err := parseKeyChords(keyName, "key name required")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1778,6 +1836,8 @@ func isExecuteAction(str string) actionType {
|
|||||||
return actPreview
|
return actPreview
|
||||||
case "change-header":
|
case "change-header":
|
||||||
return actChangeHeader
|
return actChangeHeader
|
||||||
|
case "change-footer":
|
||||||
|
return actChangeFooter
|
||||||
case "change-list-label":
|
case "change-list-label":
|
||||||
return actChangeListLabel
|
return actChangeListLabel
|
||||||
case "change-border-label":
|
case "change-border-label":
|
||||||
@@ -1788,6 +1848,12 @@ func isExecuteAction(str string) actionType {
|
|||||||
return actChangeInputLabel
|
return actChangeInputLabel
|
||||||
case "change-header-label":
|
case "change-header-label":
|
||||||
return actChangeHeaderLabel
|
return actChangeHeaderLabel
|
||||||
|
case "change-footer-label":
|
||||||
|
return actChangeFooterLabel
|
||||||
|
case "change-ghost":
|
||||||
|
return actChangeGhost
|
||||||
|
case "change-pointer":
|
||||||
|
return actChangePointer
|
||||||
case "change-preview-window":
|
case "change-preview-window":
|
||||||
return actChangePreviewWindow
|
return actChangePreviewWindow
|
||||||
case "change-preview":
|
case "change-preview":
|
||||||
@@ -1824,16 +1890,56 @@ func isExecuteAction(str string) actionType {
|
|||||||
return actTransformInputLabel
|
return actTransformInputLabel
|
||||||
case "transform-header-label":
|
case "transform-header-label":
|
||||||
return actTransformHeaderLabel
|
return actTransformHeaderLabel
|
||||||
|
case "transform-footer-label":
|
||||||
|
return actTransformFooterLabel
|
||||||
|
case "transform-footer":
|
||||||
|
return actTransformFooter
|
||||||
case "transform-header":
|
case "transform-header":
|
||||||
return actTransformHeader
|
return actTransformHeader
|
||||||
|
case "transform-ghost":
|
||||||
|
return actTransformGhost
|
||||||
case "transform-nth":
|
case "transform-nth":
|
||||||
return actTransformNth
|
return actTransformNth
|
||||||
|
case "transform-pointer":
|
||||||
|
return actTransformPointer
|
||||||
case "transform-prompt":
|
case "transform-prompt":
|
||||||
return actTransformPrompt
|
return actTransformPrompt
|
||||||
case "transform-query":
|
case "transform-query":
|
||||||
return actTransformQuery
|
return actTransformQuery
|
||||||
case "transform-search":
|
case "transform-search":
|
||||||
return actTransformSearch
|
return actTransformSearch
|
||||||
|
case "bg-transform":
|
||||||
|
return actBgTransform
|
||||||
|
case "bg-transform-list-label":
|
||||||
|
return actBgTransformListLabel
|
||||||
|
case "bg-transform-border-label":
|
||||||
|
return actBgTransformBorderLabel
|
||||||
|
case "bg-transform-preview-label":
|
||||||
|
return actBgTransformPreviewLabel
|
||||||
|
case "bg-transform-input-label":
|
||||||
|
return actBgTransformInputLabel
|
||||||
|
case "bg-transform-header-label":
|
||||||
|
return actBgTransformHeaderLabel
|
||||||
|
case "bg-transform-footer-label":
|
||||||
|
return actBgTransformFooterLabel
|
||||||
|
case "bg-transform-footer":
|
||||||
|
return actBgTransformFooter
|
||||||
|
case "bg-transform-header":
|
||||||
|
return actBgTransformHeader
|
||||||
|
case "bg-transform-ghost":
|
||||||
|
return actBgTransformGhost
|
||||||
|
case "bg-transform-nth":
|
||||||
|
return actBgTransformNth
|
||||||
|
case "bg-transform-pointer":
|
||||||
|
return actBgTransformPointer
|
||||||
|
case "bg-transform-prompt":
|
||||||
|
return actBgTransformPrompt
|
||||||
|
case "bg-transform-query":
|
||||||
|
return actBgTransformQuery
|
||||||
|
case "bg-transform-search":
|
||||||
|
return actBgTransformSearch
|
||||||
|
case "trigger":
|
||||||
|
return actTrigger
|
||||||
case "search":
|
case "search":
|
||||||
return actSearch
|
return actSearch
|
||||||
}
|
}
|
||||||
@@ -1841,7 +1947,7 @@ func isExecuteAction(str string) actionType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func parseToggleSort(keymap map[tui.Event][]*action, str string) error {
|
func parseToggleSort(keymap map[tui.Event][]*action, str string) error {
|
||||||
keys, err := parseKeyChords(str, "key name required")
|
keys, _, err := parseKeyChords(str, "key name required")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -2317,6 +2423,12 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
|
|||||||
}
|
}
|
||||||
case "--no-tmux":
|
case "--no-tmux":
|
||||||
opts.Tmux = nil
|
opts.Tmux = nil
|
||||||
|
case "--tty-default":
|
||||||
|
if opts.TtyDefault, err = nextString("tty device name required"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case "--no-tty-default":
|
||||||
|
opts.TtyDefault = ""
|
||||||
case "--force-tty-in":
|
case "--force-tty-in":
|
||||||
// NOTE: We need this because `system('fzf --tmux < /dev/tty')` doesn't
|
// NOTE: We need this because `system('fzf --tmux < /dev/tty')` doesn't
|
||||||
// work on Neovim. Same as '-' option of fzf-tmux.
|
// work on Neovim. Same as '-' option of fzf-tmux.
|
||||||
@@ -2374,7 +2486,7 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
chords, err := parseKeyChords(str, "key names required")
|
chords, _, err := parseKeyChords(str, "key names required")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -2589,6 +2701,10 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
|
|||||||
case "--no-separator":
|
case "--no-separator":
|
||||||
nosep := ""
|
nosep := ""
|
||||||
opts.Separator = &nosep
|
opts.Separator = &nosep
|
||||||
|
case "--ghost":
|
||||||
|
if opts.Ghost, err = nextString("ghost text required"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
case "--scrollbar":
|
case "--scrollbar":
|
||||||
given, bar := optionalNextString()
|
given, bar := optionalNextString()
|
||||||
if given {
|
if given {
|
||||||
@@ -2689,6 +2805,14 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
|
|||||||
if opts.HeaderLines, err = nextInt("number of header lines required"); err != nil {
|
if opts.HeaderLines, err = nextInt("number of header lines required"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
case "--no-footer":
|
||||||
|
opts.Footer = []string{}
|
||||||
|
case "--footer":
|
||||||
|
str, err := nextString("footer string required")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
opts.Footer = strLines(str)
|
||||||
case "--header-first":
|
case "--header-first":
|
||||||
opts.HeaderFirst = true
|
opts.HeaderFirst = true
|
||||||
case "--no-header-first":
|
case "--no-header-first":
|
||||||
@@ -2733,7 +2857,7 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
|
|||||||
opts.Preview.border = tui.BorderNone
|
opts.Preview.border = tui.BorderNone
|
||||||
case "--preview-border":
|
case "--preview-border":
|
||||||
hasArg, arg := optionalNextString()
|
hasArg, arg := optionalNextString()
|
||||||
if opts.Preview.border, err = parseBorder(arg, !hasArg, true); err != nil {
|
if opts.Preview.border, err = parseBorder(arg, !hasArg); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case "--height":
|
case "--height":
|
||||||
@@ -2772,14 +2896,23 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
|
|||||||
opts.BorderShape = tui.BorderNone
|
opts.BorderShape = tui.BorderNone
|
||||||
case "--border":
|
case "--border":
|
||||||
hasArg, arg := optionalNextString()
|
hasArg, arg := optionalNextString()
|
||||||
if opts.BorderShape, err = parseBorder(arg, !hasArg, false); err != nil {
|
if opts.BorderShape, err = parseBorder(arg, !hasArg); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case "--list-border":
|
case "--list-border":
|
||||||
hasArg, arg := optionalNextString()
|
hasArg, arg := optionalNextString()
|
||||||
if opts.ListBorderShape, err = parseBorder(arg, !hasArg, false); err != nil {
|
if opts.ListBorderShape, err = parseBorder(arg, !hasArg); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if opts.ListBorderShape == tui.BorderLine {
|
||||||
|
if hasArg {
|
||||||
|
// '--list-border line' is not allowed
|
||||||
|
return errors.New("list border cannot be 'line'")
|
||||||
|
}
|
||||||
|
// This is when '--style full:line' is previously specified and
|
||||||
|
// '--list-border' is specified without an argument.
|
||||||
|
opts.ListBorderShape = tui.BorderRounded
|
||||||
|
}
|
||||||
case "--no-list-border":
|
case "--no-list-border":
|
||||||
opts.ListBorderShape = tui.BorderNone
|
opts.ListBorderShape = tui.BorderNone
|
||||||
case "--no-list-label":
|
case "--no-list-label":
|
||||||
@@ -2801,14 +2934,14 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
|
|||||||
opts.HeaderBorderShape = tui.BorderNone
|
opts.HeaderBorderShape = tui.BorderNone
|
||||||
case "--header-border":
|
case "--header-border":
|
||||||
hasArg, arg := optionalNextString()
|
hasArg, arg := optionalNextString()
|
||||||
if opts.HeaderBorderShape, err = parseBorder(arg, !hasArg, false); err != nil {
|
if opts.HeaderBorderShape, err = parseBorder(arg, !hasArg); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case "--no-header-lines-border":
|
case "--no-header-lines-border":
|
||||||
opts.HeaderLinesShape = tui.BorderNone
|
opts.HeaderLinesShape = tui.BorderUndefined
|
||||||
case "--header-lines-border":
|
case "--header-lines-border":
|
||||||
hasArg, arg := optionalNextString()
|
hasArg, arg := optionalNextString()
|
||||||
if opts.HeaderLinesShape, err = parseBorder(arg, !hasArg, false); err != nil {
|
if opts.HeaderLinesShape, err = parseBorder(arg, !hasArg); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case "--no-header-label":
|
case "--no-header-label":
|
||||||
@@ -2825,11 +2958,32 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
|
|||||||
if err := parseLabelPosition(&opts.HeaderLabel, pos); err != nil {
|
if err := parseLabelPosition(&opts.HeaderLabel, pos); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
case "--no-footer-border":
|
||||||
|
opts.FooterBorderShape = tui.BorderNone
|
||||||
|
case "--footer-border":
|
||||||
|
hasArg, arg := optionalNextString()
|
||||||
|
if opts.FooterBorderShape, err = parseBorder(arg, !hasArg); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case "--no-footer-label":
|
||||||
|
opts.FooterLabel.label = ""
|
||||||
|
case "--footer-label":
|
||||||
|
if opts.FooterLabel.label, err = nextString("footer label required"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case "--footer-label-pos":
|
||||||
|
pos, err := nextString("footer label position required (positive or negative integer or 'center')")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := parseLabelPosition(&opts.FooterLabel, pos); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
case "--no-input-border":
|
case "--no-input-border":
|
||||||
opts.InputBorderShape = tui.BorderNone
|
opts.InputBorderShape = tui.BorderNone
|
||||||
case "--input-border":
|
case "--input-border":
|
||||||
hasArg, arg := optionalNextString()
|
hasArg, arg := optionalNextString()
|
||||||
if opts.InputBorderShape, err = parseBorder(arg, !hasArg, false); err != nil {
|
if opts.InputBorderShape, err = parseBorder(arg, !hasArg); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case "--no-input-label":
|
case "--no-input-label":
|
||||||
@@ -3037,6 +3191,7 @@ func applyPreset(opts *Options, preset string) error {
|
|||||||
opts.ListBorderShape = tui.BorderUndefined
|
opts.ListBorderShape = tui.BorderUndefined
|
||||||
opts.InputBorderShape = tui.BorderUndefined
|
opts.InputBorderShape = tui.BorderUndefined
|
||||||
opts.HeaderBorderShape = tui.BorderUndefined
|
opts.HeaderBorderShape = tui.BorderUndefined
|
||||||
|
opts.FooterBorderShape = tui.BorderUndefined
|
||||||
opts.Preview.border = defaultBorderShape
|
opts.Preview.border = defaultBorderShape
|
||||||
opts.Preview.info = true
|
opts.Preview.info = true
|
||||||
opts.InfoStyle = infoDefault
|
opts.InfoStyle = infoDefault
|
||||||
@@ -3048,6 +3203,7 @@ func applyPreset(opts *Options, preset string) error {
|
|||||||
opts.ListBorderShape = tui.BorderUndefined
|
opts.ListBorderShape = tui.BorderUndefined
|
||||||
opts.InputBorderShape = tui.BorderUndefined
|
opts.InputBorderShape = tui.BorderUndefined
|
||||||
opts.HeaderBorderShape = tui.BorderUndefined
|
opts.HeaderBorderShape = tui.BorderUndefined
|
||||||
|
opts.FooterBorderShape = tui.BorderLine
|
||||||
opts.Preview.border = tui.BorderLine
|
opts.Preview.border = tui.BorderLine
|
||||||
opts.Preview.info = false
|
opts.Preview.info = false
|
||||||
opts.InfoStyle = infoDefault
|
opts.InfoStyle = infoDefault
|
||||||
@@ -3063,16 +3219,22 @@ func applyPreset(opts *Options, preset string) error {
|
|||||||
}
|
}
|
||||||
if len(tokens) == 2 && len(tokens[1]) > 0 {
|
if len(tokens) == 2 && len(tokens[1]) > 0 {
|
||||||
var err error
|
var err error
|
||||||
defaultBorderShape, err = parseBorder(tokens[1], false, false)
|
defaultBorderShape, err = parseBorder(tokens[1], false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
opts.ListBorderShape = defaultBorderShape
|
if defaultBorderShape != tui.BorderLine {
|
||||||
|
opts.ListBorderShape = defaultBorderShape
|
||||||
|
}
|
||||||
opts.InputBorderShape = defaultBorderShape
|
opts.InputBorderShape = defaultBorderShape
|
||||||
opts.HeaderBorderShape = defaultBorderShape
|
opts.HeaderBorderShape = defaultBorderShape
|
||||||
|
opts.FooterBorderShape = defaultBorderShape
|
||||||
opts.Preview.border = defaultBorderShape
|
opts.Preview.border = defaultBorderShape
|
||||||
|
if defaultBorderShape == tui.BorderLine {
|
||||||
|
opts.BorderShape = defaultBorderShape
|
||||||
|
}
|
||||||
opts.Preview.info = true
|
opts.Preview.info = true
|
||||||
opts.InfoStyle = infoInlineRight
|
opts.InfoStyle = infoInlineRight
|
||||||
opts.Theme.Gutter = tui.NewColorAttr()
|
opts.Theme.Gutter = tui.NewColorAttr()
|
||||||
@@ -3145,6 +3307,10 @@ func noSeparatorLine(style infoStyle, separator bool) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (opts *Options) useTmux() bool {
|
||||||
|
return opts.Tmux != nil && len(os.Getenv("TMUX")) > 0 && opts.Tmux.index >= opts.Height.index
|
||||||
|
}
|
||||||
|
|
||||||
func (opts *Options) noSeparatorLine() bool {
|
func (opts *Options) noSeparatorLine() bool {
|
||||||
if opts.Inputless {
|
if opts.Inputless {
|
||||||
return true
|
return true
|
||||||
@@ -3176,17 +3342,12 @@ func postProcessOptions(opts *Options) error {
|
|||||||
opts.HeaderBorderShape = tui.BorderNone
|
opts.HeaderBorderShape = tui.BorderNone
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if opts.FooterBorderShape == tui.BorderUndefined {
|
||||||
|
opts.FooterBorderShape = tui.BorderLine
|
||||||
|
}
|
||||||
|
|
||||||
if opts.HeaderLinesShape == tui.BorderNone {
|
if opts.HeaderLinesShape == tui.BorderNone {
|
||||||
opts.HeaderLinesShape = tui.BorderPhantom
|
opts.HeaderLinesShape = tui.BorderPhantom
|
||||||
} else if opts.HeaderLinesShape == tui.BorderUndefined {
|
|
||||||
// In reverse-list layout, header lines should be at the top, while
|
|
||||||
// ordinary header should be at the bottom. So let's use a separate
|
|
||||||
// window for the header lines.
|
|
||||||
if opts.Layout == layoutReverseList {
|
|
||||||
opts.HeaderLinesShape = tui.BorderPhantom
|
|
||||||
} else {
|
|
||||||
opts.HeaderLinesShape = tui.BorderNone
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.Pointer == nil {
|
if opts.Pointer == nil {
|
||||||
|
@@ -142,7 +142,7 @@ func TestIrrelevantNth(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestParseKeys(t *testing.T) {
|
func TestParseKeys(t *testing.T) {
|
||||||
pairs, _ := parseKeyChords("ctrl-z,alt-z,f2,@,Alt-a,!,ctrl-G,J,g,ctrl-alt-a,ALT-enter,alt-SPACE", "")
|
pairs, _, _ := parseKeyChords("ctrl-z,alt-z,f2,@,Alt-a,!,ctrl-G,J,g,ctrl-alt-a,ALT-enter,alt-SPACE", "")
|
||||||
checkEvent := func(e tui.Event, s string) {
|
checkEvent := func(e tui.Event, s string) {
|
||||||
if pairs[e] != s {
|
if pairs[e] != s {
|
||||||
t.Errorf("%s != %s", pairs[e], s)
|
t.Errorf("%s != %s", pairs[e], s)
|
||||||
@@ -168,7 +168,7 @@ func TestParseKeys(t *testing.T) {
|
|||||||
checkEvent(tui.AltKey(' '), "alt-SPACE")
|
checkEvent(tui.AltKey(' '), "alt-SPACE")
|
||||||
|
|
||||||
// Synonyms
|
// Synonyms
|
||||||
pairs, _ = parseKeyChords("enter,Return,space,tab,btab,esc,up,down,left,right", "")
|
pairs, _, _ = parseKeyChords("enter,Return,space,tab,btab,esc,up,down,left,right", "")
|
||||||
if len(pairs) != 9 {
|
if len(pairs) != 9 {
|
||||||
t.Error(9)
|
t.Error(9)
|
||||||
}
|
}
|
||||||
@@ -182,7 +182,7 @@ func TestParseKeys(t *testing.T) {
|
|||||||
check(tui.Left, "left")
|
check(tui.Left, "left")
|
||||||
check(tui.Right, "right")
|
check(tui.Right, "right")
|
||||||
|
|
||||||
pairs, _ = parseKeyChords("Tab,Ctrl-I,PgUp,page-up,pgdn,Page-Down,Home,End,Alt-BS,Alt-BSpace,shift-left,shift-right,btab,shift-tab,return,Enter,bspace", "")
|
pairs, _, _ = parseKeyChords("Tab,Ctrl-I,PgUp,page-up,pgdn,Page-Down,Home,End,Alt-BS,Alt-BSpace,shift-left,shift-right,btab,shift-tab,return,Enter,bspace", "")
|
||||||
if len(pairs) != 11 {
|
if len(pairs) != 11 {
|
||||||
t.Error(11)
|
t.Error(11)
|
||||||
}
|
}
|
||||||
@@ -211,40 +211,40 @@ func TestParseKeysWithComma(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pairs, _ := parseKeyChords(",", "")
|
pairs, _, _ := parseKeyChords(",", "")
|
||||||
checkN(len(pairs), 1)
|
checkN(len(pairs), 1)
|
||||||
check(pairs, tui.Key(','), ",")
|
check(pairs, tui.Key(','), ",")
|
||||||
|
|
||||||
pairs, _ = parseKeyChords(",,a,b", "")
|
pairs, _, _ = parseKeyChords(",,a,b", "")
|
||||||
checkN(len(pairs), 3)
|
checkN(len(pairs), 3)
|
||||||
check(pairs, tui.Key('a'), "a")
|
check(pairs, tui.Key('a'), "a")
|
||||||
check(pairs, tui.Key('b'), "b")
|
check(pairs, tui.Key('b'), "b")
|
||||||
check(pairs, tui.Key(','), ",")
|
check(pairs, tui.Key(','), ",")
|
||||||
|
|
||||||
pairs, _ = parseKeyChords("a,b,,", "")
|
pairs, _, _ = parseKeyChords("a,b,,", "")
|
||||||
checkN(len(pairs), 3)
|
checkN(len(pairs), 3)
|
||||||
check(pairs, tui.Key('a'), "a")
|
check(pairs, tui.Key('a'), "a")
|
||||||
check(pairs, tui.Key('b'), "b")
|
check(pairs, tui.Key('b'), "b")
|
||||||
check(pairs, tui.Key(','), ",")
|
check(pairs, tui.Key(','), ",")
|
||||||
|
|
||||||
pairs, _ = parseKeyChords("a,,,b", "")
|
pairs, _, _ = parseKeyChords("a,,,b", "")
|
||||||
checkN(len(pairs), 3)
|
checkN(len(pairs), 3)
|
||||||
check(pairs, tui.Key('a'), "a")
|
check(pairs, tui.Key('a'), "a")
|
||||||
check(pairs, tui.Key('b'), "b")
|
check(pairs, tui.Key('b'), "b")
|
||||||
check(pairs, tui.Key(','), ",")
|
check(pairs, tui.Key(','), ",")
|
||||||
|
|
||||||
pairs, _ = parseKeyChords("a,,,b,c", "")
|
pairs, _, _ = parseKeyChords("a,,,b,c", "")
|
||||||
checkN(len(pairs), 4)
|
checkN(len(pairs), 4)
|
||||||
check(pairs, tui.Key('a'), "a")
|
check(pairs, tui.Key('a'), "a")
|
||||||
check(pairs, tui.Key('b'), "b")
|
check(pairs, tui.Key('b'), "b")
|
||||||
check(pairs, tui.Key('c'), "c")
|
check(pairs, tui.Key('c'), "c")
|
||||||
check(pairs, tui.Key(','), ",")
|
check(pairs, tui.Key(','), ",")
|
||||||
|
|
||||||
pairs, _ = parseKeyChords(",,,", "")
|
pairs, _, _ = parseKeyChords(",,,", "")
|
||||||
checkN(len(pairs), 1)
|
checkN(len(pairs), 1)
|
||||||
check(pairs, tui.Key(','), ",")
|
check(pairs, tui.Key(','), ",")
|
||||||
|
|
||||||
pairs, _ = parseKeyChords(",ALT-,,", "")
|
pairs, _, _ = parseKeyChords(",ALT-,,", "")
|
||||||
checkN(len(pairs), 1)
|
checkN(len(pairs), 1)
|
||||||
check(pairs, tui.AltKey(','), "ALT-,")
|
check(pairs, tui.AltKey(','), "ALT-,")
|
||||||
}
|
}
|
||||||
@@ -333,7 +333,7 @@ func TestColorSpec(t *testing.T) {
|
|||||||
t.Errorf("colors should now be equivalent: %v, %v", tui.Dark256, customized)
|
t.Errorf("colors should now be equivalent: %v, %v", tui.Dark256, customized)
|
||||||
}
|
}
|
||||||
|
|
||||||
customized, _ = parseTheme(theme, "fg:231,dark,bg:232")
|
customized, _ = parseTheme(theme, "fg:231,dark bg:232")
|
||||||
if customized.Fg != tui.Dark256.Fg || customized.Bg == tui.Dark256.Bg {
|
if customized.Fg != tui.Dark256.Fg || customized.Bg == tui.Dark256.Bg {
|
||||||
t.Errorf("color not customized")
|
t.Errorf("color not customized")
|
||||||
}
|
}
|
||||||
|
15
src/proxy.go
15
src/proxy.go
@@ -59,12 +59,12 @@ func runProxy(commandPrefix string, cmdBuilder func(temp string, needBash bool)
|
|||||||
})
|
})
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var command string
|
var command, input string
|
||||||
commandPrefix += ` --no-force-tty-in --proxy-script "$0"`
|
commandPrefix += ` --no-force-tty-in --proxy-script "$0"`
|
||||||
if opts.Input == nil && (opts.ForceTtyIn || util.IsTty(os.Stdin)) {
|
if opts.Input == nil && (opts.ForceTtyIn || util.IsTty(os.Stdin)) {
|
||||||
command = fmt.Sprintf(`%s > %q`, commandPrefix, output)
|
command = fmt.Sprintf(`%s > %q`, commandPrefix, output)
|
||||||
} else {
|
} else {
|
||||||
input, err := fifo("proxy-input")
|
input, err = fifo("proxy-input")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ExitError, err
|
return ExitError, err
|
||||||
}
|
}
|
||||||
@@ -90,9 +90,9 @@ func runProxy(commandPrefix string, cmdBuilder func(temp string, needBash bool)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// To ensure that the options are processed by a POSIX-compliant shell,
|
// * Write the command to a temporary file and run it with sh to ensure POSIX compliance.
|
||||||
// we need to write the command to a temporary file and execute it with sh.
|
// * Nullify FZF_DEFAULT_* variables as tmux popup may inject them even when undefined.
|
||||||
var exports []string
|
exports := []string{"FZF_DEFAULT_COMMAND=", "FZF_DEFAULT_OPTS=", "FZF_DEFAULT_OPTS_FILE="}
|
||||||
needBash := false
|
needBash := false
|
||||||
if withExports {
|
if withExports {
|
||||||
validIdentifier := regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*$`)
|
validIdentifier := regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*$`)
|
||||||
@@ -144,10 +144,13 @@ func runProxy(commandPrefix string, cmdBuilder func(temp string, needBash bool)
|
|||||||
env = elems[1:]
|
env = elems[1:]
|
||||||
}
|
}
|
||||||
executor := util.NewExecutor(opts.WithShell)
|
executor := util.NewExecutor(opts.WithShell)
|
||||||
ttyin, err := tui.TtyIn()
|
ttyin, err := tui.TtyIn(opts.TtyDefault)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ExitError, err
|
return ExitError, err
|
||||||
}
|
}
|
||||||
|
os.Remove(temp)
|
||||||
|
os.Remove(input)
|
||||||
|
os.Remove(output)
|
||||||
executor.Become(ttyin, env, command)
|
executor.Become(ttyin, env, command)
|
||||||
}
|
}
|
||||||
return code, err
|
return code, err
|
||||||
|
@@ -277,6 +277,9 @@ func (r *Reader) readFiles(roots []string, opts walkerOpts, ignores []string) bo
|
|||||||
ignoresFull := []string{}
|
ignoresFull := []string{}
|
||||||
ignoresSuffix := []string{}
|
ignoresSuffix := []string{}
|
||||||
sep := string(os.PathSeparator)
|
sep := string(os.PathSeparator)
|
||||||
|
if _, ok := os.LookupEnv("MSYSTEM"); ok {
|
||||||
|
sep = "/"
|
||||||
|
}
|
||||||
for _, ignore := range ignores {
|
for _, ignore := range ignores {
|
||||||
if strings.ContainsRune(ignore, os.PathSeparator) {
|
if strings.ContainsRune(ignore, os.PathSeparator) {
|
||||||
if strings.HasPrefix(ignore, sep) {
|
if strings.HasPrefix(ignore, sep) {
|
||||||
@@ -320,6 +323,9 @@ func (r *Reader) readFiles(roots []string, opts walkerOpts, ignores []string) bo
|
|||||||
return filepath.SkipDir
|
return filepath.SkipDir
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if path != sep {
|
||||||
|
path += sep
|
||||||
|
}
|
||||||
}
|
}
|
||||||
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))
|
||||||
|
@@ -19,6 +19,10 @@ type colorOffset struct {
|
|||||||
url *url
|
url *url
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (co colorOffset) IsFullBgMarker(at int32) bool {
|
||||||
|
return at == co.offset[0] && at == co.offset[1] && co.color.Attr()&tui.FullBg > 0
|
||||||
|
}
|
||||||
|
|
||||||
type Result struct {
|
type Result struct {
|
||||||
item *Item
|
item *Item
|
||||||
points [4]uint16
|
points [4]uint16
|
||||||
@@ -119,7 +123,7 @@ 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, nthOffsets []Offset, theme *tui.ColorTheme, colBase tui.ColorPair, colMatch tui.ColorPair, attrNth tui.Attr, current bool) []colorOffset {
|
func (result *Result) colorOffsets(matchOffsets []Offset, nthOffsets []Offset, theme *tui.ColorTheme, colBase tui.ColorPair, colMatch tui.ColorPair, attrNth tui.Attr) []colorOffset {
|
||||||
itemColors := result.item.Colors()
|
itemColors := result.item.Colors()
|
||||||
|
|
||||||
// No ANSI codes
|
// No ANSI codes
|
||||||
@@ -149,12 +153,20 @@ func (result *Result) colorOffsets(matchOffsets []Offset, nthOffsets []Offset, t
|
|||||||
color bool
|
color bool
|
||||||
match bool
|
match bool
|
||||||
nth bool
|
nth bool
|
||||||
|
fbg tui.Color
|
||||||
}
|
}
|
||||||
|
|
||||||
cols := make([]cellInfo, maxCol)
|
cols := make([]cellInfo, maxCol+1)
|
||||||
|
for idx := range cols {
|
||||||
|
cols[idx].fbg = -1
|
||||||
|
}
|
||||||
for colorIndex, ansi := range itemColors {
|
for colorIndex, ansi := range itemColors {
|
||||||
for i := ansi.offset[0]; i < ansi.offset[1]; i++ {
|
if ansi.offset[0] == ansi.offset[1] && ansi.color.attr&tui.FullBg > 0 {
|
||||||
cols[i] = cellInfo{colorIndex, true, false, false}
|
cols[ansi.offset[0]].fbg = ansi.color.lbg
|
||||||
|
} else {
|
||||||
|
for i := ansi.offset[0]; i < ansi.offset[1]; i++ {
|
||||||
|
cols[i] = cellInfo{colorIndex, true, false, false, cols[i].fbg}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,29 +188,31 @@ func (result *Result) colorOffsets(matchOffsets []Offset, nthOffsets []Offset, t
|
|||||||
// ------------ ---- -- ----
|
// ------------ ---- -- ----
|
||||||
// ++++++++ ++++++++++
|
// ++++++++ ++++++++++
|
||||||
// --++++++++-- --++++++++++---
|
// --++++++++-- --++++++++++---
|
||||||
var curr cellInfo = cellInfo{0, false, false, false}
|
curr := cellInfo{0, false, false, false, -1}
|
||||||
start := 0
|
start := 0
|
||||||
ansiToColorPair := func(ansi ansiOffset, base tui.ColorPair) tui.ColorPair {
|
ansiToColorPair := func(ansi ansiOffset, base tui.ColorPair) tui.ColorPair {
|
||||||
|
if !theme.Colored {
|
||||||
|
return tui.NewColorPair(-1, -1, ansi.color.attr).MergeAttr(base)
|
||||||
|
}
|
||||||
fg := ansi.color.fg
|
fg := ansi.color.fg
|
||||||
bg := ansi.color.bg
|
bg := ansi.color.bg
|
||||||
if fg == -1 {
|
if fg == -1 {
|
||||||
if current {
|
fg = colBase.Fg()
|
||||||
fg = theme.Current.Color
|
|
||||||
} else {
|
|
||||||
fg = theme.Fg.Color
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if bg == -1 {
|
if bg == -1 {
|
||||||
if current {
|
bg = colBase.Bg()
|
||||||
bg = theme.DarkBg.Color
|
|
||||||
} else {
|
|
||||||
bg = theme.Bg.Color
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return tui.NewColorPair(fg, bg, ansi.color.attr).MergeAttr(base)
|
return tui.NewColorPair(fg, bg, ansi.color.attr).MergeAttr(base)
|
||||||
}
|
}
|
||||||
var colors []colorOffset
|
var colors []colorOffset
|
||||||
add := func(idx int) {
|
add := func(idx int) {
|
||||||
|
if curr.fbg >= 0 {
|
||||||
|
colors = append(colors, colorOffset{
|
||||||
|
offset: [2]int32{int32(start), int32(start)},
|
||||||
|
color: tui.NewColorPair(-1, curr.fbg, tui.FullBg),
|
||||||
|
match: false,
|
||||||
|
url: nil})
|
||||||
|
}
|
||||||
if (curr.color || curr.nth || curr.match) && idx > start {
|
if (curr.color || curr.nth || curr.match) && idx > start {
|
||||||
if curr.match {
|
if curr.match {
|
||||||
var color tui.ColorPair
|
var color tui.ColorPair
|
||||||
@@ -208,7 +222,7 @@ func (result *Result) colorOffsets(matchOffsets []Offset, nthOffsets []Offset, t
|
|||||||
color = colBase.Merge(colMatch)
|
color = colBase.Merge(colMatch)
|
||||||
}
|
}
|
||||||
var url *url
|
var url *url
|
||||||
if curr.color && theme.Colored {
|
if curr.color {
|
||||||
ansi := itemColors[curr.index]
|
ansi := itemColors[curr.index]
|
||||||
url = ansi.color.url
|
url = ansi.color.url
|
||||||
origColor := ansiToColorPair(ansi, colMatch)
|
origColor := ansiToColorPair(ansi, colMatch)
|
||||||
@@ -223,7 +237,7 @@ func (result *Result) colorOffsets(matchOffsets []Offset, nthOffsets []Offset, t
|
|||||||
if color.Fg().IsDefault() && origColor.HasBg() {
|
if color.Fg().IsDefault() && origColor.HasBg() {
|
||||||
color = origColor
|
color = origColor
|
||||||
if curr.nth {
|
if curr.nth {
|
||||||
color = color.WithAttr(attrNth)
|
color = color.WithAttr(attrNth &^ tui.AttrRegular)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
color = origColor.MergeNonDefault(color)
|
color = origColor.MergeNonDefault(color)
|
||||||
@@ -233,10 +247,11 @@ func (result *Result) colorOffsets(matchOffsets []Offset, nthOffsets []Offset, t
|
|||||||
offset: [2]int32{int32(start), int32(idx)}, color: color, match: true, url: url})
|
offset: [2]int32{int32(start), int32(idx)}, color: color, match: true, url: url})
|
||||||
} else if curr.color {
|
} else if curr.color {
|
||||||
ansi := itemColors[curr.index]
|
ansi := itemColors[curr.index]
|
||||||
color := ansiToColorPair(ansi, colBase)
|
base := colBase
|
||||||
if curr.nth {
|
if curr.nth {
|
||||||
color = color.WithAttr(attrNth)
|
base = base.WithAttr(attrNth)
|
||||||
}
|
}
|
||||||
|
color := ansiToColorPair(ansi, base)
|
||||||
colors = append(colors, colorOffset{
|
colors = append(colors, colorOffset{
|
||||||
offset: [2]int32{int32(start), int32(idx)},
|
offset: [2]int32{int32(start), int32(idx)},
|
||||||
color: color,
|
color: color,
|
||||||
|
@@ -131,7 +131,7 @@ func TestColorOffset(t *testing.T) {
|
|||||||
|
|
||||||
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, nil, tui.Dark256, colBase, colMatch, tui.AttrUndefined, true)
|
colors := item.colorOffsets(offsets, nil, tui.Dark256, colBase, colMatch, tui.AttrUndefined)
|
||||||
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 {
|
||||||
@@ -158,7 +158,7 @@ func TestColorOffset(t *testing.T) {
|
|||||||
|
|
||||||
nthOffsets := []Offset{{37, 39}, {42, 45}}
|
nthOffsets := []Offset{{37, 39}, {42, 45}}
|
||||||
for _, attr := range []tui.Attr{tui.AttrRegular, tui.StrikeThrough} {
|
for _, attr := range []tui.Attr{tui.AttrRegular, tui.StrikeThrough} {
|
||||||
colors = item.colorOffsets(offsets, nthOffsets, tui.Dark256, colRegular, colUnderline, attr, true)
|
colors = item.colorOffsets(offsets, nthOffsets, tui.Dark256, colRegular, colUnderline, attr)
|
||||||
|
|
||||||
// [{[0 5] {1 5 0}} {[5 15] {1 5 8}} {[15 20] {1 5 0}}
|
// [{[0 5] {1 5 0}} {[5 15] {1 5 8}} {[15 20] {1 5 0}}
|
||||||
// {[22 25] {2 6 1}} {[25 27] {2 6 9}} {[27 30] {-1 -1 8}}
|
// {[22 25] {2 6 1}} {[25 27] {2 6 9}} {[27 30] {-1 -1 8}}
|
||||||
@@ -176,7 +176,7 @@ func TestColorOffset(t *testing.T) {
|
|||||||
assert(9, 35, 37, tui.NewColorPair(4, 8, tui.Bold))
|
assert(9, 35, 37, tui.NewColorPair(4, 8, tui.Bold))
|
||||||
expected := tui.Bold | attr
|
expected := tui.Bold | attr
|
||||||
if attr == tui.AttrRegular {
|
if attr == tui.AttrRegular {
|
||||||
expected = tui.AttrRegular
|
expected = tui.Bold
|
||||||
}
|
}
|
||||||
assert(10, 37, 39, tui.NewColorPair(4, 8, expected))
|
assert(10, 37, 39, tui.NewColorPair(4, 8, expected))
|
||||||
assert(11, 39, 40, tui.NewColorPair(4, 8, tui.Bold))
|
assert(11, 39, 40, tui.NewColorPair(4, 8, tui.Bold))
|
||||||
|
1405
src/terminal.go
1405
src/terminal.go
File diff suppressed because it is too large
Load Diff
@@ -12,7 +12,7 @@ import (
|
|||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func replacePlaceholderTest(template string, stripAnsi bool, delimiter Delimiter, printsep string, forcePlus bool, query string, allItems []*Item) string {
|
func replacePlaceholderTest(template string, stripAnsi bool, delimiter Delimiter, printsep string, forcePlus bool, query string, allItems [3][]*Item) string {
|
||||||
replaced, _ := replacePlaceholder(replacePlaceholderParams{
|
replaced, _ := replacePlaceholder(replacePlaceholderParams{
|
||||||
template: template,
|
template: template,
|
||||||
stripAnsi: stripAnsi,
|
stripAnsi: stripAnsi,
|
||||||
@@ -30,11 +30,11 @@ func replacePlaceholderTest(template string, stripAnsi bool, delimiter Delimiter
|
|||||||
|
|
||||||
func TestReplacePlaceholder(t *testing.T) {
|
func TestReplacePlaceholder(t *testing.T) {
|
||||||
item1 := newItem(" foo'bar \x1b[31mbaz\x1b[m")
|
item1 := newItem(" foo'bar \x1b[31mbaz\x1b[m")
|
||||||
items1 := []*Item{item1, item1}
|
items1 := [3][]*Item{{item1}, {item1}, nil}
|
||||||
items2 := []*Item{
|
items2 := [3][]*Item{
|
||||||
newItem("foo'bar \x1b[31mbaz\x1b[m"),
|
{newItem("foo'bar \x1b[31mbaz\x1b[m")},
|
||||||
newItem("foo'bar \x1b[31mbaz\x1b[m"),
|
{newItem("foo'bar \x1b[31mbaz\x1b[m"),
|
||||||
newItem("FOO'BAR \x1b[31mBAZ\x1b[m")}
|
newItem("FOO'BAR \x1b[31mBAZ\x1b[m")}, nil}
|
||||||
|
|
||||||
delim := "'"
|
delim := "'"
|
||||||
var regex *regexp.Regexp
|
var regex *regexp.Regexp
|
||||||
@@ -75,6 +75,14 @@ func TestReplacePlaceholder(t *testing.T) {
|
|||||||
result = replacePlaceholderTest("echo {}", true, Delimiter{}, printsep, false, "query", items1)
|
result = replacePlaceholderTest("echo {}", true, Delimiter{}, printsep, false, "query", items1)
|
||||||
checkFormat("echo {{.O}} foo{{.I}}bar baz{{.O}}")
|
checkFormat("echo {{.O}} foo{{.I}}bar baz{{.O}}")
|
||||||
|
|
||||||
|
// {r}, strip ansi
|
||||||
|
result = replacePlaceholderTest("echo {r}", true, Delimiter{}, printsep, false, "query", items1)
|
||||||
|
checkFormat("echo foo'bar baz")
|
||||||
|
|
||||||
|
// {r..}, strip ansi
|
||||||
|
result = replacePlaceholderTest("echo {r..}", true, Delimiter{}, printsep, false, "query", items1)
|
||||||
|
checkFormat("echo foo'bar baz")
|
||||||
|
|
||||||
// {}, with multiple items
|
// {}, with multiple items
|
||||||
result = replacePlaceholderTest("echo {}", true, Delimiter{}, printsep, false, "query", items2)
|
result = replacePlaceholderTest("echo {}", true, Delimiter{}, printsep, false, "query", items2)
|
||||||
checkFormat("echo {{.O}}foo{{.I}}bar baz{{.O}}")
|
checkFormat("echo {{.O}}foo{{.I}}bar baz{{.O}}")
|
||||||
@@ -137,11 +145,11 @@ func TestReplacePlaceholder(t *testing.T) {
|
|||||||
checkFormat("echo {{.O}} {{.O}}")
|
checkFormat("echo {{.O}} {{.O}}")
|
||||||
|
|
||||||
// No match
|
// No match
|
||||||
result = replacePlaceholderTest("echo {}/{+}", true, Delimiter{}, printsep, false, "query", []*Item{nil, nil})
|
result = replacePlaceholderTest("echo {}/{+}", true, Delimiter{}, printsep, false, "query", [3][]*Item{nil, nil, nil})
|
||||||
check("echo /")
|
check("echo /")
|
||||||
|
|
||||||
// No match, but with selections
|
// No match, but with selections
|
||||||
result = replacePlaceholderTest("echo {}/{+}", true, Delimiter{}, printsep, false, "query", []*Item{nil, item1})
|
result = replacePlaceholderTest("echo {}/{+}", true, Delimiter{}, printsep, false, "query", [3][]*Item{nil, {item1}, nil})
|
||||||
checkFormat("echo /{{.O}} foo{{.I}}bar baz{{.O}}")
|
checkFormat("echo /{{.O}} foo{{.I}}bar baz{{.O}}")
|
||||||
|
|
||||||
// String delimiter
|
// String delimiter
|
||||||
@@ -158,17 +166,18 @@ func TestReplacePlaceholder(t *testing.T) {
|
|||||||
Test single placeholders, but focus on the placeholders' parameters (e.g. flags).
|
Test single placeholders, but focus on the placeholders' parameters (e.g. flags).
|
||||||
see: TestParsePlaceholder
|
see: TestParsePlaceholder
|
||||||
*/
|
*/
|
||||||
items3 := []*Item{
|
items3 := [3][]*Item{
|
||||||
// single line
|
// single line
|
||||||
newItem("1a 1b 1c 1d 1e 1f"),
|
{newItem("1a 1b 1c 1d 1e 1f")},
|
||||||
// multi line
|
// multi line
|
||||||
newItem("1a 1b 1c 1d 1e 1f"),
|
{newItem("1a 1b 1c 1d 1e 1f"),
|
||||||
newItem("2a 2b 2c 2d 2e 2f"),
|
newItem("2a 2b 2c 2d 2e 2f"),
|
||||||
newItem("3a 3b 3c 3d 3e 3f"),
|
newItem("3a 3b 3c 3d 3e 3f"),
|
||||||
newItem("4a 4b 4c 4d 4e 4f"),
|
newItem("4a 4b 4c 4d 4e 4f"),
|
||||||
newItem("5a 5b 5c 5d 5e 5f"),
|
newItem("5a 5b 5c 5d 5e 5f"),
|
||||||
newItem("6a 6b 6c 6d 6e 6f"),
|
newItem("6a 6b 6c 6d 6e 6f"),
|
||||||
newItem("7a 7b 7c 7d 7e 7f"),
|
newItem("7a 7b 7c 7d 7e 7f")},
|
||||||
|
nil,
|
||||||
}
|
}
|
||||||
stripAnsi := false
|
stripAnsi := false
|
||||||
forcePlus := false
|
forcePlus := false
|
||||||
@@ -549,14 +558,14 @@ func newItem(str string) *Item {
|
|||||||
return &Item{origText: &bytes, text: util.ToChars([]byte(trimmed))}
|
return &Item{origText: &bytes, text: util.ToChars([]byte(trimmed))}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Functions tested in this file require array of items (allItems). The array needs
|
// Functions tested in this file require array of items (allItems).
|
||||||
// to consist of at least two nils. This is helper function.
|
// This is helper function.
|
||||||
func newItems(str ...string) []*Item {
|
func newItems(str ...string) [3][]*Item {
|
||||||
result := make([]*Item, util.Max(len(str), 2))
|
result := make([]*Item, len(str))
|
||||||
for i, s := range str {
|
for i, s := range str {
|
||||||
result[i] = newItem(s)
|
result[i] = newItem(s)
|
||||||
}
|
}
|
||||||
return result
|
return [3][]*Item{result, nil, nil}
|
||||||
}
|
}
|
||||||
|
|
||||||
// (for logging purposes)
|
// (for logging purposes)
|
||||||
@@ -565,7 +574,7 @@ func (item *Item) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to parse, execute and convert "text/template" to string. Panics on error.
|
// Helper function to parse, execute and convert "text/template" to string. Panics on error.
|
||||||
func templateToString(format string, data interface{}) string {
|
func templateToString(format string, data any) string {
|
||||||
bb := &bytes.Buffer{}
|
bb := &bytes.Buffer{}
|
||||||
|
|
||||||
err := template.Must(template.New("").Parse(format)).Execute(bb, data)
|
err := template.Must(template.New("").Parse(format)).Execute(bb, data)
|
||||||
@@ -580,7 +589,7 @@ func templateToString(format string, data interface{}) string {
|
|||||||
type give struct {
|
type give struct {
|
||||||
template string
|
template string
|
||||||
query string
|
query string
|
||||||
allItems []*Item
|
allItems [3][]*Item
|
||||||
}
|
}
|
||||||
type want struct {
|
type want struct {
|
||||||
/*
|
/*
|
||||||
@@ -618,25 +627,25 @@ func testCommands(t *testing.T, tests []testCase) {
|
|||||||
// evaluate the test cases
|
// evaluate the test cases
|
||||||
for idx, test := range tests {
|
for idx, test := range tests {
|
||||||
gotOutput := replacePlaceholderTest(
|
gotOutput := replacePlaceholderTest(
|
||||||
test.give.template, stripAnsi, delimiter, printsep, forcePlus,
|
test.template, stripAnsi, delimiter, printsep, forcePlus,
|
||||||
test.give.query,
|
test.query,
|
||||||
test.give.allItems)
|
test.allItems)
|
||||||
switch {
|
switch {
|
||||||
case test.want.output != "":
|
case test.output != "":
|
||||||
if gotOutput != test.want.output {
|
if gotOutput != test.output {
|
||||||
t.Errorf("tests[%v]:\ngave{\n\ttemplate: '%s',\n\tquery: '%s',\n\tallItems: %s}\nand got '%s',\nbut want '%s'",
|
t.Errorf("tests[%v]:\ngave{\n\ttemplate: '%s',\n\tquery: '%s',\n\tallItems: %s}\nand got '%s',\nbut want '%s'",
|
||||||
idx,
|
idx,
|
||||||
test.give.template, test.give.query, test.give.allItems,
|
test.template, test.query, test.allItems,
|
||||||
gotOutput, test.want.output)
|
gotOutput, test.output)
|
||||||
}
|
}
|
||||||
case test.want.match != "":
|
case test.match != "":
|
||||||
wantMatch := strings.ReplaceAll(test.want.match, `\`, `\\`)
|
wantMatch := strings.ReplaceAll(test.match, `\`, `\\`)
|
||||||
wantRegex := regexp.MustCompile(wantMatch)
|
wantRegex := regexp.MustCompile(wantMatch)
|
||||||
if !wantRegex.MatchString(gotOutput) {
|
if !wantRegex.MatchString(gotOutput) {
|
||||||
t.Errorf("tests[%v]:\ngave{\n\ttemplate: '%s',\n\tquery: '%s',\n\tallItems: %s}\nand got '%s',\nbut want '%s'",
|
t.Errorf("tests[%v]:\ngave{\n\ttemplate: '%s',\n\tquery: '%s',\n\tallItems: %s}\nand got '%s',\nbut want '%s'",
|
||||||
idx,
|
idx,
|
||||||
test.give.template, test.give.query, test.give.allItems,
|
test.template, test.query, test.allItems,
|
||||||
gotOutput, test.want.match)
|
gotOutput, test.match)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
t.Errorf("tests[%v]: test case does not describe 'want' property", idx)
|
t.Errorf("tests[%v]: test case does not describe 'want' property", idx)
|
||||||
|
@@ -11,10 +11,14 @@ func runTmux(args []string, opts *Options) (int, error) {
|
|||||||
// Prepare arguments
|
// Prepare arguments
|
||||||
fzf, rest := args[0], args[1:]
|
fzf, rest := args[0], args[1:]
|
||||||
args = []string{"--bind=ctrl-z:ignore"}
|
args = []string{"--bind=ctrl-z:ignore"}
|
||||||
if !opts.Tmux.border && opts.BorderShape == tui.BorderUndefined {
|
if !opts.Tmux.border && (opts.BorderShape == tui.BorderUndefined || opts.BorderShape == tui.BorderLine) {
|
||||||
// We append --border option at the end, because `--style=full:STYLE`
|
// We append --border option at the end, because `--style=full:STYLE`
|
||||||
// may have changed the default border style.
|
// may have changed the default border style.
|
||||||
rest = append(rest, "--border")
|
if tui.DefaultBorderShape == tui.BorderRounded {
|
||||||
|
rest = append(rest, "--border=rounded")
|
||||||
|
} else {
|
||||||
|
rest = append(rest, "--border=sharp")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if opts.Tmux.border && opts.Margin == defaultMargin() {
|
if opts.Tmux.border && opts.Margin == defaultMargin() {
|
||||||
args = append(args, "--margin=0,1")
|
args = append(args, "--margin=0,1")
|
||||||
|
@@ -225,7 +225,9 @@ func StripLastDelimiter(str string, delimiter Delimiter) string {
|
|||||||
locs := delimiter.regex.FindAllStringIndex(str, -1)
|
locs := delimiter.regex.FindAllStringIndex(str, -1)
|
||||||
if len(locs) > 0 {
|
if len(locs) > 0 {
|
||||||
lastLoc := locs[len(locs)-1]
|
lastLoc := locs[len(locs)-1]
|
||||||
str = str[:lastLoc[0]]
|
if lastLoc[1] == len(str) {
|
||||||
|
str = str[:lastLoc[0]]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return strings.TrimRightFunc(str, unicode.IsSpace)
|
return strings.TrimRightFunc(str, unicode.IsSpace)
|
||||||
|
@@ -13,10 +13,10 @@ var DefaultBorderShape = BorderRounded
|
|||||||
func (a Attr) Merge(b Attr) Attr {
|
func (a Attr) Merge(b Attr) Attr {
|
||||||
if b&AttrRegular > 0 {
|
if b&AttrRegular > 0 {
|
||||||
// Only keep bold attribute set by the system
|
// Only keep bold attribute set by the system
|
||||||
return b | (a & BoldForce)
|
return (b &^ AttrRegular) | (a & BoldForce)
|
||||||
}
|
}
|
||||||
|
|
||||||
return a | b
|
return (a &^ AttrRegular) | b
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -24,6 +24,7 @@ const (
|
|||||||
AttrRegular = Attr(1 << 8)
|
AttrRegular = Attr(1 << 8)
|
||||||
AttrClear = Attr(1 << 9)
|
AttrClear = Attr(1 << 9)
|
||||||
BoldForce = Attr(1 << 10)
|
BoldForce = Attr(1 << 10)
|
||||||
|
FullBg = Attr(1 << 11)
|
||||||
|
|
||||||
Bold = Attr(1)
|
Bold = Attr(1)
|
||||||
Dim = Attr(1 << 1)
|
Dim = Attr(1 << 1)
|
||||||
|
@@ -84,35 +84,39 @@ func _() {
|
|||||||
_ = x[CtrlAlt-73]
|
_ = x[CtrlAlt-73]
|
||||||
_ = x[Invalid-74]
|
_ = x[Invalid-74]
|
||||||
_ = x[Fatal-75]
|
_ = x[Fatal-75]
|
||||||
_ = x[Mouse-76]
|
_ = x[BracketedPasteBegin-76]
|
||||||
_ = x[DoubleClick-77]
|
_ = x[BracketedPasteEnd-77]
|
||||||
_ = x[LeftClick-78]
|
_ = x[Mouse-78]
|
||||||
_ = x[RightClick-79]
|
_ = x[DoubleClick-79]
|
||||||
_ = x[SLeftClick-80]
|
_ = x[LeftClick-80]
|
||||||
_ = x[SRightClick-81]
|
_ = x[RightClick-81]
|
||||||
_ = x[ScrollUp-82]
|
_ = x[SLeftClick-82]
|
||||||
_ = x[ScrollDown-83]
|
_ = x[SRightClick-83]
|
||||||
_ = x[SScrollUp-84]
|
_ = x[ScrollUp-84]
|
||||||
_ = x[SScrollDown-85]
|
_ = x[ScrollDown-85]
|
||||||
_ = x[PreviewScrollUp-86]
|
_ = x[SScrollUp-86]
|
||||||
_ = x[PreviewScrollDown-87]
|
_ = x[SScrollDown-87]
|
||||||
_ = x[Resize-88]
|
_ = x[PreviewScrollUp-88]
|
||||||
_ = x[Change-89]
|
_ = x[PreviewScrollDown-89]
|
||||||
_ = x[BackwardEOF-90]
|
_ = x[Resize-90]
|
||||||
_ = x[Start-91]
|
_ = x[Change-91]
|
||||||
_ = x[Load-92]
|
_ = x[BackwardEOF-92]
|
||||||
_ = x[Focus-93]
|
_ = x[Start-93]
|
||||||
_ = x[One-94]
|
_ = x[Load-94]
|
||||||
_ = x[Zero-95]
|
_ = x[Focus-95]
|
||||||
_ = x[Result-96]
|
_ = x[One-96]
|
||||||
_ = x[Jump-97]
|
_ = x[Zero-97]
|
||||||
_ = x[JumpCancel-98]
|
_ = x[Result-98]
|
||||||
_ = x[ClickHeader-99]
|
_ = x[Jump-99]
|
||||||
|
_ = x[JumpCancel-100]
|
||||||
|
_ = x[ClickHeader-101]
|
||||||
|
_ = x[ClickFooter-102]
|
||||||
|
_ = x[Multi-103]
|
||||||
}
|
}
|
||||||
|
|
||||||
const _EventType_name = "RuneCtrlACtrlBCtrlCCtrlDCtrlECtrlFCtrlGCtrlHTabCtrlJCtrlKCtrlLEnterCtrlNCtrlOCtrlPCtrlQCtrlRCtrlSCtrlTCtrlUCtrlVCtrlWCtrlXCtrlYCtrlZEscCtrlSpaceCtrlDeleteCtrlBackSlashCtrlRightBracketCtrlCaretCtrlSlashShiftTabBackspaceDeletePageUpPageDownUpDownLeftRightHomeEndInsertShiftUpShiftDownShiftLeftShiftRightShiftDeleteF1F2F3F4F5F6F7F8F9F10F11F12AltBackspaceAltUpAltDownAltLeftAltRightAltShiftUpAltShiftDownAltShiftLeftAltShiftRightAltCtrlAltInvalidFatalMouseDoubleClickLeftClickRightClickSLeftClickSRightClickScrollUpScrollDownSScrollUpSScrollDownPreviewScrollUpPreviewScrollDownResizeChangeBackwardEOFStartLoadFocusOneZeroResultJumpJumpCancelClickHeader"
|
const _EventType_name = "RuneCtrlACtrlBCtrlCCtrlDCtrlECtrlFCtrlGCtrlHTabCtrlJCtrlKCtrlLEnterCtrlNCtrlOCtrlPCtrlQCtrlRCtrlSCtrlTCtrlUCtrlVCtrlWCtrlXCtrlYCtrlZEscCtrlSpaceCtrlDeleteCtrlBackSlashCtrlRightBracketCtrlCaretCtrlSlashShiftTabBackspaceDeletePageUpPageDownUpDownLeftRightHomeEndInsertShiftUpShiftDownShiftLeftShiftRightShiftDeleteF1F2F3F4F5F6F7F8F9F10F11F12AltBackspaceAltUpAltDownAltLeftAltRightAltShiftUpAltShiftDownAltShiftLeftAltShiftRightAltCtrlAltInvalidFatalBracketedPasteBeginBracketedPasteEndMouseDoubleClickLeftClickRightClickSLeftClickSRightClickScrollUpScrollDownSScrollUpSScrollDownPreviewScrollUpPreviewScrollDownResizeChangeBackwardEOFStartLoadFocusOneZeroResultJumpJumpCancelClickHeaderClickFooterMulti"
|
||||||
|
|
||||||
var _EventType_index = [...]uint16{0, 4, 9, 14, 19, 24, 29, 34, 39, 44, 47, 52, 57, 62, 67, 72, 77, 82, 87, 92, 97, 102, 107, 112, 117, 122, 127, 132, 135, 144, 154, 167, 183, 192, 201, 209, 218, 224, 230, 238, 240, 244, 248, 253, 257, 260, 266, 273, 282, 291, 301, 312, 314, 316, 318, 320, 322, 324, 326, 328, 330, 333, 336, 339, 351, 356, 363, 370, 378, 388, 400, 412, 425, 428, 435, 442, 447, 452, 463, 472, 482, 492, 503, 511, 521, 530, 541, 556, 573, 579, 585, 596, 601, 605, 610, 613, 617, 623, 627, 637, 648}
|
var _EventType_index = [...]uint16{0, 4, 9, 14, 19, 24, 29, 34, 39, 44, 47, 52, 57, 62, 67, 72, 77, 82, 87, 92, 97, 102, 107, 112, 117, 122, 127, 132, 135, 144, 154, 167, 183, 192, 201, 209, 218, 224, 230, 238, 240, 244, 248, 253, 257, 260, 266, 273, 282, 291, 301, 312, 314, 316, 318, 320, 322, 324, 326, 328, 330, 333, 336, 339, 351, 356, 363, 370, 378, 388, 400, 412, 425, 428, 435, 442, 447, 466, 483, 488, 499, 508, 518, 528, 539, 547, 557, 566, 577, 592, 609, 615, 621, 632, 637, 641, 646, 649, 653, 659, 663, 673, 684, 695, 700}
|
||||||
|
|
||||||
func (i EventType) String() string {
|
func (i EventType) String() string {
|
||||||
if i < 0 || i >= EventType(len(_EventType_index)-1) {
|
if i < 0 || i >= EventType(len(_EventType_index)-1) {
|
||||||
|
@@ -8,6 +8,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
@@ -27,9 +28,9 @@ const (
|
|||||||
maxInputBuffer = 1024 * 1024
|
maxInputBuffer = 1024 * 1024
|
||||||
)
|
)
|
||||||
|
|
||||||
const consoleDevice string = "/dev/tty"
|
const DefaultTtyDevice string = "/dev/tty"
|
||||||
|
|
||||||
var offsetRegexp = regexp.MustCompile("(.*)\x1b\\[([0-9]+);([0-9]+)R")
|
var offsetRegexp = regexp.MustCompile("(.*?)\x00?\x1b\\[([0-9]+);([0-9]+)R")
|
||||||
var offsetRegexpBegin = regexp.MustCompile("^\x1b\\[[0-9]+;[0-9]+R")
|
var offsetRegexpBegin = regexp.MustCompile("^\x1b\\[[0-9]+;[0-9]+R")
|
||||||
|
|
||||||
func (r *LightRenderer) Bell() {
|
func (r *LightRenderer) Bell() {
|
||||||
@@ -95,7 +96,6 @@ func (r *LightRenderer) flushRaw(sequence string) {
|
|||||||
|
|
||||||
// Light renderer
|
// Light renderer
|
||||||
type LightRenderer struct {
|
type LightRenderer struct {
|
||||||
closed *util.AtomicBool
|
|
||||||
theme *ColorTheme
|
theme *ColorTheme
|
||||||
mouse bool
|
mouse bool
|
||||||
forceBlack bool
|
forceBlack bool
|
||||||
@@ -120,6 +120,7 @@ type LightRenderer struct {
|
|||||||
showCursor bool
|
showCursor bool
|
||||||
|
|
||||||
// Windows only
|
// Windows only
|
||||||
|
mutex sync.Mutex
|
||||||
ttyinChannel chan byte
|
ttyinChannel chan byte
|
||||||
inHandle uintptr
|
inHandle uintptr
|
||||||
outHandle uintptr
|
outHandle uintptr
|
||||||
@@ -145,13 +146,12 @@ type LightWindow struct {
|
|||||||
wrapSignWidth int
|
wrapSignWidth int
|
||||||
}
|
}
|
||||||
|
|
||||||
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(ttyDefault string, ttyin *os.File, theme *ColorTheme, forceBlack bool, mouse bool, tabstop int, clearOnExit bool, fullscreen bool, maxHeightFunc func(int) int) (Renderer, error) {
|
||||||
out, err := openTtyOut()
|
out, err := openTtyOut(ttyDefault)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
out = os.Stderr
|
out = os.Stderr
|
||||||
}
|
}
|
||||||
r := LightRenderer{
|
r := LightRenderer{
|
||||||
closed: util.NewAtomicBool(false),
|
|
||||||
theme: theme,
|
theme: theme,
|
||||||
forceBlack: forceBlack,
|
forceBlack: forceBlack,
|
||||||
mouse: mouse,
|
mouse: mouse,
|
||||||
@@ -213,7 +213,7 @@ func (r *LightRenderer) Init() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
r.enableMouse()
|
r.enableModes()
|
||||||
r.csi(fmt.Sprintf("%dA", r.MaxY()-1))
|
r.csi(fmt.Sprintf("%dA", r.MaxY()-1))
|
||||||
r.csi("G")
|
r.csi("G")
|
||||||
r.csi("K")
|
r.csi("K")
|
||||||
@@ -271,7 +271,7 @@ func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) ([]byte,
|
|||||||
c, ok := r.getch(nonblock)
|
c, ok := r.getch(nonblock)
|
||||||
if !nonblock && !ok {
|
if !nonblock && !ok {
|
||||||
r.Close()
|
r.Close()
|
||||||
return nil, errors.New("failed to read " + consoleDevice)
|
return nil, errors.New("failed to read " + DefaultTtyDevice)
|
||||||
}
|
}
|
||||||
|
|
||||||
retries := 0
|
retries := 0
|
||||||
@@ -462,10 +462,11 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
|||||||
}
|
}
|
||||||
// Bracketed paste mode: \e[200~ ... \e[201~
|
// Bracketed paste mode: \e[200~ ... \e[201~
|
||||||
if len(r.buffer) > 5 && r.buffer[3] == '0' && (r.buffer[4] == '0' || r.buffer[4] == '1') && r.buffer[5] == '~' {
|
if len(r.buffer) > 5 && r.buffer[3] == '0' && (r.buffer[4] == '0' || r.buffer[4] == '1') && r.buffer[5] == '~' {
|
||||||
// Immediately discard the sequence from the buffer and reread input
|
*sz = 6
|
||||||
r.buffer = r.buffer[6:]
|
if r.buffer[4] == '0' {
|
||||||
*sz = 0
|
return Event{BracketedPasteBegin, 0, nil}
|
||||||
return r.GetChar()
|
}
|
||||||
|
return Event{BracketedPasteEnd, 0, nil}
|
||||||
}
|
}
|
||||||
return Event{Invalid, 0, nil} // INS
|
return Event{Invalid, 0, nil} // INS
|
||||||
case '3':
|
case '3':
|
||||||
@@ -681,7 +682,7 @@ func (r *LightRenderer) rmcup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) Pause(clear bool) {
|
func (r *LightRenderer) Pause(clear bool) {
|
||||||
r.disableMouse()
|
r.disableModes()
|
||||||
r.restoreTerminal()
|
r.restoreTerminal()
|
||||||
if clear {
|
if clear {
|
||||||
if r.fullscreen {
|
if r.fullscreen {
|
||||||
@@ -694,12 +695,13 @@ func (r *LightRenderer) Pause(clear bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) enableMouse() {
|
func (r *LightRenderer) enableModes() {
|
||||||
if r.mouse {
|
if r.mouse {
|
||||||
r.csi("?1000h")
|
r.csi("?1000h")
|
||||||
r.csi("?1002h")
|
r.csi("?1002h")
|
||||||
r.csi("?1006h")
|
r.csi("?1006h")
|
||||||
}
|
}
|
||||||
|
r.csi("?2004h") // Enable bracketed paste mode
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) disableMouse() {
|
func (r *LightRenderer) disableMouse() {
|
||||||
@@ -710,6 +712,11 @@ func (r *LightRenderer) disableMouse() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *LightRenderer) disableModes() {
|
||||||
|
r.disableMouse()
|
||||||
|
r.csi("?2004l")
|
||||||
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) Resume(clear bool, sigcont bool) {
|
func (r *LightRenderer) Resume(clear bool, sigcont bool) {
|
||||||
r.setupTerminal()
|
r.setupTerminal()
|
||||||
if clear {
|
if clear {
|
||||||
@@ -718,7 +725,7 @@ func (r *LightRenderer) Resume(clear bool, sigcont bool) {
|
|||||||
} else {
|
} else {
|
||||||
r.rmcup()
|
r.rmcup()
|
||||||
}
|
}
|
||||||
r.enableMouse()
|
r.enableModes()
|
||||||
r.flush()
|
r.flush()
|
||||||
} else if sigcont && !r.fullscreen && r.mouse {
|
} else if sigcont && !r.fullscreen && r.mouse {
|
||||||
// NOTE: SIGCONT (Coming back from CTRL-Z):
|
// NOTE: SIGCONT (Coming back from CTRL-Z):
|
||||||
@@ -773,11 +780,10 @@ func (r *LightRenderer) Close() {
|
|||||||
if !r.showCursor {
|
if !r.showCursor {
|
||||||
r.csi("?25h")
|
r.csi("?25h")
|
||||||
}
|
}
|
||||||
r.disableMouse()
|
r.disableModes()
|
||||||
r.flush()
|
r.flush()
|
||||||
r.closePlatform()
|
|
||||||
r.restoreTerminal()
|
r.restoreTerminal()
|
||||||
r.closed.Set(true)
|
r.closePlatform()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) Top() int {
|
func (r *LightRenderer) Top() int {
|
||||||
@@ -823,11 +829,14 @@ func (r *LightRenderer) NewWindow(top int, left int, width int, height int, wind
|
|||||||
case WindowHeader:
|
case WindowHeader:
|
||||||
w.fg = r.theme.Header.Color
|
w.fg = r.theme.Header.Color
|
||||||
w.bg = r.theme.HeaderBg.Color
|
w.bg = r.theme.HeaderBg.Color
|
||||||
|
case WindowFooter:
|
||||||
|
w.fg = r.theme.Footer.Color
|
||||||
|
w.bg = r.theme.FooterBg.Color
|
||||||
case WindowPreview:
|
case WindowPreview:
|
||||||
w.fg = r.theme.PreviewFg.Color
|
w.fg = r.theme.PreviewFg.Color
|
||||||
w.bg = r.theme.PreviewBg.Color
|
w.bg = r.theme.PreviewBg.Color
|
||||||
}
|
}
|
||||||
if erase && !w.bg.IsDefault() && w.border.shape != BorderNone {
|
if erase && !w.bg.IsDefault() && w.border.shape != BorderNone && w.height > 0 {
|
||||||
// fzf --color bg:blue --border --padding 1,2
|
// fzf --color bg:blue --border --padding 1,2
|
||||||
w.Erase()
|
w.Erase()
|
||||||
}
|
}
|
||||||
@@ -883,6 +892,8 @@ func (w *LightWindow) drawBorderHorizontal(top, bottom bool) {
|
|||||||
color = ColInputBorder
|
color = ColInputBorder
|
||||||
case WindowHeader:
|
case WindowHeader:
|
||||||
color = ColHeaderBorder
|
color = ColHeaderBorder
|
||||||
|
case WindowFooter:
|
||||||
|
color = ColFooterBorder
|
||||||
case WindowPreview:
|
case WindowPreview:
|
||||||
color = ColPreviewBorder
|
color = ColPreviewBorder
|
||||||
}
|
}
|
||||||
@@ -908,6 +919,8 @@ func (w *LightWindow) drawBorderVertical(left, right bool) {
|
|||||||
color = ColInputBorder
|
color = ColInputBorder
|
||||||
case WindowHeader:
|
case WindowHeader:
|
||||||
color = ColHeaderBorder
|
color = ColHeaderBorder
|
||||||
|
case WindowFooter:
|
||||||
|
color = ColFooterBorder
|
||||||
case WindowPreview:
|
case WindowPreview:
|
||||||
color = ColPreviewBorder
|
color = ColPreviewBorder
|
||||||
}
|
}
|
||||||
@@ -935,6 +948,8 @@ func (w *LightWindow) drawBorderAround(onlyHorizontal bool) {
|
|||||||
color = ColInputBorder
|
color = ColInputBorder
|
||||||
case WindowHeader:
|
case WindowHeader:
|
||||||
color = ColHeaderBorder
|
color = ColHeaderBorder
|
||||||
|
case WindowFooter:
|
||||||
|
color = ColFooterBorder
|
||||||
case WindowPreview:
|
case WindowPreview:
|
||||||
color = ColPreviewBorder
|
color = ColPreviewBorder
|
||||||
}
|
}
|
||||||
|
@@ -42,26 +42,35 @@ func (r *LightRenderer) closePlatform() {
|
|||||||
r.ttyout.Close()
|
r.ttyout.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func openTty(mode int) (*os.File, error) {
|
func openTty(ttyDefault string, mode int) (*os.File, error) {
|
||||||
in, err := os.OpenFile(consoleDevice, mode, 0)
|
var in *os.File
|
||||||
if err != nil {
|
var err error
|
||||||
|
if len(ttyDefault) > 0 {
|
||||||
|
in, err = os.OpenFile(ttyDefault, mode, 0)
|
||||||
|
}
|
||||||
|
if in == nil || err != nil || ttyDefault != DefaultTtyDevice && !util.IsTty(in) {
|
||||||
tty := ttyname()
|
tty := ttyname()
|
||||||
if len(tty) > 0 {
|
if len(tty) > 0 {
|
||||||
if in, err := os.OpenFile(tty, mode, 0); err == nil {
|
if in, err := os.OpenFile(tty, mode, 0); err == nil {
|
||||||
return in, nil
|
return in, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, errors.New("failed to open " + consoleDevice)
|
if ttyDefault != DefaultTtyDevice {
|
||||||
|
if in, err = os.OpenFile(DefaultTtyDevice, mode, 0); err == nil {
|
||||||
|
return in, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, errors.New("failed to open " + DefaultTtyDevice)
|
||||||
}
|
}
|
||||||
return in, nil
|
return in, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func openTtyIn() (*os.File, error) {
|
func openTtyIn(ttyDefault string) (*os.File, error) {
|
||||||
return openTty(syscall.O_RDONLY)
|
return openTty(ttyDefault, syscall.O_RDONLY)
|
||||||
}
|
}
|
||||||
|
|
||||||
func openTtyOut() (*os.File, error) {
|
func openTtyOut(ttyDefault string) (*os.File, error) {
|
||||||
return openTty(syscall.O_WRONLY)
|
return openTty(ttyDefault, syscall.O_WRONLY)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) setupTerminal() {
|
func (r *LightRenderer) setupTerminal() {
|
||||||
|
@@ -18,6 +18,7 @@ const (
|
|||||||
var (
|
var (
|
||||||
consoleFlagsInput = uint32(windows.ENABLE_VIRTUAL_TERMINAL_INPUT | windows.ENABLE_PROCESSED_INPUT | windows.ENABLE_EXTENDED_FLAGS)
|
consoleFlagsInput = uint32(windows.ENABLE_VIRTUAL_TERMINAL_INPUT | windows.ENABLE_PROCESSED_INPUT | windows.ENABLE_EXTENDED_FLAGS)
|
||||||
consoleFlagsOutput = uint32(windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING | windows.ENABLE_PROCESSED_OUTPUT | windows.DISABLE_NEWLINE_AUTO_RETURN)
|
consoleFlagsOutput = uint32(windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING | windows.ENABLE_PROCESSED_OUTPUT | windows.DISABLE_NEWLINE_AUTO_RETURN)
|
||||||
|
counter = uint64(0)
|
||||||
)
|
)
|
||||||
|
|
||||||
// IsLightRendererSupported checks to see if the Light renderer is supported
|
// IsLightRendererSupported checks to see if the Light renderer is supported
|
||||||
@@ -61,27 +62,11 @@ func (r *LightRenderer) initPlatform() error {
|
|||||||
}
|
}
|
||||||
r.inHandle = uintptr(inHandle)
|
r.inHandle = uintptr(inHandle)
|
||||||
|
|
||||||
r.setupTerminal()
|
|
||||||
|
|
||||||
// channel for non-blocking reads. Buffer to make sure
|
// channel for non-blocking reads. Buffer to make sure
|
||||||
// we get the ESC sets:
|
// we get the ESC sets:
|
||||||
r.ttyinChannel = make(chan byte, 1024)
|
r.ttyinChannel = make(chan byte, 1024)
|
||||||
|
|
||||||
// the following allows for non-blocking IO.
|
r.setupTerminal()
|
||||||
// syscall.SetNonblock() is a NOOP under Windows.
|
|
||||||
go func() {
|
|
||||||
fd := int(r.inHandle)
|
|
||||||
b := make([]byte, 1)
|
|
||||||
for !r.closed.Get() {
|
|
||||||
// HACK: if run from PSReadline, something resets ConsoleMode to remove ENABLE_VIRTUAL_TERMINAL_INPUT.
|
|
||||||
_ = windows.SetConsoleMode(windows.Handle(r.inHandle), consoleFlagsInput)
|
|
||||||
|
|
||||||
_, err := util.Read(fd, b)
|
|
||||||
if err == nil {
|
|
||||||
r.ttyinChannel <- b[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -91,27 +76,51 @@ func (r *LightRenderer) closePlatform() {
|
|||||||
windows.SetConsoleMode(windows.Handle(r.inHandle), r.origStateInput)
|
windows.SetConsoleMode(windows.Handle(r.inHandle), r.origStateInput)
|
||||||
}
|
}
|
||||||
|
|
||||||
func openTtyIn() (*os.File, error) {
|
func openTtyIn(ttyDefault string) (*os.File, error) {
|
||||||
// not used
|
// not used
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func openTtyOut() (*os.File, error) {
|
func openTtyOut(ttyDefault string) (*os.File, error) {
|
||||||
return os.Stderr, nil
|
return os.Stderr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) setupTerminal() error {
|
func (r *LightRenderer) setupTerminal() {
|
||||||
if err := windows.SetConsoleMode(windows.Handle(r.outHandle), consoleFlagsOutput); err != nil {
|
windows.SetConsoleMode(windows.Handle(r.outHandle), consoleFlagsOutput)
|
||||||
return err
|
windows.SetConsoleMode(windows.Handle(r.inHandle), consoleFlagsInput)
|
||||||
}
|
|
||||||
return windows.SetConsoleMode(windows.Handle(r.inHandle), consoleFlagsInput)
|
// The following allows for non-blocking IO.
|
||||||
|
// syscall.SetNonblock() is a NOOP under Windows.
|
||||||
|
current := counter
|
||||||
|
go func() {
|
||||||
|
fd := int(r.inHandle)
|
||||||
|
b := make([]byte, 1)
|
||||||
|
for {
|
||||||
|
if _, err := util.Read(fd, b); err == nil {
|
||||||
|
r.mutex.Lock()
|
||||||
|
// This condition prevents the goroutine from running after the renderer
|
||||||
|
// has been closed or paused.
|
||||||
|
if current != counter {
|
||||||
|
r.mutex.Unlock()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
r.ttyinChannel <- b[0]
|
||||||
|
// HACK: if run from PSReadline, something resets ConsoleMode to remove ENABLE_VIRTUAL_TERMINAL_INPUT.
|
||||||
|
windows.SetConsoleMode(windows.Handle(r.inHandle), consoleFlagsInput)
|
||||||
|
r.mutex.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) restoreTerminal() error {
|
func (r *LightRenderer) restoreTerminal() {
|
||||||
if err := windows.SetConsoleMode(windows.Handle(r.inHandle), r.origStateInput); err != nil {
|
r.mutex.Lock()
|
||||||
return err
|
counter++
|
||||||
}
|
// We're setting ENABLE_VIRTUAL_TERMINAL_INPUT to allow escape sequences to be read during 'execute'.
|
||||||
return windows.SetConsoleMode(windows.Handle(r.outHandle), r.origStateOutput)
|
// e.g. fzf --bind 'enter:execute:less {}'
|
||||||
|
windows.SetConsoleMode(windows.Handle(r.inHandle), r.origStateInput|windows.ENABLE_VIRTUAL_TERMINAL_INPUT)
|
||||||
|
windows.SetConsoleMode(windows.Handle(r.outHandle), r.origStateOutput)
|
||||||
|
r.mutex.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) Size() TermSize {
|
func (r *LightRenderer) Size() TermSize {
|
||||||
|
@@ -103,6 +103,7 @@ const (
|
|||||||
AttrRegular = Attr(1 << 7)
|
AttrRegular = Attr(1 << 7)
|
||||||
AttrClear = Attr(1 << 8)
|
AttrClear = Attr(1 << 8)
|
||||||
BoldForce = Attr(1 << 10)
|
BoldForce = Attr(1 << 10)
|
||||||
|
FullBg = Attr(1 << 11)
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *FullscreenRenderer) Bell() {
|
func (r *FullscreenRenderer) Bell() {
|
||||||
@@ -161,10 +162,10 @@ func (c Color) Style() tcell.Color {
|
|||||||
func (a Attr) Merge(b Attr) Attr {
|
func (a Attr) Merge(b Attr) Attr {
|
||||||
if b&AttrRegular > 0 {
|
if b&AttrRegular > 0 {
|
||||||
// Only keep bold attribute set by the system
|
// Only keep bold attribute set by the system
|
||||||
return b | (a & BoldForce)
|
return (b &^ AttrRegular) | (a & BoldForce)
|
||||||
}
|
}
|
||||||
|
|
||||||
return a | b
|
return (a &^ AttrRegular) | b
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle the following as private members of FullscreenRenderer instance
|
// handle the following as private members of FullscreenRenderer instance
|
||||||
@@ -197,6 +198,7 @@ func (r *FullscreenRenderer) initScreen() error {
|
|||||||
if e = s.Init(); e != nil {
|
if e = s.Init(); e != nil {
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
s.EnablePaste()
|
||||||
if r.mouse {
|
if r.mouse {
|
||||||
s.EnableMouse()
|
s.EnableMouse()
|
||||||
} else {
|
} else {
|
||||||
@@ -266,6 +268,11 @@ func (r *FullscreenRenderer) Size() TermSize {
|
|||||||
func (r *FullscreenRenderer) GetChar() Event {
|
func (r *FullscreenRenderer) GetChar() Event {
|
||||||
ev := _screen.PollEvent()
|
ev := _screen.PollEvent()
|
||||||
switch ev := ev.(type) {
|
switch ev := ev.(type) {
|
||||||
|
case *tcell.EventPaste:
|
||||||
|
if ev.Start() {
|
||||||
|
return Event{BracketedPasteBegin, 0, nil}
|
||||||
|
}
|
||||||
|
return Event{BracketedPasteEnd, 0, nil}
|
||||||
case *tcell.EventResize:
|
case *tcell.EventResize:
|
||||||
// Ignore the first resize event
|
// Ignore the first resize event
|
||||||
// https://github.com/gdamore/tcell/blob/v2.7.0/TUTORIAL.md?plain=1#L18
|
// https://github.com/gdamore/tcell/blob/v2.7.0/TUTORIAL.md?plain=1#L18
|
||||||
@@ -594,6 +601,8 @@ func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int,
|
|||||||
normal = ColNormal
|
normal = ColNormal
|
||||||
case WindowHeader:
|
case WindowHeader:
|
||||||
normal = ColHeader
|
normal = ColHeader
|
||||||
|
case WindowFooter:
|
||||||
|
normal = ColFooter
|
||||||
case WindowInput:
|
case WindowInput:
|
||||||
normal = ColInput
|
normal = ColInput
|
||||||
case WindowPreview:
|
case WindowPreview:
|
||||||
@@ -859,6 +868,8 @@ func (w *TcellWindow) drawBorder(onlyHorizontal bool) {
|
|||||||
style = ColListBorder.style()
|
style = ColListBorder.style()
|
||||||
case WindowHeader:
|
case WindowHeader:
|
||||||
style = ColHeaderBorder.style()
|
style = ColHeaderBorder.style()
|
||||||
|
case WindowFooter:
|
||||||
|
style = ColFooterBorder.style()
|
||||||
case WindowInput:
|
case WindowInput:
|
||||||
style = ColInputBorder.style()
|
style = ColInputBorder.style()
|
||||||
case WindowPreview:
|
case WindowPreview:
|
||||||
|
@@ -10,7 +10,7 @@ import (
|
|||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func assert(t *testing.T, context string, got interface{}, want interface{}) bool {
|
func assert(t *testing.T, context string, got any, want any) bool {
|
||||||
if got == want {
|
if got == want {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
|
@@ -44,11 +44,11 @@ func ttyname() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TtyIn returns terminal device to read user input
|
// TtyIn returns terminal device to read user input
|
||||||
func TtyIn() (*os.File, error) {
|
func TtyIn(ttyDefault string) (*os.File, error) {
|
||||||
return openTtyIn()
|
return openTtyIn(ttyDefault)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TtyIn returns terminal device to write to
|
// TtyOut returns terminal device to write to
|
||||||
func TtyOut() (*os.File, error) {
|
func TtyOut(ttyDefault string) (*os.File, error) {
|
||||||
return openTtyOut()
|
return openTtyOut(ttyDefault)
|
||||||
}
|
}
|
||||||
|
@@ -11,11 +11,11 @@ func ttyname() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TtyIn on Windows returns os.Stdin
|
// TtyIn on Windows returns os.Stdin
|
||||||
func TtyIn() (*os.File, error) {
|
func TtyIn(ttyDefault string) (*os.File, error) {
|
||||||
return os.Stdin, nil
|
return os.Stdin, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TtyOut on Windows returns nil
|
// TtyOut on Windows returns nil
|
||||||
func TtyOut() (*os.File, error) {
|
func TtyOut(ttyDefault string) (*os.File, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
234
src/tui/tui.go
234
src/tui/tui.go
@@ -103,6 +103,8 @@ const (
|
|||||||
|
|
||||||
Invalid
|
Invalid
|
||||||
Fatal
|
Fatal
|
||||||
|
BracketedPasteBegin
|
||||||
|
BracketedPasteEnd
|
||||||
|
|
||||||
Mouse
|
Mouse
|
||||||
DoubleClick
|
DoubleClick
|
||||||
@@ -130,6 +132,8 @@ const (
|
|||||||
Jump
|
Jump
|
||||||
JumpCancel
|
JumpCancel
|
||||||
ClickHeader
|
ClickHeader
|
||||||
|
ClickFooter
|
||||||
|
Multi
|
||||||
)
|
)
|
||||||
|
|
||||||
func (t EventType) AsEvent() Event {
|
func (t EventType) AsEvent() Event {
|
||||||
@@ -271,6 +275,10 @@ func NewColorPair(fg Color, bg Color, attr Attr) ColorPair {
|
|||||||
return ColorPair{fg, bg, attr}
|
return ColorPair{fg, bg, attr}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NoColorPair() ColorPair {
|
||||||
|
return ColorPair{-1, -1, 0}
|
||||||
|
}
|
||||||
|
|
||||||
func (p ColorPair) Fg() Color {
|
func (p ColorPair) Fg() Color {
|
||||||
return p.fg
|
return p.fg
|
||||||
}
|
}
|
||||||
@@ -283,6 +291,10 @@ func (p ColorPair) Attr() Attr {
|
|||||||
return p.attr
|
return p.attr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p ColorPair) IsFullBgMarker() bool {
|
||||||
|
return p.attr&FullBg > 0
|
||||||
|
}
|
||||||
|
|
||||||
func (p ColorPair) HasBg() bool {
|
func (p ColorPair) HasBg() bool {
|
||||||
return p.attr&Reverse == 0 && p.bg != colDefault ||
|
return p.attr&Reverse == 0 && p.bg != colDefault ||
|
||||||
p.attr&Reverse > 0 && p.fg != colDefault
|
p.attr&Reverse > 0 && p.fg != colDefault
|
||||||
@@ -306,6 +318,12 @@ func (p ColorPair) WithAttr(attr Attr) ColorPair {
|
|||||||
return dup
|
return dup
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p ColorPair) WithBg(bg ColorAttr) ColorPair {
|
||||||
|
dup := p
|
||||||
|
bgPair := ColorPair{colUndefined, bg.Color, bg.Attr}
|
||||||
|
return dup.Merge(bgPair)
|
||||||
|
}
|
||||||
|
|
||||||
func (p ColorPair) MergeAttr(other ColorPair) ColorPair {
|
func (p ColorPair) MergeAttr(other ColorPair) ColorPair {
|
||||||
return p.WithAttr(other.attr)
|
return p.WithAttr(other.attr)
|
||||||
}
|
}
|
||||||
@@ -321,11 +339,13 @@ func (p ColorPair) MergeNonDefault(other ColorPair) ColorPair {
|
|||||||
type ColorTheme struct {
|
type ColorTheme struct {
|
||||||
Colored bool
|
Colored bool
|
||||||
Input ColorAttr
|
Input ColorAttr
|
||||||
|
Ghost ColorAttr
|
||||||
Disabled ColorAttr
|
Disabled ColorAttr
|
||||||
Fg ColorAttr
|
Fg ColorAttr
|
||||||
Bg ColorAttr
|
Bg ColorAttr
|
||||||
ListFg ColorAttr
|
ListFg ColorAttr
|
||||||
ListBg ColorAttr
|
ListBg ColorAttr
|
||||||
|
AltBg ColorAttr
|
||||||
Nth ColorAttr
|
Nth ColorAttr
|
||||||
SelectedFg ColorAttr
|
SelectedFg ColorAttr
|
||||||
SelectedBg ColorAttr
|
SelectedBg ColorAttr
|
||||||
@@ -349,6 +369,10 @@ type ColorTheme struct {
|
|||||||
HeaderBg ColorAttr
|
HeaderBg ColorAttr
|
||||||
HeaderBorder ColorAttr
|
HeaderBorder ColorAttr
|
||||||
HeaderLabel ColorAttr
|
HeaderLabel ColorAttr
|
||||||
|
Footer ColorAttr
|
||||||
|
FooterBg ColorAttr
|
||||||
|
FooterBorder ColorAttr
|
||||||
|
FooterLabel ColorAttr
|
||||||
Separator ColorAttr
|
Separator ColorAttr
|
||||||
Scrollbar ColorAttr
|
Scrollbar ColorAttr
|
||||||
Border ColorAttr
|
Border ColorAttr
|
||||||
@@ -481,7 +505,7 @@ type BorderCharacter int
|
|||||||
func MakeBorderStyle(shape BorderShape, unicode bool) BorderStyle {
|
func MakeBorderStyle(shape BorderShape, unicode bool) BorderStyle {
|
||||||
if shape == BorderNone || shape == BorderPhantom {
|
if shape == BorderNone || shape == BorderPhantom {
|
||||||
return BorderStyle{
|
return BorderStyle{
|
||||||
shape: shape,
|
shape: BorderNone,
|
||||||
top: ' ',
|
top: ' ',
|
||||||
bottom: ' ',
|
bottom: ' ',
|
||||||
left: ' ',
|
left: ' ',
|
||||||
@@ -602,6 +626,7 @@ const (
|
|||||||
WindowPreview
|
WindowPreview
|
||||||
WindowInput
|
WindowInput
|
||||||
WindowHeader
|
WindowHeader
|
||||||
|
WindowFooter
|
||||||
)
|
)
|
||||||
|
|
||||||
type Renderer interface {
|
type Renderer interface {
|
||||||
@@ -692,6 +717,7 @@ var (
|
|||||||
ColNormal ColorPair
|
ColNormal ColorPair
|
||||||
ColInput ColorPair
|
ColInput ColorPair
|
||||||
ColDisabled ColorPair
|
ColDisabled ColorPair
|
||||||
|
ColGhost ColorPair
|
||||||
ColMatch ColorPair
|
ColMatch ColorPair
|
||||||
ColCursor ColorPair
|
ColCursor ColorPair
|
||||||
ColCursorEmpty ColorPair
|
ColCursorEmpty ColorPair
|
||||||
@@ -709,6 +735,9 @@ var (
|
|||||||
ColHeader ColorPair
|
ColHeader ColorPair
|
||||||
ColHeaderBorder ColorPair
|
ColHeaderBorder ColorPair
|
||||||
ColHeaderLabel ColorPair
|
ColHeaderLabel ColorPair
|
||||||
|
ColFooter ColorPair
|
||||||
|
ColFooterBorder ColorPair
|
||||||
|
ColFooterLabel ColorPair
|
||||||
ColSeparator ColorPair
|
ColSeparator ColorPair
|
||||||
ColScrollbar ColorPair
|
ColScrollbar ColorPair
|
||||||
ColGapLine ColorPair
|
ColGapLine ColorPair
|
||||||
@@ -733,6 +762,7 @@ func EmptyTheme() *ColorTheme {
|
|||||||
Bg: ColorAttr{colUndefined, AttrUndefined},
|
Bg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
ListFg: ColorAttr{colUndefined, AttrUndefined},
|
ListFg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
ListBg: ColorAttr{colUndefined, AttrUndefined},
|
ListBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
AltBg: 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},
|
||||||
@@ -746,10 +776,12 @@ func EmptyTheme() *ColorTheme {
|
|||||||
Cursor: ColorAttr{colUndefined, AttrUndefined},
|
Cursor: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Marker: ColorAttr{colUndefined, AttrUndefined},
|
Marker: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Header: ColorAttr{colUndefined, AttrUndefined},
|
Header: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
Footer: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Border: ColorAttr{colUndefined, AttrUndefined},
|
Border: ColorAttr{colUndefined, AttrUndefined},
|
||||||
BorderLabel: ColorAttr{colUndefined, AttrUndefined},
|
BorderLabel: ColorAttr{colUndefined, AttrUndefined},
|
||||||
ListLabel: ColorAttr{colUndefined, AttrUndefined},
|
ListLabel: ColorAttr{colUndefined, AttrUndefined},
|
||||||
ListBorder: ColorAttr{colUndefined, AttrUndefined},
|
ListBorder: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
Ghost: ColorAttr{colUndefined, Dim},
|
||||||
Disabled: ColorAttr{colUndefined, AttrUndefined},
|
Disabled: ColorAttr{colUndefined, AttrUndefined},
|
||||||
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
|
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
PreviewBg: ColorAttr{colUndefined, AttrUndefined},
|
PreviewBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
@@ -765,6 +797,9 @@ func EmptyTheme() *ColorTheme {
|
|||||||
HeaderBg: ColorAttr{colUndefined, AttrUndefined},
|
HeaderBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
HeaderBorder: ColorAttr{colUndefined, AttrUndefined},
|
HeaderBorder: ColorAttr{colUndefined, AttrUndefined},
|
||||||
HeaderLabel: ColorAttr{colUndefined, AttrUndefined},
|
HeaderLabel: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
FooterBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
FooterBorder: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
FooterLabel: ColorAttr{colUndefined, AttrUndefined},
|
||||||
GapLine: ColorAttr{colUndefined, AttrUndefined},
|
GapLine: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Nth: ColorAttr{colUndefined, AttrUndefined},
|
Nth: ColorAttr{colUndefined, AttrUndefined},
|
||||||
}
|
}
|
||||||
@@ -778,6 +813,7 @@ func NoColorTheme() *ColorTheme {
|
|||||||
Bg: ColorAttr{colDefault, AttrUndefined},
|
Bg: ColorAttr{colDefault, AttrUndefined},
|
||||||
ListFg: ColorAttr{colDefault, AttrUndefined},
|
ListFg: ColorAttr{colDefault, AttrUndefined},
|
||||||
ListBg: ColorAttr{colDefault, AttrUndefined},
|
ListBg: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
AltBg: ColorAttr{colUndefined, 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},
|
||||||
@@ -793,6 +829,7 @@ func NoColorTheme() *ColorTheme {
|
|||||||
Header: ColorAttr{colDefault, AttrUndefined},
|
Header: ColorAttr{colDefault, AttrUndefined},
|
||||||
Border: ColorAttr{colDefault, AttrUndefined},
|
Border: ColorAttr{colDefault, AttrUndefined},
|
||||||
BorderLabel: ColorAttr{colDefault, AttrUndefined},
|
BorderLabel: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
Ghost: ColorAttr{colDefault, Dim},
|
||||||
Disabled: ColorAttr{colDefault, AttrUndefined},
|
Disabled: ColorAttr{colDefault, AttrUndefined},
|
||||||
PreviewFg: ColorAttr{colDefault, AttrUndefined},
|
PreviewFg: ColorAttr{colDefault, AttrUndefined},
|
||||||
PreviewBg: ColorAttr{colDefault, AttrUndefined},
|
PreviewBg: ColorAttr{colDefault, AttrUndefined},
|
||||||
@@ -810,6 +847,9 @@ func NoColorTheme() *ColorTheme {
|
|||||||
HeaderBg: ColorAttr{colDefault, AttrUndefined},
|
HeaderBg: ColorAttr{colDefault, AttrUndefined},
|
||||||
HeaderBorder: ColorAttr{colDefault, AttrUndefined},
|
HeaderBorder: ColorAttr{colDefault, AttrUndefined},
|
||||||
HeaderLabel: ColorAttr{colDefault, AttrUndefined},
|
HeaderLabel: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
FooterBg: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
FooterBorder: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
FooterLabel: ColorAttr{colDefault, AttrUndefined},
|
||||||
GapLine: ColorAttr{colDefault, AttrUndefined},
|
GapLine: ColorAttr{colDefault, AttrUndefined},
|
||||||
Nth: ColorAttr{colUndefined, AttrUndefined},
|
Nth: ColorAttr{colUndefined, AttrUndefined},
|
||||||
}
|
}
|
||||||
@@ -823,6 +863,7 @@ func init() {
|
|||||||
Bg: ColorAttr{colDefault, AttrUndefined},
|
Bg: ColorAttr{colDefault, AttrUndefined},
|
||||||
ListFg: ColorAttr{colUndefined, AttrUndefined},
|
ListFg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
ListBg: ColorAttr{colUndefined, AttrUndefined},
|
ListBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
AltBg: 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},
|
||||||
@@ -836,86 +877,10 @@ func init() {
|
|||||||
Cursor: ColorAttr{colRed, AttrUndefined},
|
Cursor: ColorAttr{colRed, AttrUndefined},
|
||||||
Marker: ColorAttr{colMagenta, AttrUndefined},
|
Marker: ColorAttr{colMagenta, AttrUndefined},
|
||||||
Header: ColorAttr{colCyan, AttrUndefined},
|
Header: ColorAttr{colCyan, AttrUndefined},
|
||||||
|
Footer: ColorAttr{colCyan, AttrUndefined},
|
||||||
Border: ColorAttr{colBlack, AttrUndefined},
|
Border: ColorAttr{colBlack, AttrUndefined},
|
||||||
BorderLabel: ColorAttr{colWhite, AttrUndefined},
|
BorderLabel: ColorAttr{colWhite, AttrUndefined},
|
||||||
Disabled: ColorAttr{colUndefined, AttrUndefined},
|
Ghost: ColorAttr{colUndefined, Dim},
|
||||||
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
|
|
||||||
PreviewBg: ColorAttr{colUndefined, AttrUndefined},
|
|
||||||
Gutter: ColorAttr{colUndefined, AttrUndefined},
|
|
||||||
PreviewBorder: ColorAttr{colUndefined, AttrUndefined},
|
|
||||||
PreviewScrollbar: ColorAttr{colUndefined, AttrUndefined},
|
|
||||||
PreviewLabel: ColorAttr{colUndefined, AttrUndefined},
|
|
||||||
ListLabel: ColorAttr{colUndefined, AttrUndefined},
|
|
||||||
ListBorder: ColorAttr{colUndefined, AttrUndefined},
|
|
||||||
Separator: 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{
|
|
||||||
Colored: true,
|
|
||||||
Input: ColorAttr{colDefault, AttrUndefined},
|
|
||||||
Fg: ColorAttr{colDefault, AttrUndefined},
|
|
||||||
Bg: ColorAttr{colDefault, AttrUndefined},
|
|
||||||
ListFg: ColorAttr{colUndefined, AttrUndefined},
|
|
||||||
ListBg: ColorAttr{colUndefined, AttrUndefined},
|
|
||||||
SelectedFg: ColorAttr{colUndefined, AttrUndefined},
|
|
||||||
SelectedBg: ColorAttr{colUndefined, AttrUndefined},
|
|
||||||
SelectedMatch: ColorAttr{colUndefined, AttrUndefined},
|
|
||||||
DarkBg: ColorAttr{236, AttrUndefined},
|
|
||||||
Prompt: ColorAttr{110, AttrUndefined},
|
|
||||||
Match: ColorAttr{108, AttrUndefined},
|
|
||||||
Current: ColorAttr{254, AttrUndefined},
|
|
||||||
CurrentMatch: ColorAttr{151, AttrUndefined},
|
|
||||||
Spinner: ColorAttr{148, AttrUndefined},
|
|
||||||
Info: ColorAttr{144, AttrUndefined},
|
|
||||||
Cursor: ColorAttr{161, AttrUndefined},
|
|
||||||
Marker: ColorAttr{168, AttrUndefined},
|
|
||||||
Header: ColorAttr{109, AttrUndefined},
|
|
||||||
Border: ColorAttr{59, AttrUndefined},
|
|
||||||
BorderLabel: ColorAttr{145, AttrUndefined},
|
|
||||||
Disabled: ColorAttr{colUndefined, AttrUndefined},
|
|
||||||
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
|
|
||||||
PreviewBg: ColorAttr{colUndefined, AttrUndefined},
|
|
||||||
Gutter: ColorAttr{colUndefined, AttrUndefined},
|
|
||||||
PreviewBorder: ColorAttr{colUndefined, AttrUndefined},
|
|
||||||
PreviewScrollbar: ColorAttr{colUndefined, AttrUndefined},
|
|
||||||
PreviewLabel: ColorAttr{colUndefined, AttrUndefined},
|
|
||||||
ListLabel: ColorAttr{colUndefined, AttrUndefined},
|
|
||||||
ListBorder: ColorAttr{colUndefined, AttrUndefined},
|
|
||||||
Separator: 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{
|
|
||||||
Colored: true,
|
|
||||||
Input: ColorAttr{colDefault, AttrUndefined},
|
|
||||||
Fg: ColorAttr{colDefault, AttrUndefined},
|
|
||||||
Bg: ColorAttr{colDefault, AttrUndefined},
|
|
||||||
ListFg: ColorAttr{colUndefined, AttrUndefined},
|
|
||||||
ListBg: ColorAttr{colUndefined, AttrUndefined},
|
|
||||||
SelectedFg: ColorAttr{colUndefined, AttrUndefined},
|
|
||||||
SelectedBg: ColorAttr{colUndefined, AttrUndefined},
|
|
||||||
SelectedMatch: ColorAttr{colUndefined, AttrUndefined},
|
|
||||||
DarkBg: ColorAttr{251, AttrUndefined},
|
|
||||||
Prompt: ColorAttr{25, AttrUndefined},
|
|
||||||
Match: ColorAttr{66, AttrUndefined},
|
|
||||||
Current: ColorAttr{237, AttrUndefined},
|
|
||||||
CurrentMatch: ColorAttr{23, AttrUndefined},
|
|
||||||
Spinner: ColorAttr{65, AttrUndefined},
|
|
||||||
Info: ColorAttr{101, AttrUndefined},
|
|
||||||
Cursor: ColorAttr{161, AttrUndefined},
|
|
||||||
Marker: ColorAttr{168, AttrUndefined},
|
|
||||||
Header: ColorAttr{31, AttrUndefined},
|
|
||||||
Border: ColorAttr{145, AttrUndefined},
|
|
||||||
BorderLabel: ColorAttr{59, 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},
|
||||||
@@ -933,6 +898,105 @@ func init() {
|
|||||||
HeaderBg: ColorAttr{colUndefined, AttrUndefined},
|
HeaderBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
HeaderBorder: ColorAttr{colUndefined, AttrUndefined},
|
HeaderBorder: ColorAttr{colUndefined, AttrUndefined},
|
||||||
HeaderLabel: ColorAttr{colUndefined, AttrUndefined},
|
HeaderLabel: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
FooterBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
FooterBorder: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
FooterLabel: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
GapLine: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
Nth: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
}
|
||||||
|
Dark256 = &ColorTheme{
|
||||||
|
Colored: true,
|
||||||
|
Input: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
Fg: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
Bg: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
ListFg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
ListBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
AltBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
SelectedFg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
SelectedBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
SelectedMatch: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
DarkBg: ColorAttr{236, AttrUndefined},
|
||||||
|
Prompt: ColorAttr{110, AttrUndefined},
|
||||||
|
Match: ColorAttr{108, AttrUndefined},
|
||||||
|
Current: ColorAttr{254, AttrUndefined},
|
||||||
|
CurrentMatch: ColorAttr{151, AttrUndefined},
|
||||||
|
Spinner: ColorAttr{148, AttrUndefined},
|
||||||
|
Info: ColorAttr{144, AttrUndefined},
|
||||||
|
Cursor: ColorAttr{161, AttrUndefined},
|
||||||
|
Marker: ColorAttr{168, AttrUndefined},
|
||||||
|
Header: ColorAttr{109, AttrUndefined},
|
||||||
|
Footer: ColorAttr{109, AttrUndefined},
|
||||||
|
Border: ColorAttr{59, AttrUndefined},
|
||||||
|
BorderLabel: ColorAttr{145, AttrUndefined},
|
||||||
|
Ghost: ColorAttr{colUndefined, Dim},
|
||||||
|
Disabled: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
PreviewBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
Gutter: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
PreviewBorder: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
PreviewScrollbar: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
PreviewLabel: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
ListLabel: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
ListBorder: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
Separator: 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},
|
||||||
|
FooterBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
FooterBorder: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
FooterLabel: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
GapLine: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
Nth: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
}
|
||||||
|
Light256 = &ColorTheme{
|
||||||
|
Colored: true,
|
||||||
|
Input: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
Fg: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
Bg: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
ListFg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
ListBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
AltBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
SelectedFg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
SelectedBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
SelectedMatch: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
DarkBg: ColorAttr{251, AttrUndefined},
|
||||||
|
Prompt: ColorAttr{25, AttrUndefined},
|
||||||
|
Match: ColorAttr{66, AttrUndefined},
|
||||||
|
Current: ColorAttr{237, AttrUndefined},
|
||||||
|
CurrentMatch: ColorAttr{23, AttrUndefined},
|
||||||
|
Spinner: ColorAttr{65, AttrUndefined},
|
||||||
|
Info: ColorAttr{101, AttrUndefined},
|
||||||
|
Cursor: ColorAttr{161, AttrUndefined},
|
||||||
|
Marker: ColorAttr{168, AttrUndefined},
|
||||||
|
Header: ColorAttr{31, AttrUndefined},
|
||||||
|
Footer: ColorAttr{31, AttrUndefined},
|
||||||
|
Border: ColorAttr{145, AttrUndefined},
|
||||||
|
BorderLabel: ColorAttr{59, AttrUndefined},
|
||||||
|
Ghost: ColorAttr{colUndefined, Dim},
|
||||||
|
Disabled: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
PreviewFg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
PreviewBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
Gutter: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
PreviewBorder: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
PreviewScrollbar: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
PreviewLabel: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
ListLabel: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
ListBorder: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
Separator: 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},
|
||||||
|
FooterBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
FooterBorder: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
FooterLabel: ColorAttr{colUndefined, AttrUndefined},
|
||||||
GapLine: ColorAttr{colUndefined, AttrUndefined},
|
GapLine: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Nth: ColorAttr{colUndefined, AttrUndefined},
|
Nth: ColorAttr{colUndefined, AttrUndefined},
|
||||||
}
|
}
|
||||||
@@ -968,6 +1032,7 @@ func InitTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool, hasInp
|
|||||||
theme.Cursor = o(baseTheme.Cursor, theme.Cursor)
|
theme.Cursor = o(baseTheme.Cursor, theme.Cursor)
|
||||||
theme.Marker = o(baseTheme.Marker, theme.Marker)
|
theme.Marker = o(baseTheme.Marker, theme.Marker)
|
||||||
theme.Header = o(baseTheme.Header, theme.Header)
|
theme.Header = o(baseTheme.Header, theme.Header)
|
||||||
|
theme.Footer = o(baseTheme.Footer, theme.Footer)
|
||||||
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)
|
||||||
|
|
||||||
@@ -981,6 +1046,7 @@ func InitTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool, hasInp
|
|||||||
theme.SelectedFg = o(theme.ListFg, theme.SelectedFg)
|
theme.SelectedFg = o(theme.ListFg, theme.SelectedFg)
|
||||||
theme.SelectedBg = o(theme.ListBg, theme.SelectedBg)
|
theme.SelectedBg = o(theme.ListBg, theme.SelectedBg)
|
||||||
theme.SelectedMatch = o(theme.Match, theme.SelectedMatch)
|
theme.SelectedMatch = o(theme.Match, theme.SelectedMatch)
|
||||||
|
theme.Ghost = o(theme.Input, theme.Ghost)
|
||||||
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)
|
||||||
theme.PreviewFg = o(theme.Fg, theme.PreviewFg)
|
theme.PreviewFg = o(theme.Fg, theme.PreviewFg)
|
||||||
@@ -1020,6 +1086,10 @@ func InitTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool, hasInp
|
|||||||
theme.HeaderBorder = o(theme.Border, theme.HeaderBorder)
|
theme.HeaderBorder = o(theme.Border, theme.HeaderBorder)
|
||||||
theme.HeaderLabel = o(theme.BorderLabel, theme.HeaderLabel)
|
theme.HeaderLabel = o(theme.BorderLabel, theme.HeaderLabel)
|
||||||
|
|
||||||
|
theme.FooterBg = o(theme.Bg, theme.FooterBg)
|
||||||
|
theme.FooterBorder = o(theme.Border, theme.FooterBorder)
|
||||||
|
theme.FooterLabel = o(theme.BorderLabel, theme.FooterLabel)
|
||||||
|
|
||||||
initPalette(theme)
|
initPalette(theme)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1037,7 +1107,8 @@ func initPalette(theme *ColorTheme) {
|
|||||||
ColNormal = pair(theme.ListFg, theme.ListBg)
|
ColNormal = pair(theme.ListFg, theme.ListBg)
|
||||||
ColSelected = pair(theme.SelectedFg, theme.SelectedBg)
|
ColSelected = pair(theme.SelectedFg, theme.SelectedBg)
|
||||||
ColInput = pair(theme.Input, theme.InputBg)
|
ColInput = pair(theme.Input, theme.InputBg)
|
||||||
ColDisabled = pair(theme.Disabled, theme.ListBg)
|
ColGhost = pair(theme.Ghost, theme.InputBg)
|
||||||
|
ColDisabled = pair(theme.Disabled, theme.InputBg)
|
||||||
ColMatch = pair(theme.Match, theme.ListBg)
|
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)
|
||||||
@@ -1072,6 +1143,9 @@ func initPalette(theme *ColorTheme) {
|
|||||||
ColHeader = pair(theme.Header, theme.HeaderBg)
|
ColHeader = pair(theme.Header, theme.HeaderBg)
|
||||||
ColHeaderBorder = pair(theme.HeaderBorder, theme.HeaderBg)
|
ColHeaderBorder = pair(theme.HeaderBorder, theme.HeaderBg)
|
||||||
ColHeaderLabel = pair(theme.HeaderLabel, theme.HeaderBg)
|
ColHeaderLabel = pair(theme.HeaderLabel, theme.HeaderBg)
|
||||||
|
ColFooter = pair(theme.Footer, theme.FooterBg)
|
||||||
|
ColFooterBorder = pair(theme.FooterBorder, theme.FooterBg)
|
||||||
|
ColFooterLabel = pair(theme.FooterLabel, theme.FooterBg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runeWidth(r rune) int {
|
func runeWidth(r rune) int {
|
||||||
|
@@ -184,6 +184,12 @@ func (chars *Chars) TrailingWhitespaces() int {
|
|||||||
return whitespaces
|
return whitespaces
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (chars *Chars) TrimTrailingWhitespaces(maxIndex int) {
|
||||||
|
whitespaces := chars.TrailingWhitespaces()
|
||||||
|
end := len(chars.slice) - whitespaces
|
||||||
|
chars.slice = chars.slice[0:Max(end, maxIndex)]
|
||||||
|
}
|
||||||
|
|
||||||
func (chars *Chars) TrimSuffix(runes []rune) {
|
func (chars *Chars) TrimSuffix(runes []rune) {
|
||||||
lastIdx := len(chars.slice)
|
lastIdx := len(chars.slice)
|
||||||
firstIdx := lastIdx - len(runes)
|
firstIdx := lastIdx - len(runes)
|
||||||
@@ -289,9 +295,10 @@ func (chars *Chars) Lines(multiLine bool, maxLines int, wrapCols int, wrapSignWi
|
|||||||
line = line[:len(line)-1]
|
line = line[:len(line)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasWrapSign := false
|
||||||
for {
|
for {
|
||||||
cols := wrapCols
|
cols := wrapCols
|
||||||
if len(wrapped) > 0 {
|
if hasWrapSign {
|
||||||
cols -= wrapSignWidth
|
cols -= wrapSignWidth
|
||||||
}
|
}
|
||||||
_, overflowIdx := RunesWidth(line, 0, tabstop, cols)
|
_, overflowIdx := RunesWidth(line, 0, tabstop, cols)
|
||||||
@@ -304,9 +311,11 @@ func (chars *Chars) Lines(multiLine bool, maxLines int, wrapCols int, wrapSignWi
|
|||||||
return wrapped, true
|
return wrapped, true
|
||||||
}
|
}
|
||||||
wrapped = append(wrapped, line[:overflowIdx])
|
wrapped = append(wrapped, line[:overflowIdx])
|
||||||
|
hasWrapSign = true
|
||||||
line = line[overflowIdx:]
|
line = line[overflowIdx:]
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
hasWrapSign = false
|
||||||
|
|
||||||
// Restore trailing '\n'
|
// Restore trailing '\n'
|
||||||
if newline {
|
if newline {
|
||||||
|
@@ -76,7 +76,7 @@ func TestCharsLines(t *testing.T) {
|
|||||||
check(true, 100, 3, 1, 1, 8, false)
|
check(true, 100, 3, 1, 1, 8, false)
|
||||||
|
|
||||||
// With wrap sign (3 + 2)
|
// With wrap sign (3 + 2)
|
||||||
check(true, 100, 3, 2, 1, 12, false)
|
check(true, 100, 3, 2, 1, 10, false)
|
||||||
|
|
||||||
// With wrap sign (3 + 2) and no multi-line
|
// With wrap sign (3 + 2) and no multi-line
|
||||||
check(false, 100, 3, 2, 1, 13, false)
|
check(false, 100, 3, 2, 1, 13, false)
|
||||||
|
39
src/util/concurrent_set.go
Normal file
39
src/util/concurrent_set.go
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import "sync"
|
||||||
|
|
||||||
|
// ConcurrentSet is a thread-safe set implementation.
|
||||||
|
type ConcurrentSet[T comparable] struct {
|
||||||
|
lock sync.RWMutex
|
||||||
|
items map[T]struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewConcurrentSet creates a new ConcurrentSet.
|
||||||
|
func NewConcurrentSet[T comparable]() *ConcurrentSet[T] {
|
||||||
|
return &ConcurrentSet[T]{
|
||||||
|
items: make(map[T]struct{}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add adds an item to the set.
|
||||||
|
func (s *ConcurrentSet[T]) Add(item T) {
|
||||||
|
s.lock.Lock()
|
||||||
|
defer s.lock.Unlock()
|
||||||
|
s.items[item] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove removes an item from the set.
|
||||||
|
func (s *ConcurrentSet[T]) Remove(item T) {
|
||||||
|
s.lock.Lock()
|
||||||
|
defer s.lock.Unlock()
|
||||||
|
delete(s.items, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForEach iterates over each item in the set and applies the provided function.
|
||||||
|
func (s *ConcurrentSet[T]) ForEach(fn func(item T)) {
|
||||||
|
s.lock.RLock()
|
||||||
|
defer s.lock.RUnlock()
|
||||||
|
for item := range s.items {
|
||||||
|
fn(item)
|
||||||
|
}
|
||||||
|
}
|
@@ -6,7 +6,7 @@ import "sync"
|
|||||||
type EventType int
|
type EventType int
|
||||||
|
|
||||||
// Events is a type that associates EventType to any data
|
// Events is a type that associates EventType to any data
|
||||||
type Events map[EventType]interface{}
|
type Events map[EventType]any
|
||||||
|
|
||||||
// EventBox is used for coordinating events
|
// EventBox is used for coordinating events
|
||||||
type EventBox struct {
|
type EventBox struct {
|
||||||
@@ -36,7 +36,7 @@ func (b *EventBox) Wait(callback func(*Events)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set turns on the event type on the box
|
// Set turns on the event type on the box
|
||||||
func (b *EventBox) Set(event EventType, value interface{}) {
|
func (b *EventBox) Set(event EventType, value any) {
|
||||||
b.cond.L.Lock()
|
b.cond.L.Lock()
|
||||||
b.events[event] = value
|
b.events[event] = value
|
||||||
if _, found := b.ignore[event]; !found {
|
if _, found := b.ignore[event]; !found {
|
||||||
|
@@ -97,24 +97,12 @@ func Min32(first int32, second int32) int32 {
|
|||||||
|
|
||||||
// Constrain32 limits the given 32-bit integer with the upper and lower bounds
|
// Constrain32 limits the given 32-bit integer with the upper and lower bounds
|
||||||
func Constrain32(val int32, min int32, max int32) int32 {
|
func Constrain32(val int32, min int32, max int32) int32 {
|
||||||
if val < min {
|
return Max32(Min32(val, max), min)
|
||||||
return min
|
|
||||||
}
|
|
||||||
if val > max {
|
|
||||||
return max
|
|
||||||
}
|
|
||||||
return val
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Constrain limits the given integer with the upper and lower bounds
|
// Constrain limits the given integer with the upper and lower bounds
|
||||||
func Constrain(val int, min int, max int) int {
|
func Constrain(val int, min int, max int) int {
|
||||||
if val < min {
|
return Max(Min(val, max), min)
|
||||||
return min
|
|
||||||
}
|
|
||||||
if val > max {
|
|
||||||
return max
|
|
||||||
}
|
|
||||||
return val
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func AsUint16(val int) uint16 {
|
func AsUint16(val int) uint16 {
|
||||||
|
@@ -238,6 +238,11 @@ class TestCore < TestInteractive
|
|||||||
assert_equal %w[5555 55], fzf_output_lines
|
assert_equal %w[5555 55], fzf_output_lines
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_select_1_accept_nth
|
||||||
|
tmux.send_keys "seq 1 100 | #{fzf(:with_nth, '..,..', :print_query, :q, 5555, :'1', :accept_nth, '"{1} // {1}"')}", :Enter
|
||||||
|
assert_equal ['5555', '55 // 55'], fzf_output_lines
|
||||||
|
end
|
||||||
|
|
||||||
def test_exit_0
|
def test_exit_0
|
||||||
tmux.send_keys "seq 1 100 | #{fzf(:with_nth, '..,..', :print_query, :q, 555_555, :'0')}", :Enter
|
tmux.send_keys "seq 1 100 | #{fzf(:with_nth, '..,..', :print_query, :q, 555_555, :'0')}", :Enter
|
||||||
assert_equal %w[555555], fzf_output_lines
|
assert_equal %w[555555], fzf_output_lines
|
||||||
@@ -827,6 +832,24 @@ class TestCore < TestInteractive
|
|||||||
tmux.until { |lines| assert(lines.any? { it.include?('jump cancelled at 3') }) }
|
tmux.until { |lines| assert(lines.any? { it.include?('jump cancelled at 3') }) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_jump_no_pointer
|
||||||
|
tmux.send_keys "seq 100 | #{FZF} --pointer= --jump-labels 12345 --bind ctrl-j:jump", :Enter
|
||||||
|
tmux.until { |lines| assert_equal 100, lines.match_count }
|
||||||
|
tmux.send_keys 'C-j'
|
||||||
|
tmux.until { |lines| assert_equal '5 5', lines[-7] }
|
||||||
|
tmux.send_keys 'C-c'
|
||||||
|
tmux.until { |lines| assert_equal ' 5', lines[-7] }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_jump_no_pointer_no_marker
|
||||||
|
tmux.send_keys "seq 100 | #{FZF} --pointer= --marker= --jump-labels 12345 --bind ctrl-j:jump", :Enter
|
||||||
|
tmux.until { |lines| assert_equal 100, lines.match_count }
|
||||||
|
tmux.send_keys 'C-j'
|
||||||
|
tmux.until { |lines| assert_equal '55', lines[-7] }
|
||||||
|
tmux.send_keys 'C-c'
|
||||||
|
tmux.until { |lines| assert_equal '5', lines[-7] }
|
||||||
|
end
|
||||||
|
|
||||||
def test_pointer
|
def test_pointer
|
||||||
tmux.send_keys "seq 10 | #{fzf("--pointer '>>'")}", :Enter
|
tmux.send_keys "seq 10 | #{fzf("--pointer '>>'")}", :Enter
|
||||||
# Assert that specified pointer is displayed
|
# Assert that specified pointer is displayed
|
||||||
@@ -1392,6 +1415,11 @@ class TestCore < TestInteractive
|
|||||||
tmux.until { assert_match(%r{ --1/10000/10000-- *$}, it[-1]) }
|
tmux.until { assert_match(%r{ --1/10000/10000-- *$}, it[-1]) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_info_command_inline_right_no_ansi
|
||||||
|
tmux.send_keys(%(seq 10000 | #{FZF} --info-command 'echo -e "--$FZF_POS/$FZF_INFO--"' --info inline-right), :Enter)
|
||||||
|
tmux.until { assert_match(%r{ --1/10000/10000-- *$}, it[-1]) }
|
||||||
|
end
|
||||||
|
|
||||||
def test_info_command_and_focus
|
def test_info_command_and_focus
|
||||||
tmux.send_keys(%(seq 100 | #{FZF} --separator x --info-command 'echo $FZF_POS' --bind focus:clear-query), :Enter)
|
tmux.send_keys(%(seq 100 | #{FZF} --separator x --info-command 'echo $FZF_POS' --bind focus:clear-query), :Enter)
|
||||||
tmux.until { assert_match(/^ 1 xx/, it[-2]) }
|
tmux.until { assert_match(/^ 1 xx/, it[-2]) }
|
||||||
@@ -1609,14 +1637,16 @@ class TestCore < TestInteractive
|
|||||||
end
|
end
|
||||||
|
|
||||||
def test_env_vars
|
def test_env_vars
|
||||||
def to_vars(lines)
|
def env_vars
|
||||||
lines.select { it.start_with?('FZF_') }.to_h do
|
return {} unless File.exist?(tempname)
|
||||||
key, val = it.split('=', 2)
|
|
||||||
|
File.readlines(tempname).select { it.start_with?('FZF_') }.to_h do
|
||||||
|
key, val = it.chomp.split('=', 2)
|
||||||
[key.to_sym, val]
|
[key.to_sym, val]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
tmux.send_keys %(seq 100 | #{FZF} --multi --reverse --preview-window up,99%,noborder --preview 'env | grep ^FZF_ | sort' --no-input --bind enter:show-input+refresh-preview,space:disable-search+refresh-preview), :Enter
|
tmux.send_keys %(seq 100 | #{FZF} --multi --reverse --preview-window 0 --preview 'env | grep ^FZF_ | sort > #{tempname}' --no-input --bind enter:show-input+refresh-preview,space:disable-search+refresh-preview), :Enter
|
||||||
expected = {
|
expected = {
|
||||||
FZF_TOTAL_COUNT: '100',
|
FZF_TOTAL_COUNT: '100',
|
||||||
FZF_MATCH_COUNT: '100',
|
FZF_MATCH_COUNT: '100',
|
||||||
@@ -1625,31 +1655,32 @@ class TestCore < TestInteractive
|
|||||||
FZF_KEY: '',
|
FZF_KEY: '',
|
||||||
FZF_POS: '1',
|
FZF_POS: '1',
|
||||||
FZF_QUERY: '',
|
FZF_QUERY: '',
|
||||||
FZF_PROMPT: '>',
|
FZF_POINTER: '>',
|
||||||
|
FZF_PROMPT: '> ',
|
||||||
FZF_INPUT_STATE: 'hidden'
|
FZF_INPUT_STATE: 'hidden'
|
||||||
}
|
}
|
||||||
tmux.until do |lines|
|
tmux.until do
|
||||||
assert_equal expected, to_vars(lines).slice(*expected.keys)
|
assert_equal expected, env_vars.slice(*expected.keys)
|
||||||
end
|
end
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
tmux.until do |lines|
|
tmux.until do
|
||||||
expected.merge!(FZF_INPUT_STATE: 'enabled', FZF_ACTION: 'show-input', FZF_KEY: 'enter')
|
expected.merge!(FZF_INPUT_STATE: 'enabled', FZF_ACTION: 'show-input', FZF_KEY: 'enter')
|
||||||
assert_equal expected, to_vars(lines).slice(*expected.keys)
|
assert_equal expected, env_vars.slice(*expected.keys)
|
||||||
end
|
end
|
||||||
tmux.send_keys :Tab, :Tab
|
tmux.send_keys :Tab, :Tab
|
||||||
tmux.until do |lines|
|
tmux.until do
|
||||||
expected.merge!(FZF_ACTION: 'toggle-down', FZF_KEY: 'tab', FZF_POS: '3', FZF_SELECT_COUNT: '2')
|
expected.merge!(FZF_ACTION: 'toggle-down', FZF_KEY: 'tab', FZF_POS: '3', FZF_SELECT_COUNT: '2')
|
||||||
assert_equal expected, to_vars(lines).slice(*expected.keys)
|
assert_equal expected, env_vars.slice(*expected.keys)
|
||||||
end
|
end
|
||||||
tmux.send_keys '99'
|
tmux.send_keys '99'
|
||||||
tmux.until do |lines|
|
tmux.until do
|
||||||
expected.merge!(FZF_ACTION: 'char', FZF_KEY: '9', FZF_QUERY: '99', FZF_MATCH_COUNT: '1', FZF_POS: '1')
|
expected.merge!(FZF_ACTION: 'char', FZF_KEY: '9', FZF_QUERY: '99', FZF_MATCH_COUNT: '1', FZF_POS: '1')
|
||||||
assert_equal expected, to_vars(lines).slice(*expected.keys)
|
assert_equal expected, env_vars.slice(*expected.keys)
|
||||||
end
|
end
|
||||||
tmux.send_keys :Space
|
tmux.send_keys :Space
|
||||||
tmux.until do |lines|
|
tmux.until do
|
||||||
expected.merge!(FZF_INPUT_STATE: 'disabled', FZF_ACTION: 'disable-search', FZF_KEY: 'space')
|
expected.merge!(FZF_INPUT_STATE: 'disabled', FZF_ACTION: 'disable-search', FZF_KEY: 'space')
|
||||||
assert_equal expected, to_vars(lines).slice(*expected.keys)
|
assert_equal expected, env_vars.slice(*expected.keys)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1773,12 +1804,265 @@ class TestCore < TestInteractive
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_accept_nth_template
|
def test_accept_nth_regex_delimiter_strip_last
|
||||||
tmux.send_keys %(echo "foo ,bar,baz" | #{FZF} -d, --accept-nth '1st: {1}, 3rd: {3}, 2nd: {2}' --sync --bind start:accept > #{tempname}), :Enter
|
tmux.send_keys %((echo "foo:,bar:,baz"; echo "foo:,bar:,baz:,qux:,") | #{FZF} --multi --delimiter='[:,]+' --accept-nth 2.. --sync --bind 'load:select-all+accept' > #{tempname}), :Enter
|
||||||
wait do
|
wait do
|
||||||
assert_path_exists tempname
|
assert_path_exists tempname
|
||||||
# Last delimiter and the whitespaces are removed
|
# Last delimiter and the whitespaces are removed
|
||||||
assert_equal ['1st: foo, 3rd: baz, 2nd: bar'], File.readlines(tempname, chomp: true)
|
assert_equal ['bar:,baz', 'bar:,baz:,qux'], File.readlines(tempname, chomp: true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_accept_nth_template
|
||||||
|
tmux.send_keys %(echo "foo ,bar,baz" | #{FZF} -d, --accept-nth '[{n}] 1st: {1}, 3rd: {3}, 2nd: {2}' --sync --bind start:accept > #{tempname}), :Enter
|
||||||
|
wait do
|
||||||
|
assert_path_exists tempname
|
||||||
|
# Last delimiter and the whitespaces are removed
|
||||||
|
assert_equal ['[0] 1st: foo, 3rd: baz, 2nd: bar'], File.readlines(tempname, chomp: true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_ghost
|
||||||
|
tmux.send_keys %(seq 100 | #{FZF} --prompt 'X ' --ghost 'Type in query ...' --bind 'space:change-ghost:Y Z' --bind 'enter:transform-ghost:echo Z Y'), :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 100, lines.match_count
|
||||||
|
assert_includes lines, 'X Type in query ...'
|
||||||
|
end
|
||||||
|
tmux.send_keys '100'
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 1, lines.match_count
|
||||||
|
assert_includes lines, 'X 100'
|
||||||
|
end
|
||||||
|
tmux.send_keys 'C-u'
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 100, lines.match_count
|
||||||
|
assert_includes lines, 'X Type in query ...'
|
||||||
|
end
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until { |lines| assert_includes lines, 'X Y Z' }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { |lines| assert_includes lines, 'X Z Y' }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_ghost_inline
|
||||||
|
tmux.send_keys %(seq 100 | #{FZF} --info 'inline: Y' --no-separator --prompt 'X ' --ghost 'Type in query ...'), :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_includes lines, 'X Type in query ... Y100/100'
|
||||||
|
end
|
||||||
|
tmux.send_keys '100'
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_includes lines, 'X 100 Y1/100'
|
||||||
|
end
|
||||||
|
tmux.send_keys 'C-u'
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_includes lines, 'X Type in query ... Y100/100'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_offset_middle
|
||||||
|
tmux.send_keys %(seq 1000 | #{FZF} --sync --no-input --reverse --height 5 --scroll-off 0 --bind space:offset-middle), :Enter
|
||||||
|
line = nil
|
||||||
|
tmux.until { |lines| line = lines.index('> 1') }
|
||||||
|
tmux.send_keys :PgDn
|
||||||
|
tmux.until { |lines| assert_includes lines[line + 4], '> 5' }
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until { |lines| assert_includes lines[line + 2], '> 5' }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_no_input_query
|
||||||
|
tmux.send_keys %(seq 1000 | #{FZF} --no-input --query 555 --bind space:toggle-input), :Enter
|
||||||
|
tmux.until { |lines| assert_includes lines, '> 555' }
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 1, lines.match_count
|
||||||
|
assert_includes lines, '> 555'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_no_input_change_query
|
||||||
|
tmux.send_keys %(seq 1000 | #{FZF} --multi --query 999 --no-input --bind 'enter:show-input+change-query(555)+hide-input,space:change-query(555)+select'), :Enter
|
||||||
|
tmux.until { |lines| assert_includes lines, '> 999' }
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_includes lines, '>>999'
|
||||||
|
refute_includes lines, '> 555'
|
||||||
|
end
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
refute_includes lines, '>>999'
|
||||||
|
assert_includes lines, '> 555'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_search_override_query_in_no_input_mode
|
||||||
|
tmux.send_keys %(seq 1000 | #{FZF} --sync --no-input --bind 'enter:show-input+change-query(555)+hide-input+search(999),space:search(111)+show-input+change-query(777)'), :Enter
|
||||||
|
tmux.until { |lines| assert_includes lines, '> 1' }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { |lines| assert_includes lines, '> 999' }
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until { |lines| assert_includes lines, '> 777' }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_change_pointer
|
||||||
|
tmux.send_keys %(seq 2 | #{FZF} --bind 'a:change-pointer(a),b:change-pointer(bb),c:change-pointer(),d:change-pointer(ddd)'), :Enter
|
||||||
|
tmux.until { |lines| assert_includes lines, '> 1' }
|
||||||
|
tmux.send_keys 'a'
|
||||||
|
tmux.until { |lines| assert_includes lines, 'a 1' }
|
||||||
|
tmux.send_keys 'b'
|
||||||
|
tmux.until { |lines| assert_includes lines, 'bb 1' }
|
||||||
|
tmux.send_keys 'c'
|
||||||
|
tmux.until { |lines| assert_includes lines, ' 1' }
|
||||||
|
tmux.send_keys 'd'
|
||||||
|
tmux.until { |lines| refute_includes lines, 'ddd 1' }
|
||||||
|
tmux.send_keys :Up
|
||||||
|
tmux.until { |lines| assert_includes lines, ' 2' }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_transform_pointer
|
||||||
|
tmux.send_keys %(seq 2 | #{FZF} --bind 'a:transform-pointer(echo a),b:transform-pointer(echo bb),c:transform-pointer(),d:transform-pointer(echo ddd)'), :Enter
|
||||||
|
tmux.until { |lines| assert_includes lines, '> 1' }
|
||||||
|
tmux.send_keys 'a'
|
||||||
|
tmux.until { |lines| assert_includes lines, 'a 1' }
|
||||||
|
tmux.send_keys 'b'
|
||||||
|
tmux.until { |lines| assert_includes lines, 'bb 1' }
|
||||||
|
tmux.send_keys 'c'
|
||||||
|
tmux.until { |lines| assert_includes lines, ' 1' }
|
||||||
|
tmux.send_keys 'd'
|
||||||
|
tmux.until { |lines| refute_includes lines, 'ddd 1' }
|
||||||
|
tmux.send_keys :Up
|
||||||
|
tmux.until { |lines| assert_includes lines, ' 2' }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_change_header_on_header_window
|
||||||
|
tmux.send_keys %(seq 100 | #{FZF} --list-border --input-border --bind 'start:change-header(foo),space:change-header(bar)'), :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert lines.any_include?('100/100')
|
||||||
|
assert lines.any_include?('foo')
|
||||||
|
end
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until { |lines| assert lines.any_include?('bar') }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_trailing_new_line
|
||||||
|
tmux.send_keys %(echo -en "foo\n" | fzf --read0 --no-multi-line), :Enter
|
||||||
|
tmux.until { |lines| assert_includes lines, '> foo␊' }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_async_transform
|
||||||
|
time = Time.now
|
||||||
|
tmux.send_keys %(
|
||||||
|
seq 100 | #{FZF} --style full --border --preview : \
|
||||||
|
--bind 'focus:bg-transform-header(sleep 0.5; echo th.)' \
|
||||||
|
--bind 'focus:+bg-transform-footer(sleep 0.5; echo tf.)' \
|
||||||
|
--bind 'focus:+bg-transform-border-label(sleep 0.5; echo tbl.)' \
|
||||||
|
--bind "focus:+bg-transform-preview-label(sleep 0.5; echo tpl.)" \
|
||||||
|
--bind 'focus:+bg-transform-input-label(sleep 0.5; echo til.)' \
|
||||||
|
--bind 'focus:+bg-transform-list-label(sleep 0.5; echo tll.)' \
|
||||||
|
--bind 'focus:+bg-transform-header-label(sleep 0.5; echo thl.)' \
|
||||||
|
--bind 'focus:+bg-transform-footer-label(sleep 0.5; echo tfl.)' \
|
||||||
|
--bind 'focus:+bg-transform-prompt(sleep 0.5; echo tp.)' \
|
||||||
|
--bind 'focus:+bg-transform-ghost(sleep 0.5; echo tg.)'
|
||||||
|
).strip, :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert lines.any_include?('100/100')
|
||||||
|
%w[th tf tbl tpl til tll thl tfl tp tg].each do
|
||||||
|
assert lines.any_include?("#{it}.")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elapsed = Time.now - time
|
||||||
|
assert elapsed < 2
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_bg_cancel
|
||||||
|
tmux.send_keys %(seq 0 1 | #{FZF} --bind 'space:bg-cancel+bg-transform-header(sleep {}; echo [{}])'), :Enter
|
||||||
|
tmux.until { assert_equal 2, it.match_count }
|
||||||
|
tmux.send_keys '1'
|
||||||
|
tmux.until { assert_equal 1, it.match_count }
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.send_keys :BSpace
|
||||||
|
tmux.until { assert_equal 2, it.match_count }
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until { |lines| assert lines.any_include?('[0]') }
|
||||||
|
sleep 2
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert lines.any_include?('[0]')
|
||||||
|
refute lines.any_include?('[1]')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_render_order
|
||||||
|
tmux.send_keys %(seq 100 | #{FZF} --bind='focus:preview(echo boom)+change-footer(bam)'), :Enter
|
||||||
|
tmux.until { assert_equal 100, it.match_count }
|
||||||
|
tmux.until { assert it.any_include?('boom') }
|
||||||
|
tmux.until { assert it.any_include?('bam') }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_multi_event
|
||||||
|
tmux.send_keys %(seq 100 | #{FZF} --multi --bind 'multi:transform-footer:(( FZF_SELECT_COUNT )) && echo "Selected $FZF_SELECT_COUNT item(s)"'), :Enter
|
||||||
|
tmux.until { assert_equal 100, it.match_count }
|
||||||
|
tmux.send_keys :Tab
|
||||||
|
tmux.until { assert_equal 1, it.select_count }
|
||||||
|
tmux.until { assert it.any_include?('Selected 1 item(s)') }
|
||||||
|
tmux.send_keys :Tab
|
||||||
|
tmux.until { assert_equal 0, it.select_count }
|
||||||
|
tmux.until { refute it.any_include?('Selected') }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_preserve_selection_on_revision_bump
|
||||||
|
tmux.send_keys %(seq 100 | #{FZF} --multi --sync --query "'1" --bind 'a:select-all+change-header(pressed a),b:change-header(pressed b)+change-nth(1),c:exclude'), :Enter
|
||||||
|
tmux.until do
|
||||||
|
assert_equal 20, it.match_count
|
||||||
|
assert_equal 0, it.select_count
|
||||||
|
end
|
||||||
|
tmux.send_keys :a
|
||||||
|
tmux.until do
|
||||||
|
assert_equal 20, it.match_count
|
||||||
|
assert_equal 20, it.select_count
|
||||||
|
assert it.any_include?('pressed a')
|
||||||
|
end
|
||||||
|
tmux.send_keys :b
|
||||||
|
tmux.until do
|
||||||
|
assert_equal 20, it.match_count
|
||||||
|
assert_equal 20, it.select_count
|
||||||
|
refute it.any_include?('pressed a')
|
||||||
|
assert it.any_include?('pressed b')
|
||||||
|
end
|
||||||
|
tmux.send_keys :a
|
||||||
|
tmux.until do
|
||||||
|
assert_equal 20, it.match_count
|
||||||
|
assert_equal 20, it.select_count
|
||||||
|
assert it.any_include?('pressed a')
|
||||||
|
refute it.any_include?('pressed b')
|
||||||
|
end
|
||||||
|
tmux.send_keys :c
|
||||||
|
tmux.until do
|
||||||
|
assert_equal 19, it.match_count
|
||||||
|
assert_equal 19, it.select_count
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_trigger
|
||||||
|
tmux.send_keys %(seq 100 | #{FZF} --bind 'a:up+trigger(a),b:trigger(a,a,b,a)'), :Enter
|
||||||
|
tmux.until { assert_equal 100, it.match_count }
|
||||||
|
tmux.until { |lines| assert_includes lines, '> 1' }
|
||||||
|
tmux.send_keys :a
|
||||||
|
tmux.until { |lines| assert_includes lines, '> 3' }
|
||||||
|
tmux.send_keys :b
|
||||||
|
tmux.until { |lines| assert_includes lines, '> 9' }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_change_nth_unset_default
|
||||||
|
tmux.send_keys %(echo foo bar | #{FZF} --nth 2 --query fb --bind space:change-nth:), :Enter
|
||||||
|
tmux.until do
|
||||||
|
assert_equal 1, it.item_count
|
||||||
|
assert_equal 0, it.match_count
|
||||||
|
end
|
||||||
|
|
||||||
|
tmux.send_keys :Space
|
||||||
|
|
||||||
|
tmux.until do
|
||||||
|
assert_equal 1, it.item_count
|
||||||
|
assert_equal 1, it.match_count
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@@ -304,11 +304,11 @@ class TestFilter < TestBase
|
|||||||
def test_boundary_match
|
def test_boundary_match
|
||||||
# Underscore boundaries should be ranked lower
|
# Underscore boundaries should be ranked lower
|
||||||
{
|
{
|
||||||
default: [' x '] + %w[/x/ [x] -x- -x_ _x- _x_],
|
default: [' xyz '] + %w[/xyz/ [xyz] -xyz- -xyz_ _xyz- _xyz_],
|
||||||
path: ['/x/', ' x '] + %w[[x] -x- -x_ _x- _x_],
|
path: ['/xyz/', ' xyz '] + %w[[xyz] -xyz- -xyz_ _xyz- _xyz_],
|
||||||
history: ['[x]', '-x-', ' x '] + %w[/x/ -x_ _x- _x_]
|
history: ['[xyz]', '-xyz-', ' xyz '] + %w[/xyz/ -xyz_ _xyz- _xyz_]
|
||||||
}.each do |scheme, expected|
|
}.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)
|
result = `printf -- 'xxyzx\n-xxyz\nxyzx-\n_xyz_\n_xyz-\n-xyz_\n[xyz]\n-xyz-\n xyz \n/xyz/\n' | #{FZF} -f"'xyz'" --scheme=#{scheme}`.lines(chomp: true)
|
||||||
assert_equal expected, result
|
assert_equal expected, result
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@@ -39,11 +39,11 @@ class TestLayout < TestInteractive
|
|||||||
tmux.send_keys "seq 1000 | #{FZF} --header foobar --header-lines 3 --header-first", :Enter
|
tmux.send_keys "seq 1000 | #{FZF} --header foobar --header-lines 3 --header-first", :Enter
|
||||||
block = <<~OUTPUT
|
block = <<~OUTPUT
|
||||||
> 4
|
> 4
|
||||||
997/997
|
|
||||||
>
|
|
||||||
3
|
3
|
||||||
2
|
2
|
||||||
1
|
1
|
||||||
|
997/997
|
||||||
|
>
|
||||||
foobar
|
foobar
|
||||||
OUTPUT
|
OUTPUT
|
||||||
tmux.until { assert_block(block, it) }
|
tmux.until { assert_block(block, it) }
|
||||||
@@ -53,10 +53,10 @@ class TestLayout < TestInteractive
|
|||||||
tmux.send_keys "seq 1000 | #{FZF} --header foobar --header-lines 3 --header-first --reverse --inline-info", :Enter
|
tmux.send_keys "seq 1000 | #{FZF} --header foobar --header-lines 3 --header-first --reverse --inline-info", :Enter
|
||||||
block = <<~OUTPUT
|
block = <<~OUTPUT
|
||||||
foobar
|
foobar
|
||||||
|
> < 997/997
|
||||||
1
|
1
|
||||||
2
|
2
|
||||||
3
|
3
|
||||||
> < 997/997
|
|
||||||
> 4
|
> 4
|
||||||
OUTPUT
|
OUTPUT
|
||||||
tmux.until { assert_block(block, it) }
|
tmux.until { assert_block(block, it) }
|
||||||
@@ -148,10 +148,10 @@ class TestLayout < TestInteractive
|
|||||||
│
|
│
|
||||||
│ 4
|
│ 4
|
||||||
│ > 3
|
│ > 3
|
||||||
│ 2/2
|
|
||||||
│ >
|
|
||||||
│ 2
|
│ 2
|
||||||
│ 1
|
│ 1
|
||||||
|
│ 2/2
|
||||||
|
│ >
|
||||||
│ foo
|
│ foo
|
||||||
╰───────
|
╰───────
|
||||||
OUTPUT
|
OUTPUT
|
||||||
@@ -609,11 +609,11 @@ class TestLayout < TestInteractive
|
|||||||
│ 4
|
│ 4
|
||||||
│ > 3
|
│ > 3
|
||||||
╰──────────
|
╰──────────
|
||||||
|
2
|
||||||
|
1
|
||||||
98/98 ─
|
98/98 ─
|
||||||
>
|
>
|
||||||
╭──────────
|
╭──────────
|
||||||
│ 2
|
|
||||||
│ 1
|
|
||||||
│ hello
|
│ hello
|
||||||
╰──────────
|
╰──────────
|
||||||
BLOCK
|
BLOCK
|
||||||
@@ -666,12 +666,12 @@ class TestLayout < TestInteractive
|
|||||||
│ 4
|
│ 4
|
||||||
│ > 3
|
│ > 3
|
||||||
╰──────────
|
╰──────────
|
||||||
|
98/98 ─
|
||||||
|
>
|
||||||
╔══════════
|
╔══════════
|
||||||
║ 2
|
║ 2
|
||||||
║ 1
|
║ 1
|
||||||
╚══════════
|
╚══════════
|
||||||
98/98 ─
|
|
||||||
>
|
|
||||||
BLOCK
|
BLOCK
|
||||||
tmux.until { assert_block(block1, it) }
|
tmux.until { assert_block(block1, it) }
|
||||||
|
|
||||||
@@ -979,6 +979,128 @@ class TestLayout < TestInteractive
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_layout_default_with_footer
|
||||||
|
prefix = %[
|
||||||
|
seq 3 | #{FZF} --no-list-border --height ~100% \
|
||||||
|
--border sharp --footer "$(seq 201 202)" --footer-label FOOT --footer-label-pos 3 \
|
||||||
|
--header-label HEAD --header-label-pos 3:bottom \
|
||||||
|
--bind 'space:transform-footer-label(echo foot)+change-header-label(head)'
|
||||||
|
].strip + ' '
|
||||||
|
suffixes = [
|
||||||
|
%(),
|
||||||
|
%[--header "$(seq 101 102)"],
|
||||||
|
%[--header "$(seq 101 102)" --header-first],
|
||||||
|
%[--header "$(seq 101 102)" --header-lines 2],
|
||||||
|
%[--header "$(seq 101 102)" --header-lines 2 --header-first],
|
||||||
|
%[--header "$(seq 101 102)" --header-border sharp],
|
||||||
|
%[--header "$(seq 101 102)" --header-border sharp --header-first],
|
||||||
|
%[--header "$(seq 101 102)" --header-border sharp --header-lines 2],
|
||||||
|
%[--header "$(seq 101 102)" --header-border sharp --header-lines 2 --no-header-lines-border],
|
||||||
|
%[--header "$(seq 101 102)" --header-border sharp --header-lines 2 --header-lines-border none],
|
||||||
|
%[--header "$(seq 101 102)" --header-border sharp --header-lines 2 --header-lines-border sharp],
|
||||||
|
%[--header "$(seq 101 102)" --header-border sharp --header-lines 2 --header-lines-border sharp --header-first --input-border sharp],
|
||||||
|
%[--header "$(seq 101 102)" --header-border sharp --header-lines 2 --header-lines-border sharp --header-first --no-input],
|
||||||
|
%[--header "$(seq 101 102)" --footer-border sharp --input-border line],
|
||||||
|
%[--header "$(seq 101 102)" --style full:sharp --header-first]
|
||||||
|
]
|
||||||
|
output = <<~BLOCK
|
||||||
|
┌──────── ┌──────── ┌──────── ┌──────── ┌──────── ┌──────── ┌──────── ┌──────── ┌──────── ┌──────── ┌──────── ┌───────── ┌──────── ┌──────── ┌─────────
|
||||||
|
│ 201 │ 201 │ 201 │ 201 │ 201 │ 201 │ 201 │ 201 │ 201 │ 201 │ 201 │ 201 │ 201 │ ┌─FOOT─ │ ┌─FOOT──
|
||||||
|
│ 202 │ 202 │ 202 │ 202 │ 202 │ 202 │ 202 │ 202 │ 202 │ 202 │ 202 │ 202 │ 202 │ │ 201 │ │ 201
|
||||||
|
│ ──FOOT─ │ ──FOOT─ │ ──FOOT─ │ ──FOOT─ │ ──FOOT─ │ ──FOOT─ │ ──FOOT─ │ ──FOOT─ │ ──FOOT─ │ ──FOOT─ │ ──FOOT─ │ ──FOOT── │ ──FOOT─ │ │ 202 │ │ 202
|
||||||
|
│ 3 │ 3 │ 3 │ > 3 │ > 3 │ 3 │ 3 │ > 3 │ > 3 │ > 3 │ > 3 │ > 3 │ > 3 │ └────── │ └───────
|
||||||
|
│ 2 │ 2 │ 2 │ 2 │ 2 │ 2 │ 2 │ ┌────── │ ┌────── │ 2 │ ┌────── │ ┌─────── │ ┌────── │ 3 │ ┌───────
|
||||||
|
│ > 1 │ > 1 │ > 1 │ 1 │ 1 │ > 1 │ > 1 │ │ 2 │ │ 2 │ 1 │ │ 2 │ │ 2 │ │ 2 │ 2 │ │ 3
|
||||||
|
│ 3/3 ─ │ 101 │ 3/3 ─ │ 101 │ 1/1 ─ │ ┌────── │ 3/3 ─ │ │ 1 │ │ 1 │ ┌────── │ │ 1 │ │ 1 │ │ 1 │ > 1 │ │ 2
|
||||||
|
│ > │ 102 │ > │ 102 │ > │ │ 101 │ > │ │ 101 │ │ 101 │ │ 101 │ └────── │ └─────── │ └────── │ 101 │ │ > 1
|
||||||
|
└──────── │ 3/3 ─ │ 101 │ 1/1 ─ │ 101 │ │ 102 │ ┌────── │ │ 102 │ │ 102 │ │ 102 │ ┌────── │ ┌─────── │ ┌────── │ 102 │ └───────
|
||||||
|
│ > │ 102 │ > │ 102 │ └─HEAD─ │ │ 101 │ └─HEAD─ │ └─HEAD─ │ └─HEAD─ │ │ 101 │ │ 1/1 │ │ 101 │ ─────── │ ┌───────
|
||||||
|
└──────── └──────── └──────── └──────── │ 3/3 ─ │ │ 102 │ 1/1 ─ │ 1/1 ─ │ 1/1 ─ │ │ 102 │ │ > │ │ 102 │ 3/3 │ │ >
|
||||||
|
│ > │ └─HEAD─ │ > │ > │ > │ └─HEAD─ │ └─────── │ └─HEAD─ │ > │ └───────
|
||||||
|
└──────── └──────── └──────── └──────── └──────── │ 1/1 ─ │ ┌─────── └──────── └──────── │ ┌───────
|
||||||
|
│ > │ │ 101 │ │ 101
|
||||||
|
└──────── │ │ 102 │ │ 102
|
||||||
|
│ └─HEAD── │ └─HEAD──
|
||||||
|
└───────── └─────────
|
||||||
|
BLOCK
|
||||||
|
|
||||||
|
expects = []
|
||||||
|
output.each_line.first.scan(/\S+/) do
|
||||||
|
offset = Regexp.last_match.offset(0)
|
||||||
|
expects << output.lines.filter_map { it[offset[0]...offset[1]]&.strip }.take_while { !it.empty? }.join("\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
suffixes.zip(expects).each do |suffix, block|
|
||||||
|
tmux.send_keys(prefix + suffix, :Enter)
|
||||||
|
tmux.until { assert_block(block, it) }
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until { assert_block(block.downcase, it) }
|
||||||
|
|
||||||
|
teardown
|
||||||
|
setup
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_layout_reverse_list_with_footer
|
||||||
|
prefix = %[
|
||||||
|
seq 3 | #{FZF} --layout reverse-list --no-list-border --height ~100% \
|
||||||
|
--border sharp --footer "$(seq 201 202)" --footer-label FOOT --footer-label-pos 3 \
|
||||||
|
--header-label HEAD --header-label-pos 3:bottom \
|
||||||
|
--bind 'space:transform-footer-label(echo foot)+change-header-label(head)'
|
||||||
|
].strip + ' '
|
||||||
|
suffixes = [
|
||||||
|
%(),
|
||||||
|
%[--header "$(seq 101 102)"],
|
||||||
|
%[--header "$(seq 101 102)" --header-first],
|
||||||
|
%[--header "$(seq 101 102)" --header-lines 2],
|
||||||
|
%[--header "$(seq 101 102)" --header-lines 2 --header-first],
|
||||||
|
%[--header "$(seq 101 102)" --header-border sharp],
|
||||||
|
%[--header "$(seq 101 102)" --header-border sharp --header-first],
|
||||||
|
%[--header "$(seq 101 102)" --header-border sharp --header-lines 2],
|
||||||
|
%[--header "$(seq 101 102)" --header-border sharp --header-lines 2 --header-lines-border sharp],
|
||||||
|
%[--header "$(seq 101 102)" --header-border sharp --header-lines 2 --header-lines-border sharp --header-first --input-border sharp],
|
||||||
|
%[--header "$(seq 101 102)" --header-border sharp --header-lines 2 --header-lines-border sharp --header-first --no-input],
|
||||||
|
%[--header "$(seq 101 102)" --footer-border sharp --input-border line],
|
||||||
|
%[--header "$(seq 101 102)" --style full:sharp --header-first]
|
||||||
|
]
|
||||||
|
output = <<~BLOCK
|
||||||
|
┌──────── ┌──────── ┌──────── ┌──────── ┌──────── ┌──────── ┌──────── ┌──────── ┌──────── ┌───────── ┌──────── ┌──────── ┌─────────
|
||||||
|
│ 201 │ 201 │ 201 │ 201 │ 201 │ 201 │ 201 │ 201 │ 201 │ 201 │ 201 │ ┌─FOOT─ │ ┌─FOOT──
|
||||||
|
│ 202 │ 202 │ 202 │ 202 │ 202 │ 202 │ 202 │ 202 │ 202 │ 202 │ 202 │ │ 201 │ │ 201
|
||||||
|
│ ──FOOT─ │ ──FOOT─ │ ──FOOT─ │ ──FOOT─ │ ──FOOT─ │ ──FOOT─ │ ──FOOT─ │ ──FOOT─ │ ──FOOT─ │ ──FOOT── │ ──FOOT─ │ │ 202 │ │ 202
|
||||||
|
│ > 1 │ > 1 │ > 1 │ 1 │ 1 │ > 1 │ > 1 │ 1 │ ┌────── │ ┌─────── │ ┌────── │ └────── │ └───────
|
||||||
|
│ 2 │ 2 │ 2 │ 2 │ 2 │ 2 │ 2 │ 2 │ │ 1 │ │ 1 │ │ 1 │ > 1 │ ┌───────
|
||||||
|
│ 3 │ 3 │ 3 │ > 3 │ > 3 │ 3 │ 3 │ > 3 │ │ 2 │ │ 2 │ │ 2 │ 2 │ │ > 1
|
||||||
|
│ 3/3 ─ │ 101 │ 3/3 ─ │ 101 │ 1/1 ─ │ ┌────── │ 3/3 ─ │ ┌────── │ └────── │ └─────── │ └────── │ 3 │ │ 2
|
||||||
|
│ > │ 102 │ > │ 102 │ > │ │ 101 │ > │ │ 101 │ > 3 │ > 3 │ > 3 │ 101 │ │ 3
|
||||||
|
└──────── │ 3/3 ─ │ 101 │ 1/1 ─ │ 101 │ │ 102 │ ┌────── │ │ 102 │ ┌────── │ ┌─────── │ ┌────── │ 102 │ └───────
|
||||||
|
│ > │ 102 │ > │ 102 │ └─HEAD─ │ │ 101 │ └─HEAD─ │ │ 101 │ │ 1/1 │ │ 101 │ ─────── │ ┌───────
|
||||||
|
└──────── └──────── └──────── └──────── │ 3/3 ─ │ │ 102 │ 1/1 ─ │ │ 102 │ │ > │ │ 102 │ 3/3 │ │ >
|
||||||
|
│ > │ └─HEAD─ │ > │ └─HEAD─ │ └─────── │ └─HEAD─ │ > │ └───────
|
||||||
|
└──────── └──────── └──────── │ 1/1 ─ │ ┌─────── └──────── └──────── │ ┌───────
|
||||||
|
│ > │ │ 101 │ │ 101
|
||||||
|
└──────── │ │ 102 │ │ 102
|
||||||
|
│ └─HEAD── │ └─HEAD──
|
||||||
|
└───────── └─────────
|
||||||
|
BLOCK
|
||||||
|
|
||||||
|
expects = []
|
||||||
|
output.each_line.first.scan(/\S+/) do
|
||||||
|
offset = Regexp.last_match.offset(0)
|
||||||
|
expects << output.lines.filter_map { it[offset[0]...offset[1]]&.strip }.take_while { !it.empty? }.join("\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
suffixes.zip(expects).each do |suffix, block|
|
||||||
|
tmux.send_keys(prefix + suffix, :Enter)
|
||||||
|
tmux.until { assert_block(block, it) }
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until { assert_block(block.downcase, it) }
|
||||||
|
|
||||||
|
teardown
|
||||||
|
setup
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_change_header_and_label_at_once
|
def test_change_header_and_label_at_once
|
||||||
tmux.send_keys %(seq 10 | #{FZF} --border sharp --header-border sharp --header-label-pos 3 --bind 'focus:change-header(header)+change-header-label(label)'), :Enter
|
tmux.send_keys %(seq 10 | #{FZF} --border sharp --header-border sharp --header-label-pos 3 --bind 'focus:change-header(header)+change-header-label(label)'), :Enter
|
||||||
block = <<~BLOCK
|
block = <<~BLOCK
|
||||||
@@ -991,4 +1113,121 @@ class TestLayout < TestInteractive
|
|||||||
BLOCK
|
BLOCK
|
||||||
tmux.until { assert_block(block, it) }
|
tmux.until { assert_block(block, it) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_label_truncation
|
||||||
|
command = <<~CMD
|
||||||
|
seq 10 | #{FZF} --style full --border --header-lines=1 --preview ':' \\
|
||||||
|
--border-label "#{'b' * 1000}" \\
|
||||||
|
--preview-label "#{'p' * 1000}" \\
|
||||||
|
--header-label "#{'h' * 1000}" \\
|
||||||
|
--header-label "#{'h' * 1000}" \\
|
||||||
|
--input-label "#{'i' * 1000}" \\
|
||||||
|
--list-label "#{'l' * 1000}"
|
||||||
|
CMD
|
||||||
|
writelines(command.lines.map(&:chomp))
|
||||||
|
tmux.send_keys("sh #{tempname}", :Enter)
|
||||||
|
tmux.until do |lines|
|
||||||
|
text = lines.join
|
||||||
|
assert_includes text, 'b··'
|
||||||
|
assert_includes text, 'l··p'
|
||||||
|
assert_includes text, 'p··'
|
||||||
|
assert_includes text, 'h··'
|
||||||
|
assert_includes text, 'i··'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_separator_no_ellipsis
|
||||||
|
tmux.send_keys %(seq 10 | #{FZF} --separator "$(seq 1000 | tr '\\n' ' ')"), :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal 10, lines.match_count
|
||||||
|
refute_includes lines.join, '··'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_header_border_no_pointer_and_marker
|
||||||
|
tmux.send_keys %(seq 10 | #{FZF} --header-lines 1 --header-border sharp --no-list-border --pointer '' --marker ''), :Enter
|
||||||
|
block = <<~BLOCK
|
||||||
|
┌──────
|
||||||
|
│ 1
|
||||||
|
└──────
|
||||||
|
9/9 ─
|
||||||
|
>
|
||||||
|
BLOCK
|
||||||
|
tmux.until { assert_block(block, it) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_combinations
|
||||||
|
skip unless ENV['LONGTEST']
|
||||||
|
|
||||||
|
base = [
|
||||||
|
'--pointer=@',
|
||||||
|
'--exact',
|
||||||
|
'--query=123',
|
||||||
|
'--header="$(seq 101 103)"',
|
||||||
|
'--header-lines=3',
|
||||||
|
'--footer "$(seq 201 203)"',
|
||||||
|
'--preview "echo foobar"'
|
||||||
|
]
|
||||||
|
options = [
|
||||||
|
['--separator==', '--no-separator'],
|
||||||
|
['--info=default', '--info=inline', '--info=inline-right'],
|
||||||
|
['--no-input-border', '--input-border'],
|
||||||
|
['--no-header-border', '--header-border=none', '--header-border'],
|
||||||
|
['--no-header-lines-border', '--header-lines-border'],
|
||||||
|
['--no-footer-border', '--footer-border'],
|
||||||
|
['--no-list-border', '--list-border'],
|
||||||
|
['--preview-window=right', '--preview-window=up', '--preview-window=down', '--preview-window=left'],
|
||||||
|
['--header-first', '--no-header-first'],
|
||||||
|
['--layout=default', '--layout=reverse', '--layout=reverse-list']
|
||||||
|
]
|
||||||
|
# Combination of all options
|
||||||
|
combinations = options[0].product(*options.drop(1))
|
||||||
|
combinations.each_with_index do |combination, index|
|
||||||
|
opts = base + combination
|
||||||
|
command = %(seq 1001 2000 | #{FZF} #{opts.join(' ')})
|
||||||
|
puts "# #{index + 1}/#{combinations.length}\n#{command}"
|
||||||
|
tmux.send_keys command, :Enter
|
||||||
|
tmux.until do |lines|
|
||||||
|
layout = combination.find { it.start_with?('--layout=') }.split('=').last
|
||||||
|
header_first = combination.include?('--header-first')
|
||||||
|
|
||||||
|
# Input
|
||||||
|
input = lines.index { it.include?('> 123') }
|
||||||
|
assert(input)
|
||||||
|
|
||||||
|
# Info
|
||||||
|
info = lines.index { it.include?('11/997') }
|
||||||
|
assert(info)
|
||||||
|
|
||||||
|
assert(layout == 'reverse' ? input <= info : input >= info)
|
||||||
|
|
||||||
|
# List
|
||||||
|
item1 = lines.index { it.include?('1230') }
|
||||||
|
item2 = lines.index { it.include?('1231') }
|
||||||
|
assert_equal(item1, layout == 'default' ? item2 + 1 : item2 - 1)
|
||||||
|
|
||||||
|
# Preview
|
||||||
|
assert(lines.any? { it.include?('foobar') })
|
||||||
|
|
||||||
|
# Header
|
||||||
|
header1 = lines.index { it.include?('101') }
|
||||||
|
header2 = lines.index { it.include?('102') }
|
||||||
|
assert_equal(header2, header1 + 1)
|
||||||
|
assert((layout == 'reverse') == header_first ? input > header1 : input < header1)
|
||||||
|
|
||||||
|
# Footer
|
||||||
|
footer1 = lines.index { it.include?('201') }
|
||||||
|
footer2 = lines.index { it.include?('202') }
|
||||||
|
assert_equal(footer2, footer1 + 1)
|
||||||
|
assert(layout == 'reverse' ? footer1 > item2 : footer1 < item2)
|
||||||
|
|
||||||
|
# Header lines
|
||||||
|
hline1 = lines.index { it.include?('1001') }
|
||||||
|
hline2 = lines.index { it.include?('1002') }
|
||||||
|
assert_equal(hline1, layout == 'default' ? hline2 + 1 : hline2 - 1)
|
||||||
|
assert(layout == 'reverse' ? hline1 > header1 : hline1 < header1)
|
||||||
|
end
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@@ -189,6 +189,20 @@ class TestPreview < TestInteractive
|
|||||||
tmux.until { |lines| assert_includes lines[1], ' {//1 10/1 10 /123//0 9} ' }
|
tmux.until { |lines| assert_includes lines[1], ' {//1 10/1 10 /123//0 9} ' }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_preview_asterisk
|
||||||
|
tmux.send_keys %(seq 5 | #{FZF} --multi --preview 'echo [{}/{+}/{*}/{*n}]' --preview-window '+{1}'), :Enter
|
||||||
|
tmux.until { |lines| assert_equal 5, lines.match_count }
|
||||||
|
tmux.until { |lines| assert_includes lines[1], ' [1/1/1 2 3 4 5/0 1 2 3 4] ' }
|
||||||
|
tmux.send_keys :BTab
|
||||||
|
tmux.until { |lines| assert_includes lines[1], ' [2/1/1 2 3 4 5/0 1 2 3 4] ' }
|
||||||
|
tmux.send_keys :BTab
|
||||||
|
tmux.until { |lines| assert_includes lines[1], ' [3/1 2/1 2 3 4 5/0 1 2 3 4] ' }
|
||||||
|
tmux.send_keys '5'
|
||||||
|
tmux.until { |lines| assert_includes lines[1], ' [5/1 2/5/4] ' }
|
||||||
|
tmux.send_keys '5'
|
||||||
|
tmux.until { |lines| assert_includes lines[1], ' [/1 2//] ' }
|
||||||
|
end
|
||||||
|
|
||||||
def test_preview_file
|
def test_preview_file
|
||||||
tmux.send_keys %[(echo foo bar; echo bar foo) | #{FZF} --multi --preview 'cat {+f} {+f2} {+nf} {+fn}' --print0], :Enter
|
tmux.send_keys %[(echo foo bar; echo bar foo) | #{FZF} --multi --preview 'cat {+f} {+f2} {+nf} {+fn}' --print0], :Enter
|
||||||
tmux.until { |lines| assert_includes lines[1], ' foo barbar00 ' }
|
tmux.until { |lines| assert_includes lines[1], ' foo barbar00 ' }
|
||||||
@@ -544,4 +558,18 @@ class TestPreview < TestInteractive
|
|||||||
tmux.send_keys :Up
|
tmux.send_keys :Up
|
||||||
tmux.until { |lines| assert_includes lines, '> 2' }
|
tmux.until { |lines| assert_includes lines, '> 2' }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_preview_query_should_not_be_affected_by_search
|
||||||
|
tmux.send_keys "seq 1 | #{FZF} --bind 'change:transform-search(echo {q:1})' --preview 'echo [{q}/{}]'", :Enter
|
||||||
|
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||||
|
tmux.send_keys '1'
|
||||||
|
tmux.until { |lines| assert lines.any_include?('[1/1]') }
|
||||||
|
tmux.send_keys :Space
|
||||||
|
tmux.until { |lines| assert lines.any_include?('[1 /1]') }
|
||||||
|
tmux.send_keys '2'
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert lines.any_include?('[1 2/1]')
|
||||||
|
assert_equal 1, lines.match_count
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@@ -73,7 +73,7 @@ module TestShell
|
|||||||
tmux.prepare
|
tmux.prepare
|
||||||
tmux.send_keys :Escape, :c
|
tmux.send_keys :Escape, :c
|
||||||
lines = tmux.until { |lines| assert_operator lines.match_count, :>, 0 }
|
lines = tmux.until { |lines| assert_operator lines.match_count, :>, 0 }
|
||||||
expected = lines.reverse.find { |l| l.start_with?('> ') }[2..]
|
expected = lines.reverse.find { |l| l.start_with?('> ') }[2..].chomp('/')
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
tmux.send_keys :pwd, :Enter
|
tmux.send_keys :pwd, :Enter
|
||||||
@@ -241,7 +241,7 @@ module CompletionTest
|
|||||||
tmux.until do |lines|
|
tmux.until do |lines|
|
||||||
assert_equal 1, lines.match_count
|
assert_equal 1, lines.match_count
|
||||||
assert_includes lines, '> 55'
|
assert_includes lines, '> 55'
|
||||||
assert_includes lines, '> /tmp/fzf-test/d55'
|
assert_includes lines, '> /tmp/fzf-test/d55/'
|
||||||
end
|
end
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
tmux.until(true) { |lines| assert_equal 'cd /tmp/fzf-test/d55/', lines[-1] }
|
tmux.until(true) { |lines| assert_equal 'cd /tmp/fzf-test/d55/', lines[-1] }
|
||||||
@@ -482,4 +482,36 @@ class TestFish < TestBase
|
|||||||
tmux.send_keys "set -g #{name} '#{val}'", :Enter
|
tmux.send_keys "set -g #{name} '#{val}'", :Enter
|
||||||
tmux.prepare
|
tmux.prepare
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_ctrl_r_multi
|
||||||
|
tmux.send_keys ':', :Enter
|
||||||
|
tmux.send_keys 'echo "foo', :Enter, 'bar"', :Enter
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys 'echo "bar', :Enter, 'foo"', :Enter
|
||||||
|
tmux.prepare
|
||||||
|
tmux.send_keys 'C-l', 'C-r'
|
||||||
|
block = <<~BLOCK
|
||||||
|
echo "foo
|
||||||
|
bar"
|
||||||
|
echo "bar
|
||||||
|
foo"
|
||||||
|
BLOCK
|
||||||
|
tmux.until do |lines|
|
||||||
|
block.lines.each_with_index do |line, idx|
|
||||||
|
assert_includes lines[-6 + idx], line.chomp
|
||||||
|
end
|
||||||
|
end
|
||||||
|
tmux.send_keys :BTab, :BTab
|
||||||
|
tmux.until { |lines| assert_includes lines[-2], '(2)' }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
block = <<~BLOCK
|
||||||
|
echo "bar
|
||||||
|
foo"
|
||||||
|
echo "foo
|
||||||
|
bar"
|
||||||
|
BLOCK
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert_equal block.lines.map(&:chomp), lines
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
Reference in New Issue
Block a user