m/fzf
1
0
mirror of https://github.com/junegunn/fzf.git synced 2025-11-08 11:23:47 -05:00

Compare commits

...

55 Commits

Author SHA1 Message Date
dependabot[bot]
76dcfe85f2 Bump github/codeql-action from 3 to 4
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3 to 4.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-13 13:32:21 +00:00
Junegunn Choi
8cdfb23df6 0.66.0 2025-10-12 22:17:52 +09:00
Junegunn Choi
4ffde48e2f Fix --bold inheritance
Fix #4548
2025-10-12 13:58:46 +09:00
Junegunn Choi
f2b33f038a Revert "Make query string in --disabled state bold as before"
This reverts commit ab407c4645.
2025-10-12 13:58:46 +09:00
junegunn
d5913bf86e Deploying to master from @ junegunn/fzf@0e9026b817 🚀 2025-10-12 00:02:08 +00:00
Jacobo de Vera
0e9026b817 feat: Allow disabling Ctrl-R binding in shell integration (#4535)
Close #4417
2025-10-12 01:57:31 +09:00
Junegunn Choi
ab407c4645 Make query string in --disabled state bold as before
Fix #4546
2025-10-11 09:35:48 +09:00
Junegunn Choi
91c4bef35f Update CHANGELOG 2025-10-10 03:41:37 +09:00
Junegunn Choi
bf77206221 Improve Unix domain socket handling
- Check if the file is in use
- Change the permission to 0600
2025-10-09 13:52:10 +09:00
Junegunn Choi
0cb1be3f04 Fix --help output: socket path cannot be omitted 2025-10-09 01:12:30 +09:00
Junegunn Choi
01cb38a5fb Add Unix domain socket support for --listen
Close #4541
2025-10-09 01:07:59 +09:00
Junegunn Choi
c38c6cad79 Update CHANGELOG 2025-10-09 00:17:00 +09:00
Junegunn Choi
ba6fc40cfd Add 'best' to man page 2025-10-09 00:17:00 +09:00
Junegunn Choi
dd46a256c0 Fix offset-up and offset-down with --layout=reverse-list
Related: 3df06a1c68
2025-10-09 00:17:00 +09:00
Junegunn Choi
d19ce0ad8d Add 'best' action 2025-10-09 00:17:00 +09:00
Junegunn Choi
ed7becfb47 Go to the closest match when disabling raw mode 2025-10-09 00:17:00 +09:00
Junegunn Choi
9ace1351ff ADD $FZF_DIRECTION 2025-10-09 00:17:00 +09:00
Junegunn Choi
e1de29bc40 CTRL-R: Bind ALT-R to toggle-raw 2025-10-09 00:17:00 +09:00
Junegunn Choi
0df7d10550 Rename: '--color hidden' to '--color nomatch' 2025-10-09 00:17:00 +09:00
Junegunn Choi
91e119a77e Fix non-matching items not refreshing after clearing query 2025-10-09 00:17:00 +09:00
Junegunn Choi
3984161f6c Fix: 'hidden' style not applied to text without colors 2025-10-09 00:17:00 +09:00
Junegunn Choi
91beacf0f4 Add special 'strip' style attribute for stripping colors
Test cases:
  fd --color always | fzf --ansi --delimiter /
  fd --color always | fzf --ansi --delimiter / --nth -1 --color fg:dim,nth:regular
  fd --color always | fzf --ansi --delimiter / --nth -1 --color fg:dim:strip,nth:regular
  fd --color always | fzf --ansi --delimiter / --nth -1 --color fg:dim:strip,nth:regular --raw
  fd --color always | fzf --ansi --delimiter / --nth -1 --color fg:dim:strip,nth:regular,hidden:strikethrough --raw
  fd --color always | fzf --ansi --delimiter / --nth -1 --color fg:dim:strip,nth:regular,hidden:strip:strikethrough --raw
  fd --color always | fzf --ansi --delimiter / --nth -1 --color fg:dim:strip,nth:regular,hidden:strip:dim:strikethrough --raw
2025-10-09 00:17:00 +09:00
Junegunn Choi
e6ad01fb90 Revise color configuration 2025-10-09 00:17:00 +09:00
Junegunn Choi
ce2200e908 Do not allow gutter characters with width other than 1 2025-10-09 00:17:00 +09:00
Junegunn Choi
548061dbde --gutter ' ' --color gutter:reverse 2025-10-09 00:17:00 +09:00
Junegunn Choi
8f0c91545d Add $FZF_RAW for conditional actions 2025-10-09 00:17:00 +09:00
Junegunn Choi
0eefcf348e Update CHANGELOG 2025-10-09 00:17:00 +09:00
Junegunn Choi
c1f8d18a0c Add enable-raw and disable-raw actions 2025-10-09 00:17:00 +09:00
Junegunn Choi
8585969d6d Refactor action implementation 2025-10-09 00:17:00 +09:00
Junegunn Choi
8a943a9b1a Remove TODO comments 2025-10-09 00:17:00 +09:00
Junegunn Choi
c87a8eccd4 Add '--bind ctrl-x:toggle-raw' to CTRL-R bindings 2025-10-09 00:17:00 +09:00
Junegunn Choi
65df0abf0e Introduce 'raw' mode 2025-10-09 00:17:00 +09:00
junegunn
b51bc6b50e Deploying to master from @ junegunn/fzf@febaadbee5 🚀 2025-10-05 00:02:16 +00:00
Junegunn Choi
febaadbee5 Fix stray character artifacts when scrollbar is hidden
Fix #4537
2025-10-04 21:56:56 +09:00
dependabot[bot]
0e67c5aa7a Bump actions/setup-go from 5 to 6 (#4513)
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 5 to 6.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-29 21:20:26 +09:00
mickychang9
760d1b7c58 refactor: use maps.Copy and maps.Clone (#4518)
Signed-off-by: mickychang9 <mickychang9@outlook.com>
2025-09-29 18:11:19 +09:00
Junegunn Choi
9bdacc8df2 Update --help output to avoid confusion 2025-09-28 23:56:51 +09:00
junegunn
8e936ecfa7 Deploying to master from @ junegunn/fzf@db2e95b1f2 🚀 2025-09-28 00:02:15 +00:00
Junegunn Choi
db2e95b1f2 Remove unused field 2025-09-27 22:34:12 +09:00
alex-huff
687074e772 merger: fix chunk cache never getting cleared (#4531)
Commit 7fc13c5 indroduced less aggressive cache invalidation for the
chunk cache but saved the new revision before comparing it with the old
one, and so the cache was never considered invalid.

Fixes #4529
2025-09-27 09:01:13 +09:00
Junegunn Choi
3401c2e0c7 Remove alignment in .tool-versions for RuboCop 2025-09-24 22:41:54 +09:00
Junegunn Choi
e8cb315419 Apply shfmt to bash script files (make fmt) 2025-09-24 22:41:54 +09:00
Junegunn Choi
f0c4ee4047 make lint: Perform bash script linting 2025-09-24 22:41:54 +09:00
xieyonn
de0df2422a feat: add make fmt for *.sh *.bash
1. add .editorconfig file, add rules for .sh .bash files.
2. add make fmt target, use:
    - gofmt *.go.
    - shfmt *.sh *.bash, shell/completion.bash, shell/key-bindings.bash
        need a left indent due to an outermost if block.
3. add shfmt check for bash scripts in make lint target.
4. install shfmt in actions.
2025-09-24 22:41:54 +09:00
Massimo Mund
148b0a94cd tui/light: consume full 7-byte CSI sequences to prevent leftover printing (#4528)
- Fix parsing in escSequence so 7-byte CSI forms (e.g. ESC [ 5 ; 10 ~) set *sz = 7 and the entire sequence is consumed.
- Prevents trailing bytes (like 10~) from remaining in the input buffer and being printed as stray characters.
2025-09-23 23:33:41 +09:00
Junegunn Choi
ca294109c3 Apply RuboCop suggestions 2025-09-22 21:23:54 +09:00
Junegunn Choi
9cad2686e9 Update .tool-versions 2025-09-21 21:19:49 +09:00
junegunn
9a45172232 Deploying to master from @ junegunn/fzf@2a92c7d792 🚀 2025-09-21 00:02:16 +00:00
Junegunn Choi
2a92c7d792 Adjust base16 (16) theme (#4501)
Motivation:

`--color base16` can be a better default than `dark` or `light`, since it uses
the colors defined by the current theme. This usually blends in more
naturally and works well in both light and dark modes.

However, some elements were previously hard-coded with white or black
foreground colors, which can cause rendering issues in certain terminal
themes.
2025-09-17 19:38:49 +09:00
Junegunn Choi
f5975cf870 Add --gutter to --help and man page 2025-09-16 21:30:01 +09:00
Junegunn Choi
a67aa85820 Style change: thinner gutter column (#4521) 2025-09-16 21:22:56 +09:00
dependabot[bot]
c5cabe1691 Bump github.com/charlievieth/fastwalk from 1.0.13 to 1.0.14 (#4522)
Bumps [github.com/charlievieth/fastwalk](https://github.com/charlievieth/fastwalk) from 1.0.13 to 1.0.14.
- [Release notes](https://github.com/charlievieth/fastwalk/releases)
- [Commits](https://github.com/charlievieth/fastwalk/compare/v1.0.13...v1.0.14)

---
updated-dependencies:
- dependency-name: github.com/charlievieth/fastwalk
  dependency-version: 1.0.14
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-16 01:19:14 +09:00
Junegunn Choi
cbed41cd82 No emoji 2025-09-14 21:08:48 +09:00
Junegunn Choi
6684771cbf Fix CTRL-Z for tcell renderer by using the official API
See https://github.com/gdamore/tcell/pull/431
2025-09-14 11:41:12 +09:00
Junegunn Choi
f5f894ea47 Fix rendering of multiple OSC 8 links in a single line
Fix #4517
2025-09-14 11:26:47 +09:00
45 changed files with 1858 additions and 831 deletions

20
.editorconfig Normal file
View File

@@ -0,0 +1,20 @@
root = true
[*.{sh,bash}]
indent_style = space
indent_size = 2
simplify = true
binary_next_line = false
switch_case_indent = true
space_redirects = true
function_next_line = false
# also bash scripts.
[{install,uninstall,bin/fzf-preview.sh,bin/fzf-tmux}]
indent_style = space
indent_size = 2
simplify = true
binary_next_line = false
switch_case_indent = true
space_redirects = true
function_next_line = false

View File

@@ -33,12 +33,12 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
uses: github/codeql-action/init@v4
with:
languages: ${{ matrix.language }}
- name: Autobuild
uses: github/codeql-action/autobuild@v3
uses: github/codeql-action/autobuild@v4
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
uses: github/codeql-action/analyze@v4

View File

@@ -23,17 +23,17 @@ jobs:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: "1.23"
- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.4.1
ruby-version: 3.4.6
- name: Install packages
run: sudo apt-get install --yes zsh fish tmux
run: sudo apt-get install --yes zsh fish tmux shfmt
- name: Install Ruby gems
run: bundle install

View File

@@ -20,7 +20,7 @@ jobs:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version: "1.23"
@@ -30,7 +30,7 @@ jobs:
ruby-version: 3.0.0
- name: Install packages
run: HOMEBREW_NO_INSTALL_CLEANUP=1 brew install fish zsh tmux
run: HOMEBREW_NO_INSTALL_CLEANUP=1 brew install fish zsh tmux shfmt
- name: Install Ruby gems
run: gem install --no-document minitest:5.14.2 rubocop:1.0.0 rubocop-minitest:0.10.1 rubocop-performance:1.8.1

View File

@@ -1,2 +1,3 @@
golang 1.23.12
ruby 3.4.1
golang 1.23
ruby 3.4
shfmt 3.12

View File

@@ -1,6 +1,295 @@
CHANGELOG
=========
0.66.0
------
### Quick summary
This version introduces many new features centered around the new "raw" mode.
| Type | Class | Name | Description |
| :-- | :-- | :-- | :-- |
| New | Option | `--raw` | Enable raw mode by default |
| New | Option | `--gutter CHAR` | Set the gutter column character |
| New | Option | `--gutter-raw CHAR` | Set the gutter column character in raw mode |
| Enhancement | Option | `--listen SOCKET` | Added support for Unix domain sockets |
| New | Action | `toggle-raw` | Toggle raw mode |
| New | Action | `enable-raw` | Enable raw mode |
| New | Action | `disable-raw` | Disable raw mode |
| New | Action | `up-match` | Move up to the matching item |
| New | Action | `down-match` | Move down to the matching item |
| New | Action | `best` | Move to the matching item with the best score |
| New | Color | `nomatch` | Color for non-matching items in raw mode |
| New | Env Var | `FZF_RAW` | Matching status in raw mode (0, 1, or undefined) |
| New | Env Var | `FZF_DIRECTION` | `up` or `down` depending on the layout |
| New | Env Var | `FZF_SOCK` | Path to the Unix domain socket fzf is listening on |
| Enhancement | Key | `CTRL-N` | `down` -> `down-match` |
| Enhancement | Key | `CTRL-P` | `up` -> `up-match` |
| Enhancement | Shell | `CTRL-R` binding | Toggle raw mode with `ALT-R` |
| Enhancement | Shell | `CTRL-R` binding | Opt-out with an empty `FZF_CTRL_R_COMMAND` |
### 1. Introducing "raw" mode
![](https://github.com/user-attachments/assets/9640ae11-b5f7-43fb-95f1-c29307fc17c2)
This version introduces a new "raw" mode (named so because it shows the list
"unfiltered"). In raw mode, non-matching items stay in their original positions,
but appear dimmed. This allows you see surrounding items of a match and better
understand the context of it. You can enable raw mode by default with `--raw`,
but it's often more useful when toggled dynamically with the `toggle-raw`
action.
```sh
tree | fzf --reverse --bind alt-r:toggle-raw
```
While non-matching items are displayed in a dimmed color, they are treated just
like matching items, so you place the cursor on them and perform any action. If
you prefer to navigate only through matching items, use the `down-match` and
`up-match` actions, which are from now on bound to `CTRL-N` and `CTRL-P`
respectively, and also to `ALT-DOWN` and `ALT-UP`.
| Key | Action | With `--history` |
| :-- | :-- | :-- |
| `down` | `down` | |
| `up` | `up` | |
| `ctrl-j` | `down` | |
| `ctrl-k` | `up` | |
| `ctrl-n` | `down-match` | `next-history` |
| `ctrl-p` | `up-match` | `prev-history` |
| `alt-down` | `down-match` | |
| `alt-up` | `up-match` | |
> [!NOTE]
> `CTRL-N` and `CTRL-P` are bound to `next-history` and `prev-history` when
> `--history` option is enabled, so in that case, you'll need to manually bind
> them, or use `ALT-DOWN` and `ALT-UP` instead.
> [!TIP]
> `up-match` and `down-match` are equivalent to `up` and `down` when not in
> raw mode, so you can safely bind them to `up` and `arrow` keys if you prefer.
> ```sh
> fzf --bind up:up-match,down:down-match
> ```
#### Customizing the behavior
In raw mode, the input list is presented in its original order, unfiltered, and
your cursor will not move to the matching item automatically. Here are ways to
customize the behavior.
```sh
# When the result list is updated, move the cursor to the item with the best score
# (assuming sorting is not disabled)
fzf --raw --bind result:best
# Move to the first matching item in the original list
# - $FZF_RAW is set to 0 when raw mode is enabled and the current item is a non-match
# - $FZF_DIRECTION is set to either 'up' or 'down' depending on the layout direction
fzf --raw --bind 'result:first+transform:[[ $FZF_RAW = 0 ]] && echo $FZF_DIRECTION-match'
```
#### Customizing the look
##### Gutter
To make the mode visually distinct, the gutter column is rendered in a dashed
line using `▖` character. But you can customize it with the `--gutter-raw CHAR`
option.
```sh
# Use a thinner gutter instead of the default dashed line
fzf --bind alt-r:toggle-raw --gutter-raw ▎
```
##### Color and style of non-matching items
Non-matching items are displayed in a dimmed color by default, but you can
change it with the `--color nomatch:...` option.
```sh
fzf --raw --color nomatch:red
fzf --raw --color nomatch:red:dim
fzf --raw --color nomatch:red:dim:strikethrough
fzf --raw --color nomatch:red:dim:strikethrough:italic
```
For colored input, dimming alone may not be enough, and you may prefer to remove
colors entirely. For that case, a new special style attribute `strip` has been
added.
```sh
fd --color always | fzf --ansi --raw --color nomatch:dim:strip:strikethrough
```
#### Conditional actions for raw mode
You may want to perform different actions depending on whether the current item
is a match or not. For that, fzf now exports `$FZF_RAW` environment variable.
It's:
- Undefined if raw mode is disabled
- `1` if the current item is a match
- `0` otherwise
```sh
# Do not allow selecting non-matching items
fzf --raw --bind 'enter:transform:[[ ${FZF_RAW-1} = 1 ]] && echo accept || echo bell'
```
#### Leveraging raw mode in shell integration
The `CTRL-R` binding (command history) now lets you toggle raw mode with `ALT-R`.
### 2. Style changes
The screenshot on the right shows the updated gutter style:
![](https://github.com/user-attachments/assets/8ea7b5ef-c99e-4686-905b-22eb078b700a)
This version includes a few minor updates to fzf's classic visual style:
- The gutter column is now narrower, rendered with the left-half block character (`▌`).
- Markers no longer use background colors.
- The `--color base16` theme (alias: `16`) has been updated for better compatibility with both dark and light themes.
### 3. `--listen` now supports Unix domain sockets
If an argument to `--listen` ends with `.sock`, fzf will listen on a Unix
domain socket at the specified path.
```sh
fzf --listen /tmp/fzf.sock --no-tmux
# GET
curl --unix-socket /tmp/fzf.sock http
# POST
curl --unix-socket /tmp/fzf.sock http -d up
```
Note that any existing file at the given path will be removed before creating
the socket, so avoid using an important file path.
### 4. Added options
#### `--gutter CHAR`
The gutter column can now be customized using `--gutter CHAR` and styled with
`--color gutter:...`. Examples:
```sh
# Right-aligned gutter
fzf --gutter '▐'
# Even thinner gutter
fzf --gutter '▎'
# Yellow checker pattern
fzf --gutter '▚' --color gutter:yellow
# Classic style
fzf --gutter ' ' --color gutter:reverse
```
#### `--gutter-raw CHAR`
As noted above, the `--gutter-raw CHAR` option was also added for customizing the gutter column in raw mode.
### 5. Added actions
The following actions were introduced to support working with raw mode:
| Action | Description |
| :-- | :-- |
| `toggle-raw` | Toggle raw mode |
| `enable-raw` | Enable raw mode |
| `disable-raw` | Disable raw mode |
| `up-match` | Move up to the matching item; identical to `up` if raw mode is disabled |
| `down-match` | Move down to the matching item; identical to `down` if raw mode is disabled |
| `best` | Move to the matching item with the best score; identical to `first` if raw mode is disabled |
### 6. Added environment variables
#### `$FZF_DIRECTION`
`$FZF_DIRECTION` is now exported to child processes, indicating the list direction of the current layout:
- `up` for the default layout
- `down` for `reverse` or `reverse-list`
This simplifies writing transform actions involving layout-dependent actions
like `{up,down}-match`, `{up,down}-selected`, and `toggle+{up,down}`.
```sh
fzf --raw --bind 'result:first+transform:[[ $FZF_RAW = 0 ]] && echo $FZF_DIRECTION-match'
```
#### `$FZF_SOCK`
When fzf is listening on a Unix domain socket using `--listen`, the path to the
socket is exported as `$FZF_SOCK`, analogous to `$FZF_PORT` for TCP sockets.
#### `$FZF_RAW`
As described above, `$FZF_RAW` is now exported to child processes in raw mode,
indicating whether the current item is a match (`1`) or not (`0`). It is not
defined when not in raw mode.
#### `$FZF_CTRL_R_COMMAND`
You can opt-out `CTRL-R` binding from the shell integration by setting
`FZF_CTRL_R_COMMAND` to an empty string. Setting it to any other value is not
supported and will result in a warning.
```sh
# Disable the CTRL-R binding from the shell integration
FZF_CTRL_R_COMMAND= eval "$(fzf --bash)"
```
### 7. Added key support for `--bind`
Pull request [#3996](https://github.com/junegunn/fzf/pull/3996) added support
for many additional keys for `--bind` option, such as `ctrl-backspace`.
### 8. Breaking changes
#### Hiding the gutter column
In the previous versions, the recommended way to hide the gutter column was to
set `--color gutter:-1`. That's because the gutter column was just a space
character, reversed. But now that it's using a visible character (`▌`), applying
the default color is no longer enough to hide it. Instead, you can set it to
a space character.
```sh
# Hide the gutter column
fzf --gutter ' '
# Classic style
fzf --gutter ' ' --color gutter:reverse
```
#### `--color` option
In the previous versions, some elements had default style attributes applied and
you would have to explicitly unset them with `regular` attribute if you wanted
to reset them. This is no longer needed now, as the default style attributes
are applied only when you do not specify any color or style for that element.
```sh
# No 'dim', just red and italic.
fzf --ghost 'Type to search' --color ghost:red:italic
```
#### Compatibility changes
Starting with this release, fzf is built with Go 1.23. Support for some old OS versions has been dropped.
See https://go.dev/wiki/MinimumRequirements.
0.65.2
------
- Bug fixes and improvements

View File

@@ -5,6 +5,15 @@ MAKEFILE := $(realpath $(lastword $(MAKEFILE_LIST)))
ROOT_DIR := $(shell dirname $(MAKEFILE))
SOURCES := $(wildcard *.go src/*.go src/*/*.go shell/*sh man/man1/*.1) $(MAKEFILE)
BASH_SCRIPTS := $(ROOT_DIR)/bin/fzf-preview.sh \
$(ROOT_DIR)/bin/fzf-tmux \
$(ROOT_DIR)/install \
$(ROOT_DIR)/uninstall \
$(ROOT_DIR)/shell/common.sh \
$(ROOT_DIR)/shell/update.sh \
$(ROOT_DIR)/shell/completion.bash \
$(ROOT_DIR)/shell/key-bindings.bash
ifdef FZF_VERSION
VERSION := $(FZF_VERSION)
else
@@ -88,9 +97,14 @@ itest:
bench:
cd src && SHELL=/bin/sh GOOS= $(GO) test -v -tags "$(TAGS)" -run=Bench -bench=. -benchmem
lint: $(SOURCES) test/*.rb test/lib/*.rb
lint: $(SOURCES) test/*.rb test/lib/*.rb ${BASH_SCRIPTS}
[ -z "$$(gofmt -s -d src)" ] || (gofmt -s -d src; exit 1)
bundle exec rubocop -a --require rubocop-minitest --require rubocop-performance
shell/update.sh --check ${BASH_SCRIPTS}
fmt: $(SOURCES) $(BASH_SCRIPTS)
gofmt -s -w src
shell/update.sh ${BASH_SCRIPTS}
install: bin/fzf
@@ -189,4 +203,4 @@ update:
$(GO) get -u
$(GO) mod tidy
.PHONY: all generate build release test itest bench lint install clean docker docker-test update
.PHONY: all generate build release test itest bench lint install clean docker docker-test update fmt

File diff suppressed because one or more lines are too long

View File

@@ -49,9 +49,9 @@ if [[ ! $type =~ image/ ]]; then
fi
dim=${FZF_PREVIEW_COLUMNS}x${FZF_PREVIEW_LINES}
if [[ $dim = x ]]; then
if [[ $dim == x ]]; then
dim=$(stty size < /dev/tty | awk '{print $2 "x" $1}')
elif ! [[ $KITTY_WINDOW_ID ]] && (( FZF_PREVIEW_TOP + FZF_PREVIEW_LINES == $(stty size < /dev/tty | awk '{print $1}') )); then
elif ! [[ $KITTY_WINDOW_ID ]] && ((FZF_PREVIEW_TOP + FZF_PREVIEW_LINES == $(stty size < /dev/tty | awk '{print $1}'))); then
# Avoid scrolling issue when the Sixel image touches the bottom of the screen
# * https://github.com/junegunn/fzf/issues/2544
dim=${FZF_PREVIEW_COLUMNS}x$((FZF_PREVIEW_LINES - 1))

View File

@@ -8,7 +8,7 @@ fail() {
}
fzf="$(command which fzf)" || fzf="$(dirname "$0")/fzf"
[[ -x "$fzf" ]] || fail 'fzf executable not found'
[[ -x $fzf ]] || fail 'fzf executable not found'
args=()
opt=""
@@ -16,8 +16,8 @@ skip=""
swap=""
close=""
term=""
[[ -n "$LINES" ]] && lines=$LINES || lines=$(tput lines) || lines=$(tmux display-message -p "#{pane_height}")
[[ -n "$COLUMNS" ]] && columns=$COLUMNS || columns=$(tput cols) || columns=$(tmux display-message -p "#{pane_width}")
[[ -n $LINES ]] && lines=$LINES || lines=$(tput lines) || lines=$(tmux display-message -p "#{pane_height}")
[[ -n $COLUMNS ]] && columns=$COLUMNS || columns=$(tput cols) || columns=$(tmux display-message -p "#{pane_width}")
tmux_version=$(tmux -V | sed 's/[^0-9.]//g')
tmux_32=$(awk '{print ($1 >= 3.2)}' <<< "$tmux_version" 2> /dev/null || bc -l <<< "$tmux_version >= 3.2")
@@ -47,7 +47,7 @@ help() {
while [[ $# -gt 0 ]]; do
arg="$1"
shift
[[ -z "$skip" ]] && case "$arg" in
[[ -z $skip ]] && case "$arg" in
-)
term=1
;;
@@ -58,19 +58,19 @@ while [[ $# -gt 0 ]]; do
echo "fzf-tmux (with fzf $("$fzf" --version))"
exit
;;
-p*|-w*|-h*|-x*|-y*|-d*|-u*|-r*|-l*)
if [[ "$arg" =~ ^-[pwhxy] ]]; then
[[ "$opt" =~ "-E" ]] || opt="-E"
elif [[ "$arg" =~ ^.[lr] ]]; then
-p* | -w* | -h* | -x* | -y* | -d* | -u* | -r* | -l*)
if [[ $arg =~ ^-[pwhxy] ]]; then
[[ $opt =~ "-E" ]] || opt="-E"
elif [[ $arg =~ ^.[lr] ]]; then
opt="-h"
if [[ "$arg" =~ ^.l ]]; then
if [[ $arg =~ ^.l ]]; then
opt="$opt -d"
swap="; swap-pane -D ; select-pane -L"
close="; tmux swap-pane -D"
fi
else
opt=""
if [[ "$arg" =~ ^.u ]]; then
if [[ $arg =~ ^.u ]]; then
opt="$opt -d"
swap="; swap-pane -D ; select-pane -U"
close="; tmux swap-pane -D"
@@ -79,7 +79,7 @@ while [[ $# -gt 0 ]]; do
if [[ ${#arg} -gt 2 ]]; then
size="${arg:2}"
else
if [[ "$1" =~ ^[0-9%,]+$ ]] || [[ "$1" =~ ^[A-Z]$ ]]; then
if [[ $1 =~ ^[0-9%,]+$ ]] || [[ $1 =~ ^[A-Z]$ ]]; then
size="$1"
shift
else
@@ -87,37 +87,37 @@ while [[ $# -gt 0 ]]; do
fi
fi
if [[ "$arg" =~ ^-p ]]; then
if [[ -n "$size" ]]; then
if [[ $arg =~ ^-p ]]; then
if [[ -n $size ]]; then
w=${size%%,*}
h=${size##*,}
opt="$opt -w$w -h$h"
fi
elif [[ "$arg" =~ ^-[whxy] ]]; then
elif [[ $arg =~ ^-[whxy] ]]; then
opt="$opt ${arg:0:2}$size"
elif [[ "$size" =~ %$ ]]; then
size=${size:0:((${#size}-1))}
if [[ $tmux_32 = 1 ]]; then
if [[ -n "$swap" ]]; then
opt="$opt -l $(( 100 - size ))%"
elif [[ $size =~ %$ ]]; then
size=${size:0:${#size}-1}
if [[ $tmux_32 == 1 ]]; then
if [[ -n $swap ]]; then
opt="$opt -l $((100 - size))%"
else
opt="$opt -l $size%"
fi
else
if [[ -n "$swap" ]]; then
opt="$opt -p $(( 100 - size ))"
if [[ -n $swap ]]; then
opt="$opt -p $((100 - size))"
else
opt="$opt -p $size"
fi
fi
else
if [[ -n "$swap" ]]; then
if [[ "$arg" =~ ^.l ]]; then
if [[ -n $swap ]]; then
if [[ $arg =~ ^.l ]]; then
max=$columns
else
max=$lines
fi
size=$(( max - size ))
size=$((max - size))
[[ $size -lt 0 ]] && size=0
opt="$opt -l $size"
else
@@ -135,10 +135,10 @@ while [[ $# -gt 0 ]]; do
args+=("$arg")
;;
esac
[[ -n "$skip" ]] && args+=("$arg")
[[ -n $skip ]] && args+=("$arg")
done
if [[ -z "$TMUX" ]]; then
if [[ -z $TMUX ]]; then
"$fzf" "${args[@]}"
exit $?
fi
@@ -149,7 +149,7 @@ fi
args=("${args[@]}" "--no-height" "--bind=ctrl-z:ignore" "--no-tmux")
# Handle zoomed tmux pane without popup options by moving it to a temp window
if [[ ! "$opt" =~ "-E" ]] && tmux list-panes -F '#F' | grep -q Z; then
if [[ ! $opt =~ "-E" ]] && tmux list-panes -F '#F' | grep -q Z; then
zoomed_without_popup=1
original_window=$(tmux display-message -p "#{window_id}")
tmp_window=$(tmux new-window -d -P -F "#{window_id}" "bash -c 'while :; do for c in \\| / - '\\;' do sleep 0.2; printf \"\\r\$c fzf-tmux is running\\r\"; done; done'")
@@ -165,22 +165,22 @@ fifo1="${TMPDIR:-/tmp}/fzf-fifo1-$id"
fifo2="${TMPDIR:-/tmp}/fzf-fifo2-$id"
fifo3="${TMPDIR:-/tmp}/fzf-fifo3-$id"
if tmux_win_opts=$(tmux show-options -p remain-on-exit \; show-options -p synchronize-panes 2> /dev/null); then
tmux_win_opts=( $(sed '/ off/d; s/synchronize-panes/set-option -p synchronize-panes/; s/remain-on-exit/set-option -p remain-on-exit/; s/$/ \\;/' <<< "$tmux_win_opts") )
tmux_win_opts=($(sed '/ off/d; s/synchronize-panes/set-option -p synchronize-panes/; s/remain-on-exit/set-option -p remain-on-exit/; s/$/ \\;/' <<< "$tmux_win_opts"))
tmux_off_opts='; set-option -p synchronize-panes off ; set-option -p remain-on-exit off'
else
tmux_win_opts=( $(tmux show-window-options remain-on-exit \; show-window-options synchronize-panes | sed '/ off/d; s/^/set-window-option /; s/$/ \\;/') )
tmux_win_opts=($(tmux show-window-options remain-on-exit \; show-window-options synchronize-panes | sed '/ off/d; s/^/set-window-option /; s/$/ \\;/'))
tmux_off_opts='; set-window-option synchronize-panes off ; set-window-option remain-on-exit off'
fi
cleanup() {
\rm -f $argsf $fifo1 $fifo2 $fifo3
# Restore tmux window options
if [[ "${#tmux_win_opts[@]}" -gt 1 ]]; then
if [[ ${#tmux_win_opts[@]} -gt 1 ]]; then
eval "tmux ${tmux_win_opts[*]}"
fi
# Remove temp window if we were zoomed without popup options
if [[ -n "$zoomed_without_popup" ]]; then
if [[ -n $zoomed_without_popup ]]; then
tmux display-message -p "#{window_id}" > /dev/null
tmux swap-pane -t $original_window \; \
select-window -t $original_window \; \
@@ -197,10 +197,10 @@ trap 'cleanup 1' SIGUSR1
trap 'cleanup' EXIT
envs="export TERM=$TERM "
if [[ "$opt" =~ "-E" ]]; then
if [[ $tmux_version = 3.2 ]]; then
if [[ $opt =~ "-E" ]]; then
if [[ $tmux_version == 3.2 ]]; then
FZF_DEFAULT_OPTS="--margin 0,1 $FZF_DEFAULT_OPTS"
elif [[ $tmux_32 = 1 ]]; then
elif [[ $tmux_32 == 1 ]]; then
FZF_DEFAULT_OPTS="--border $FZF_DEFAULT_OPTS"
opt="-B $opt"
else
@@ -211,8 +211,8 @@ fi
envs="$envs FZF_DEFAULT_COMMAND=$(printf %q "$FZF_DEFAULT_COMMAND")"
envs="$envs FZF_DEFAULT_OPTS=$(printf %q "$FZF_DEFAULT_OPTS")"
envs="$envs FZF_DEFAULT_OPTS_FILE=$(printf %q "$FZF_DEFAULT_OPTS_FILE")"
[[ -n "$RUNEWIDTH_EASTASIAN" ]] && envs="$envs RUNEWIDTH_EASTASIAN=$(printf %q "$RUNEWIDTH_EASTASIAN")"
[[ -n "$BAT_THEME" ]] && envs="$envs BAT_THEME=$(printf %q "$BAT_THEME")"
[[ -n $RUNEWIDTH_EASTASIAN ]] && envs="$envs RUNEWIDTH_EASTASIAN=$(printf %q "$RUNEWIDTH_EASTASIAN")"
[[ -n $BAT_THEME ]] && envs="$envs BAT_THEME=$(printf %q "$BAT_THEME")"
echo "$envs;" > "$argsf"
# Build arguments to fzf
@@ -224,9 +224,9 @@ close="; trap - EXIT SIGINT SIGTERM $close"
export TMUX=$(cut -d , -f 1,2 <<< "$TMUX")
mkfifo -m o+w $fifo2
if [[ "$opt" =~ "-E" ]]; then
if [[ $opt =~ "-E" ]]; then
cat $fifo2 &
if [[ -n "$term" ]] || [[ -t 0 ]]; then
if [[ -n $term ]] || [[ -t 0 ]]; then
cat <<< "\"$fzf\" $opts > $fifo2; out=\$? $close; exit \$out" >> $argsf
else
mkfifo $fifo1
@@ -239,7 +239,7 @@ if [[ "$opt" =~ "-E" ]]; then
fi
mkfifo -m o+w $fifo3
if [[ -n "$term" ]] || [[ -t 0 ]]; then
if [[ -n $term ]] || [[ -t 0 ]]; then
cat <<< "\"$fzf\" $opts > $fifo2; echo \$? > $fifo3 $close" >> $argsf
else
mkfifo $fifo1
@@ -249,6 +249,9 @@ fi
tmux \
split-window -c "$PWD" $opt "bash -c 'exec -a fzf bash $argsf'" $swap \
$tmux_off_opts \
> /dev/null 2>&1 || { "$fzf" "${args[@]}"; exit $?; }
> /dev/null 2>&1 || {
"$fzf" "${args[@]}"
exit $?
}
cat $fifo2
exit "$(cat $fifo3)"

2
go.mod
View File

@@ -1,7 +1,7 @@
module github.com/junegunn/fzf
require (
github.com/charlievieth/fastwalk v1.0.13
github.com/charlievieth/fastwalk v1.0.14
github.com/gdamore/tcell/v2 v2.9.0
github.com/junegunn/go-shellwords v0.0.0-20250127100254-2aa3b3277741
github.com/mattn/go-isatty v0.0.20

4
go.sum
View File

@@ -1,5 +1,5 @@
github.com/charlievieth/fastwalk v1.0.13 h1:rCdesaKpxBft4jdNqKbJtTS23Dfhem3eEPE0jfj//xc=
github.com/charlievieth/fastwalk v1.0.13/go.mod h1:diVcUreiU1aQ4/Wu3NbxxH4/KYdKpLDojrQ1Bb2KgNY=
github.com/charlievieth/fastwalk v1.0.14 h1:3Eh5uaFGwHZd8EGwTjJnSpBkfwfsak9h6ICgnWlhAyg=
github.com/charlievieth/fastwalk v1.0.14/go.mod h1:diVcUreiU1aQ4/Wu3NbxxH4/KYdKpLDojrQ1Bb2KgNY=
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/tcell/v2 v2.9.0 h1:N6t+eqK7/xwtRPwxzs1PXeRWnm0H9l02CrgJ7DLn1ys=

110
install
View File

@@ -2,7 +2,7 @@
set -u
version=0.65.2
version=0.66.0
auto_completion=
key_bindings=
update_config=2
@@ -46,16 +46,16 @@ for opt in "$@"; do
prefix_expand=${XDG_CONFIG_HOME:-$HOME/.config}/fzf/fzf
mkdir -p "${XDG_CONFIG_HOME:-$HOME/.config}/fzf"
;;
--key-bindings) key_bindings=1 ;;
--no-key-bindings) key_bindings=0 ;;
--completion) auto_completion=1 ;;
--no-completion) auto_completion=0 ;;
--update-rc) update_config=1 ;;
--no-update-rc) update_config=0 ;;
--bin) ;;
--no-bash) shells=${shells/bash/} ;;
--no-zsh) shells=${shells/zsh/} ;;
--no-fish) shells=${shells/fish/} ;;
--key-bindings) key_bindings=1 ;;
--no-key-bindings) key_bindings=0 ;;
--completion) auto_completion=1 ;;
--no-completion) auto_completion=0 ;;
--update-rc) update_config=1 ;;
--no-update-rc) update_config=0 ;;
--bin) ;;
--no-bash) shells=${shells/bash/} ;;
--no-zsh) shells=${shells/zsh/} ;;
--no-fish) shells=${shells/fish/} ;;
*)
echo "unknown option: $opt"
help
@@ -104,7 +104,7 @@ check_binary() {
link_fzf_in_path() {
if which_fzf="$(command -v fzf)"; then
echo " - Found in \$PATH"
echo ' - Found in $PATH'
echo " - Creating symlink: bin/fzf -> $which_fzf"
(cd "$fzf_base"/bin && rm -f fzf && ln -sf "$which_fzf" fzf)
check_binary && return
@@ -114,22 +114,22 @@ link_fzf_in_path() {
try_curl() {
command -v curl > /dev/null &&
if [[ $1 =~ tar.gz$ ]]; then
curl -fL $1 | tar --no-same-owner -xzf -
else
local temp=${TMPDIR:-/tmp}/fzf.zip
curl -fLo "$temp" $1 && unzip -o "$temp" && rm -f "$temp"
fi
if [[ $1 =~ tar.gz$ ]]; then
curl -fL $1 | tar --no-same-owner -xzf -
else
local temp=${TMPDIR:-/tmp}/fzf.zip
curl -fLo "$temp" $1 && unzip -o "$temp" && rm -f "$temp"
fi
}
try_wget() {
command -v wget > /dev/null &&
if [[ $1 =~ tar.gz$ ]]; then
wget -O - $1 | tar --no-same-owner -xzf -
else
local temp=${TMPDIR:-/tmp}/fzf.zip
wget -O "$temp" $1 && unzip -o "$temp" && rm -f "$temp"
fi
if [[ $1 =~ tar.gz$ ]]; then
wget -O - $1 | tar --no-same-owner -xzf -
else
local temp=${TMPDIR:-/tmp}/fzf.zip
wget -O "$temp" $1 && unzip -o "$temp" && rm -f "$temp"
fi
}
download() {
@@ -164,29 +164,29 @@ download() {
}
# Try to download binary executable
archi=$(uname -smo 2>/dev/null || uname -sm)
archi=$(uname -smo 2> /dev/null || uname -sm)
binary_available=1
binary_error=""
case "$archi" in
Darwin\ arm64*) download fzf-$version-darwin_arm64.tar.gz ;;
Darwin\ x86_64*) download fzf-$version-darwin_amd64.tar.gz ;;
Linux\ armv5*) download fzf-$version-linux_armv5.tar.gz ;;
Linux\ armv6*) download fzf-$version-linux_armv6.tar.gz ;;
Linux\ armv7*) download fzf-$version-linux_armv7.tar.gz ;;
Linux\ armv8*) download fzf-$version-linux_arm64.tar.gz ;;
Darwin\ arm64*) download fzf-$version-darwin_arm64.tar.gz ;;
Darwin\ x86_64*) download fzf-$version-darwin_amd64.tar.gz ;;
Linux\ armv5*) download fzf-$version-linux_armv5.tar.gz ;;
Linux\ armv6*) download fzf-$version-linux_armv6.tar.gz ;;
Linux\ armv7*) download fzf-$version-linux_armv7.tar.gz ;;
Linux\ armv8*) download fzf-$version-linux_arm64.tar.gz ;;
Linux\ aarch64\ Android) download fzf-$version-android_arm64.tar.gz ;;
Linux\ aarch64*) download fzf-$version-linux_arm64.tar.gz ;;
Linux\ loongarch64*) download fzf-$version-linux_loong64.tar.gz ;;
Linux\ ppc64le*) download fzf-$version-linux_ppc64le.tar.gz ;;
Linux\ *64*) download fzf-$version-linux_amd64.tar.gz ;;
Linux\ s390x*) download fzf-$version-linux_s390x.tar.gz ;;
FreeBSD\ *64*) download fzf-$version-freebsd_amd64.tar.gz ;;
OpenBSD\ *64*) download fzf-$version-openbsd_amd64.tar.gz ;;
CYGWIN*\ *64*) download fzf-$version-windows_amd64.zip ;;
MINGW*\ *64*) download fzf-$version-windows_amd64.zip ;;
MSYS*\ *64*) download fzf-$version-windows_amd64.zip ;;
Windows*\ *64*) download fzf-$version-windows_amd64.zip ;;
*) binary_available=0 binary_error=1 ;;
Linux\ aarch64*) download fzf-$version-linux_arm64.tar.gz ;;
Linux\ loongarch64*) download fzf-$version-linux_loong64.tar.gz ;;
Linux\ ppc64le*) download fzf-$version-linux_ppc64le.tar.gz ;;
Linux\ *64*) download fzf-$version-linux_amd64.tar.gz ;;
Linux\ s390x*) download fzf-$version-linux_s390x.tar.gz ;;
FreeBSD\ *64*) download fzf-$version-freebsd_amd64.tar.gz ;;
OpenBSD\ *64*) download fzf-$version-openbsd_amd64.tar.gz ;;
CYGWIN*\ *64*) download fzf-$version-windows_amd64.zip ;;
MINGW*\ *64*) download fzf-$version-windows_amd64.zip ;;
MSYS*\ *64*) download fzf-$version-windows_amd64.zip ;;
Windows*\ *64*) download fzf-$version-windows_amd64.zip ;;
*) binary_available=0 binary_error=1 ;;
esac
cd "$fzf_base"
@@ -215,7 +215,7 @@ if [ -n "$binary_error" ]; then
fi
fi
[[ "$*" =~ "--bin" ]] && exit 0
[[ $* =~ "--bin" ]] && exit 0
for s in $shells; do
if ! command -v "$s" > /dev/null; then
@@ -242,7 +242,7 @@ fi
echo
for shell in $shells; do
[[ "$shell" = fish ]] && continue
[[ $shell == fish ]] && continue
src=${prefix_expand}.${shell}
echo -n "Generate $src ... "
@@ -266,7 +266,7 @@ fi
EOF
if [[ $auto_completion -eq 1 ]] && [[ $key_bindings -eq 1 ]]; then
if [[ "$shell" = zsh ]]; then
if [[ $shell == zsh ]]; then
echo "source <(fzf --$shell)" >> "$src"
else
echo "eval \"\$(fzf --$shell)\"" >> "$src"
@@ -286,7 +286,7 @@ EOF
done
# fish
if [[ "$shells" =~ fish ]]; then
if [[ $shells =~ fish ]]; then
echo -n "Update fish_user_paths ... "
fish << EOF
echo \$fish_user_paths | \grep "$fzf_base"/bin > /dev/null
@@ -318,9 +318,9 @@ append_line() {
sed 's/^/ Line /' <<< "$lines"
update=0
if ! \grep -qv "^[0-9]*:[[:space:]]*#" <<< "$lines" ; then
if ! \grep -qv "^[0-9]*:[[:space:]]*#" <<< "$lines"; then
echo " - But they all seem to be commented"
ask " - Continue modifying $file?"
ask " - Continue modifying $file?"
update=$?
fi
fi
@@ -356,12 +356,12 @@ if [ $update_config -eq 2 ]; then
fi
echo
for shell in $shells; do
[[ "$shell" = fish ]] && continue
[[ $shell == fish ]] && continue
[ $shell = zsh ] && dest=${ZDOTDIR:-~}/.zshrc || dest=~/.bashrc
append_line $update_config "[ -f ${prefix}.${shell} ] && source ${prefix}.${shell}" "$dest" "${prefix}.${shell}"
done
if [ $key_bindings -eq 1 ] && [[ "$shells" =~ fish ]]; then
if [ $key_bindings -eq 1 ] && [[ $shells =~ fish ]]; then
bind_file="${fish_dir}/functions/fish_user_key_bindings.fish"
if [ ! -e "$bind_file" ]; then
mkdir -p "${fish_dir}/functions"
@@ -386,13 +386,13 @@ fi
if [ $update_config -eq 1 ]; then
echo 'Finished. Restart your shell or reload config file.'
if [[ "$shells" =~ bash ]]; then
if [[ $shells =~ bash ]]; then
echo -n ' source ~/.bashrc # bash'
[[ "$archi" =~ Darwin ]] && echo -n ' (.bashrc should be loaded from .bash_profile)'
[[ $archi =~ Darwin ]] && echo -n ' (.bashrc should be loaded from .bash_profile)'
echo
fi
[[ "$shells" =~ zsh ]] && echo " source ${ZDOTDIR:-~}/.zshrc # zsh"
[[ "$shells" =~ fish ]] && [ $key_bindings -eq 1 ] && echo ' fzf_key_bindings # fish'
[[ $shells =~ zsh ]] && echo " source ${ZDOTDIR:-~}/.zshrc # zsh"
[[ $shells =~ fish ]] && [ $key_bindings -eq 1 ] && echo ' fzf_key_bindings # fish'
echo
echo 'Use uninstall script to remove fzf.'
echo

View File

@@ -1,4 +1,4 @@
$version="0.65.2"
$version="0.66.0"
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition

View File

@@ -11,7 +11,7 @@ import (
"github.com/junegunn/fzf/src/protector"
)
var version = "0.65"
var version = "0.66"
var revision = "devel"
//go:embed shell/key-bindings.bash

View File

@@ -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
THE SOFTWARE.
..
.TH fzf\-tmux 1 "Aug 2025" "fzf 0.65.2" "fzf\-tmux - open fzf in tmux split pane"
.TH fzf\-tmux 1 "Oct 2025" "fzf 0.66.0" "fzf\-tmux - open fzf in tmux split pane"
.SH NAME
fzf\-tmux - open fzf in tmux split pane

View File

@@ -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
THE SOFTWARE.
..
.TH fzf 1 "Aug 2025" "fzf 0.65.2" "fzf - a command-line fuzzy finder"
.TH fzf 1 "Oct 2025" "fzf 0.66.0" "fzf - a command-line fuzzy finder"
.SH NAME
fzf - a command-line fuzzy finder
@@ -246,11 +246,11 @@ color mappings. Each entry is separated by a comma and/or whitespaces.
.RS
.B BASE SCHEME:
(default: \fBdark\fR on 256-color terminal, otherwise \fB16\fR; If \fBNO_COLOR\fR is set, \fBbw\fR)
(default: \fBdark\fR on 256-color terminal, otherwise \fBbase16\fR; If \fBNO_COLOR\fR is set, \fBbw\fR)
\fBdark \fRColor scheme for dark 256-color terminal
\fBlight \fRColor scheme for light 256-color terminal
\fB16 \fRColor scheme for 16-color terminal
\fBdark \fRColor scheme for dark terminal
\fBlight \fRColor scheme for light terminal
\fBbase16 \fRColor scheme using base 16 colors (alias: \fB16\fR)
\fBbw \fRNo colors (equivalent to \fB\-\-no\-color\fR)
.B COLOR NAMES:
@@ -299,6 +299,7 @@ color mappings. Each entry is separated by a comma and/or whitespaces.
\fBheader (header\-fg) \fRHeader
\fBfooter (footer\-fg) \fRFooter
\fBnth \fRParts of the line specified by \fB\-\-nth\fR (only supports attributes)
\fBnomatch \fRNon-matching items in raw mode (default: \fBdim\fR)
.B ANSI COLORS:
\fB\-1 \fRDefault terminal foreground/background color
@@ -324,7 +325,8 @@ color mappings. Each entry is separated by a comma and/or whitespaces.
\fB#rrggbb \fR24-bit colors
.B ANSI ATTRIBUTES: (Only applies to foreground colors)
\fBregular \fRClears previously set attributes; should precede the other ones
\fBregular \fRClear previously set attributes; should precede the other ones
\fBstrip \fRRemove colors
\fBbold\fR
\fBunderline\fR
\fBreverse\fR
@@ -596,6 +598,9 @@ Indicator for wrapped lines. The default is '↳ ' or '> ' depending on
.B "\-\-no\-multi\-line"
Disable multi-line display of items when using \fB\-\-read0\fR
.TP
.B "\-\-raw"
Enable raw mode where non-matching items are also displayed in a dimmed color.
.TP
.B "\-\-track"
Make fzf track the current selection when the result list is updated.
This can be useful when browsing logs using fzf with sorting disabled. It is
@@ -643,6 +648,9 @@ on the center of the screen.
.BI "\-\-jump\-labels=" "CHARS"
Label characters for \fBjump\fR mode.
.TP
.BI "\-\-gutter=" "CHAR"
Character used for the gutter column (default: '▌' unless \fB\-\-no\-unicode\fR is given)
.TP
.BI "\-\-pointer=" "STR"
Pointer to the current line (default: '▌' or '>' depending on \fB\-\-no\-unicode\fR)
.TP
@@ -1125,19 +1133,25 @@ On Windows, the default value is \fBcmd /s/c\fR when \fB$SHELL\fR is not
set.
.TP
.B "\-\-listen[=[ADDR:]PORT]" "\-\-listen\-unsafe[=[ADDR:]PORT]"
Start HTTP server and listen on the given address. It allows external processes
to send actions to perform via POST method.
.B "\-\-listen[=SOCKET_PATH|[ADDR:]PORT]" "\-\-listen\-unsafe[=[ADDR:]PORT]"
Start HTTP server and listen on the given address or Unix socket. It allows
external processes to send actions to perform via POST method and query the
program state via GET method. For the argument to be recognized as a socket
path, it must have \fB.sock\fR extension.
- If the port number is omitted or given as 0, fzf will automatically choose
a port and export it as \fBFZF_PORT\fR environment variable to the child processes
a port and export it as \fBFZF_PORT\fR environment variable to the child processes.
- If a Unix socket path is given, fzf will create a Unix domain socket at the
given path. The existing file will be removed. The path to the socket file
is exported as \fBFZF_SOCK\fR environment variable.
- If \fBFZF_API_KEY\fR environment variable is set, the server would require
sending an API key with the same value in the \fBx\-api\-key\fR HTTP header
sending an API key with the same value in the \fBx\-api\-key\fR HTTP header.
- \fBFZF_API_KEY\fR is required for a non-localhost listen address
- \fBFZF_API_KEY\fR is required for a non-localhost listen address.
- To allow remote process execution, use \fB\-\-listen\-unsafe\fR
- To allow remote process execution, use \fB\-\-listen\-unsafe\fR.
e.g.
\fB# Start HTTP server on port 6266
@@ -1176,6 +1190,18 @@ e.g.
'
\fR
Here is an example script that uses a Unix socket instead of a TCP port.
\fB
fzf --listen=/tmp/fzf.sock
# GET
curl --unix-socket /tmp/fzf.sock http
# POST
curl --unix-socket /tmp/fzf.sock http -d up
\fR
.SS DIRECTORY TRAVERSAL
.TP
.B "\-\-walker=[file][,dir][,follow][,hidden]"
@@ -1327,6 +1353,8 @@ fzf exports the following environment variables to its child processes.
.br
.BR FZF_COLUMNS " Number of columns fzf takes up excluding padding and margin"
.br
.BR FZF_DIRECTION " Direction of the list (\fBup\fR or \fBdown\fR)"
.br
.BR FZF_TOTAL_COUNT " Total number of items"
.br
.BR FZF_MATCH_COUNT " Number of matched items"
@@ -1363,6 +1391,8 @@ fzf exports the following environment variables to its child processes.
.br
.BR FZF_PORT " Port number when \-\-listen option is used"
.br
.BR FZF_SOCK " Unix socket path when \-\-listen option is used"
.br
.BR FZF_PREVIEW_TOP " Top position of the preview window"
.br
.BR FZF_PREVIEW_LEFT " Left position of the preview window"
@@ -1370,6 +1400,8 @@ fzf exports the following environment variables to its child processes.
.BR FZF_PREVIEW_LINES " Number of lines in the preview window"
.br
.BR FZF_PREVIEW_COLUMNS " Number of columns in the preview window"
.br
.BR FZF_RAW " Only in raw mode. 1 if the current item matches, 0 otherwise"
.SH EXTENDED SEARCH MODE
@@ -1814,6 +1846,7 @@ 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)
\fBbeginning\-of\-line\fR \fIctrl\-a home\fR
\fBbell\fR (ring the terminal bell)
\fBbest\fR (move to the best match; same as \fBfirst\fR if raw mode is disabled)
\fBbg\-cancel\fR (cancel background transform processes)
\fBcancel\fR (clear query string if not empty, abort fzf otherwise)
\fBchange\-border\-label(...)\fR (change \fB\-\-border\-label\fR to the given string)
@@ -1838,9 +1871,13 @@ A key or an event can be bound to one or more of the following actions.
\fBdelete\-char\fR \fIdel\fR
\fBdelete\-char/eof\fR \fIctrl\-d\fR (same as \fBdelete\-char\fR except aborts fzf if query is empty)
\fBdeselect\fR
\fBdeselect\-all\fR (deselect all matches; to also clear non-matched selections, use \fBclear\-multi\fR)
\fBdeselect\-all\fR (deselect all matches; to also clear non-matching selections, use \fBclear\-multi\fR)
\fBdisable\-raw\fR (disable raw mode)
\fBdisable\-search\fR (disable search functionality)
\fBdown\fR \fIctrl\-j ctrl\-n down\fR
\fBdown\fR \fIctrl\-j down\fR
\fBdown\-match\fR \fIctrl\-n\fR \fIalt\-down\fR (move to the match below the cursor)
\fBdown\-selected\fR (move to the selected item below the cursor)
\fBenable\-raw\fR (enable raw mode)
\fBenable\-search\fR (enable search functionality)
\fBend\-of\-line\fR \fIctrl\-e end\fR
\fBexclude\fR (exclude the current item from the result)
@@ -1858,7 +1895,7 @@ A key or an event can be bound to one or more of the following actions.
\fBkill\-word\fR \fIalt\-d\fR
\fBlast\fR (move to the last match; same as \fBpos(\-1)\fR)
\fBnext\-history\fR (\fIctrl\-n\fR on \fB\-\-history\fR)
\fBnext\-selected\fR (move to the next selected item)
\fBnext\-selected\fR (synonym to \fBdown\-selected\fR)
\fBpage\-down\fR \fIpgdn\fR
\fBpage\-up\fR \fIpgup\fR
\fBhalf\-page\-down\fR
@@ -1871,7 +1908,7 @@ A key or an event can be bound to one or more of the following actions.
\fBoffset\-middle\fR (place the current item is in the middle of the screen)
\fBpos(...)\fR (move cursor to the numeric position; negative number to count from the end)
\fBprev\-history\fR (\fIctrl\-p\fR on \fB\-\-history\fR)
\fBprev\-selected\fR (move to the previous selected item)
\fBprev\-selected\fR (synonym to \fBup\-selected\fR)
\fBpreview(...)\fR (see below for the details)
\fBpreview\-down\fR \fIshift\-down\fR
\fBpreview\-up\fR \fIshift\-up\fR
@@ -1906,6 +1943,7 @@ A key or an event can be bound to one or more of the following actions.
\fBtoggle\-multi\-line\fR
\fBtoggle\-preview\fR
\fBtoggle\-preview\-wrap\fR
\fBtoggle\-raw\fR (toggle raw mode for displaying non-matching items)
\fBtoggle\-search\fR (toggle search functionality)
\fBtoggle\-sort\fR
\fBtoggle\-track\fR (toggle global tracking option (\fB\-\-track\fR))
@@ -1932,7 +1970,9 @@ A key or an event can be bound to one or more of the following actions.
\fBunix\-line\-discard\fR \fIctrl\-u\fR
\fBunix\-word\-rubout\fR \fIctrl\-w\fR
\fBuntrack\-current\fR (stop tracking the current item; no-op if global tracking is enabled)
\fBup\fR \fIctrl\-k ctrl\-p up\fR
\fBup\fR \fIctrl\-k up\fR
\fBup\-match\fR \fIctrl\-p\fR \fIalt\-up\fR (move to the match above the cursor)
\fBup\-selected\fR (move to the selected item above the cursor)
\fByank\fR \fIctrl\-y\fR
Each \fBtransform*\fR action has a corresponding \fBbg\-transform*\fR

View File

@@ -1,4 +1,3 @@
__fzf_defaults() {
# $1: Prepend to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
# $2: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
@@ -22,12 +21,12 @@ __fzf_exec_awk() {
# 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
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
[[ $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

View File

@@ -31,9 +31,10 @@ if [[ $- =~ i ]]; then
###########################################################
#----BEGIN shfmt
#----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
# To modify it, one can edit "common.sh" and run "./update.sh" to apply
# the changes. See code comments in "common.sh" for the implementation details.
__fzf_defaults() {
@@ -47,10 +48,10 @@ __fzf_exec_awk() {
__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
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
[[ $n == mawk ]] && ((d >= 20230302 && (x * 1000 + y) * 1000 + z >= 1003004)) && __fzf_awk=mawk
fi
fi
LC_ALL=C exec "$__fzf_awk" "$@"
@@ -58,9 +59,9 @@ __fzf_exec_awk() {
#----END INCLUDE
__fzf_comprun() {
if [[ "$(type -t _fzf_comprun 2>&1)" = function ]]; then
if [[ "$(type -t _fzf_comprun 2>&1)" == function ]]; then
_fzf_comprun "$@"
elif [[ -n "${TMUX_PANE-}" ]] && { [[ "${FZF_TMUX:-0}" != 0 ]] || [[ -n "${FZF_TMUX_OPTS-}" ]]; }; then
elif [[ -n ${TMUX_PANE-} ]] && { [[ ${FZF_TMUX:-0} != 0 ]] || [[ -n ${FZF_TMUX_OPTS-} ]]; }; then
shift
fzf-tmux ${FZF_TMUX_OPTS:--d${FZF_TMUX_HEIGHT:-40%}} -- "$@"
else
@@ -72,13 +73,13 @@ __fzf_comprun() {
__fzf_orig_completion() {
local l comp f cmd
while read -r l; do
if [[ "$l" =~ ^(.*\ -F)\ *([^ ]*).*\ ([^ ]*)$ ]]; then
if [[ $l =~ ^(.*\ -F)\ *([^ ]*).*\ ([^ ]*)$ ]]; then
comp="${BASH_REMATCH[1]}"
f="${BASH_REMATCH[2]}"
cmd="${BASH_REMATCH[3]}"
[[ "$f" = _fzf_* ]] && continue
[[ $f == _fzf_* ]] && continue
printf -v "_fzf_orig_completion_${cmd//[^A-Za-z0-9_]/_}" "%s" "${comp} %s ${cmd} #${f}"
if [[ "$l" = *" -o nospace "* ]] && [[ ! "${__fzf_nospace_commands-}" = *" $cmd "* ]]; then
if [[ $l == *" -o nospace "* ]] && [[ ${__fzf_nospace_commands-} != *" $cmd "* ]]; then
__fzf_nospace_commands="${__fzf_nospace_commands-} $cmd "
fi
fi
@@ -114,7 +115,7 @@ _fzf_opts_completion() {
local cur prev opts
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
prev="${COMP_WORDS[COMP_CWORD - 1]}"
opts="
+c --no-color
+i --no-ignore-case
@@ -197,28 +198,28 @@ _fzf_opts_completion() {
--"
case "${prev}" in
--scheme)
COMPREPLY=( $(compgen -W "default path history" -- "$cur") )
return 0
;;
--tiebreak)
COMPREPLY=( $(compgen -W "length chunk begin end index" -- "$cur") )
return 0
;;
--color)
COMPREPLY=( $(compgen -W "dark light 16 bw no" -- "$cur") )
return 0
;;
--layout)
COMPREPLY=( $(compgen -W "default reverse reverse-list" -- "$cur") )
return 0
;;
--info)
COMPREPLY=( $(compgen -W "default right hidden inline inline-right" -- "$cur") )
return 0
;;
--preview-window)
COMPREPLY=( $(compgen -W "
--scheme)
COMPREPLY=($(compgen -W "default path history" -- "$cur"))
return 0
;;
--tiebreak)
COMPREPLY=($(compgen -W "length chunk begin end index" -- "$cur"))
return 0
;;
--color)
COMPREPLY=($(compgen -W "dark light 16 bw no" -- "$cur"))
return 0
;;
--layout)
COMPREPLY=($(compgen -W "default reverse reverse-list" -- "$cur"))
return 0
;;
--info)
COMPREPLY=($(compgen -W "default right hidden inline inline-right" -- "$cur"))
return 0
;;
--preview-window)
COMPREPLY=($(compgen -W "
default
hidden
nohidden
@@ -244,21 +245,21 @@ _fzf_opts_completion() {
border-left
border-right
follow
nofollow" -- "$cur") )
return 0
;;
--border)
COMPREPLY=( $(compgen -W "rounded sharp bold block thinblock double horizontal vertical top bottom left right none" -- "$cur") )
return 0
;;
--border-label-pos|--preview-label-pos)
COMPREPLY=( $(compgen -W "center bottom top" -- "$cur") )
return 0
;;
nofollow" -- "$cur"))
return 0
;;
--border)
COMPREPLY=($(compgen -W "rounded sharp bold block thinblock double horizontal vertical top bottom left right none" -- "$cur"))
return 0
;;
--border-label-pos | --preview-label-pos)
COMPREPLY=($(compgen -W "center bottom top" -- "$cur"))
return 0
;;
esac
if [[ "$cur" =~ ^-|\+ ]]; then
COMPREPLY=( $(compgen -W "${opts}" -- "$cur") )
if [[ $cur =~ ^-|\+ ]]; then
COMPREPLY=($(compgen -W "${opts}" -- "$cur"))
return 0
fi
@@ -272,7 +273,7 @@ _fzf_handle_dynamic_completion() {
orig_cmd="$1"
if __fzf_orig_completion_get_orig_func "$cmd"; then
"$REPLY" "$@"
elif [[ -n "${_fzf_completion_loader-}" ]]; then
elif [[ -n ${_fzf_completion_loader-} ]]; then
orig_complete=$(complete -p "$orig_cmd" 2> /dev/null)
$_fzf_completion_loader "$@"
ret=$?
@@ -286,7 +287,7 @@ _fzf_handle_dynamic_completion() {
__fzf_orig_completion_instantiate "$cmd" "${BASH_REMATCH[1]}" &&
orig_complete=$REPLY
if [[ "${__fzf_nospace_commands-}" = *" $orig_cmd "* ]]; then
if [[ ${__fzf_nospace_commands-} == *" $orig_cmd "* ]]; then
eval "${orig_complete/ -F / -o nospace -F }"
else
eval "$orig_complete"
@@ -306,18 +307,18 @@ __fzf_generic_path_completion() {
COMPREPLY=()
trigger=${FZF_COMPLETION_TRIGGER-'**'}
[[ $COMP_CWORD -ge 0 ]] && cur="${COMP_WORDS[COMP_CWORD]}"
if [[ "$cur" == *"$trigger" ]] && [[ $cur != *'$('* ]] && [[ $cur != *':='* ]] && [[ $cur != *'`'* ]]; then
if [[ $cur == *"$trigger" ]] && [[ $cur != *'$('* ]] && [[ $cur != *':='* ]] && [[ $cur != *'`'* ]]; then
base=${cur:0:${#cur}-${#trigger}}
eval "base=$base" 2> /dev/null || return
dir=
[[ $base = *"/"* ]] && dir="$base"
[[ $base == *"/"* ]] && dir="$base"
while true; do
if [[ -z "$dir" ]] || [[ -d "$dir" ]]; then
leftover=${base/#"$dir"}
leftover=${leftover/#\/}
[[ -z "$dir" ]] && dir='.'
[[ "$dir" != "/" ]] && dir="${dir/%\//}"
if [[ -z $dir ]] || [[ -d $dir ]]; then
leftover=${base/#"$dir"/}
leftover=${leftover/#\//}
[[ -z $dir ]] && dir='.'
[[ $dir != "/" ]] && dir="${dir/%\//}"
matches=$(
export FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse --scheme=path" "${FZF_COMPLETION_OPTS-} $2")
unset FZF_DEFAULT_COMMAND FZF_DEFAULT_OPTS_FILE
@@ -337,11 +338,11 @@ __fzf_generic_path_completion() {
done
)
matches=${matches% }
[[ -z "$3" ]] && [[ "${__fzf_nospace_commands-}" = *" ${COMP_WORDS[0]} "* ]] && matches="$matches "
if [[ -n "$matches" ]]; then
COMPREPLY=( "$matches" )
[[ -z $3 ]] && [[ ${__fzf_nospace_commands-} == *" ${COMP_WORDS[0]} "* ]] && matches="$matches "
if [[ -n $matches ]]; then
COMPREPLY=("$matches")
else
COMPREPLY=( "$cur" )
COMPREPLY=("$cur")
fi
# To redraw line after fzf closes (printf '\e[5n')
bind '"\e[0n": redraw-current-line' 2> /dev/null
@@ -349,7 +350,7 @@ __fzf_generic_path_completion() {
return 0
fi
dir=$(command dirname "$dir")
[[ "$dir" =~ /$ ]] || dir="$dir"/
[[ $dir =~ /$ ]] || dir="$dir"/
done
else
shift
@@ -365,15 +366,15 @@ _fzf_complete() {
args=("$@")
sep=
for i in "${!args[@]}"; do
if [[ "${args[$i]}" = -- ]]; then
if [[ ${args[$i]} == -- ]]; then
sep=$i
break
fi
done
if [[ -n "$sep" ]]; then
if [[ -n $sep ]]; then
str_arg=
rest=("${args[@]:$((sep + 1)):${#args[@]}}")
args=("${args[@]:0:$sep}")
args=("${args[@]:0:sep}")
else
str_arg=$1
args=()
@@ -388,15 +389,16 @@ _fzf_complete() {
trigger=${FZF_COMPLETION_TRIGGER-'**'}
cmd="${COMP_WORDS[0]}"
cur="${COMP_WORDS[COMP_CWORD]}"
if [[ "$cur" == *"$trigger" ]] && [[ $cur != *'$('* ]] && [[ $cur != *':='* ]] && [[ $cur != *'`'* ]]; then
if [[ $cur == *"$trigger" ]] && [[ $cur != *'$('* ]] && [[ $cur != *':='* ]] && [[ $cur != *'`'* ]]; then
cur=${cur:0:${#cur}-${#trigger}}
selected=$(
FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse" "${FZF_COMPLETION_OPTS-} $str_arg") \
FZF_DEFAULT_OPTS_FILE='' \
__fzf_comprun "${rest[0]}" "${args[@]}" -q "$cur" | eval "$post" | command tr '\n' ' ')
__fzf_comprun "${rest[0]}" "${args[@]}" -q "$cur" | eval "$post" | command tr '\n' ' '
)
selected=${selected% } # Strip trailing space not to repeat "-o nospace"
if [[ -n "$selected" ]]; then
if [[ -n $selected ]]; then
COMPREPLY=("$selected")
else
COMPREPLY=("$cur")
@@ -454,10 +456,10 @@ _fzf_proc_completion() {
'
_fzf_complete -m --header-lines=1 --no-preview --wrap --color fg:dim,nth:regular \
--bind "click-header:transform:$transformer" -- "$@" < <(
command ps -eo user,pid,ppid,start,time,command 2> /dev/null ||
command ps -eo user,pid,ppid,time,args 2> /dev/null || # For BusyBox
command ps --everyone --full --windows # For cygwin
)
command ps -eo user,pid,ppid,start,time,command 2> /dev/null ||
command ps -eo user,pid,ppid,time,args 2> /dev/null || # For BusyBox
command ps --everyone --full --windows # For cygwin
)
}
_fzf_proc_completion_post() {
@@ -540,12 +542,12 @@ _fzf_host_completion() {
# > and the third argument ($3) is the word preceding the word being completed on the current command line.
_fzf_complete_ssh() {
case $3 in
-i|-F|-E)
-i | -F | -E)
_fzf_path_completion "$@"
;;
*)
local user=
[[ "$2" =~ '@' ]] && user="${2%%@*}@"
[[ $2 =~ '@' ]] && user="${2%%@*}@"
_fzf_complete +m -- "$@" < <(__fzf_list_hosts | __fzf_exec_awk -v user="$user" '{print user $0}')
;;
esac
@@ -676,12 +678,13 @@ _fzf_setup_completion() {
__fzf_orig_completion < <(complete -p "$@" 2> /dev/null)
for cmd in "$@"; do
case "$kind" in
dir) __fzf_defc "$cmd" "$fn" "-o nospace -o dirnames" ;;
var) __fzf_defc "$cmd" "$fn" "-o default -o nospace -v" ;;
dir) __fzf_defc "$cmd" "$fn" "-o nospace -o dirnames" ;;
var) __fzf_defc "$cmd" "$fn" "-o default -o nospace -v" ;;
alias) __fzf_defc "$cmd" "$fn" "-a" ;;
*) __fzf_defc "$cmd" "$fn" "-o default -o bashdefault" ;;
*) __fzf_defc "$cmd" "$fn" "-o default -o bashdefault" ;;
esac
done
}
#----END shfmt
fi

View File

@@ -98,7 +98,7 @@ 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
# To modify it, one can edit "common.sh" and run "./update.sh" to apply
# the changes. See code comments in "common.sh" for the implementation details.
__fzf_defaults() {
@@ -112,10 +112,10 @@ __fzf_exec_awk() {
__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
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
[[ $n == mawk ]] && ((d >= 20230302 && (x * 1000 + y) * 1000 + z >= 1003004)) && __fzf_awk=mawk
fi
fi
LC_ALL=C exec "$__fzf_awk" "$@"

View File

@@ -7,6 +7,7 @@
# - $FZF_TMUX_OPTS
# - $FZF_CTRL_T_COMMAND
# - $FZF_CTRL_T_OPTS
# - $FZF_CTRL_R_COMMAND
# - $FZF_CTRL_R_OPTS
# - $FZF_ALT_C_COMMAND
# - $FZF_ALT_C_OPTS
@@ -17,9 +18,10 @@ if [[ $- =~ i ]]; then
# Key bindings
# ------------
#----BEGIN shfmt
#----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
# To modify it, one can edit "common.sh" and run "./update.sh" to apply
# the changes. See code comments in "common.sh" for the implementation details.
__fzf_defaults() {
@@ -33,10 +35,10 @@ __fzf_exec_awk() {
__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
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
[[ $n == mawk ]] && ((d >= 20230302 && (x * 1000 + y) * 1000 + z >= 1003004)) && __fzf_awk=mawk
fi
fi
LC_ALL=C exec "$__fzf_awk" "$@"
@@ -45,30 +47,30 @@ __fzf_exec_awk() {
__fzf_select__() {
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_FILE='' $(__fzfcmd) "$@" |
FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse --walker=file,dir,follow,hidden --scheme=path" "${FZF_CTRL_T_OPTS-} -m") \
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd) "$@" |
while read -r item; do
printf '%q ' "$item" # escape special chars
printf '%q ' "$item" # escape special chars
done
}
__fzfcmd() {
[[ -n "${TMUX_PANE-}" ]] && { [[ "${FZF_TMUX:-0}" != 0 ]] || [[ -n "${FZF_TMUX_OPTS-}" ]]; } &&
[[ -n ${TMUX_PANE-} ]] && { [[ ${FZF_TMUX:-0} != 0 ]] || [[ -n ${FZF_TMUX_OPTS-} ]]; } &&
echo "fzf-tmux ${FZF_TMUX_OPTS:--d${FZF_TMUX_HEIGHT:-40%}} -- " || echo "fzf"
}
fzf-file-widget() {
local selected="$(__fzf_select__ "$@")"
READLINE_LINE="${READLINE_LINE:0:$READLINE_POINT}$selected${READLINE_LINE:$READLINE_POINT}"
READLINE_POINT=$(( READLINE_POINT + ${#selected} ))
READLINE_LINE="${READLINE_LINE:0:READLINE_POINT}$selected${READLINE_LINE:READLINE_POINT}"
READLINE_POINT=$((READLINE_POINT + ${#selected}))
}
__fzf_cd__() {
local dir
dir=$(
FZF_DEFAULT_COMMAND=${FZF_ALT_C_COMMAND:-} \
FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse --walker=dir,follow,hidden --scheme=path" "${FZF_ALT_C_OPTS-} +m") \
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd)
FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse --walker=dir,follow,hidden --scheme=path" "${FZF_ALT_C_OPTS-} +m") \
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd)
) && printf 'builtin cd -- %q' "$(builtin unset CDPATH && builtin cd -- "$dir" && builtin pwd)"
}
@@ -80,11 +82,11 @@ if command -v perl > /dev/null; then
set +o pipefail
builtin fc -lnr -2147483648 |
last_hist=$(HISTTIMEFORMAT='' builtin history 1) command perl -n -l0 -e "$script" |
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,alt-r:toggle-raw --wrap-sign '"$'\t'"↳ ' --highlight-line ${FZF_CTRL_R_OPTS-} +m --read0") \
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd) --query "$READLINE_LINE"
) || return
READLINE_LINE=$(command perl -pe 's/^\d*\t//' <<< "$output")
if [[ -z "$READLINE_POINT" ]]; then
if [[ -z $READLINE_POINT ]]; then
echo "$READLINE_LINE"
else
READLINE_POINT=0x7fffffff
@@ -93,7 +95,7 @@ if command -v perl > /dev/null; then
else # awk - fallback for POSIX systems
__fzf_history__() {
local output script
[[ $(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 } }
NR==1 { b = substr($0, 2); next }
/^\t/ { P(b); b = substr($0, 2); next }
@@ -101,13 +103,13 @@ else # awk - fallback for POSIX systems
END { if (NR) P(b) }'
output=$(
set +o pipefail
builtin fc -lnr -2147483648 2> /dev/null | # ( $'\t '<lines>$'\n' )* ; <lines> ::= [^\n]* ( $'\n'<lines> )*
__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") \
builtin fc -lnr -2147483648 2> /dev/null | # ( $'\t '<lines>$'\n' )* ; <lines> ::= [^\n]* ( $'\n'<lines> )*
__fzf_exec_awk "$script" | # ( <counter>$'\t'<lines>$'\000' )*
FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort,alt-r:toggle-raw --wrap-sign '"$'\t'"↳ ' --highlight-line ${FZF_CTRL_R_OPTS-} +m --read0") \
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd) --query "$READLINE_LINE"
) || return
READLINE_LINE=${output#*$'\t'}
if [[ -z "$READLINE_POINT" ]]; then
if [[ -z $READLINE_POINT ]]; then
echo "$READLINE_LINE"
else
READLINE_POINT=0x7fffffff
@@ -122,37 +124,48 @@ bind -m vi-command '"\C-z": emacs-editing-mode'
bind -m vi-insert '"\C-z": emacs-editing-mode'
bind -m emacs-standard '"\C-z": vi-editing-mode'
if (( BASH_VERSINFO[0] < 4 )); then
if ((BASH_VERSINFO[0] < 4)); then
# CTRL-T - Paste the selected file path into the command line
if [[ "${FZF_CTRL_T_COMMAND-x}" != "" ]]; then
if [[ ${FZF_CTRL_T_COMMAND-x} != "" ]]; then
bind -m emacs-standard '"\C-t": " \C-b\C-k \C-u`__fzf_select__`\e\C-e\er\C-a\C-y\C-h\C-e\e \C-y\ey\C-x\C-x\C-f\C-y\ey\C-_"'
bind -m vi-command '"\C-t": "\C-z\C-t\C-z"'
bind -m vi-insert '"\C-t": "\C-z\C-t\C-z"'
fi
# CTRL-R - Paste the selected command from history into the command line
bind -m emacs-standard '"\C-r": "\C-e \C-u\C-y\ey\C-u`__fzf_history__`\e\C-e\er"'
bind -m vi-command '"\C-r": "\C-z\C-r\C-z"'
bind -m vi-insert '"\C-r": "\C-z\C-r\C-z"'
if [[ ${FZF_CTRL_R_COMMAND-x} != "" ]]; then
if [[ -n ${FZF_CTRL_R_COMMAND-} ]]; then
echo "warning: FZF_CTRL_R_COMMAND is set to a custom command, but custom commands are not yet supported for CTRL-R" >&2
fi
bind -m emacs-standard '"\C-r": "\C-e \C-u\C-y\ey\C-u`__fzf_history__`\e\C-e\er"'
bind -m vi-command '"\C-r": "\C-z\C-r\C-z"'
bind -m vi-insert '"\C-r": "\C-z\C-r\C-z"'
fi
else
# CTRL-T - Paste the selected file path into the command line
if [[ "${FZF_CTRL_T_COMMAND-x}" != "" ]]; then
if [[ ${FZF_CTRL_T_COMMAND-x} != "" ]]; then
bind -m emacs-standard -x '"\C-t": fzf-file-widget'
bind -m vi-command -x '"\C-t": fzf-file-widget'
bind -m vi-insert -x '"\C-t": fzf-file-widget'
fi
# CTRL-R - Paste the selected command from history into the command line
bind -m emacs-standard -x '"\C-r": __fzf_history__'
bind -m vi-command -x '"\C-r": __fzf_history__'
bind -m vi-insert -x '"\C-r": __fzf_history__'
if [[ ${FZF_CTRL_R_COMMAND-x} != "" ]]; then
if [[ -n ${FZF_CTRL_R_COMMAND-} ]]; then
echo "warning: FZF_CTRL_R_COMMAND is set to a custom command, but custom commands are not yet supported for CTRL-R" >&2
fi
bind -m emacs-standard -x '"\C-r": __fzf_history__'
bind -m vi-command -x '"\C-r": __fzf_history__'
bind -m vi-insert -x '"\C-r": __fzf_history__'
fi
fi
# ALT-C - cd into the selected directory
if [[ "${FZF_ALT_C_COMMAND-x}" != "" ]]; then
if [[ ${FZF_ALT_C_COMMAND-x} != "" ]]; then
bind -m emacs-standard '"\ec": " \C-b\C-k \C-u`__fzf_cd__`\e\C-e\er\C-m\C-y\C-h\e \C-y\ey\C-x\C-x\C-d\C-y\ey\C-_"'
bind -m vi-command '"\ec": "\C-z\ec\C-z"'
bind -m vi-insert '"\ec": "\C-z\ec\C-z"'
fi
#----END shfmt
fi

View File

@@ -7,6 +7,7 @@
# - $FZF_TMUX_OPTS
# - $FZF_CTRL_T_COMMAND
# - $FZF_CTRL_T_OPTS
# - $FZF_CTRL_R_COMMAND
# - $FZF_CTRL_R_OPTS
# - $FZF_ALT_C_COMMAND
# - $FZF_ALT_C_OPTS
@@ -159,7 +160,7 @@ function fzf_key_bindings
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" \
"--bind=ctrl-r:toggle-sort,alt-r:toggle-raw --highlight-line $FZF_CTRL_R_OPTS" \
'--accept-nth=2.. --read0 --print0 --with-shell='(status fish-path)\\ -c)
set -lx FZF_DEFAULT_OPTS_FILE
@@ -214,8 +215,13 @@ function fzf_key_bindings
commandline -f repaint
end
bind \cr fzf-history-widget
bind -M insert \cr fzf-history-widget
if not set -q FZF_CTRL_R_COMMAND; or test -n "$FZF_CTRL_R_COMMAND"
if test -n "$FZF_CTRL_R_COMMAND"
echo "warning: FZF_CTRL_R_COMMAND is set to a custom command, but custom commands are not yet supported for CTRL-R" >&2
end
bind \cr fzf-history-widget
bind -M insert \cr fzf-history-widget
end
if not set -q FZF_CTRL_T_COMMAND; or test -n "$FZF_CTRL_T_COMMAND"
bind \ct fzf-file-widget

View File

@@ -7,6 +7,7 @@
# - $FZF_TMUX_OPTS
# - $FZF_CTRL_T_COMMAND
# - $FZF_CTRL_T_OPTS
# - $FZF_CTRL_R_COMMAND
# - $FZF_CTRL_R_OPTS
# - $FZF_ALT_C_COMMAND
# - $FZF_ALT_C_OPTS
@@ -40,7 +41,7 @@ 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
# To modify it, one can edit "common.sh" and run "./update.sh" to apply
# the changes. See code comments in "common.sh" for the implementation details.
__fzf_defaults() {
@@ -54,10 +55,10 @@ __fzf_exec_awk() {
__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
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
[[ $n == mawk ]] && ((d >= 20230302 && (x * 1000 + y) * 1000 + z >= 1003004)) && __fzf_awk=mawk
fi
fi
LC_ALL=C exec "$__fzf_awk" "$@"
@@ -132,11 +133,11 @@ fzf-history-widget() {
if zmodload -F zsh/parameter p:{commands,history} 2>/dev/null && (( ${+commands[perl]} )); then
selected="$(printf '%s\t%s\000' "${(kv)history[@]}" |
perl -0 -ne 'if (!$seen{(/^\s*[0-9]+\**\t(.*)/s, $1)}++) { s/\n/\n\t/g; print; }' |
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,alt-r:toggle-raw --wrap-sign '\t↳ ' --highlight-line ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER} +m --read0") \
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd))"
else
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,alt-r:toggle-raw --wrap-sign '\t↳ ' --highlight-line ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER} +m") \
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd))"
fi
local ret=$?
@@ -150,10 +151,15 @@ fzf-history-widget() {
zle reset-prompt
return $ret
}
zle -N fzf-history-widget
bindkey -M emacs '^R' fzf-history-widget
bindkey -M vicmd '^R' fzf-history-widget
bindkey -M viins '^R' fzf-history-widget
if [[ ${FZF_CTRL_R_COMMAND-x} != "" ]]; then
if [[ -n ${FZF_CTRL_R_COMMAND-} ]]; then
echo "warning: FZF_CTRL_R_COMMAND is set to a custom command, but custom commands are not yet supported for CTRL-R" >&2
fi
zle -N fzf-history-widget
bindkey -M emacs '^R' fzf-history-widget
bindkey -M vicmd '^R' fzf-history-widget
bindkey -M viins '^R' fzf-history-widget
fi
fi
} always {

View File

@@ -1,31 +0,0 @@
#!/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

68
shell/update.sh Executable file
View File

@@ -0,0 +1,68 @@
#!/usr/bin/env bash
# This script applies the contents of "common.sh" to the other files.
set -e
dir=${0%"${0##*/}"}
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.sh" to apply
# the changes. See code comments in "common.sh" for the implementation details.
EOF
echo
grep -v '^[[:blank:]]*#' "$dir/common.sh" # remove code comments in common.sh
sed -n '/^#----END INCLUDE/,$p' "$1"
} > "$1.part"
mv -f "$1.part" "$1"
}
update "$dir/completion.bash"
update "$dir/completion.zsh"
update "$dir/key-bindings.bash"
update "$dir/key-bindings.zsh"
# Check if --check is in ARGV
check=0
rest=()
for arg in "$@"; do
case $arg in
--check) check=1 ;;
*) rest+=("$arg") ;;
esac
done
fmt() {
if ! grep -q "^#----BEGIN shfmt" "$1"; then
if [[ $check == 1 ]]; then
shfmt -d "$1"
return $?
else
shfmt -w "$1"
fi
else
{
sed -n '1,/^#----BEGIN shfmt/p' "$1" | sed '$d'
sed -n '/^#----BEGIN shfmt/,/^#----END shfmt/p' "$1" | shfmt --filename "$1"
sed -n '/^#----END shfmt/,$p' "$1" | sed '1d'
} > "$1.part"
if [[ $check == 1 ]]; then
diff -q "$1" "$1.part"
ret=$?
rm -f "$1.part"
return $ret
fi
mv -f "$1.part" "$1"
fi
}
for file in "${rest[@]}"; do
fmt "$file" || exit $?
done

View File

@@ -77,106 +77,112 @@ func _() {
_ = x[actToggleWrap-66]
_ = x[actToggleMultiLine-67]
_ = x[actToggleHscroll-68]
_ = x[actTrackCurrent-69]
_ = x[actToggleInput-70]
_ = x[actHideInput-71]
_ = x[actShowInput-72]
_ = x[actUntrackCurrent-73]
_ = x[actDown-74]
_ = x[actUp-75]
_ = x[actPageUp-76]
_ = x[actPageDown-77]
_ = x[actPosition-78]
_ = x[actHalfPageUp-79]
_ = x[actHalfPageDown-80]
_ = x[actOffsetUp-81]
_ = x[actOffsetDown-82]
_ = x[actOffsetMiddle-83]
_ = x[actJump-84]
_ = x[actJumpAccept-85]
_ = x[actPrintQuery-86]
_ = x[actRefreshPreview-87]
_ = x[actReplaceQuery-88]
_ = x[actToggleSort-89]
_ = x[actShowPreview-90]
_ = x[actHidePreview-91]
_ = x[actTogglePreview-92]
_ = x[actTogglePreviewWrap-93]
_ = x[actTransform-94]
_ = x[actTransformBorderLabel-95]
_ = x[actTransformGhost-96]
_ = x[actTransformHeader-97]
_ = x[actTransformFooter-98]
_ = x[actTransformHeaderLabel-99]
_ = x[actTransformFooterLabel-100]
_ = x[actTransformInputLabel-101]
_ = x[actTransformListLabel-102]
_ = x[actTransformNth-103]
_ = x[actTransformPointer-104]
_ = x[actTransformPreviewLabel-105]
_ = x[actTransformPrompt-106]
_ = x[actTransformQuery-107]
_ = x[actTransformSearch-108]
_ = x[actTrigger-109]
_ = x[actBgTransform-110]
_ = x[actBgTransformBorderLabel-111]
_ = x[actBgTransformGhost-112]
_ = x[actBgTransformHeader-113]
_ = x[actBgTransformFooter-114]
_ = x[actBgTransformHeaderLabel-115]
_ = x[actBgTransformFooterLabel-116]
_ = x[actBgTransformInputLabel-117]
_ = x[actBgTransformListLabel-118]
_ = x[actBgTransformNth-119]
_ = x[actBgTransformPointer-120]
_ = x[actBgTransformPreviewLabel-121]
_ = x[actBgTransformPrompt-122]
_ = x[actBgTransformQuery-123]
_ = x[actBgTransformSearch-124]
_ = x[actBgCancel-125]
_ = x[actSearch-126]
_ = x[actPreview-127]
_ = x[actPreviewTop-128]
_ = x[actPreviewBottom-129]
_ = x[actPreviewUp-130]
_ = x[actPreviewDown-131]
_ = x[actPreviewPageUp-132]
_ = x[actPreviewPageDown-133]
_ = x[actPreviewHalfPageUp-134]
_ = x[actPreviewHalfPageDown-135]
_ = x[actPrevHistory-136]
_ = x[actPrevSelected-137]
_ = x[actPrint-138]
_ = x[actPut-139]
_ = x[actNextHistory-140]
_ = x[actNextSelected-141]
_ = x[actExecute-142]
_ = x[actExecuteSilent-143]
_ = x[actExecuteMulti-144]
_ = x[actSigStop-145]
_ = x[actFirst-146]
_ = x[actLast-147]
_ = x[actReload-148]
_ = x[actReloadSync-149]
_ = x[actDisableSearch-150]
_ = x[actEnableSearch-151]
_ = x[actSelect-152]
_ = x[actDeselect-153]
_ = x[actUnbind-154]
_ = x[actRebind-155]
_ = x[actToggleBind-156]
_ = x[actBecome-157]
_ = x[actShowHeader-158]
_ = x[actHideHeader-159]
_ = x[actBell-160]
_ = x[actExclude-161]
_ = x[actExcludeMulti-162]
_ = x[actAsync-163]
_ = x[actToggleRaw-69]
_ = x[actEnableRaw-70]
_ = x[actDisableRaw-71]
_ = x[actTrackCurrent-72]
_ = x[actToggleInput-73]
_ = x[actHideInput-74]
_ = x[actShowInput-75]
_ = x[actUntrackCurrent-76]
_ = x[actDown-77]
_ = x[actDownMatch-78]
_ = x[actUp-79]
_ = x[actUpMatch-80]
_ = x[actPageUp-81]
_ = x[actPageDown-82]
_ = x[actPosition-83]
_ = x[actHalfPageUp-84]
_ = x[actHalfPageDown-85]
_ = x[actOffsetUp-86]
_ = x[actOffsetDown-87]
_ = x[actOffsetMiddle-88]
_ = x[actJump-89]
_ = x[actJumpAccept-90]
_ = x[actPrintQuery-91]
_ = x[actRefreshPreview-92]
_ = x[actReplaceQuery-93]
_ = x[actToggleSort-94]
_ = x[actShowPreview-95]
_ = x[actHidePreview-96]
_ = x[actTogglePreview-97]
_ = x[actTogglePreviewWrap-98]
_ = x[actTransform-99]
_ = x[actTransformBorderLabel-100]
_ = x[actTransformGhost-101]
_ = x[actTransformHeader-102]
_ = x[actTransformFooter-103]
_ = x[actTransformHeaderLabel-104]
_ = x[actTransformFooterLabel-105]
_ = x[actTransformInputLabel-106]
_ = x[actTransformListLabel-107]
_ = x[actTransformNth-108]
_ = x[actTransformPointer-109]
_ = x[actTransformPreviewLabel-110]
_ = x[actTransformPrompt-111]
_ = x[actTransformQuery-112]
_ = x[actTransformSearch-113]
_ = x[actTrigger-114]
_ = x[actBgTransform-115]
_ = x[actBgTransformBorderLabel-116]
_ = x[actBgTransformGhost-117]
_ = x[actBgTransformHeader-118]
_ = x[actBgTransformFooter-119]
_ = x[actBgTransformHeaderLabel-120]
_ = x[actBgTransformFooterLabel-121]
_ = x[actBgTransformInputLabel-122]
_ = x[actBgTransformListLabel-123]
_ = x[actBgTransformNth-124]
_ = x[actBgTransformPointer-125]
_ = x[actBgTransformPreviewLabel-126]
_ = x[actBgTransformPrompt-127]
_ = x[actBgTransformQuery-128]
_ = x[actBgTransformSearch-129]
_ = x[actBgCancel-130]
_ = x[actSearch-131]
_ = x[actPreview-132]
_ = x[actPreviewTop-133]
_ = x[actPreviewBottom-134]
_ = x[actPreviewUp-135]
_ = x[actPreviewDown-136]
_ = x[actPreviewPageUp-137]
_ = x[actPreviewPageDown-138]
_ = x[actPreviewHalfPageUp-139]
_ = x[actPreviewHalfPageDown-140]
_ = x[actPrevHistory-141]
_ = x[actPrevSelected-142]
_ = x[actPrint-143]
_ = x[actPut-144]
_ = x[actNextHistory-145]
_ = x[actNextSelected-146]
_ = x[actExecute-147]
_ = x[actExecuteSilent-148]
_ = x[actExecuteMulti-149]
_ = x[actSigStop-150]
_ = x[actBest-151]
_ = x[actFirst-152]
_ = x[actLast-153]
_ = x[actReload-154]
_ = x[actReloadSync-155]
_ = x[actDisableSearch-156]
_ = x[actEnableSearch-157]
_ = x[actSelect-158]
_ = x[actDeselect-159]
_ = x[actUnbind-160]
_ = x[actRebind-161]
_ = x[actToggleBind-162]
_ = x[actBecome-163]
_ = x[actShowHeader-164]
_ = x[actHideHeader-165]
_ = x[actBell-166]
_ = x[actExclude-167]
_ = x[actExcludeMulti-168]
_ = x[actAsync-169]
}
const _actionType_name = "actIgnoreactStartactClickactInvalidactBracketedPasteBeginactBracketedPasteEndactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactBackwardSubWordactCancelactChangeBorderLabelactChangeGhostactChangeHeaderactChangeFooteractChangeHeaderLabelactChangeFooterLabelactChangeInputLabelactChangeListLabelactChangeMultiactChangeNthactChangePointeractChangePreviewactChangePreviewLabelactChangePreviewWindowactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactForwardSubWordactKillLineactKillWordactKillSubWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactBackwardKillSubWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactToggleWrapactToggleMultiLineactToggleHscrollactTrackCurrentactToggleInputactHideInputactShowInputactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformGhostactTransformHeaderactTransformFooteractTransformHeaderLabelactTransformFooterLabelactTransformInputLabelactTransformListLabelactTransformNthactTransformPointeractTransformPreviewLabelactTransformPromptactTransformQueryactTransformSearchactTriggeractBgTransformactBgTransformBorderLabelactBgTransformGhostactBgTransformHeaderactBgTransformFooteractBgTransformHeaderLabelactBgTransformFooterLabelactBgTransformInputLabelactBgTransformListLabelactBgTransformNthactBgTransformPointeractBgTransformPreviewLabelactBgTransformPromptactBgTransformQueryactBgTransformSearchactBgCancelactSearchactPreviewactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactToggleBindactBecomeactShowHeaderactHideHeaderactBellactExcludeactExcludeMultiactAsync"
const _actionType_name = "actIgnoreactStartactClickactInvalidactBracketedPasteBeginactBracketedPasteEndactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactBackwardSubWordactCancelactChangeBorderLabelactChangeGhostactChangeHeaderactChangeFooteractChangeHeaderLabelactChangeFooterLabelactChangeInputLabelactChangeListLabelactChangeMultiactChangeNthactChangePointeractChangePreviewactChangePreviewLabelactChangePreviewWindowactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactForwardSubWordactKillLineactKillWordactKillSubWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactBackwardKillSubWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactToggleWrapactToggleMultiLineactToggleHscrollactToggleRawactEnableRawactDisableRawactTrackCurrentactToggleInputactHideInputactShowInputactUntrackCurrentactDownactDownMatchactUpactUpMatchactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactOffsetMiddleactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformGhostactTransformHeaderactTransformFooteractTransformHeaderLabelactTransformFooterLabelactTransformInputLabelactTransformListLabelactTransformNthactTransformPointeractTransformPreviewLabelactTransformPromptactTransformQueryactTransformSearchactTriggeractBgTransformactBgTransformBorderLabelactBgTransformGhostactBgTransformHeaderactBgTransformFooteractBgTransformHeaderLabelactBgTransformFooterLabelactBgTransformInputLabelactBgTransformListLabelactBgTransformNthactBgTransformPointeractBgTransformPreviewLabelactBgTransformPromptactBgTransformQueryactBgTransformSearchactBgCancelactSearchactPreviewactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPrintactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactBestactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactToggleBindactBecomeactShowHeaderactHideHeaderactBellactExcludeactExcludeMultiactAsync"
var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 57, 77, 84, 92, 110, 118, 127, 144, 165, 180, 201, 225, 240, 258, 267, 287, 301, 316, 331, 351, 371, 390, 408, 422, 434, 450, 466, 487, 509, 524, 538, 552, 565, 582, 590, 603, 619, 631, 639, 653, 667, 684, 695, 706, 720, 738, 755, 762, 781, 803, 815, 829, 838, 853, 865, 878, 889, 900, 912, 926, 947, 962, 975, 993, 1009, 1024, 1038, 1050, 1062, 1079, 1086, 1091, 1100, 1111, 1122, 1135, 1150, 1161, 1174, 1189, 1196, 1209, 1222, 1239, 1254, 1267, 1281, 1295, 1311, 1331, 1343, 1366, 1383, 1401, 1419, 1442, 1465, 1487, 1508, 1523, 1542, 1566, 1584, 1601, 1619, 1629, 1643, 1668, 1687, 1707, 1727, 1752, 1777, 1801, 1824, 1841, 1862, 1888, 1908, 1927, 1947, 1958, 1967, 1977, 1990, 2006, 2018, 2032, 2048, 2066, 2086, 2108, 2122, 2137, 2145, 2151, 2165, 2180, 2190, 2206, 2221, 2231, 2239, 2246, 2255, 2268, 2284, 2299, 2308, 2319, 2328, 2337, 2350, 2359, 2372, 2385, 2392, 2402, 2417, 2425}
var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 57, 77, 84, 92, 110, 118, 127, 144, 165, 180, 201, 225, 240, 258, 267, 287, 301, 316, 331, 351, 371, 390, 408, 422, 434, 450, 466, 487, 509, 524, 538, 552, 565, 582, 590, 603, 619, 631, 639, 653, 667, 684, 695, 706, 720, 738, 755, 762, 781, 803, 815, 829, 838, 853, 865, 878, 889, 900, 912, 926, 947, 962, 975, 993, 1009, 1021, 1033, 1046, 1061, 1075, 1087, 1099, 1116, 1123, 1135, 1140, 1150, 1159, 1170, 1181, 1194, 1209, 1220, 1233, 1248, 1255, 1268, 1281, 1298, 1313, 1326, 1340, 1354, 1370, 1390, 1402, 1425, 1442, 1460, 1478, 1501, 1524, 1546, 1567, 1582, 1601, 1625, 1643, 1660, 1678, 1688, 1702, 1727, 1746, 1766, 1786, 1811, 1836, 1860, 1883, 1900, 1921, 1947, 1967, 1986, 2006, 2017, 2026, 2036, 2049, 2065, 2077, 2091, 2107, 2125, 2145, 2167, 2181, 2196, 2204, 2210, 2224, 2239, 2249, 2265, 2280, 2290, 2297, 2305, 2312, 2321, 2334, 2350, 2365, 2374, 2385, 2394, 2403, 2416, 2425, 2438, 2451, 2458, 2468, 2483, 2491}
func (i actionType) String() string {
if i < 0 || i >= actionType(len(_actionType_index)-1) {

View File

@@ -2,6 +2,7 @@
package fzf
import (
"maps"
"os"
"sync"
"time"
@@ -225,10 +226,7 @@ func Run(opts *Options) (int, error) {
}
patternBuilder := func(runes []rune) *Pattern {
denyMutex.Lock()
denylistCopy := make(map[int32]struct{})
for k, v := range denylist {
denylistCopy[k] = v
}
denylistCopy := maps.Clone(denylist)
denyMutex.Unlock()
return BuildPattern(cache, patternCache,
opts.Fuzzy, opts.FuzzyAlgo, opts.Extended, opts.Case, opts.Normalize, forward, withPos,
@@ -269,11 +267,11 @@ func Run(opts *Options) (int, error) {
// NOTE: Streaming filter is inherently not compatible with --tail
snapshot, _, _ := chunkList.Snapshot(opts.Tail)
merger, _ := matcher.scan(MatchRequest{
result := matcher.scan(MatchRequest{
chunks: snapshot,
pattern: pattern})
for i := 0; i < merger.Length(); i++ {
opts.Printer(merger.Get(i).item.AsString(opts.Ansi))
for i := 0; i < result.merger.Length(); i++ {
opts.Printer(result.merger.Get(i).item.AsString(opts.Ansi))
found = true
}
}
@@ -481,12 +479,13 @@ func Run(opts *Options) (int, error) {
case EvtSearchFin:
switch val := value.(type) {
case *Merger:
case MatchResult:
merger := val.merger
if deferred {
count := val.Length()
count := merger.Length()
if opts.Select1 && count > 1 || opts.Exit0 && !opts.Select1 && count > 0 {
determine(val.final)
} else if val.final {
determine(merger.final)
} else if merger.final {
if opts.Exit0 && count == 0 || opts.Select1 && count == 1 {
if opts.PrintQuery {
opts.Printer(opts.Query)
@@ -504,7 +503,7 @@ func Run(opts *Options) (int, error) {
}
}
for i := 0; i < count; i++ {
opts.Printer(transformer(val.Get(i).item))
opts.Printer(transformer(merger.Get(i).item))
}
if count == 0 {
exitCode = ExitNoMatch
@@ -512,7 +511,7 @@ func Run(opts *Options) (int, error) {
stop = true
return
}
determine(val.final)
determine(merger.final)
}
}
terminal.UpdateList(val)

View File

@@ -19,6 +19,20 @@ type MatchRequest struct {
revision revision
}
type MatchResult struct {
merger *Merger
passMerger *Merger
cancelled bool
}
func (mr MatchResult) cacheable() bool {
return mr.merger != nil && mr.merger.cacheable()
}
func (mr MatchResult) final() bool {
return mr.merger != nil && mr.merger.final
}
// Matcher is responsible for performing search
type Matcher struct {
cache *ChunkCache
@@ -29,7 +43,7 @@ type Matcher struct {
reqBox *util.EventBox
partitions int
slab []*util.Slab
mergerCache map[string]*Merger
mergerCache map[string]MatchResult
revision revision
}
@@ -51,7 +65,7 @@ func NewMatcher(cache *ChunkCache, patternBuilder func([]rune) *Pattern,
reqBox: util.NewEventBox(),
partitions: partitions,
slab: make([]*util.Slab, partitions),
mergerCache: make(map[string]*Merger),
mergerCache: make(map[string]MatchResult),
revision: revision}
}
@@ -85,43 +99,42 @@ func (m *Matcher) Loop() {
cacheCleared := false
if request.sort != m.sort || request.revision != m.revision {
m.sort = request.sort
m.revision = request.revision
m.mergerCache = make(map[string]*Merger)
m.mergerCache = make(map[string]MatchResult)
if !request.revision.compatible(m.revision) {
m.cache.Clear()
}
m.revision = request.revision
cacheCleared = true
}
// Restart search
patternString := request.pattern.AsString()
var merger *Merger
cancelled := false
var result MatchResult
count := CountItems(request.chunks)
if !cacheCleared {
if count == prevCount {
// Look up mergerCache
if cached, found := m.mergerCache[patternString]; found && cached.final == request.final {
merger = cached
if cached, found := m.mergerCache[patternString]; found && cached.final() == request.final {
result = cached
}
} else {
// Invalidate mergerCache
prevCount = count
m.mergerCache = make(map[string]*Merger)
m.mergerCache = make(map[string]MatchResult)
}
}
if merger == nil {
merger, cancelled = m.scan(request)
if result.merger == nil {
result = m.scan(request)
}
if !cancelled {
if merger.cacheable() {
m.mergerCache[patternString] = merger
if !result.cancelled {
if result.cacheable() {
m.mergerCache[patternString] = result
}
merger.final = request.final
m.eventBox.Set(EvtSearchFin, merger)
result.merger.final = request.final
m.eventBox.Set(EvtSearchFin, result)
}
}
}
@@ -152,16 +165,18 @@ type partialResult struct {
matches []Result
}
func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
func (m *Matcher) scan(request MatchRequest) MatchResult {
startedAt := time.Now()
numChunks := len(request.chunks)
if numChunks == 0 {
return EmptyMerger(request.revision), false
m := EmptyMerger(request.revision)
return MatchResult{m, m, false}
}
pattern := request.pattern
passMerger := PassMerger(&request.chunks, m.tac, request.revision)
if pattern.IsEmpty() {
return PassMerger(&request.chunks, m.tac, request.revision), false
return MatchResult{passMerger, passMerger, false}
}
minIndex := request.chunks[0].items[0].Index()
@@ -224,7 +239,7 @@ func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
}
if m.reqBox.Peek(reqReset) {
return nil, wait()
return MatchResult{nil, nil, wait()}
}
if time.Since(startedAt) > progressMinDuration {
@@ -237,7 +252,8 @@ func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
partialResult := <-resultChan
partialResults[partialResult.index] = partialResult.matches
}
return NewMerger(pattern, partialResults, m.sort && request.pattern.sortable, m.tac, request.revision, minIndex, maxIndex), false
merger := NewMerger(pattern, partialResults, m.sort && request.pattern.sortable, m.tac, request.revision, minIndex, maxIndex)
return MatchResult{merger, passMerger, false}
}
// Reset is called to interrupt/signal the ongoing search

View File

@@ -141,6 +141,15 @@ func (mg *Merger) Get(idx int) Result {
panic(fmt.Sprintf("Index out of bounds (unsorted, %d/%d)", idx, mg.count))
}
func (mg *Merger) ToMap() map[int32]Result {
ret := make(map[int32]Result, mg.count)
for i := 0; i < mg.count; i++ {
result := mg.Get(i)
ret[result.Index()] = result
}
return ret
}
func (mg *Merger) cacheable() bool {
return mg.count < mergerCacheMax
}

View File

@@ -3,6 +3,7 @@ package fzf
import (
"errors"
"fmt"
"maps"
"os"
"regexp"
"strconv"
@@ -59,7 +60,7 @@ Usage: fzf [options]
GLOBAL STYLE
--style=PRESET Apply a style preset [default|minimal|full[:BORDER_STYLE]
--color=COLSPEC Base scheme (dark|light|16|bw) and/or custom colors
--color=COLSPEC Base scheme (dark|light|base16|bw) and/or custom colors
--no-color Disable colors
--no-bold Do not use bold text
@@ -97,6 +98,7 @@ Usage: fzf [options]
--wrap Enable line wrap
--wrap-sign=STR Indicator for wrapped lines
--no-multi-line Disable multi-line display of items when using --read0
--raw Enable raw mode (show non-matching items)
--track Track the current selection when the result is updated
--tac Reverse the order of the input
--gap[=N] Render empty lines between each item
@@ -109,6 +111,8 @@ Usage: fzf [options]
--hscroll-off=COLS Number of screen columns to keep to the right of the
highlighted substring (default: 10)
--jump-labels=CHARS Label characters for jump mode
--gutter=CHAR Character used for the gutter column (default: '▌')
--gutter-raw=CHAR Character used for the gutter column in raw mode (default: '▖')
--pointer=STR Pointer to the current line (default: '▌' or '>')
--marker=STR Multi-select marker (default: '┃' or '>')
--marker-multi-line=STR Multi-select marker for multi-line entries;
@@ -202,8 +206,10 @@ Usage: fzf [options]
ADVANCED
--with-shell=STR Shell command and flags to start child processes with
--listen[=[ADDR:]PORT] Start HTTP server to receive actions (POST /)
--listen[=[ADDR:]PORT] Start HTTP server to receive actions via TCP
(To allow remote process execution, use --listen-unsafe)
--listen=SOCKET_PATH Start HTTP server to receive actions via Unix domain socket
(Path should end with .sock)
DIRECTORY TRAVERSAL (Only used when $FZF_DEFAULT_COMMAND is not set)
--walker=OPTS [file][,dir][,follow][,hidden] (default: file,follow,hidden)
@@ -212,8 +218,8 @@ Usage: fzf [options]
(default: .git,node_modules)
HISTORY
--history=FILE History file
--history-size=N Maximum number of history entries (default: 1000)
--history=FILE File to store fzf search history (*not* shell command history)
--history-size=N Maximum number of entries to keep in the file (default: 1000)
SHELL INTEGRATION
--bash Print script to set up Bash shell integration
@@ -560,6 +566,7 @@ type Options struct {
AcceptNth func(Delimiter) func([]Token, int32) string
Delimiter Delimiter
Sort int
Raw bool
Track trackOption
Tac bool
Tail int
@@ -567,6 +574,7 @@ type Options struct {
Multi int
Ansi bool
Mouse bool
BaseTheme *tui.ColorTheme
Theme *tui.ColorTheme
Black bool
Bold bool
@@ -590,6 +598,8 @@ type Options struct {
Separator *string
JumpLabels string
Prompt string
Gutter *string
GutterRaw *string
Pointer *string
Marker *string
MarkerMulti *[3]string
@@ -665,9 +675,9 @@ func defaultPreviewOpts(command string) previewOpts {
func defaultOptions() *Options {
var theme *tui.ColorTheme
if os.Getenv("NO_COLOR") != "" {
theme = tui.NoColorTheme()
theme = tui.NoColorTheme
} else {
theme = tui.EmptyTheme()
theme = tui.EmptyTheme
}
return &Options{
@@ -710,6 +720,8 @@ func defaultOptions() *Options {
Separator: nil,
JumpLabels: defaultJumpLabels,
Prompt: "> ",
Gutter: nil,
GutterRaw: nil,
Pointer: nil,
Marker: nil,
MarkerMulti: nil,
@@ -1308,8 +1320,9 @@ func dupeTheme(theme *tui.ColorTheme) *tui.ColorTheme {
return &dupe
}
func parseTheme(defaultTheme *tui.ColorTheme, str string) (*tui.ColorTheme, error) {
func parseTheme(defaultTheme *tui.ColorTheme, str string) (*tui.ColorTheme, *tui.ColorTheme, error) {
var err error
var baseTheme *tui.ColorTheme
theme := dupeTheme(defaultTheme)
rrggbb := regexp.MustCompile("^#[0-9a-fA-F]{6}$")
comma := regexp.MustCompile(`[\s,]+`)
@@ -1320,13 +1333,17 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) (*tui.ColorTheme, erro
}
switch str {
case "dark":
baseTheme = tui.Dark256
theme = dupeTheme(tui.Dark256)
case "light":
baseTheme = tui.Light256
theme = dupeTheme(tui.Light256)
case "16":
case "base16", "16":
baseTheme = tui.Default16
theme = dupeTheme(tui.Default16)
case "bw", "no":
theme = tui.NoColorTheme()
baseTheme = tui.NoColorTheme
theme = dupeTheme(tui.NoColorTheme)
default:
fail := func() {
// Let the code proceed to simplify the error handling
@@ -1351,6 +1368,8 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) (*tui.ColorTheme, erro
cattr.Attr |= tui.Bold
case "dim":
cattr.Attr |= tui.Dim
case "strip":
cattr.Attr |= tui.Strip
case "italic":
cattr.Attr |= tui.Italic
case "underline":
@@ -1438,6 +1457,8 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) (*tui.ColorTheme, erro
mergeAttr(&theme.SelectedBg)
case "nth":
mergeAttr(&theme.Nth)
case "nomatch":
mergeAttr(&theme.Nomatch)
case "gutter":
mergeAttr(&theme.Gutter)
case "hl":
@@ -1503,7 +1524,7 @@ func parseTheme(defaultTheme *tui.ColorTheme, str string) (*tui.ColorTheme, erro
}
}
}
return theme, err
return baseTheme, theme, err
}
func parseWalkerOpts(str string) (walkerOpts, error) {
@@ -1737,6 +1758,12 @@ func parseActionList(masked string, original string, prevActions []*action, putA
appendAction(actToggleMultiLine)
case "toggle-hscroll":
appendAction(actToggleHscroll)
case "toggle-raw":
appendAction(actToggleRaw)
case "enable-raw":
appendAction(actEnableRaw)
case "disable-raw":
appendAction(actDisableRaw)
case "show-header":
appendAction(actShowHeader)
case "hide-header":
@@ -1757,12 +1784,18 @@ func parseActionList(masked string, original string, prevActions []*action, putA
appendAction(actToggle)
case "down":
appendAction(actDown)
case "down-match":
appendAction(actDownMatch)
case "up":
appendAction(actUp)
case "up-match":
appendAction(actUpMatch)
case "first", "top":
appendAction(actFirst)
case "last":
appendAction(actLast)
case "best":
appendAction(actBest)
case "page-up":
appendAction(actPageUp)
case "page-down":
@@ -1775,9 +1808,9 @@ func parseActionList(masked string, original string, prevActions []*action, putA
appendAction(actPrevHistory)
case "next-history":
appendAction(actNextHistory)
case "prev-selected":
case "up-selected", "prev-selected":
appendAction(actPrevSelected)
case "next-selected":
case "down-selected", "next-selected":
appendAction(actNextSelected)
case "show-preview":
appendAction(actShowPreview)
@@ -2600,9 +2633,7 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
if err != nil {
return err
}
for k, v := range chords {
opts.Expect[k] = v
}
maps.Copy(opts.Expect, chords)
case "--no-expect":
opts.Expect = make(map[tui.Event]string)
case "--enabled", "--no-phony":
@@ -2630,11 +2661,15 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
case "--color":
_, spec := optionalNextString()
if len(spec) == 0 {
opts.Theme = tui.EmptyTheme()
opts.Theme = tui.EmptyTheme
} else {
if opts.Theme, err = parseTheme(opts.Theme, spec); err != nil {
var baseTheme *tui.ColorTheme
if baseTheme, opts.Theme, err = parseTheme(opts.Theme, spec); err != nil {
return err
}
if baseTheme != nil {
opts.BaseTheme = baseTheme
}
}
case "--toggle-sort":
str, err := nextString("key name required")
@@ -2680,6 +2715,10 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
}
case "+s", "--no-sort":
opts.Sort = 0
case "--raw":
opts.Raw = true
case "--no-raw":
opts.Raw = false
case "--track":
opts.Track = trackEnabled
case "--no-track":
@@ -2716,7 +2755,8 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
case "--no-mouse":
opts.Mouse = false
case "+c", "--no-color":
opts.Theme = tui.NoColorTheme()
opts.BaseTheme = tui.NoColorTheme
opts.Theme = tui.NoColorTheme
case "+2", "--no-256":
opts.Theme = tui.Default16
case "--black":
@@ -2857,6 +2897,20 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
if err != nil {
return err
}
case "--gutter":
str, err := nextString("gutter character required")
if err != nil {
return err
}
str = firstLine(str)
opts.Gutter = &str
case "--gutter-raw":
str, err := nextString("gutter character for raw mode required")
if err != nil {
return err
}
str = firstLine(str)
opts.GutterRaw = &str
case "--pointer":
str, err := nextString("pointer sign required")
if err != nil {
@@ -3355,26 +3409,31 @@ func applyPreset(opts *Options, preset string) error {
return nil
}
func validateSign(sign string, signOptName string) error {
if uniseg.StringWidth(sign) > 2 {
return fmt.Errorf("%v display width should be up to 2", signOptName)
func validateSign(sign string, signOptName string, maxWidth int) error {
if uniseg.StringWidth(sign) > maxWidth {
return fmt.Errorf("%v display width should be up to %d", signOptName, maxWidth)
}
return nil
}
func validateOptions(opts *Options) error {
if opts.Pointer != nil {
if err := validateSign(*opts.Pointer, "pointer"); err != nil {
if err := validateSign(*opts.Pointer, "pointer", 2); err != nil {
return err
}
}
if opts.Marker != nil {
if err := validateSign(*opts.Marker, "marker"); err != nil {
if err := validateSign(*opts.Marker, "marker", 2); err != nil {
return err
}
}
if opts.Gutter != nil && uniseg.StringWidth(*opts.Gutter) != 1 ||
opts.GutterRaw != nil && uniseg.StringWidth(*opts.GutterRaw) != 1 {
return errors.New("gutter display width should be 1")
}
if opts.Scrollbar != nil {
runes := []rune(*opts.Scrollbar)
if len(runes) > 2 {
@@ -3574,23 +3633,6 @@ func postProcessOptions(opts *Options) error {
}
}
if opts.Bold {
theme := opts.Theme
boldify := func(c tui.ColorAttr) tui.ColorAttr {
dup := c
if (c.Attr & tui.AttrRegular) == 0 {
dup.Attr |= tui.BoldForce
}
return dup
}
theme.Current = boldify(theme.Current)
theme.CurrentMatch = boldify(theme.CurrentMatch)
theme.Prompt = boldify(theme.Prompt)
theme.Input = boldify(theme.Input)
theme.Cursor = boldify(theme.Cursor)
theme.Spinner = boldify(theme.Spinner)
}
// If --height option is not supported on the platform, just ignore it
if !tui.IsLightRendererSupported() && opts.Height.size > 0 {
opts.Height = heightSpec{}

View File

@@ -300,8 +300,12 @@ func TestBind(t *testing.T) {
}
func TestColorSpec(t *testing.T) {
var base *tui.ColorTheme
theme := tui.Dark256
dark, _ := parseTheme(theme, "dark")
base, dark, _ := parseTheme(theme, "dark")
if *dark != *base {
t.Errorf("incorrect base theme returned")
}
if *dark != *theme {
t.Errorf("colors should be equivalent")
}
@@ -309,7 +313,10 @@ func TestColorSpec(t *testing.T) {
t.Errorf("point should not be equivalent")
}
light, _ := parseTheme(theme, "dark,light")
base, light, _ := parseTheme(theme, "dark,light")
if *light != *base {
t.Errorf("incorrect base theme returned")
}
if *light == *theme {
t.Errorf("should not be equivalent")
}
@@ -320,7 +327,7 @@ func TestColorSpec(t *testing.T) {
t.Errorf("point should not be equivalent")
}
customized, _ := parseTheme(theme, "fg:231,bg:232")
_, customized, _ := parseTheme(theme, "fg:231,bg:232")
if customized.Fg.Color != 231 || customized.Bg.Color != 232 {
t.Errorf("color not customized")
}
@@ -333,7 +340,7 @@ func TestColorSpec(t *testing.T) {
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 {
t.Errorf("color not customized")
}
@@ -350,8 +357,8 @@ func TestDefaultCtrlNP(t *testing.T) {
t.Error()
}
}
check([]string{}, tui.CtrlN, actDown)
check([]string{}, tui.CtrlP, actUp)
check([]string{}, tui.CtrlN, actDownMatch)
check([]string{}, tui.CtrlP, actUpMatch)
check([]string{"--bind=ctrl-n:accept"}, tui.CtrlN, actAccept)
check([]string{"--bind=ctrl-p:accept"}, tui.CtrlP, actAccept)
@@ -462,7 +469,7 @@ func TestValidateSign(t *testing.T) {
}
for _, testCase := range testCases {
err := validateSign(testCase.inputSign, "")
err := validateSign(testCase.inputSign, "", 2)
if testCase.isValid && err != nil {
t.Errorf("Input sign `%s` caused error", testCase.inputSign)
}

View File

@@ -123,7 +123,7 @@ func minRank() Result {
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) []colorOffset {
func (result *Result) colorOffsets(matchOffsets []Offset, nthOffsets []Offset, theme *tui.ColorTheme, colBase tui.ColorPair, colMatch tui.ColorPair, attrNth tui.Attr, hidden bool) []colorOffset {
itemColors := result.item.Colors()
// No ANSI codes
@@ -194,6 +194,10 @@ func (result *Result) colorOffsets(matchOffsets []Offset, nthOffsets []Offset, t
if !theme.Colored {
return tui.NewColorPair(-1, -1, ansi.color.attr).MergeAttr(base)
}
// fd --color always | fzf --ansi --delimiter / --nth -1 --color fg:dim:strip,nth:regular
if base.ShouldStripColors() {
return base
}
fg := ansi.color.fg
bg := ansi.color.bg
if fg == -1 {
@@ -251,6 +255,9 @@ func (result *Result) colorOffsets(matchOffsets []Offset, nthOffsets []Offset, t
if curr.nth {
base = base.WithAttr(attrNth)
}
if hidden {
base = base.WithFg(theme.Nomatch)
}
color := ansiToColorPair(ansi, base)
colors = append(colors, colorOffset{
offset: [2]int32{int32(start), int32(idx)},
@@ -258,9 +265,13 @@ func (result *Result) colorOffsets(matchOffsets []Offset, nthOffsets []Offset, t
match: false,
url: ansi.color.url})
} else {
color := colBase.WithAttr(attrNth)
if hidden {
color = color.WithFg(theme.Nomatch)
}
colors = append(colors, colorOffset{
offset: [2]int32{int32(start), int32(idx)},
color: colBase.WithAttr(attrNth),
color: color,
match: false,
url: nil})
}

View File

@@ -131,7 +131,7 @@ func TestColorOffset(t *testing.T) {
colBase := tui.NewColorPair(89, 189, tui.AttrUndefined)
colMatch := tui.NewColorPair(99, 199, tui.AttrUndefined)
colors := item.colorOffsets(offsets, nil, tui.Dark256, colBase, colMatch, tui.AttrUndefined)
colors := item.colorOffsets(offsets, nil, tui.Dark256, colBase, colMatch, tui.AttrUndefined, false)
assert := func(idx int, b int32, e int32, c tui.ColorPair) {
o := colors[idx]
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}}
for _, attr := range []tui.Attr{tui.AttrRegular, tui.StrikeThrough} {
colors = item.colorOffsets(offsets, nthOffsets, tui.Dark256, colRegular, colUnderline, attr)
colors = item.colorOffsets(offsets, nthOffsets, tui.Dark256, colRegular, colUnderline, attr, false)
// [{[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}}

View File

@@ -46,15 +46,20 @@ type httpServer struct {
type listenAddress struct {
host string
port int
sock string
}
func (addr listenAddress) IsLocal() bool {
return addr.host == "localhost" || addr.host == "127.0.0.1"
return addr.host == "localhost" || addr.host == "127.0.0.1" || len(addr.sock) > 0
}
var defaultListenAddr = listenAddress{"localhost", 0}
var defaultListenAddr = listenAddress{"localhost", 0, ""}
func parseListenAddress(address string) (listenAddress, error) {
if strings.HasSuffix(address, ".sock") {
return listenAddress{"", 0, address}, nil
}
parts := strings.SplitN(address, ":", 3)
if len(parts) == 1 {
parts = []string{"localhost", parts[0]}
@@ -70,7 +75,7 @@ func parseListenAddress(address string) (listenAddress, error) {
if len(parts[0]) == 0 {
parts[0] = "localhost"
}
return listenAddress{parts[0], port}, nil
return listenAddress{parts[0], port, ""}, nil
}
func startHttpServer(address listenAddress, actionChannel chan []*action, getHandler func(getParams) string) (net.Listener, int, error) {
@@ -80,21 +85,40 @@ func startHttpServer(address listenAddress, actionChannel chan []*action, getHan
if !address.IsLocal() && len(apiKey) == 0 {
return nil, port, errors.New("FZF_API_KEY is required to allow remote access")
}
addrStr := fmt.Sprintf("%s:%d", host, port)
listener, err := net.Listen("tcp", addrStr)
if err != nil {
return nil, port, fmt.Errorf("failed to listen on %s", addrStr)
}
if port == 0 {
addr := listener.Addr().String()
parts := strings.Split(addr, ":")
if len(parts) < 2 {
return nil, port, fmt.Errorf("cannot extract port: %s", addr)
var listener net.Listener
var err error
if len(address.sock) > 0 {
if _, err := os.Stat(address.sock); err == nil {
// Check if the socket is already in use
if conn, err := net.Dial("unix", address.sock); err == nil {
conn.Close()
return nil, 0, fmt.Errorf("socket already in use: %s", address.sock)
}
os.Remove(address.sock)
}
var err error
port, err = strconv.Atoi(parts[len(parts)-1])
listener, err = net.Listen("unix", address.sock)
if err != nil {
return nil, port, err
return nil, 0, fmt.Errorf("failed to listen on %s", address.sock)
}
os.Chmod(address.sock, 0600)
} else {
addrStr := fmt.Sprintf("%s:%d", host, port)
listener, err = net.Listen("tcp", addrStr)
if err != nil {
return nil, port, fmt.Errorf("failed to listen on %s", addrStr)
}
if port == 0 {
addr := listener.Addr().String()
parts := strings.Split(addr, ":")
if len(parts) < 2 {
return nil, port, fmt.Errorf("cannot extract port: %s", addr)
}
var err error
port, err = strconv.Atoi(parts[len(parts)-1])
if err != nil {
return nil, port, err
}
}
}

View File

@@ -6,6 +6,7 @@ import (
"encoding/json"
"fmt"
"io"
"maps"
"math"
"net"
"os"
@@ -179,6 +180,7 @@ type itemLine struct {
result Result
empty bool
other bool
hidden bool
}
func (t *Terminal) inListWindow() bool {
@@ -272,9 +274,12 @@ type Terminal struct {
footerLabel labelPrinter
footerLabelLen int
footerLabelOpts labelOpts
gutterReverse bool
gutterRawReverse bool
pointer string
pointerLen int
pointerEmpty string
pointerEmptyRaw string
marker string
markerLen int
markerEmpty string
@@ -381,6 +386,9 @@ type Terminal struct {
printer func(string)
printsep string
merger *Merger
passMerger *Merger
resultMerger *Merger
matchMap map[int32]Result
selected map[int32]selectedItem
version int64
revision revision
@@ -399,7 +407,6 @@ type Terminal struct {
initFunc func() error
prevLines []itemLine
suppress bool
sigstop bool
startChan chan fitpad
killChan chan bool
serverInputChan chan []*action
@@ -428,6 +435,7 @@ type Terminal struct {
clickFooterColumn int
proxyScript string
numLinesCache map[int32]numLinesCacheValue
raw bool
}
type numLinesCacheValue struct {
@@ -568,13 +576,18 @@ const (
actToggleWrap
actToggleMultiLine
actToggleHscroll
actToggleRaw
actEnableRaw
actDisableRaw
actTrackCurrent
actToggleInput
actHideInput
actShowInput
actUntrackCurrent
actDown
actDownMatch
actUp
actUpMatch
actPageUp
actPageDown
actPosition
@@ -650,6 +663,7 @@ const (
actExecuteSilent
actExecuteMulti // Deprecated
actSigStop
actBest
actFirst
actLast
actReload
@@ -795,8 +809,10 @@ func defaultKeymap() map[tui.Event][]*action {
add(tui.CtrlK, actUp)
add(tui.CtrlL, actClearScreen)
add(tui.Enter, actAccept)
add(tui.CtrlN, actDown)
add(tui.CtrlP, actUp)
add(tui.CtrlN, actDownMatch)
add(tui.CtrlP, actUpMatch)
add(tui.AltDown, actDownMatch)
add(tui.AltUp, actUpMatch)
add(tui.CtrlU, actUnixLineDiscard)
add(tui.CtrlW, actUnixWordRubout)
add(tui.CtrlY, actYank)
@@ -950,11 +966,9 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
wordRubout = fmt.Sprintf("%s[^%s]", sep, sep)
wordNext = fmt.Sprintf("[^%s]%s|(.$)", sep, sep)
}
keymapCopy := make(map[tui.Event][]*action)
for key, action := range opts.Keymap {
keymapCopy[key] = action
}
keymapCopy := maps.Clone(opts.Keymap)
em := EmptyMerger(revision{})
t := Terminal{
initDelay: delay,
infoCommand: opts.InfoCommand,
@@ -1041,6 +1055,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
nth: opts.Nth,
nthCurrent: opts.Nth,
tabstop: opts.Tabstop,
raw: opts.Raw,
hasStartActions: false,
hasResultActions: false,
hasFocusActions: false,
@@ -1054,7 +1069,10 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
printer: opts.Printer,
printsep: opts.PrintSep,
proxyScript: opts.ProxyScript,
merger: EmptyMerger(revision{}),
merger: em,
passMerger: em,
resultMerger: em,
matchMap: make(map[int32]Result),
selected: make(map[int32]selectedItem),
runningCmds: util.NewConcurrentSet[*runningCmd](),
reqBox: util.NewEventBox(),
@@ -1091,13 +1109,45 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
t.acceptNth = opts.AcceptNth(t.delimiter)
}
baseTheme := opts.BaseTheme
if baseTheme == nil {
baseTheme = renderer.DefaultTheme()
}
// This should be called before accessing tui.Color*
tui.InitTheme(opts.Theme, renderer.DefaultTheme(), opts.Black, opts.InputBorderShape.Visible(), opts.HeaderBorderShape.Visible())
tui.InitTheme(opts.Theme, baseTheme, opts.Bold, opts.Black, opts.InputBorderShape.Visible(), opts.HeaderBorderShape.Visible())
// Gutter character
var gutterChar, gutterRawChar string
if opts.Gutter != nil {
gutterChar = *opts.Gutter
} else if t.unicode {
gutterChar = "▌"
} else {
gutterChar = " "
t.gutterReverse = true
}
if opts.GutterRaw != nil {
gutterRawChar = *opts.GutterRaw
} else if t.unicode {
gutterRawChar = "▖"
} else {
gutterRawChar = ":"
t.gutterRawReverse = false
}
t.prompt, t.promptLen = t.parsePrompt(opts.Prompt)
// Pre-calculated empty pointer and marker signs
t.pointerEmpty = strings.Repeat(" ", t.pointerLen)
if t.pointerLen == 0 {
t.pointerEmpty = ""
t.pointerEmptyRaw = ""
} else {
t.pointerEmpty = gutterChar + strings.Repeat(" ", util.Max(0, t.pointerLen-1))
t.pointerEmptyRaw = gutterRawChar + strings.Repeat(" ", util.Max(0, t.pointerLen-1))
}
t.markerEmpty = strings.Repeat(" ", t.markerLen)
// Labels
t.listLabel, t.listLabelLen = t.ansiLabelPrinter(opts.ListLabel.label, &tui.ColListLabel, false)
t.borderLabel, t.borderLabelLen = t.ansiLabelPrinter(opts.BorderLabel.label, &tui.ColBorderLabel, false)
t.previewLabel, t.previewLabelLen = t.ansiLabelPrinter(opts.PreviewLabel.label, &tui.ColPreviewLabel, false)
@@ -1218,7 +1268,9 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
return nil, err
}
t.listener = listener
t.listenPort = &port
if port > 0 {
t.listenPort = &port
}
}
if t.hasStartActions {
@@ -1242,6 +1294,9 @@ func (t *Terminal) environForPreview() []string {
func (t *Terminal) environImpl(forPreview bool) []string {
env := os.Environ()
if t.listenAddr != nil && len(t.listenAddr.sock) > 0 {
env = append(env, "FZF_SOCK="+t.listenAddr.sock)
}
if t.listenPort != nil {
env = append(env, fmt.Sprintf("FZF_PORT=%d", *t.listenPort))
}
@@ -1256,9 +1311,21 @@ func (t *Terminal) environImpl(forPreview bool) []string {
env = append(env, "FZF_LIST_LABEL="+t.listLabelOpts.label)
env = append(env, "FZF_INPUT_LABEL="+t.inputLabelOpts.label)
env = append(env, "FZF_HEADER_LABEL="+t.headerLabelOpts.label)
direction := "down"
if t.layout == layoutDefault {
direction = "up"
}
env = append(env, "FZF_DIRECTION="+direction)
if len(t.nthCurrent) > 0 {
env = append(env, "FZF_NTH="+RangesToString(t.nthCurrent))
}
if t.raw {
val := "0"
if t.isCurrentItemMatch() {
val = "1"
}
env = append(env, "FZF_RAW="+val)
}
inputState := "enabled"
if t.inputless {
inputState = "hidden"
@@ -1267,7 +1334,7 @@ func (t *Terminal) environImpl(forPreview bool) []string {
}
env = append(env, "FZF_INPUT_STATE="+inputState)
env = append(env, fmt.Sprintf("FZF_TOTAL_COUNT=%d", t.count))
env = append(env, fmt.Sprintf("FZF_MATCH_COUNT=%d", t.merger.Length()))
env = append(env, fmt.Sprintf("FZF_MATCH_COUNT=%d", t.resultMerger.Length()))
env = append(env, fmt.Sprintf("FZF_SELECT_COUNT=%d", len(t.selected)))
env = append(env, fmt.Sprintf("FZF_LINES=%d", t.areaLines))
env = append(env, fmt.Sprintf("FZF_COLUMNS=%d", t.areaColumns))
@@ -1428,7 +1495,7 @@ func (t *Terminal) ansiLabelPrinter(str string, color *tui.ColorPair, fill bool)
printFn := func(window tui.Window, limit int) {
if offsets == nil {
// tui.Col* are not initialized until renderer.Init()
offsets = result.colorOffsets(nil, nil, t.theme, *color, *color, t.nthAttr)
offsets = result.colorOffsets(nil, nil, t.theme, *color, *color, t.nthAttr, false)
}
for limit > 0 {
if length > limit {
@@ -1491,7 +1558,7 @@ func (t *Terminal) parsePrompt(prompt string) (func(), int) {
return 1
}
t.printHighlighted(
Result{item: item}, tui.ColPrompt, tui.ColPrompt, false, false, line, line, true, preTask, nil)
Result{item: item}, tui.ColPrompt, tui.ColPrompt, false, false, false, line, line, true, preTask, nil)
})
t.wrap = wrap
}
@@ -1678,7 +1745,8 @@ func (t *Terminal) UpdateProgress(progress float32) {
}
// UpdateList updates Merger to display the list
func (t *Terminal) UpdateList(merger *Merger) {
func (t *Terminal) UpdateList(result MatchResult) {
merger := result.merger
t.mutex.Lock()
prevIndex := minItem.Index()
newRevision := merger.Revision()
@@ -1691,6 +1759,15 @@ func (t *Terminal) UpdateList(merger *Merger) {
}
t.progress = 100
t.merger = merger
t.resultMerger = merger
t.passMerger = result.passMerger
if t.raw {
t.merger = result.passMerger
t.matchMap = t.resultMerger.ToMap()
} else {
t.merger = result.merger
t.matchMap = make(map[int32]Result)
}
if t.revision != newRevision {
if !t.revision.compatible(newRevision) {
// Reloaded: clear selection
@@ -1739,7 +1816,7 @@ func (t *Terminal) UpdateList(merger *Merger) {
}
needActivation := false
if !t.reading {
switch t.merger.Length() {
switch t.resultMerger.Length() {
case 0:
zero := tui.Zero.AsEvent()
if _, prs := t.keymap[zero]; prs {
@@ -2382,6 +2459,13 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
innerHeight-shrink, tui.WindowList, noBorder, true)
}
if len(t.scrollbar) == 0 {
for y := 0; y < t.window.Height(); y++ {
t.window.Move(y, t.window.Width()-1)
t.window.Print(" ")
}
}
createInnerWindow := func(b tui.Window, shape tui.BorderShape, windowType tui.WindowType, shift int) tui.Window {
top := b.Top()
left := b.Left() + shift
@@ -2779,7 +2863,7 @@ func (t *Terminal) printInfoImpl() {
return
}
found := t.merger.Length()
found := t.resultMerger.Length()
total := util.Max(found, t.count)
output := fmt.Sprintf("%d/%d", found, total)
if t.toggleSort {
@@ -2996,7 +3080,7 @@ func (t *Terminal) printFooter() {
colors: colors}
t.printHighlighted(Result{item: item},
tui.ColFooter, tui.ColFooter, false, false, line, line, true,
tui.ColFooter, tui.ColFooter, false, false, false, line, line, true,
func(markerClass) int {
t.footerWindow.Print(indent)
return indentSize
@@ -3068,7 +3152,7 @@ func (t *Terminal) printHeaderImpl(window tui.Window, borderShape tui.BorderShap
colors: colors}
t.printHighlighted(Result{item: item},
tui.ColHeader, tui.ColHeader, false, false, line, line, true,
tui.ColHeader, tui.ColHeader, false, false, false, line, line, true,
func(markerClass) int {
t.window.Print(indent)
return indentSize
@@ -3096,9 +3180,25 @@ func (t *Terminal) renderEmptyLine(line int, barRange [2]int) {
t.renderBar(line, barRange)
}
func (t *Terminal) gutter(current bool) {
var color tui.ColorPair
if current {
color = tui.ColCurrentCursorEmpty
} else if !t.raw && t.gutterReverse || t.raw && t.gutterRawReverse {
color = tui.ColCursorEmpty
} else {
color = tui.ColCursorEmptyChar
}
gutter := t.pointerEmpty
if t.raw {
gutter = t.pointerEmptyRaw
}
t.window.CPrint(color, gutter)
}
func (t *Terminal) renderGapLine(line int, barRange [2]int, drawLine bool) {
t.move(line, 0, false)
t.window.CPrint(tui.ColCursorEmpty, t.pointerEmpty)
t.gutter(false)
t.window.Print(t.markerEmpty)
x := t.pointerLen + t.markerLen
@@ -3128,7 +3228,8 @@ func (t *Terminal) printList() {
for line, itemCount := startLine, 0; line <= maxy; line, itemCount = line+1, itemCount+1 {
if itemCount < count {
item := t.merger.Get(itemCount + t.offset)
line = t.printItem(item, line, maxy, itemCount, itemCount == t.cy-t.offset, barRange)
current := itemCount == t.cy-t.offset
line = t.printItem(item, line, maxy, itemCount, current, barRange)
} else if !t.prevLines[line].empty {
t.renderEmptyLine(line, barRange)
}
@@ -3150,6 +3251,14 @@ func (t *Terminal) printBar(lineNum int, forceRedraw bool, barRange [2]int) bool
func (t *Terminal) printItem(result Result, line int, maxLine int, index int, current bool, barRange [2]int) int {
item := result.item
matched := true
var matchResult Result
if t.raw {
if matchResult, matched = t.matchMap[item.Index()]; matched {
result = matchResult
}
}
_, selected := t.selected[item.Index()]
label := ""
extraWidth := 0
@@ -3180,7 +3289,7 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
// Avoid unnecessary redraw
numLines, _ := t.numItemLines(item, maxLine-line+1)
newLine := itemLine{valid: true, firstLine: line, numLines: numLines, cy: index + t.offset, current: current, selected: selected, label: label,
result: result, queryLen: len(t.input), width: 0, hasBar: line >= barRange[0] && line < barRange[1]}
result: result, queryLen: len(t.input), width: 0, hasBar: line >= barRange[0] && line < barRange[1], hidden: !matched}
prevLine := t.prevLines[line]
forceRedraw := !prevLine.valid || prevLine.other || prevLine.firstLine != newLine.firstLine
printBar := func(lineNum int, forceRedraw bool) bool {
@@ -3188,6 +3297,7 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
}
if !forceRedraw &&
prevLine.hidden == newLine.hidden &&
prevLine.numLines == newLine.numLines &&
prevLine.current == newLine.current &&
prevLine.selected == newLine.selected &&
@@ -3262,7 +3372,7 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
return indentSize
}
if len(label) == 0 {
t.window.CPrint(tui.ColCurrentCursorEmpty, t.pointerEmpty)
t.gutter(true)
} else {
t.window.CPrint(tui.ColCurrentCursor, label)
}
@@ -3276,7 +3386,7 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
}
return indentSize
}
finalLineNum = t.printHighlighted(result, tui.ColCurrent, tui.ColCurrentMatch, true, true, line, maxLine, forceRedraw, preTask, postTask)
finalLineNum = t.printHighlighted(result, tui.ColCurrent, tui.ColCurrentMatch, true, true, !matched, line, maxLine, forceRedraw, preTask, postTask)
} else {
preTask := func(marker markerClass) int {
w := t.window.Width() - t.pointerLen
@@ -3284,7 +3394,7 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
return indentSize
}
if len(label) == 0 {
t.window.CPrint(tui.ColCursorEmpty, t.pointerEmpty)
t.gutter(false)
} else {
t.window.CPrint(tui.ColCursor, label)
}
@@ -3310,7 +3420,7 @@ func (t *Terminal) printItem(result Result, line int, maxLine int, index int, cu
base = base.WithBg(altBg)
match = match.WithBg(altBg)
}
finalLineNum = t.printHighlighted(result, base, match, false, true, line, maxLine, forceRedraw, preTask, postTask)
finalLineNum = t.printHighlighted(result, base, match, false, true, !matched, line, maxLine, forceRedraw, preTask, postTask)
}
for i := 0; i < t.gap && finalLineNum < maxLine; i++ {
finalLineNum++
@@ -3357,13 +3467,13 @@ func (t *Terminal) overflow(runes []rune, max int) bool {
return t.displayWidthWithLimit(runes, 0, max) > max
}
func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMatch tui.ColorPair, current bool, match bool, lineNum int, maxLineNum int, forceRedraw bool, preTask func(markerClass) int, postTask func(int, int, bool, bool, tui.ColorPair)) int {
func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMatch tui.ColorPair, current bool, match bool, hidden bool, lineNum int, maxLineNum int, forceRedraw bool, preTask func(markerClass) int, postTask func(int, int, bool, bool, tui.ColorPair)) int {
var displayWidth int
item := result.item
matchOffsets := []Offset{}
var pos *[]int
if match && t.merger.pattern != nil {
_, matchOffsets, pos = t.merger.pattern.MatchItem(item, true, t.slab)
if match && t.resultMerger.pattern != nil {
_, matchOffsets, pos = t.resultMerger.pattern.MatchItem(item, true, t.slab)
}
charOffsets := matchOffsets
if pos != nil {
@@ -3395,7 +3505,7 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
}
if !wholeCovered && t.nthAttr > 0 {
var tokens []Token
if item.transformed != nil && item.transformed.revision == t.merger.revision {
if item.transformed != nil && item.transformed.revision == t.resultMerger.revision {
tokens = item.transformed.tokens
} else {
tokens = Transform(Tokenize(item.text.ToString(), t.delimiter), t.nthCurrent)
@@ -3409,7 +3519,7 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
sort.Sort(ByOrder(nthOffsets))
}
}
allOffsets := result.colorOffsets(charOffsets, nthOffsets, t.theme, colBase, colMatch, t.nthAttr)
allOffsets := result.colorOffsets(charOffsets, nthOffsets, t.theme, colBase, colMatch, t.nthAttr, hidden)
maxLines := 1
if t.canSpanMultiLines() {
@@ -3608,7 +3718,11 @@ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMat
}
if maxWidth > 0 {
t.printColoredString(t.window, line, offsets, colBase)
color := colBase
if hidden {
color = color.WithFg(t.theme.Nomatch)
}
t.printColoredString(t.window, line, offsets, color)
}
if postTask != nil {
postTask(actualLineNum, displayWidth, wasWrapped, forceRedraw, lbg)
@@ -3630,7 +3744,7 @@ func (t *Terminal) printColoredString(window tui.Window, text []rune, offsets []
for _, offset := range offsets {
b := util.Constrain32(offset.offset[0], index, maxOffset)
e := util.Constrain32(offset.offset[1], index, maxOffset)
if url != nil && offset.url == nil {
if url != nil && offset.url != url {
url = nil
window.LinkEnd()
}
@@ -4643,6 +4757,33 @@ func (t *Terminal) currentItem() *Item {
return nil
}
func (t *Terminal) isCurrentItemMatch() bool {
cnt := t.merger.Length()
if t.cy >= 0 && cnt > 0 && cnt > t.cy {
if !t.raw {
return true
}
item := t.merger.Get(t.cy).item
return t.isItemMatch(item)
}
return false
}
func (t *Terminal) isItemMatch(item *Item) bool {
_, matched := t.matchMap[item.Index()]
return matched
}
func (t *Terminal) filterSelected() {
filtered := make(map[int32]selectedItem)
for k, v := range t.selected {
if t.isItemMatch(v.item) {
filtered[k] = v
}
}
t.selected = filtered
}
func (t *Terminal) buildPlusList(template string, forcePlus bool) (bool, [3][]*Item) {
current := t.currentItem()
slot, plus, asterisk, forceUpdate := hasPreviewFlags(template)
@@ -5883,8 +6024,9 @@ func (t *Terminal) Loop() error {
}
case actSelectAll:
if t.multi > 0 {
for i := 0; i < t.merger.Length(); i++ {
if !t.selectItem(t.merger.Get(i).item) {
// Limit the scope only to the matching items
for i := 0; i < t.resultMerger.Length(); i++ {
if !t.selectItem(t.resultMerger.Get(i).item) {
break
}
}
@@ -5892,8 +6034,10 @@ func (t *Terminal) Loop() error {
}
case actDeselectAll:
if t.multi > 0 {
for i := 0; i < t.merger.Length() && len(t.selected) > 0; i++ {
t.deselectItem(t.merger.Get(i).item)
// Also limit the scope only to the matching items, while this may
// not be straightforward in raw mode.
for i := 0; i < t.resultMerger.Length() && len(t.selected) > 0; i++ {
t.deselectItem(t.resultMerger.Get(i).item)
}
req(reqList, reqInfo)
}
@@ -5921,17 +6065,17 @@ func (t *Terminal) Loop() error {
case actToggleAll:
if t.multi > 0 {
prevIndexes := make(map[int]struct{})
for i := 0; i < t.merger.Length() && len(t.selected) > 0; i++ {
item := t.merger.Get(i).item
for i := 0; i < t.resultMerger.Length() && len(t.selected) > 0; i++ {
item := t.resultMerger.Get(i).item
if _, found := t.selected[item.Index()]; found {
prevIndexes[i] = struct{}{}
t.deselectItem(item)
}
}
for i := 0; i < t.merger.Length(); i++ {
for i := 0; i < t.resultMerger.Length(); i++ {
if _, found := prevIndexes[i]; !found {
item := t.merger.Get(i).item
item := t.resultMerger.Get(i).item
if !t.selectItem(item) {
break
}
@@ -5959,11 +6103,88 @@ func (t *Terminal) Loop() error {
t.vmove(1, true)
req(reqList)
}
case actDown:
t.vmove(-1, true)
case actDown, actDownMatch, actUp, actUpMatch:
dir := -1
if a.t == actUp || a.t == actUpMatch {
dir = 1
}
if t.raw && (a.t == actDownMatch || a.t == actUpMatch) {
if t.resultMerger.Length() > 0 {
prevCy := t.cy
for t.vmove(dir, true) && !t.isCurrentItemMatch() {
}
if !t.isCurrentItemMatch() {
t.vset(prevCy)
}
}
} else {
t.vmove(dir, true)
}
req(reqList)
case actUp:
t.vmove(1, true)
case actToggleRaw, actEnableRaw, actDisableRaw:
prevRaw := t.raw
newRaw := t.raw
switch a.t {
case actEnableRaw:
newRaw = true
case actDisableRaw:
newRaw = false
case actToggleRaw:
newRaw = !t.raw
}
if prevRaw == newRaw {
break
}
prevPos := t.cy - t.offset
prevIndex := t.currentIndex()
if newRaw {
// Build matchMap if not available
if len(t.matchMap) == 0 {
t.matchMap = t.resultMerger.ToMap()
}
t.merger = t.passMerger
} else {
// Find the closest matching item
if !t.isCurrentItemMatch() && t.resultMerger.Length() > 1 {
distance := 0
Loop:
for {
distance++
checks := 0
for _, cy := range []int{t.cy + distance, t.cy - distance} {
if cy >= 0 && cy < t.merger.Length() {
checks++
item := t.merger.Get(cy).item
if t.isItemMatch(item) {
prevIndex = item.Index()
break Loop
}
}
}
if checks == 0 {
break
}
}
}
t.merger = t.resultMerger
// Need to remove non-matching items from the selection
if t.multi > 0 && len(t.selected) > 0 {
t.filterSelected()
req(reqInfo)
}
}
t.raw = newRaw
// Try to retain position
if prevIndex != minItem.Index() {
t.cy = util.Max(0, t.merger.FindIndex(prevIndex))
t.offset = t.cy - prevPos
}
// List needs to be rerendered
t.forceRerenderList()
req(reqList)
case actAccept:
req(reqClose)
@@ -5988,8 +6209,14 @@ func (t *Terminal) Loop() error {
t.version++
req(reqList, reqInfo)
}
case actFirst:
t.vset(0)
case actFirst, actBest:
if t.raw && a.t == actBest {
if t.resultMerger.Length() > 0 {
t.vset(t.merger.FindIndex(t.resultMerger.Get(0).item.Index()))
}
} else {
t.vset(0)
}
t.constrain()
req(reqList)
case actLast:
@@ -6117,7 +6344,7 @@ func (t *Terminal) Loop() error {
if a.t == actOffsetDown {
diff = -1
}
if t.layout == layoutReverse {
if t.layout != layoutDefault {
diff *= -1
}
t.offset += diff
@@ -6125,7 +6352,7 @@ func (t *Terminal) Loop() error {
t.constrain()
if before != t.offset {
t.offset = before
if t.layout == layoutReverse {
if t.layout != layoutDefault {
diff *= -1
}
t.vmove(diff, false)
@@ -6807,7 +7034,7 @@ func (t *Terminal) Loop() error {
reload := changed || newCommand != nil
var reloadRequest *searchRequest
if reload {
reloadRequest = &searchRequest{sort: t.sort, sync: reloadSync, nth: newNth, command: newCommand, environ: t.environ(), changed: changed, denylist: denylist, revision: t.merger.Revision()}
reloadRequest = &searchRequest{sort: t.sort, sync: reloadSync, nth: newNth, command: newCommand, environ: t.environ(), changed: changed, denylist: denylist, revision: t.resultMerger.Revision()}
}
// Dispatch queued background requests
@@ -6927,7 +7154,8 @@ func (t *Terminal) constrain() {
}
}
func (t *Terminal) vmove(o int, allowCycle bool) {
// Returns true if the cursor position is successfully updated
func (t *Terminal) vmove(o int, allowCycle bool) bool {
if t.layout != layoutDefault {
o *= -1
}
@@ -6944,7 +7172,7 @@ func (t *Terminal) vmove(o int, allowCycle bool) {
}
}
}
t.vset(dest)
return t.vset(dest)
}
func (t *Terminal) vset(o int) bool {
@@ -7011,9 +7239,9 @@ func (t *Terminal) dumpStatus(params getParams) string {
selected[i] = t.dumpItem(selectedItems[i+params.offset].item)
}
matches := make([]StatusItem, util.Max(0, util.Min(params.limit, t.merger.Length()-params.offset)))
matches := make([]StatusItem, util.Max(0, util.Min(params.limit, t.resultMerger.Length()-params.offset)))
for i := range matches {
matches[i] = t.dumpItem(t.merger.Get(i + params.offset).item)
matches[i] = t.dumpItem(t.resultMerger.Get(i + params.offset).item)
}
var current *StatusItem
@@ -7030,7 +7258,7 @@ func (t *Terminal) dumpStatus(params getParams) string {
Position: t.cy,
Sort: t.sort,
TotalCount: t.count,
MatchCount: t.merger.Length(),
MatchCount: t.resultMerger.Length(),
Current: current,
Matches: matches,
Selected: selected,

View File

@@ -2,30 +2,7 @@
package tui
type Attr int32
func HasFullscreenRenderer() bool {
return false
}
var DefaultBorderShape = BorderRounded
func (a Attr) Merge(b Attr) Attr {
if b&AttrRegular > 0 {
// Only keep bold attribute set by the system
return (b &^ AttrRegular) | (a & BoldForce)
}
return (a &^ AttrRegular) | b
}
const (
AttrUndefined = Attr(0)
AttrRegular = Attr(1 << 8)
AttrClear = Attr(1 << 9)
BoldForce = Attr(1 << 10)
FullBg = Attr(1 << 11)
Bold = Attr(1)
Dim = Attr(1 << 1)
Italic = Attr(1 << 2)
@@ -36,6 +13,12 @@ const (
StrikeThrough = Attr(1 << 7)
)
func HasFullscreenRenderer() bool {
return false
}
var DefaultBorderShape = BorderRounded
func (r *FullscreenRenderer) Init() error { return nil }
func (r *FullscreenRenderer) DefaultTheme() *ColorTheme { return nil }
func (r *FullscreenRenderer) Resize(maxHeightFunc func(int) int) {}

View File

@@ -479,6 +479,7 @@ func (r *LightRenderer) escSequence(sz *int) Event {
return Event{Delete, 0, nil}
}
if len(r.buffer) == 7 && r.buffer[6] == '~' && r.buffer[4] == '1' {
*sz = 7
switch r.buffer[5] {
case '0':
return Event{AltShiftDelete, 0, nil}
@@ -525,6 +526,7 @@ func (r *LightRenderer) escSequence(sz *int) Event {
return Event{PageUp, 0, nil}
}
if len(r.buffer) == 7 && r.buffer[6] == '~' && r.buffer[4] == '1' {
*sz = 7
switch r.buffer[5] {
case '0':
return Event{AltShiftPageUp, 0, nil}
@@ -569,6 +571,7 @@ func (r *LightRenderer) escSequence(sz *int) Event {
return Event{PageDown, 0, nil}
}
if len(r.buffer) == 7 && r.buffer[6] == '~' && r.buffer[4] == '1' {
*sz = 7
switch r.buffer[5] {
case '0':
return Event{AltShiftPageDown, 0, nil}

View File

@@ -36,8 +36,6 @@ func (p ColorPair) style() tcell.Style {
return style.Foreground(asTcellColor(p.Fg())).Background(asTcellColor(p.Bg()))
}
type Attr int32
type TcellWindow struct {
color bool
windowType WindowType
@@ -98,14 +96,6 @@ const (
Italic = Attr(tcell.AttrItalic)
)
const (
AttrUndefined = Attr(0)
AttrRegular = Attr(1 << 7)
AttrClear = Attr(1 << 8)
BoldForce = Attr(1 << 10)
FullBg = Attr(1 << 11)
)
func (r *FullscreenRenderer) Bell() {
_screen.Beep()
}
@@ -159,15 +149,6 @@ func (c Color) Style() tcell.Color {
}
}
func (a Attr) Merge(b Attr) Attr {
if b&AttrRegular > 0 {
// Only keep bold attribute set by the system
return (b &^ AttrRegular) | (a & BoldForce)
}
return (a &^ AttrRegular) | b
}
// handle the following as private members of FullscreenRenderer instance
// they are declared here to prevent introducing tcell library in non-windows builds
var (
@@ -724,13 +705,13 @@ func (r *FullscreenRenderer) GetChar() Event {
func (r *FullscreenRenderer) Pause(clear bool) {
if clear {
r.Close()
_screen.Suspend()
}
}
func (r *FullscreenRenderer) Resume(clear bool, sigcont bool) {
if clear {
r.initScreen()
_screen.Resume()
}
}

View File

@@ -8,6 +8,26 @@ import (
"github.com/rivo/uniseg"
)
type Attr int32
const (
AttrUndefined = Attr(0)
AttrRegular = Attr(1 << 8)
AttrClear = Attr(1 << 9)
BoldForce = Attr(1 << 10)
FullBg = Attr(1 << 11)
Strip = Attr(1 << 12)
)
func (a Attr) Merge(b Attr) Attr {
if b&AttrRegular > 0 {
// Only keep bold attribute set by the system
return (b &^ AttrRegular) | (a & BoldForce)
}
return (a &^ AttrRegular) | b
}
// Types of user action
//
//go:generate stringer -type=EventType
@@ -275,6 +295,13 @@ func (a ColorAttr) IsColorDefined() bool {
return a.Color != colUndefined
}
func (a ColorAttr) IsAttrDefined() bool {
return a.Attr != AttrUndefined
}
func (a ColorAttr) IsUndefined() bool {
return !a.IsColorDefined() && !a.IsAttrDefined()
}
func NewColorAttr() ColorAttr {
return ColorAttr{Color: colUndefined, Attr: AttrUndefined}
}
@@ -303,6 +330,14 @@ const (
colMagenta
colCyan
colWhite
colGrey
colBrightRed
colBrightGreen
colBrightYellow
colBrightBlue
colBrightMagenta
colBrightCyan
colBrightWhite
)
type FillReturn int
@@ -350,6 +385,10 @@ func (p ColorPair) IsFullBgMarker() bool {
return p.attr&FullBg > 0
}
func (p ColorPair) ShouldStripColors() bool {
return p.attr&Strip > 0
}
func (p ColorPair) HasBg() bool {
return p.attr&Reverse == 0 && p.bg != colDefault ||
p.attr&Reverse > 0 && p.fg != colDefault
@@ -373,6 +412,12 @@ func (p ColorPair) WithAttr(attr Attr) ColorPair {
return dup
}
func (p ColorPair) WithFg(fg ColorAttr) ColorPair {
dup := p
fgPair := ColorPair{fg.Color, colUndefined, fg.Attr}
return dup.Merge(fgPair)
}
func (p ColorPair) WithBg(bg ColorAttr) ColorPair {
dup := p
bgPair := ColorPair{colUndefined, bg.Color, bg.Attr}
@@ -402,6 +447,7 @@ type ColorTheme struct {
ListBg ColorAttr
AltBg ColorAttr
Nth ColorAttr
Nomatch ColorAttr
SelectedFg ColorAttr
SelectedBg ColorAttr
SelectedMatch ColorAttr
@@ -764,9 +810,11 @@ func NewFullscreenRenderer(theme *ColorTheme, forceBlack bool, mouse bool) Rende
}
var (
Default16 *ColorTheme
Dark256 *ColorTheme
Light256 *ColorTheme
NoColorTheme *ColorTheme
EmptyTheme *ColorTheme
Default16 *ColorTheme
Dark256 *ColorTheme
Light256 *ColorTheme
ColPrompt ColorPair
ColNormal ColorPair
@@ -776,6 +824,7 @@ var (
ColMatch ColorPair
ColCursor ColorPair
ColCursorEmpty ColorPair
ColCursorEmptyChar ColorPair
ColMarker ColorPair
ColSelected ColorPair
ColSelectedMatch ColorPair
@@ -809,167 +858,170 @@ var (
ColInputLabel ColorPair
)
func EmptyTheme() *ColorTheme {
return &ColorTheme{
Colored: true,
Input: ColorAttr{colUndefined, AttrUndefined},
Fg: ColorAttr{colUndefined, AttrUndefined},
Bg: ColorAttr{colUndefined, 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{colUndefined, AttrUndefined},
Prompt: ColorAttr{colUndefined, AttrUndefined},
Match: ColorAttr{colUndefined, AttrUndefined},
Current: ColorAttr{colUndefined, AttrUndefined},
CurrentMatch: ColorAttr{colUndefined, AttrUndefined},
Spinner: ColorAttr{colUndefined, AttrUndefined},
Info: ColorAttr{colUndefined, AttrUndefined},
Cursor: ColorAttr{colUndefined, AttrUndefined},
Marker: ColorAttr{colUndefined, AttrUndefined},
Header: ColorAttr{colUndefined, AttrUndefined},
Footer: ColorAttr{colUndefined, AttrUndefined},
Border: ColorAttr{colUndefined, AttrUndefined},
BorderLabel: ColorAttr{colUndefined, AttrUndefined},
ListLabel: ColorAttr{colUndefined, AttrUndefined},
ListBorder: ColorAttr{colUndefined, 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},
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},
}
}
func NoColorTheme() *ColorTheme {
return &ColorTheme{
Colored: false,
Input: ColorAttr{colDefault, AttrUndefined},
Fg: ColorAttr{colDefault, AttrUndefined},
Bg: ColorAttr{colDefault, AttrUndefined},
ListFg: ColorAttr{colDefault, AttrUndefined},
ListBg: ColorAttr{colDefault, AttrUndefined},
AltBg: ColorAttr{colUndefined, AttrUndefined},
SelectedFg: ColorAttr{colDefault, AttrUndefined},
SelectedBg: ColorAttr{colDefault, AttrUndefined},
SelectedMatch: ColorAttr{colDefault, AttrUndefined},
DarkBg: ColorAttr{colDefault, AttrUndefined},
Prompt: ColorAttr{colDefault, AttrUndefined},
Match: ColorAttr{colDefault, Underline},
Current: ColorAttr{colDefault, Reverse},
CurrentMatch: ColorAttr{colDefault, Reverse | Underline},
Spinner: ColorAttr{colDefault, AttrUndefined},
Info: ColorAttr{colDefault, AttrUndefined},
Cursor: ColorAttr{colDefault, AttrUndefined},
Marker: ColorAttr{colDefault, AttrUndefined},
Header: ColorAttr{colDefault, AttrUndefined},
Border: ColorAttr{colDefault, AttrUndefined},
BorderLabel: ColorAttr{colDefault, AttrUndefined},
Ghost: ColorAttr{colDefault, Dim},
Disabled: ColorAttr{colDefault, AttrUndefined},
PreviewFg: ColorAttr{colDefault, AttrUndefined},
PreviewBg: ColorAttr{colDefault, AttrUndefined},
Gutter: ColorAttr{colDefault, AttrUndefined},
PreviewBorder: ColorAttr{colDefault, AttrUndefined},
PreviewScrollbar: ColorAttr{colDefault, AttrUndefined},
PreviewLabel: ColorAttr{colDefault, AttrUndefined},
ListLabel: ColorAttr{colDefault, AttrUndefined},
ListBorder: ColorAttr{colDefault, AttrUndefined},
Separator: ColorAttr{colDefault, AttrUndefined},
Scrollbar: ColorAttr{colDefault, AttrUndefined},
InputBg: ColorAttr{colDefault, AttrUndefined},
InputBorder: ColorAttr{colDefault, AttrUndefined},
InputLabel: ColorAttr{colDefault, AttrUndefined},
HeaderBg: ColorAttr{colDefault, AttrUndefined},
HeaderBorder: ColorAttr{colDefault, AttrUndefined},
HeaderLabel: ColorAttr{colDefault, AttrUndefined},
FooterBg: ColorAttr{colDefault, AttrUndefined},
FooterBorder: ColorAttr{colDefault, AttrUndefined},
FooterLabel: ColorAttr{colDefault, AttrUndefined},
GapLine: ColorAttr{colDefault, AttrUndefined},
Nth: ColorAttr{colUndefined, AttrUndefined},
}
}
func init() {
defaultColor := ColorAttr{colDefault, AttrUndefined}
undefined := ColorAttr{colUndefined, AttrUndefined}
NoColorTheme = &ColorTheme{
Colored: false,
Input: defaultColor,
Fg: defaultColor,
Bg: defaultColor,
ListFg: defaultColor,
ListBg: defaultColor,
AltBg: undefined,
SelectedFg: defaultColor,
SelectedBg: defaultColor,
SelectedMatch: defaultColor,
DarkBg: defaultColor,
Prompt: defaultColor,
Match: defaultColor,
Current: undefined,
CurrentMatch: undefined,
Spinner: defaultColor,
Info: defaultColor,
Cursor: defaultColor,
Marker: defaultColor,
Header: defaultColor,
Border: undefined,
BorderLabel: defaultColor,
Ghost: undefined,
Disabled: defaultColor,
PreviewFg: defaultColor,
PreviewBg: defaultColor,
Gutter: undefined,
PreviewBorder: defaultColor,
PreviewScrollbar: defaultColor,
PreviewLabel: defaultColor,
ListLabel: defaultColor,
ListBorder: defaultColor,
Separator: defaultColor,
Scrollbar: defaultColor,
InputBg: defaultColor,
InputBorder: defaultColor,
InputLabel: defaultColor,
HeaderBg: defaultColor,
HeaderBorder: defaultColor,
HeaderLabel: defaultColor,
FooterBg: defaultColor,
FooterBorder: defaultColor,
FooterLabel: defaultColor,
GapLine: defaultColor,
Nth: undefined,
Nomatch: undefined,
}
EmptyTheme = &ColorTheme{
Colored: true,
Input: undefined,
Fg: undefined,
Bg: undefined,
ListFg: undefined,
ListBg: undefined,
AltBg: undefined,
SelectedFg: undefined,
SelectedBg: undefined,
SelectedMatch: undefined,
DarkBg: undefined,
Prompt: undefined,
Match: undefined,
Current: undefined,
CurrentMatch: undefined,
Spinner: undefined,
Info: undefined,
Cursor: undefined,
Marker: undefined,
Header: undefined,
Footer: undefined,
Border: undefined,
BorderLabel: undefined,
ListLabel: undefined,
ListBorder: undefined,
Ghost: undefined,
Disabled: undefined,
PreviewFg: undefined,
PreviewBg: undefined,
Gutter: undefined,
PreviewBorder: undefined,
PreviewScrollbar: undefined,
PreviewLabel: undefined,
Separator: undefined,
Scrollbar: undefined,
InputBg: undefined,
InputBorder: undefined,
InputLabel: undefined,
HeaderBg: undefined,
HeaderBorder: undefined,
HeaderLabel: undefined,
FooterBg: undefined,
FooterBorder: undefined,
FooterLabel: undefined,
GapLine: undefined,
Nth: undefined,
Nomatch: undefined,
}
Default16 = &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{colBlack, AttrUndefined},
Input: defaultColor,
Fg: defaultColor,
Bg: defaultColor,
ListFg: undefined,
ListBg: undefined,
AltBg: undefined,
SelectedFg: undefined,
SelectedBg: undefined,
SelectedMatch: undefined,
DarkBg: ColorAttr{colGrey, AttrUndefined},
Prompt: ColorAttr{colBlue, AttrUndefined},
Match: ColorAttr{colGreen, AttrUndefined},
Current: ColorAttr{colYellow, AttrUndefined},
CurrentMatch: ColorAttr{colGreen, AttrUndefined},
Current: ColorAttr{colBrightWhite, AttrUndefined},
CurrentMatch: ColorAttr{colBrightGreen, AttrUndefined},
Spinner: ColorAttr{colGreen, AttrUndefined},
Info: ColorAttr{colWhite, AttrUndefined},
Info: ColorAttr{colYellow, AttrUndefined},
Cursor: ColorAttr{colRed, AttrUndefined},
Marker: ColorAttr{colMagenta, AttrUndefined},
Header: ColorAttr{colCyan, AttrUndefined},
Footer: ColorAttr{colCyan, AttrUndefined},
Border: ColorAttr{colBlack, AttrUndefined},
BorderLabel: ColorAttr{colWhite, 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},
Border: undefined,
BorderLabel: defaultColor,
Ghost: undefined,
Disabled: undefined,
PreviewFg: undefined,
PreviewBg: undefined,
Gutter: undefined,
PreviewBorder: undefined,
PreviewScrollbar: undefined,
PreviewLabel: undefined,
ListLabel: undefined,
ListBorder: undefined,
Separator: undefined,
Scrollbar: undefined,
InputBg: undefined,
InputBorder: undefined,
InputLabel: undefined,
HeaderBg: undefined,
HeaderBorder: undefined,
HeaderLabel: undefined,
FooterBg: undefined,
FooterBorder: undefined,
FooterLabel: undefined,
GapLine: undefined,
Nth: undefined,
Nomatch: undefined,
}
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},
Input: defaultColor,
Fg: defaultColor,
Bg: defaultColor,
ListFg: undefined,
ListBg: undefined,
AltBg: undefined,
SelectedFg: undefined,
SelectedBg: undefined,
SelectedMatch: undefined,
DarkBg: ColorAttr{236, AttrUndefined},
Prompt: ColorAttr{110, AttrUndefined},
Match: ColorAttr{108, AttrUndefined},
@@ -983,41 +1035,43 @@ func init() {
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},
Ghost: undefined,
Disabled: undefined,
PreviewFg: undefined,
PreviewBg: undefined,
Gutter: undefined,
PreviewBorder: undefined,
PreviewScrollbar: undefined,
PreviewLabel: undefined,
ListLabel: undefined,
ListBorder: undefined,
Separator: undefined,
Scrollbar: undefined,
InputBg: undefined,
InputBorder: undefined,
InputLabel: undefined,
HeaderBg: undefined,
HeaderBorder: undefined,
HeaderLabel: undefined,
FooterBg: undefined,
FooterBorder: undefined,
FooterLabel: undefined,
GapLine: undefined,
Nth: undefined,
Nomatch: undefined,
}
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},
Input: defaultColor,
Fg: defaultColor,
Bg: defaultColor,
ListFg: undefined,
ListBg: undefined,
AltBg: undefined,
SelectedFg: undefined,
SelectedBg: undefined,
SelectedMatch: undefined,
DarkBg: ColorAttr{251, AttrUndefined},
Prompt: ColorAttr{25, AttrUndefined},
Match: ColorAttr{66, AttrUndefined},
@@ -1031,37 +1085,54 @@ func init() {
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},
Nth: ColorAttr{colUndefined, AttrUndefined},
Ghost: undefined,
Disabled: undefined,
PreviewFg: undefined,
PreviewBg: undefined,
Gutter: undefined,
PreviewBorder: undefined,
PreviewScrollbar: undefined,
PreviewLabel: undefined,
ListLabel: undefined,
ListBorder: undefined,
Separator: undefined,
Scrollbar: undefined,
InputBg: undefined,
InputBorder: undefined,
InputLabel: undefined,
HeaderBg: undefined,
HeaderBorder: undefined,
HeaderLabel: undefined,
FooterBg: undefined,
FooterBorder: undefined,
FooterLabel: undefined,
GapLine: undefined,
Nth: undefined,
Nomatch: undefined,
}
}
func InitTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool, hasInputWindow bool, hasHeaderWindow bool) {
func InitTheme(theme *ColorTheme, baseTheme *ColorTheme, boldify bool, forceBlack bool, hasInputWindow bool, hasHeaderWindow bool) {
if forceBlack {
theme.Bg = ColorAttr{colBlack, AttrUndefined}
}
if boldify {
boldify := func(c ColorAttr) ColorAttr {
dup := c
if (c.Attr & AttrRegular) == 0 {
dup.Attr |= BoldForce
}
return dup
}
theme.Current = boldify(theme.Current)
theme.CurrentMatch = boldify(theme.CurrentMatch)
theme.Prompt = boldify(theme.Prompt)
theme.Input = boldify(theme.Input)
theme.Cursor = boldify(theme.Cursor)
theme.Spinner = boldify(theme.Spinner)
}
o := func(a ColorAttr, b ColorAttr) ColorAttr {
c := a
if b.Color != colUndefined {
@@ -1077,18 +1148,36 @@ func InitTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool, hasInp
theme.Bg = o(baseTheme.Bg, theme.Bg)
theme.DarkBg = o(baseTheme.DarkBg, theme.DarkBg)
theme.Prompt = o(baseTheme.Prompt, theme.Prompt)
theme.Match = o(baseTheme.Match, theme.Match)
match := theme.Match
if !baseTheme.Colored && match.IsUndefined() {
match.Attr = Underline
}
theme.Match = o(baseTheme.Match, match)
// Inherit from 'fg', so that we don't have to write 'current-fg:dim'
// e.g. fzf --delimiter / --nth -1 --color fg:dim,nth:regular
theme.Current = theme.Fg.Merge(o(baseTheme.Current, theme.Current))
theme.CurrentMatch = o(baseTheme.CurrentMatch, theme.CurrentMatch)
current := theme.Current
if !baseTheme.Colored && current.IsUndefined() {
current.Attr = Reverse
}
theme.Current = theme.Fg.Merge(o(baseTheme.Current, current))
currentMatch := theme.CurrentMatch
if !baseTheme.Colored && currentMatch.IsUndefined() {
currentMatch.Attr = Reverse | Underline
}
theme.CurrentMatch = o(baseTheme.CurrentMatch, currentMatch)
theme.Spinner = o(baseTheme.Spinner, theme.Spinner)
theme.Info = o(baseTheme.Info, theme.Info)
theme.Cursor = o(baseTheme.Cursor, theme.Cursor)
theme.Marker = o(baseTheme.Marker, theme.Marker)
theme.Header = o(baseTheme.Header, theme.Header)
theme.Footer = o(baseTheme.Footer, theme.Footer)
theme.Border = o(baseTheme.Border, theme.Border)
// If border color is undefined, set it to default color with dim attribute.
border := theme.Border
if baseTheme.Border.IsUndefined() && border.IsUndefined() {
border.Attr = Dim
}
theme.Border = o(baseTheme.Border, border)
theme.BorderLabel = o(baseTheme.BorderLabel, theme.BorderLabel)
undefined := NewColorAttr()
@@ -1101,9 +1190,23 @@ func InitTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool, hasInp
theme.SelectedFg = o(theme.ListFg, theme.SelectedFg)
theme.SelectedBg = o(theme.ListBg, theme.SelectedBg)
theme.SelectedMatch = o(theme.Match, theme.SelectedMatch)
theme.Ghost = o(theme.Input, theme.Ghost)
ghost := theme.Ghost
if ghost.IsUndefined() {
ghost.Attr = Dim
} else if ghost.IsColorDefined() && !ghost.IsAttrDefined() {
// Don't want to inherit 'bold' from 'input'
ghost.Attr = AttrRegular
}
theme.Ghost = o(theme.Input, ghost)
theme.Disabled = o(theme.Input, theme.Disabled)
theme.Gutter = o(theme.DarkBg, theme.Gutter)
// Use dim gutter on non-colored themes if undefined
gutter := theme.Gutter
if !baseTheme.Colored && gutter.IsUndefined() {
gutter.Attr = Dim
}
theme.Gutter = o(theme.DarkBg, gutter)
theme.PreviewFg = o(theme.Fg, theme.PreviewFg)
theme.PreviewBg = o(theme.Bg, theme.PreviewBg)
theme.PreviewLabel = o(theme.BorderLabel, theme.PreviewLabel)
@@ -1145,6 +1248,10 @@ func InitTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool, hasInp
theme.FooterBorder = o(theme.Border, theme.FooterBorder)
theme.FooterLabel = o(theme.BorderLabel, theme.FooterLabel)
if theme.Nomatch.IsUndefined() {
theme.Nomatch.Attr = Dim
}
initPalette(theme)
}
@@ -1168,10 +1275,11 @@ func initPalette(theme *ColorTheme) {
ColSelectedMatch = pair(theme.SelectedMatch, theme.SelectedBg)
ColCursor = pair(theme.Cursor, theme.Gutter)
ColCursorEmpty = pair(blank, theme.Gutter)
ColCursorEmptyChar = pair(theme.Gutter, theme.ListBg)
if theme.SelectedBg.Color != theme.ListBg.Color {
ColMarker = pair(theme.Marker, theme.SelectedBg)
} else {
ColMarker = pair(theme.Marker, theme.Gutter)
ColMarker = pair(theme.Marker, theme.ListBg)
}
ColCurrent = pair(theme.Current, theme.DarkBg)
ColCurrentMatch = pair(theme.CurrentMatch, theme.DarkBg)

View File

@@ -24,7 +24,7 @@ DEFAULT_TIMEOUT = 10
FILE = File.expand_path(__FILE__)
BASE = File.expand_path('../..', __dir__)
Dir.chdir(BASE)
FZF = "FZF_DEFAULT_OPTS=\"--no-scrollbar --pointer \\> --marker \\>\" FZF_DEFAULT_COMMAND= #{BASE}/bin/fzf".freeze
FZF = %(FZF_DEFAULT_OPTS="--no-scrollbar --gutter ' ' --pointer '>' --marker '>'" FZF_DEFAULT_COMMAND= #{BASE}/bin/fzf).freeze
def wait(timeout = DEFAULT_TIMEOUT)
since = Time.now

View File

@@ -1682,6 +1682,7 @@ class TestCore < TestInteractive
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 = {
FZF_DIRECTION: 'down',
FZF_TOTAL_COUNT: '100',
FZF_MATCH_COUNT: '100',
FZF_SELECT_COUNT: '0',
@@ -2004,7 +2005,7 @@ class TestCore < TestInteractive
end
end
elapsed = Time.now - time
assert elapsed < 2
assert_operator elapsed, :<, 2
end
def test_bg_cancel
@@ -2017,7 +2018,7 @@ class TestCore < TestInteractive
tmux.until { assert_equal 2, it.match_count }
tmux.send_keys :Space
tmux.until { |lines| assert lines.any_include?('[0]') }
sleep 2
sleep(2)
tmux.until do |lines|
assert lines.any_include?('[0]')
refute lines.any_include?('[1]')

View File

@@ -403,7 +403,7 @@ class TestExec < TestInteractive
end
def test_become
tmux.send_keys "seq 100 | #{FZF} --bind 'enter:become:seq {} | #{FZF}'", :Enter
tmux.send_keys "seq 100 | fzf --bind 'enter:become:seq {} | fzf'", :Enter
tmux.until { |lines| assert_equal 100, lines.match_count }
tmux.send_keys 999
tmux.until { |lines| assert_equal 0, lines.match_count }

View File

@@ -178,8 +178,8 @@ class TestLayout < TestInteractive
tmux.send_keys 'seq 3 | fzf --height ~100% --info=inline --border rounded', :Enter
expected = <<~OUTPUT
3
2
3
2
> 1
> < 3/3
@@ -197,8 +197,8 @@ class TestLayout < TestInteractive
3
2
3
2
> 1
> < 3/3
@@ -247,7 +247,7 @@ class TestLayout < TestInteractive
tmux.send_keys 'seq 100 | fzf --height ~5 --info=inline --border rounded', :Enter
expected = <<~OUTPUT
2
2
> 1
> < 100/100
@@ -275,12 +275,12 @@ class TestLayout < TestInteractive
def test_fzf_multi_line
tmux.send_keys %[(echo -en '0\\0'; echo -en '1\\n2\\0'; seq 1000) | fzf --read0 --multi --bind load:select-all --border rounded], :Enter
block = <<~BLOCK
998
999
1000
1
2
998
999
1000
1
2
>>0
3/3 (3)
>
@@ -312,11 +312,11 @@ class TestLayout < TestInteractive
>
3/3 (3)
>>0
1
2
1
2
3
1
2
1
2
3
BLOCK
tmux.until { assert_block(block, it) }
end
@@ -1156,6 +1156,65 @@ class TestLayout < TestInteractive
tmux.until { assert_block(block, it) }
end
def test_gutter_default
tmux.send_keys %(seq 10 | fzf), :Enter
block = <<~BLOCK
3
2
> 1
10/10
>
BLOCK
tmux.until { assert_block(block, it) }
end
def test_gutter_default_no_unicode
tmux.send_keys %(seq 10 | fzf --no-unicode), :Enter
block = <<~BLOCK
3
2
> 1
10/10
>
BLOCK
tmux.until { assert_block(block, it) }
end
def test_gutter_custom
tmux.send_keys %(seq 10 | fzf --gutter x), :Enter
block = <<~BLOCK
x 3
x 2
> 1
10/10
>
BLOCK
tmux.until { assert_block(block, it) }
end
# https://github.com/junegunn/fzf/issues/4537
def test_no_scrollbar_preview_toggle
x = 'x' * 300
y = 'y' * 300
tmux.send_keys %(yes #{x} | head -1000 | fzf --bind 'tab:toggle-preview' --border --no-scrollbar --preview 'echo #{y}' --preview-window 'border-left'), :Enter
# │ ▌ xxxxxxxx·· │ yyyyyyyy│
tmux.until do |lines|
lines.any? { it.match?(/x·· │ y+│$/) }
end
tmux.send_keys :Tab
# │ ▌ xxxxxxxx·· │
tmux.until do |lines|
lines.none? { it.match?(/x··y│$/) }
end
tmux.send_keys :Tab
tmux.until do |lines|
lines.any? { it.match?(/x·· │ y+│$/) }
end
end
def test_combinations
skip unless ENV['LONGTEST']

113
test/test_raw.rb Normal file
View File

@@ -0,0 +1,113 @@
# frozen_string_literal: true
require_relative 'lib/common'
# Testing raw mode
class TestRaw < TestInteractive
def test_raw_mode
tmux.send_keys %(seq 1000 | #{FZF} --raw --bind ctrl-x:toggle-raw,a:enable-raw,b:disable-raw --gutter '▌' --multi --bind 'space:transform-prompt:echo "[[$FZF_RAW]] "'), :Enter
tmux.until { assert_equal 1000, it.match_count }
tmux.send_keys 1
tmux.until { assert_equal 272, it.match_count }
tmux.send_keys :Up
tmux.until { assert_includes it, '> 2' }
tmux.send_keys 'C-p'
tmux.until do
assert_includes it, '> 10'
assert_includes it, '▖ 9'
end
tmux.send_keys 'C-x'
tmux.until do
assert_includes it, '> 10'
assert_includes it, '▌ 1'
end
tmux.send_keys :Up, 'C-x'
tmux.until do
assert_includes it, '> 11'
assert_includes it, '▖ 10'
end
tmux.send_keys 1
tmux.until { assert_equal 28, it.match_count }
tmux.send_keys 'C-p'
tmux.until do
assert_includes it, '> 101'
assert_includes it, '▖ 100'
end
tmux.send_keys 'C-n'
tmux.until do
assert_includes it, '> 11'
assert_includes it, '▖ 10'
end
tmux.send_keys :Tab, :Tab, :Tab
tmux.until { assert_equal 3, it.select_count }
tmux.send_keys 'C-x'
tmux.until do
assert_equal 1, it.select_count
assert_includes it, '▌ 110'
assert_includes it, '>>11'
end
tmux.send_keys 'a'
tmux.until do
assert_equal 1, it.select_count
assert_includes it, '>>11'
assert_includes it, '▖ 10'
end
tmux.send_keys :Down, :Space
tmux.until { assert_includes it, '[[0]] 11' }
tmux.send_keys :Up, :Space
tmux.until { assert_includes it, '[[1]] 11' }
tmux.send_keys 'b'
tmux.until do
assert_equal 1, it.select_count
assert_includes it, '▌ 110'
assert_includes it, '>>11'
end
tmux.send_keys :Space
tmux.until { assert_includes it, '[[]] 11' }
tmux.send_keys 'C-u', '5'
tmux.until { assert_includes it, '> 5' }
tmux.send_keys 'C-x', 'C-p', 'C-p'
tmux.until do
assert_includes it, '> 25'
assert_includes it, '▖ 24'
end
tmux.send_keys 'C-x'
tmux.until do
assert_includes it, '> 25'
assert_includes it, '▌ 15'
end
# 35 is the closest match in raw mode
tmux.send_keys 'C-x', :Up, :Up, :Up, :Up, :Up, :Up, 'C-x'
tmux.until do
assert_includes it, '> 35'
assert_includes it, '▌ 25'
end
end
def test_raw_best
tmux.send_keys %(seq 1000 | #{FZF} --raw --bind space:best), :Enter
tmux.send_keys 999
tmux.until { assert_includes it, '> 1' }
tmux.send_keys :Space
tmux.until { assert_includes it, '> 999' }
end
end

View File

@@ -64,19 +64,19 @@ remove_line() {
line_no=1
continue
fi
line_no=$(( $(sed 's/:.*//' <<< "$line") + line_no - 1 ))
line_no=$(($(sed 's/:.*//' <<< "$line") + line_no - 1))
content=$(sed 's/^[0-9]*://' <<< "$line")
match=1
echo " - Line #$line_no: $content"
echo " - Line #$line_no: $content"
[ "$content" = "$1" ] || ask " - Remove?"
if [ $? -eq 0 ]; then
temp=$(mktemp)
awk -v n=$line_no 'NR == n {next} {print}' "$src" > "$temp" &&
cat "$temp" > "$src" && rm -f "$temp" || break
echo " - Removed"
echo " - Removed"
else
echo " - Skipped"
line_no=$(( line_no + 1 ))
echo " - Skipped"
line_no=$((line_no + 1))
fi
done
[ $match -eq 0 ] && echo " - Nothing found"
@@ -109,6 +109,6 @@ if [ -d "${fish_dir}/functions" ]; then
fi
config_dir=$(dirname "$prefix_expand")
if [[ "$xdg" = 1 ]] && [[ "$config_dir" = */fzf ]] && [[ -d "$config_dir" ]]; then
if [[ $xdg == 1 ]] && [[ $config_dir == */fzf ]] && [[ -d $config_dir ]]; then
rmdir "$config_dir"
fi