Compare commits

...

21 Commits

Author SHA1 Message Date
Junegunn Choi 62899fd74e 0.71.0 2026-04-04 14:04:34 +09:00
Junegunn Choi d1cea64a0e Allow adaptive height with negative value
Fix #4682
2026-04-04 10:13:19 +09:00
bitraid d7a132b8bf Update changelog 2026-04-04 09:48:00 +09:00
bitraid 59b82670f7 fish: Key bindings now requires fish v3.4.0+ 2026-04-04 09:47:59 +09:00
bitraid c516acaa89 Add .fish to .editorconfig shell script rules 2026-04-04 09:47:59 +09:00
bitraid 11ff4745ad Fix typo in install script 2026-04-04 09:47:59 +09:00
bitraid 23164c2263 fish: Completion script rewrite (SHIFT-TAB) 2026-04-04 09:47:59 +09:00
bitraid 9f8294de62 fish: Fixes and improvements to CTRL-R (#4703)
Fixes:
- Commands with trailing newlines
- Very long previews

Improvements:
- SHIFT-DELETE performance
- Bind ALT-T: cycle command prefix (timestamp, date/time, none)
- Set comment color for prefix
2026-04-04 09:47:59 +09:00
Junegunn Choi b7138e67a6 Update README 2026-04-03 18:58:57 +09:00
Junegunn Choi 0f0751b3b6 Add CODEOWNERS 2026-04-03 00:01:37 +09:00
dependabot[bot] cc16a97a40 Bump ruby/setup-ruby from 1.296.0 to 1.299.0 (#4753)
Bumps [ruby/setup-ruby](https://github.com/ruby/setup-ruby) from 1.296.0 to 1.299.0.
- [Release notes](https://github.com/ruby/setup-ruby/releases)
- [Changelog](https://github.com/ruby/setup-ruby/blob/master/release.rb)
- [Commits](https://github.com/ruby/setup-ruby/compare/eab2afb99481ca09a4e91171a8e0aee0e89bfedd...3ff19f5e2baf30647122352b96108b1fbe250c64)

---
updated-dependencies:
- dependency-name: ruby/setup-ruby
  dependency-version: 1.299.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-31 00:10:35 +09:00
dagecko 3ccf162cf0 fix: pin 6 unpinned action(s) (#4750) 2026-03-30 13:20:11 +09:00
PJ Eby fff7eda9d7 Terminate child processes on Windows (#4723)
Windows doesn't have signals, the default Kill doesn't actually kill
anything, and other forms of termination don't allow cleanup. So we
spawn preview processes in a new process group and send them a
CTRL_BREAK_EVENT to terminate.

However, we only do this for "pwsh" (PowerShell 7+) and unknown/
posix-ish shells, because cmd.exe and Windows PowerShell
("powershell.exe") don't always exit on Ctrl-Break. pwsh also needs
the -NonInteractive flag to exit on Ctrl-Break.

If the process wasn't given its own group, or if sending the console
control event fails, we fall back to the standard Kill (which likely
won't help, but doesn't hurt to try).

Fix #3134
2026-03-29 12:40:24 +09:00
junegunn d198c0c9de Deploying to master from @ junegunn/fzf@55d5b153e6 🚀 2026-03-28 15:05:52 +00:00
harrywzl 55d5b153e6 docs: fix duplicate table rows and minor grammar/formatting in fzf.txt (#4743) 2026-03-27 15:01:12 +09:00
Junegunn Choi 57695b0309 Remove deprecated FZF_TMUX vars from shell script headers 2026-03-26 21:35:14 +09:00
Junegunn Choi 6f17d49dbb Support zellij floating pane via --popup (new name for --tmux) (#4145) 2026-03-26 21:32:56 +09:00
Junegunn Choi 87586ac5eb Only use --no-same-owner with tar when supported
Fix #4740
Fix #4741
2026-03-26 21:06:58 +09:00
Junegunn Choi 12be5e7b83 Skip adaptive height validation when --tmux overrides --height
Fix #4742
2026-03-26 20:56:36 +09:00
Qingwei Li 1acf980e95 enhancement: move server declaration to go func to allocate it in stack (#4739)
Fix #4735
Close #4736
2026-03-25 15:22:49 +09:00
cui 14e3995a06 Fix nthTransformer parts slice preallocation (#4734)
Use make([]NthParts, 0, len(indexes)) so the slice starts empty with
reserved capacity. The previous length-len(indexes) allocation left
leading zero NthParts entries before appended elements.
2026-03-23 09:55:00 +09:00
32 changed files with 497 additions and 639 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
root = true root = true
[*.{sh,bash}] [*.{sh,bash,fish}]
indent_style = space indent_style = space
indent_size = 2 indent_size = 2
simplify = true simplify = true
+1
View File
@@ -0,0 +1 @@
* @junegunn
+1 -1
View File
@@ -28,7 +28,7 @@ jobs:
go-version: "1.23" go-version: "1.23"
- name: Setup Ruby - name: Setup Ruby
uses: ruby/setup-ruby@v1 uses: ruby/setup-ruby@3ff19f5e2baf30647122352b96108b1fbe250c64 # v1
with: with:
ruby-version: 3.4.6 ruby-version: 3.4.6
+1 -1
View File
@@ -25,7 +25,7 @@ jobs:
go-version: "1.23" go-version: "1.23"
- name: Setup Ruby - name: Setup Ruby
uses: ruby/setup-ruby@v1 uses: ruby/setup-ruby@3ff19f5e2baf30647122352b96108b1fbe250c64 # v1
with: with:
ruby-version: 3.0.0 ruby-version: 3.0.0
+2 -2
View File
@@ -12,13 +12,13 @@ jobs:
uses: actions/checkout@v5 uses: actions/checkout@v5
- name: Generate Sponsors 💖 - name: Generate Sponsors 💖
uses: JamesIves/github-sponsors-readme-action@v1 uses: JamesIves/github-sponsors-readme-action@2fd9142e765f755780202122261dc85e78459405 # v1
with: with:
token: ${{ secrets.SPONSORS_TOKEN }} token: ${{ secrets.SPONSORS_TOKEN }}
file: 'README.md' file: 'README.md'
- name: Deploy to GitHub Pages 🚀 - name: Deploy to GitHub Pages 🚀
uses: JamesIves/github-pages-deploy-action@v4 uses: JamesIves/github-pages-deploy-action@d92aa235d04922e8f08b40ce78cc5442fcfbfa2f # v4
with: with:
branch: master branch: master
folder: '.' folder: '.'
+1 -1
View File
@@ -7,4 +7,4 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v5
- uses: crate-ci/typos@v1.29.4 - uses: crate-ci/typos@685eb3d55be2f85191e8c84acb9f44d7756f84ab # v1.29.4
+1 -1
View File
@@ -7,7 +7,7 @@ jobs:
publish: publish:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: vedantmgoyal2009/winget-releaser@v2 - uses: vedantmgoyal2009/winget-releaser@4ffc7888bffd451b357355dc214d43bb9f23917e # v2
with: with:
identifier: junegunn.fzf identifier: junegunn.fzf
installers-regex: '-windows_(armv7|arm64|amd64)\.zip$' installers-regex: '-windows_(armv7|arm64|amd64)\.zip$'
+22 -6
View File
@@ -3,6 +3,12 @@ CHANGELOG
0.71.0 0.71.0
------ ------
_Release highlights: https://junegunn.github.io/fzf/releases/0.71.0/_
- Added `--popup` as a new name for `--tmux` with Zellij support
- `--popup` starts fzf in a tmux popup or a Zellij floating pane
- `--tmux` is now an alias for `--popup`
- Requires tmux 3.3+ or Zellij 0.44+
- Cross-reload item identity with `--id-nth` - Cross-reload item identity with `--id-nth`
- Added `--id-nth=NTH` to define item identity fields for cross-reload operations - Added `--id-nth=NTH` to define item identity fields for cross-reload operations
- When a `reload` is triggered with tracking enabled, fzf searches for the tracked item by its identity fields in the new list. - When a `reload` is triggered with tracking enabled, fzf searches for the tracked item by its identity fields in the new list.
@@ -17,24 +23,34 @@ CHANGELOG
- The search performance now scales linearly with the number of CPU cores, as we dropped static partitioning to allow better load balancing across threads. - The search performance now scales linearly with the number of CPU cores, as we dropped static partitioning to allow better load balancing across threads.
``` ```
=== query: 'linux' === === query: 'linux' ===
[all] baseline: 17.12ms current: 14.28ms (1.20x) matches: 179966 (12.79%) [all] baseline: 21.95ms current: 17.47ms (1.26x) matches: 179966 (12.79%)
[1T] baseline: 136.49ms current: 137.25ms (0.99x) matches: 179966 (12.79%) [1T] baseline: 179.63ms current: 180.53ms (1.00x) matches: 179966 (12.79%)
[2T] baseline: 75.74ms current: 68.75ms (1.10x) matches: 179966 (12.79%) [2T] baseline: 97.38ms current: 90.05ms (1.08x) matches: 179966 (12.79%)
[4T] baseline: 41.16ms current: 34.97ms (1.18x) matches: 179966 (12.79%) [4T] baseline: 53.83ms current: 44.77ms (1.20x) matches: 179966 (12.79%)
[8T] baseline: 32.82ms current: 17.79ms (1.84x) matches: 179966 (12.79%) [8T] baseline: 41.66ms current: 22.58ms (1.84x) matches: 179966 (12.79%)
``` ```
- Improved the cache structure, reducing memory footprint per entry by 86x. - Improved the cache structure, reducing memory footprint per entry by 86x.
- With the reduced per-entry cost, the cache now has broader coverage. - With the reduced per-entry cost, the cache now has broader coverage.
- Shell integration improvements - Shell integration improvements
- bash: CTRL-R now supports multi-select and `shift-delete` to delete history entries (#4715) - bash: CTRL-R now supports multi-select and `shift-delete` to delete history entries (#4715)
- fish: Improved command history (CTRL-R) (#4703) (@bitraid) - fish:
- Improved command history (CTRL-R) (#4703) (@bitraid)
- Rewrite completion script (SHIFT-TAB) (#4731) (@bitraid)
- Increase minimum fish version requirement to 3.4.0 (#4731) (@bitraid)
- `GET /` HTTP endpoint now includes `positions` field in each match entry, providing the indices of matched characters for external highlighting (#4726) - `GET /` HTTP endpoint now includes `positions` field in each match entry, providing the indices of matched characters for external highlighting (#4726)
- Allow adaptive height with negative value (`--height=~-HEIGHT`) (#4682)
- Bug fixes - Bug fixes
- `--walker=follow` no longer follows symlinks whose target is an ancestor of the walker root, avoiding severe resource exhaustion when a symlink points outside the tree (e.g. Wine's `z:` → `/`) (#4710) - `--walker=follow` no longer follows symlinks whose target is an ancestor of the walker root, avoiding severe resource exhaustion when a symlink points outside the tree (e.g. Wine's `z:` → `/`) (#4710)
- Fixed AWK tokenizer not treating a new line character as whitespace - Fixed AWK tokenizer not treating a new line character as whitespace
- Fixed `--{accept,with}-nth` removing trailing whitespaces with a non-default `--delimiter` - Fixed `--{accept,with}-nth` removing trailing whitespaces with a non-default `--delimiter`
- Fixed OSC8 hyperlinks being mangled when the URL contains unicode characters (#4707) - Fixed OSC8 hyperlinks being mangled when the URL contains unicode characters (#4707)
- Fixed `--with-shell` not handling quoted arguments correctly (#4709) - Fixed `--with-shell` not handling quoted arguments correctly (#4709)
- Fixed child processes not being terminated on Windows (#4723) (@pjeby)
- Fixed preview scrollbar not rendered after `toggle-preview`
- Fixed preview follow/scroll with long wrapped lines
- Fixed tab width when `--frozen-left` is used
- Fixed preview mouse events being processed when no preview window exists
- zsh: Fixed history widget when `sh_glob` option is on (#4714) (@EvanHahn)
0.70.0 0.70.0
------ ------
+83 -39
View File
File diff suppressed because one or more lines are too long
+2 -4
View File
@@ -112,7 +112,7 @@ the whole if we start off with `:FZF` command.
" Bang version starts fzf in fullscreen mode " Bang version starts fzf in fullscreen mode
:FZF! :FZF!
< <
Similarly to {ctrlp.vim}{3}, use enter key, CTRL-T, CTRL-X or CTRL-V to open Similarly to {ctrlp.vim}{3}, use Enter key, CTRL-T, CTRL-X or CTRL-V to open
selected files in the current window, in new tabs, in horizontal splits, or in selected files in the current window, in new tabs, in horizontal splits, or in
vertical splits respectively. vertical splits respectively.
@@ -218,7 +218,6 @@ list:
`fg` / `bg` / `hl` | Item (foreground / background / highlight) `fg` / `bg` / `hl` | Item (foreground / background / highlight)
`fg+` / `bg+` / `hl+` | Current item (foreground / background / highlight) `fg+` / `bg+` / `hl+` | Current item (foreground / background / highlight)
`preview-fg` / `preview-bg` | Preview window text and background `preview-fg` / `preview-bg` | Preview window text and background
`hl` / `hl+` | Highlighted substrings (normal / current)
`gutter` | Background of the gutter on the left `gutter` | Background of the gutter on the left
`pointer` | Pointer to the current line ( `>` ) `pointer` | Pointer to the current line ( `>` )
`marker` | Multi-select marker ( `>` ) `marker` | Multi-select marker ( `>` )
@@ -229,7 +228,6 @@ list:
`query` | Query string `query` | Query string
`disabled` | Query string when search is disabled `disabled` | Query string when search is disabled
`prompt` | Prompt before query ( `> ` ) `prompt` | Prompt before query ( `> ` )
`pointer` | Pointer to the current line ( `>` )
----------------------------+------------------------------------------------------ ----------------------------+------------------------------------------------------
- `component` specifies the component (`fg` / `bg`) from which to extract the - `component` specifies the component (`fg` / `bg`) from which to extract the
color when considering each of the following highlight groups color when considering each of the following highlight groups
@@ -245,7 +243,7 @@ if it exists, - otherwise use the `fg` attribute of the `Comment` highlight
group if it exists, - otherwise fall back to the default color settings for group if it exists, - otherwise fall back to the default color settings for
the prompt. the prompt.
You can examine the color option generated according the setting by printing You can examine the color option generated according to the setting by printing
the result of `fzf#wrap()` function like so: the result of `fzf#wrap()` function like so:
> >
:echo fzf#wrap() :echo fzf#wrap()
+9 -4
View File
@@ -2,7 +2,7 @@
set -u set -u
version=0.70.0 version=0.71.0
auto_completion= auto_completion=
key_bindings= key_bindings=
update_config=2 update_config=2
@@ -112,10 +112,15 @@ link_fzf_in_path() {
return 1 return 1
} }
tar_opts="-xzf -"
if tar --no-same-owner -tf /dev/null 2> /dev/null; then
tar_opts="--no-same-owner $tar_opts"
fi
try_curl() { try_curl() {
command -v curl > /dev/null && command -v curl > /dev/null &&
if [[ $1 =~ tar.gz$ ]]; then if [[ $1 =~ tar.gz$ ]]; then
curl -fL $1 | tar --no-same-owner -xzf - curl -fL $1 | tar $tar_opts
else else
local temp=${TMPDIR:-/tmp}/fzf.zip local temp=${TMPDIR:-/tmp}/fzf.zip
curl -fLo "$temp" $1 && unzip -o "$temp" && rm -f "$temp" curl -fLo "$temp" $1 && unzip -o "$temp" && rm -f "$temp"
@@ -125,7 +130,7 @@ try_curl() {
try_wget() { try_wget() {
command -v wget > /dev/null && command -v wget > /dev/null &&
if [[ $1 =~ tar.gz$ ]]; then if [[ $1 =~ tar.gz$ ]]; then
wget -O - $1 | tar --no-same-owner -xzf - wget -O - $1 | tar $tar_opts
else else
local temp=${TMPDIR:-/tmp}/fzf.zip local temp=${TMPDIR:-/tmp}/fzf.zip
wget -O "$temp" $1 && unzip -o "$temp" && rm -f "$temp" wget -O "$temp" $1 && unzip -o "$temp" && rm -f "$temp"
@@ -439,7 +444,7 @@ if [ $update_config -eq 1 ]; then
echo echo
fi fi
[[ $shells =~ zsh ]] && echo " source ${ZDOTDIR:-~}/.zshrc # zsh" [[ $shells =~ zsh ]] && echo " source ${ZDOTDIR:-~}/.zshrc # zsh"
[[ $shells =~ fish && $lno_func -ne 0 ]] && echo ' fzf_user_key_bindings # fish' [[ $shells =~ fish && $lno_func -ne 0 ]] && echo ' fish_user_key_bindings # fish'
echo echo
echo 'Use uninstall script to remove fzf.' echo 'Use uninstall script to remove fzf.'
echo echo
+1 -1
View File
@@ -1,4 +1,4 @@
$version="0.70.0" $version="0.71.0"
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition $fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
+1 -1
View File
@@ -11,7 +11,7 @@ import (
"github.com/junegunn/fzf/src/protector" "github.com/junegunn/fzf/src/protector"
) )
var version = "0.70" var version = "0.71"
var revision = "devel" var revision = "devel"
//go:embed shell/key-bindings.bash //go:embed shell/key-bindings.bash
+1 -1
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 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
.. ..
.TH fzf\-tmux 1 "Mar 2026" "fzf 0.70.0" "fzf\-tmux - open fzf in tmux split pane" .TH fzf\-tmux 1 "Apr 2026" "fzf 0.71.0" "fzf\-tmux - open fzf in tmux split pane"
.SH NAME .SH NAME
fzf\-tmux - open fzf in tmux split pane fzf\-tmux - open fzf in tmux split pane
+17 -14
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 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
.. ..
.TH fzf 1 "Mar 2026" "fzf 0.70.0" "fzf - a command-line fuzzy finder" .TH fzf 1 "Apr 2026" "fzf 0.71.0" "fzf - a command-line fuzzy finder"
.SH NAME .SH NAME
fzf - a command-line fuzzy finder fzf - a command-line fuzzy finder
@@ -384,7 +384,7 @@ Use black background
.SS DISPLAY MODE .SS DISPLAY MODE
.TP .TP
.BI "\-\-height=" "[~]HEIGHT[%]" .BI "\-\-height=" "[~][\-]HEIGHT[%]"
Display fzf window below the cursor with the given height instead of using Display fzf window below the cursor with the given height instead of using
the full screen. the full screen.
@@ -394,17 +394,19 @@ height minus the given value.
fzf \-\-height=\-1 fzf \-\-height=\-1
When prefixed with \fB~\fR, fzf will automatically determine the height in the When prefixed with \fB~\fR, fzf will automatically determine the height in the
range according to the input size. range according to the input size. You can combine \fB~\fR with a negative
value.
# Will not take up 100% of the screen # Will not take up 100% of the screen
seq 5 | fzf \-\-height=~100% seq 5 | fzf \-\-height=~100%
# Adapt to input size, up to terminal height minus 1
seq 5 | fzf \-\-height=~\-1
Adaptive height has the following limitations: Adaptive height has the following limitations:
.br .br
* Cannot be used with top/bottom margin and padding given in percent size * Cannot be used with top/bottom margin and padding given in percent size
.br .br
* Negative value is not allowed
.br
* It will not find the right size when there are multi-line items * It will not find the right size when there are multi-line items
.TP .TP
@@ -415,25 +417,26 @@ layout options so that the specified number of items are visible in the list
section (default: \fB10+\fR). section (default: \fB10+\fR).
Ignored when \fB\-\-height\fR is not specified or set as an absolute value. Ignored when \fB\-\-height\fR is not specified or set as an absolute value.
.TP .TP
.BI "\-\-tmux" "[=[center|top|bottom|left|right][,SIZE[%]][,SIZE[%]][,border-native]]" .BI "\-\-popup" "[=[center|top|bottom|left|right][,SIZE[%]][,SIZE[%]][,border-native]]"
Start fzf in a tmux popup (default \fBcenter,50%\fR). Requires tmux 3.3 or Start fzf in a tmux popup or in a Zellij floating pane (default
later. This option is ignored if you are not running fzf inside tmux. \fBcenter,50%\fR). Requires tmux 3.3+ or Zellij 0.44+. This option is ignored if you
are not running fzf inside tmux or Zellij. \fB\-\-tmux\fR is an alias for this option.
e.g. e.g.
\fB# Popup in the center with 70% width and height \fB# Popup in the center with 70% width and height
fzf \-\-tmux 70% fzf \-\-popup 70%
# Popup on the left with 40% width and 100% height # Popup on the left with 40% width and 100% height
fzf \-\-tmux right,40% fzf \-\-popup right,40%
# Popup on the bottom with 100% width and 30% height # Popup on the bottom with 100% width and 30% height
fzf \-\-tmux bottom,30% fzf \-\-popup bottom,30%
# Popup on the top with 80% width and 40% height # Popup on the top with 80% width and 40% height
fzf \-\-tmux top,80%,40% fzf \-\-popup top,80%,40%
# Popup with a native tmux border in the center with 80% width and height # Popup with a native tmux or Zellij border in the center with 80% width and height
fzf \-\-tmux center,80%,border\-native\fR fzf \-\-popup center,80%,border\-native\fR
.SS LAYOUT .SS LAYOUT
.TP .TP
-141
View File
@@ -1,141 +0,0 @@
function __fzf_defaults
# $argv[1]: Prepend to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
# $argv[2..]: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
test -n "$FZF_TMUX_HEIGHT"; or set -l FZF_TMUX_HEIGHT 40%
string join ' ' -- \
"--height $FZF_TMUX_HEIGHT --min-height=20+ --bind=ctrl-z:ignore" $argv[1] \
(test -r "$FZF_DEFAULT_OPTS_FILE"; and string join -- ' ' <$FZF_DEFAULT_OPTS_FILE) \
$FZF_DEFAULT_OPTS $argv[2..-1]
end
function __fzfcmd
test -n "$FZF_TMUX_HEIGHT"; or set -l FZF_TMUX_HEIGHT 40%
if test -n "$FZF_TMUX_OPTS"
echo "fzf-tmux $FZF_TMUX_OPTS -- "
else if test "$FZF_TMUX" = "1"
echo "fzf-tmux -d$FZF_TMUX_HEIGHT -- "
else
echo "fzf"
end
end
function __fzf_cmd_tokens -d 'Return command line tokens, skipping leading env assignments and command prefixes'
# Get tokens - use version-appropriate flags
set -l tokens
if test (string match -r -- '^\d+' $version) -ge 4
set -- tokens (commandline -xpc)
else
set -- tokens (commandline -opc)
end
# Filter out leading environment variable assignments
set -l -- var_count 0
for i in $tokens
if string match -qr -- '^[\w]+=' $i
set var_count (math $var_count + 1)
else
break
end
end
set -e -- tokens[0..$var_count]
# Skip command prefixes so callers see the actual command name,
# e.g. "builtin cd""cd", "env VAR=1 command cd""cd"
while true
switch "$tokens[1]"
case builtin command
set -e -- tokens[1]
test "$tokens[1]" = "--"; and set -e -- tokens[1]
case env
set -e -- tokens[1]
test "$tokens[1]" = "--"; and set -e -- tokens[1]
while string match -qr -- '^[\w]+=' "$tokens[1]"
set -e -- tokens[1]
end
case '*'
break
end
end
string escape -n -- $tokens
end
function __fzf_parse_commandline -d 'Parse the current command line token and return split of existing filepath, fzf query, and optional -option= prefix'
set -l fzf_query ''
set -l prefix ''
set -l dir '.'
# Set variables containing the major and minor fish version numbers, using
# a method compatible with all supported fish versions.
set -l -- fish_major (string match -r -- '^\d+' $version)
set -l -- fish_minor (string match -r -- '^\d+\.(\d+)' $version)[2]
# fish v3.3.0 and newer: Don't use option prefix if " -- " is preceded.
set -l -- match_regex '(?<fzf_query>[\s\S]*?(?=\n?$)$)'
set -l -- prefix_regex '^-[^\s=]+=|^-(?!-)\S'
if test "$fish_major" -eq 3 -a "$fish_minor" -lt 3
or string match -q -v -- '* -- *' (string sub -l (commandline -Cp) -- (commandline -p))
set -- match_regex "(?<prefix>$prefix_regex)?$match_regex"
end
# Set $prefix and expanded $fzf_query with preserved trailing newlines.
if test "$fish_major" -ge 4
# fish v4.0.0 and newer
string match -q -r -- $match_regex (commandline --current-token --tokens-expanded | string collect -N)
else if test "$fish_major" -eq 3 -a "$fish_minor" -ge 2
# fish v3.2.0 - v3.7.1 (last v3)
string match -q -r -- $match_regex (commandline --current-token --tokenize | string collect -N)
eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^\\\(?=~)|\\\(?=\$\w)' '')
else
# fish older than v3.2.0 (v3.1b1 - v3.1.2)
set -l -- cl_token (commandline --current-token --tokenize | string collect -N)
set -- prefix (string match -r -- $prefix_regex $cl_token)
set -- fzf_query (string replace -- "$prefix" '' $cl_token | string collect -N)
eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^\\\(?=~)|\\\(?=\$\w)|\\\n\\\n$' '')
end
if test -n "$fzf_query"
# Normalize path in $fzf_query, set $dir to the longest existing directory.
if test \( "$fish_major" -ge 4 \) -o \( "$fish_major" -eq 3 -a "$fish_minor" -ge 5 \)
# fish v3.5.0 and newer
set -- fzf_query (path normalize -- $fzf_query)
set -- dir $fzf_query
while not path is -d $dir
set -- dir (path dirname $dir)
end
else
# fish older than v3.5.0 (v3.1b1 - v3.4.1)
if test "$fish_major" -eq 3 -a "$fish_minor" -ge 2
# fish v3.2.0 - v3.4.1
string match -q -r -- '(?<fzf_query>^[\s\S]*?(?=\n?$)$)' \
(string replace -r -a -- '(?<=/)/|(?<!^)/+(?!\n)$' '' $fzf_query | string collect -N)
else
# fish v3.1b1 - v3.1.2
set -- fzf_query (string replace -r -a -- '(?<=/)/|(?<!^)/+(?!\n)$' '' $fzf_query | string collect -N)
eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r '\\\n$' '')
end
set -- dir $fzf_query
while not test -d "$dir"
set -- dir (dirname -z -- "$dir" | string split0)
end
end
if not string match -q -- '.' $dir; or string match -q -r -- '^\./|^\.$' $fzf_query
# Strip $dir from $fzf_query - preserve trailing newlines.
if test "$fish_major" -ge 4
# fish v4.0.0 and newer
string match -q -r -- '^'(string escape --style=regex -- $dir)'/?(?<fzf_query>[\s\S]*)' $fzf_query
else if test "$fish_major" -eq 3 -a "$fish_minor" -ge 2
# fish v3.2.0 - v3.7.1 (last v3)
string match -q -r -- '^/?(?<fzf_query>[\s\S]*?(?=\n?$)$)' \
(string replace -- "$dir" '' $fzf_query | string collect -N)
else
# fish older than v3.2.0 (v3.1b1 - v3.1.2)
set -- fzf_query (string replace -- "$dir" '' $fzf_query | string collect -N)
eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^/?|\\\n$' '')
end
end
end
string escape -n -- "$dir" "$fzf_query" "$prefix"
end
-2
View File
@@ -4,8 +4,6 @@
# / __/ / /_/ __/ # / __/ / /_/ __/
# /_/ /___/_/ completion.bash # /_/ /___/_/ completion.bash
# #
# - $FZF_TMUX (default: 0)
# - $FZF_TMUX_OPTS (default: empty)
# - $FZF_COMPLETION_TRIGGER (default: '**') # - $FZF_COMPLETION_TRIGGER (default: '**')
# - $FZF_COMPLETION_OPTS (default: empty) # - $FZF_COMPLETION_OPTS (default: empty)
# - $FZF_COMPLETION_PATH_OPTS (default: empty) # - $FZF_COMPLETION_PATH_OPTS (default: empty)
+160 -232
View File
@@ -4,238 +4,166 @@
# / __/ / /_/ __/ # / __/ / /_/ __/
# /_/ /___/_/ completion.fish # /_/ /___/_/ completion.fish
# #
# - $FZF_COMPLETION_OPTS (default: empty) # - $FZF_COMPLETION_OPTS
# - $FZF_EXPANSION_OPTS
function fzf_completion_setup # The oldest supported fish version is 3.4.0. For this message being able to be
# displayed on older versions, the command substitution syntax $() should not
#----BEGIN INCLUDE common.fish # be used anywhere in the script, otherwise the source command will fail.
# NOTE: Do not directly edit this section, which is copied from "common.fish". if string match -qr -- '^[12]\\.|^3\\.[0-3]' $version
# To modify it, one can edit "common.fish" and run "./update.sh" to apply echo "fzf completion script requires fish version 3.4.0 or newer." >&2
# the changes. See code comments in "common.fish" for the implementation details. return 1
else if not command -q fzf
function __fzf_defaults echo "fzf was not found in path." >&2
test -n "$FZF_TMUX_HEIGHT"; or set -l FZF_TMUX_HEIGHT 40% return 1
string join ' ' -- \
"--height $FZF_TMUX_HEIGHT --min-height=20+ --bind=ctrl-z:ignore" $argv[1] \
(test -r "$FZF_DEFAULT_OPTS_FILE"; and string join -- ' ' <$FZF_DEFAULT_OPTS_FILE) \
$FZF_DEFAULT_OPTS $argv[2..-1]
end
function __fzfcmd
test -n "$FZF_TMUX_HEIGHT"; or set -l FZF_TMUX_HEIGHT 40%
if test -n "$FZF_TMUX_OPTS"
echo "fzf-tmux $FZF_TMUX_OPTS -- "
else if test "$FZF_TMUX" = "1"
echo "fzf-tmux -d$FZF_TMUX_HEIGHT -- "
else
echo "fzf"
end
end
function __fzf_cmd_tokens -d 'Return command line tokens, skipping leading env assignments and command prefixes'
set -l tokens
if test (string match -r -- '^\d+' $version) -ge 4
set -- tokens (commandline -xpc)
else
set -- tokens (commandline -opc)
end
set -l -- var_count 0
for i in $tokens
if string match -qr -- '^[\w]+=' $i
set var_count (math $var_count + 1)
else
break
end
end
set -e -- tokens[0..$var_count]
while true
switch "$tokens[1]"
case builtin command
set -e -- tokens[1]
test "$tokens[1]" = "--"; and set -e -- tokens[1]
case env
set -e -- tokens[1]
test "$tokens[1]" = "--"; and set -e -- tokens[1]
while string match -qr -- '^[\w]+=' "$tokens[1]"
set -e -- tokens[1]
end
case '*'
break
end
end
string escape -n -- $tokens
end
function __fzf_parse_commandline -d 'Parse the current command line token and return split of existing filepath, fzf query, and optional -option= prefix'
set -l fzf_query ''
set -l prefix ''
set -l dir '.'
set -l -- fish_major (string match -r -- '^\d+' $version)
set -l -- fish_minor (string match -r -- '^\d+\.(\d+)' $version)[2]
set -l -- match_regex '(?<fzf_query>[\s\S]*?(?=\n?$)$)'
set -l -- prefix_regex '^-[^\s=]+=|^-(?!-)\S'
if test "$fish_major" -eq 3 -a "$fish_minor" -lt 3
or string match -q -v -- '* -- *' (string sub -l (commandline -Cp) -- (commandline -p))
set -- match_regex "(?<prefix>$prefix_regex)?$match_regex"
end
if test "$fish_major" -ge 4
string match -q -r -- $match_regex (commandline --current-token --tokens-expanded | string collect -N)
else if test "$fish_major" -eq 3 -a "$fish_minor" -ge 2
string match -q -r -- $match_regex (commandline --current-token --tokenize | string collect -N)
eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^\\\(?=~)|\\\(?=\$\w)' '')
else
set -l -- cl_token (commandline --current-token --tokenize | string collect -N)
set -- prefix (string match -r -- $prefix_regex $cl_token)
set -- fzf_query (string replace -- "$prefix" '' $cl_token | string collect -N)
eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^\\\(?=~)|\\\(?=\$\w)|\\\n\\\n$' '')
end
if test -n "$fzf_query"
if test \( "$fish_major" -ge 4 \) -o \( "$fish_major" -eq 3 -a "$fish_minor" -ge 5 \)
set -- fzf_query (path normalize -- $fzf_query)
set -- dir $fzf_query
while not path is -d $dir
set -- dir (path dirname $dir)
end
else
if test "$fish_major" -eq 3 -a "$fish_minor" -ge 2
string match -q -r -- '(?<fzf_query>^[\s\S]*?(?=\n?$)$)' \
(string replace -r -a -- '(?<=/)/|(?<!^)/+(?!\n)$' '' $fzf_query | string collect -N)
else
set -- fzf_query (string replace -r -a -- '(?<=/)/|(?<!^)/+(?!\n)$' '' $fzf_query | string collect -N)
eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r '\\\n$' '')
end
set -- dir $fzf_query
while not test -d "$dir"
set -- dir (dirname -z -- "$dir" | string split0)
end
end
if not string match -q -- '.' $dir; or string match -q -r -- '^\./|^\.$' $fzf_query
if test "$fish_major" -ge 4
string match -q -r -- '^'(string escape --style=regex -- $dir)'/?(?<fzf_query>[\s\S]*)' $fzf_query
else if test "$fish_major" -eq 3 -a "$fish_minor" -ge 2
string match -q -r -- '^/?(?<fzf_query>[\s\S]*?(?=\n?$)$)' \
(string replace -- "$dir" '' $fzf_query | string collect -N)
else
set -- fzf_query (string replace -- "$dir" '' $fzf_query | string collect -N)
eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^/?|\\\n$' '')
end
end
end
string escape -n -- "$dir" "$fzf_query" "$prefix"
end
#----END INCLUDE
# Use complete builtin for specific commands
function __fzf_complete_native
set -l -- token (commandline -t)
set -l -- completions (eval complete -C \"$argv[1]\")
test -n "$completions"; or begin commandline -f repaint; return; end
# Calculate tabstop based on longest completion item (sample first 500 for performance)
set -l -- tabstop 20
set -l -- sample_size (math "min(500, "(count $completions)")")
for c in $completions[1..$sample_size]
set -l -- len (string length -V -- (string split -- \t $c))
test -n "$len[2]" -a "$len[1]" -gt "$tabstop"
and set -- tabstop $len[1]
end
# limit to 120 to prevent long lines
set -- tabstop (math "min($tabstop + 4, 120)")
set -l result
set -lx -- FZF_DEFAULT_OPTS (__fzf_defaults \
"--reverse --delimiter=\\t --nth=1 --tabstop=$tabstop --color=fg:dim,nth:regular" \
$FZF_COMPLETION_OPTS $argv[2..-1] --accept-nth=1 --read0 --print0)
set -- result (string join0 -- $completions | eval (__fzfcmd) | string split0)
and begin
set -l -- tail ' '
# Append / to bare ~username results (fish omits it unlike other shells)
set -- result (string replace -r -- '^(~\w+)\s?$' '$1/' $result)
# Don't add trailing space if single result is a directory
test (count $result) -eq 1
and string match -q -- '*/' "$result"; and set -- tail ''
set -l -- result (string escape -n -- $result)
string match -q -- '~*' "$token"
and set result (string replace -r -- '^\\\\~' '~' $result)
string match -q -- '$*' "$token"
and set result (string replace -r -- '^\\\\\$' '\$' $result)
commandline -rt -- (string join ' ' -- $result)$tail
end
commandline -f repaint
end
function _fzf_complete
set -l -- args (string escape -- $argv | string join ' ' | string split -- ' -- ')
set -l -- post_func (status function)_(string split -- ' ' $args[2])[1]_post
set -lx -- FZF_DEFAULT_OPTS (__fzf_defaults --reverse $FZF_COMPLETION_OPTS $args[1])
set -lx FZF_DEFAULT_OPTS_FILE
set -lx FZF_DEFAULT_COMMAND
set -l -- fzf_query (commandline -t | string escape)
set -l result
eval (__fzfcmd) --query=$fzf_query | while read -l r; set -a -- result $r; end
and if functions -q $post_func
commandline -rt -- (string collect -- $result | eval $post_func $args[2] | string join ' ')' '
else
commandline -rt -- (string join -- ' ' (string escape -- $result))' '
end
commandline -f repaint
end
# Kill completion (process selection)
function _fzf_complete_kill
set -l -- fzf_query (commandline -t | string escape)
set -lx -- FZF_DEFAULT_OPTS (__fzf_defaults --reverse $FZF_COMPLETION_OPTS \
--accept-nth=2 -m --header-lines=1 --no-preview --wrap)
set -lx FZF_DEFAULT_OPTS_FILE
if type -q ps
set -l -- ps_cmd 'begin command ps -eo user,pid,ppid,start,time,command 2>/dev/null;' \
'or command ps -eo user,pid,ppid,time,args 2>/dev/null;' \
'or command ps --everyone --full --windows 2>/dev/null; end'
set -l -- result (eval $ps_cmd \| (__fzfcmd) --query=$fzf_query)
and commandline -rt -- (string join ' ' -- $result)" "
else
__fzf_complete_native "kill " --multi --query=$fzf_query
end
commandline -f repaint
end
# Main completion function
function fzf-completion
set -l -- tokens (__fzf_cmd_tokens)
set -l -- current_token (commandline -t)
set -l -- cmd_name $tokens[1]
# Route to appropriate completion function
if test -n "$tokens"; and functions -q _fzf_complete_$cmd_name
_fzf_complete_$cmd_name $tokens
else
set -l -- fzf_opt --query=$current_token --multi
__fzf_complete_native "$tokens $current_token" $fzf_opt
end
end
# Bind Shift-Tab to fzf-completion (Tab retains native Fish behavior)
if test (string match -r -- '^\d+' $version) -ge 4
bind shift-tab fzf-completion
bind -M insert shift-tab fzf-completion
else
bind -k btab fzf-completion
bind -M insert -k btab fzf-completion
end
end end
# Run setup function fzf_complete -w fzf -d 'fzf command completion and wildcard expansion search'
fzf_completion_setup # Restore the default shift-tab behavior on tab completions
if commandline --paging-mode
commandline -f complete-and-search
return
end
# Remove any trailing unescaped backslash from token and update command line
set -l -- token (string replace -r -- '(?<!\\\\)(?:\\\\\\\\)*\\K\\\\$' '' (commandline -t | string collect) | string collect)
commandline -rt -- $token
# Remove any line breaks from token
set -- token (string replace -ra -- '\\\\\\n' '' $token | string collect)
# regex: Match token with unescaped/unquoted glob character
set -l -- r_glob '^(?:[^\'"\\\\*]|\\\\[\\S\\s]|\'(?:\\\\[\\S\\s]|[^\'\\\\])*\'|"(?:\\\\[\\S\\s]|[^"\\\\])*")*\\*[\\S\\s]*$'
# regex: Match any unbalanced quote character
set -l -- r_quote '^(?>(?:\\\\[\\s\\S]|"(?:[^"\\\\]|\\\\[\\s\\S])*"|\'(?:[^\'\\\\]|\\\\[\\s\\S])*\'|[^\'"\\\\]+)*)\\K[\'"]'
# The expansion pattern is the token with any open quote closed, or is empty.
set -l -- glob_pattern (string match -r -- $r_glob $token | string collect)(string match -r -- $r_quote $token | string collect -a)
set -l -- cl_tokenize_opt '--tokens-expanded'
string match -q -- '3.*' $version
and set -- cl_tokenize_opt '--tokenize'
# Set command line tokens without any leading variable definitions or launcher
# commands (including their options, but not any option arguments).
set -l -- r_cmd '^(?:(?:builtin|command|doas|env|sudo|\\w+=\\S*|-\\S+)\\s+)*\\K[\\s\\S]+'
set -l -- cmd (commandline $cl_tokenize_opt --input=(commandline -pc | string match -r $r_cmd))
test -z "$token"
and set -a -- cmd ''
# Set fzf options
test -z "$FZF_TMUX_HEIGHT"
and set -l -- FZF_TMUX_HEIGHT 40%
set -lax -- FZF_DEFAULT_OPTS \
"--height=$FZF_TMUX_HEIGHT --min-height=20+ --bind=ctrl-z:ignore" \
(test -r "$FZF_DEFAULT_OPTS_FILE"; and string join -- ' ' <$FZF_DEFAULT_OPTS_FILE) \
$FZF_DEFAULT_OPTS '--bind=alt-r:toggle-raw --multi --wrap=word --reverse' \
(if test -n "$glob_pattern"; string collect -- $FZF_EXPANSION_OPTS; else;
string collect -- $FZF_COMPLETION_OPTS; end; string escape -n -- $argv) \
--with-shell=(status fish-path)\\ -c
set -lx FZF_DEFAULT_OPTS_FILE
set -l -- fzf_cmd fzf
test "$FZF_TMUX" = 1
and set -- fzf_cmd fzf-tmux $FZF_TMUX_OPTS -d$FZF_TMUX_HEIGHT --
set -l result
# Get the completion list from stdin when it's not a tty
if not isatty stdin
set -l -- custom_post_func _fzf_post_complete_$cmd[1]
functions -q $custom_post_func
or set -- custom_post_func _fzf_complete_$cmd[1]_post
if functions -q $custom_post_func
$fzf_cmd | $custom_post_func $cmd | while read -l r; set -a -- result $r; end
else if string match -q -- '*--print0*' "$FZF_DEFAULT_OPTS"
$fzf_cmd | while read -lz r; set -a -- result $r; end
else
$fzf_cmd | while read -l r; set -a -- result $r; end
end
# Wildcard expansion
else if test -n "$glob_pattern"
# Set the command to be run by fzf, so there is a visual indicator and an
# easy way to abort on long recursive searches.
set -lx -- FZF_DEFAULT_COMMAND "for i in $glob_pattern;" \
'test -d "$i"; and string match -qv -- "*/" $i; and set -- i $i/;' \
'string join0 -- $i; end'
set -- result (string escape -n -- ($fzf_cmd --read0 --print0 --scheme=path --no-multi-line | string split0))
# Command completion
else
# Call custom function if defined
set -l -- custom_func _fzf_complete_$cmd[1]
if functions -q $custom_func; and not set -q __fzf_no_custom_complete
set -lx __fzf_no_custom_complete
$custom_func $cmd
return
end
# Workaround for complete not having newlines in results
if string match -qr -- '\\n' $token
set -- token (string replace -ra -- '(?<!\\\\)(?:\\\\\\\\)*\\K\\\\\$' '\\\\\\\\\$' $token | string collect)
set -- token (string unescape -- $token | string collect)
set -- token (string replace -ra -- '\\n' '\\\\n' $token | string collect)
end
set -- list (complete -C --escape -- (string join -- ' ' (commandline -pc $cl_tokenize_opt) $token | string collect))
if test -n "$list"
# Get the initial tabstop value
if set -l -- tabstop (string match -rga -- '--tabstop[= ](?:0*)([1-9]\\d+|[4-9])' "$FZF_DEFAULT_OPTS")[-1]
set -- tabstop (math $tabstop - 4)
else
set -- tabstop 4
end
# Determine the tabstop length for description alignment
set -l -- max_columns (math $COLUMNS - 40)
for i in $list[1..500]
set -l -- item (string split -f 1 -- \t $i)
and set -l -- len (string length -V -- $item)
and test "$len" -gt "$tabstop" -a "$len" -lt "$max_columns"
and set -- tabstop $len
end
set -- tabstop (math $tabstop + 4)
set -- result (string collect -- $list | $fzf_cmd --delimiter="\t" --tabstop=$tabstop --wrap-sign=\t"↳ " --accept-nth=1)
end
end
# Update command line
if test -n "$result"
# No extra space after single selection that ends with path separator
set -l -- tail ' '
test (count $result) -eq 1
and string match -q -- '*/' "$result"
and set -- tail ''
commandline -rt -- (string join -- ' ' $result)$tail
end
commandline -f repaint
end
function _fzf_complete
set -l fzf_args
for i in $argv
string match -q -- '--' $i; and break
set -a -- fzf_args $i
end
fzf_complete $fzf_args
end
# Bind to shift-tab
if string match -qr -- '^\\d\\d+|^[4-9]' $version
bind shift-tab fzf_complete
bind -M insert shift-tab fzf_complete
else
bind -k btab fzf_complete
bind -M insert -k btab fzf_complete
end
-2
View File
@@ -4,8 +4,6 @@
# / __/ / /_/ __/ # / __/ / /_/ __/
# /_/ /___/_/ completion.zsh # /_/ /___/_/ completion.zsh
# #
# - $FZF_TMUX (default: 0)
# - $FZF_TMUX_OPTS (default: empty)
# - $FZF_COMPLETION_TRIGGER (default: '**') # - $FZF_COMPLETION_TRIGGER (default: '**')
# - $FZF_COMPLETION_OPTS (default: empty) # - $FZF_COMPLETION_OPTS (default: empty)
# - $FZF_COMPLETION_PATH_OPTS (default: empty) # - $FZF_COMPLETION_PATH_OPTS (default: empty)
-1
View File
@@ -4,7 +4,6 @@
# / __/ / /_/ __/ # / __/ / /_/ __/
# /_/ /___/_/ key-bindings.bash # /_/ /___/_/ key-bindings.bash
# #
# - $FZF_TMUX_OPTS
# - $FZF_CTRL_T_COMMAND # - $FZF_CTRL_T_COMMAND
# - $FZF_CTRL_T_OPTS # - $FZF_CTRL_T_OPTS
# - $FZF_CTRL_R_COMMAND # - $FZF_CTRL_R_COMMAND
+46 -113
View File
@@ -4,7 +4,6 @@
# / __/ / /_/ __/ # / __/ / /_/ __/
# /_/ /___/_/ key-bindings.fish # /_/ /___/_/ key-bindings.fish
# #
# - $FZF_TMUX_OPTS
# - $FZF_CTRL_T_COMMAND # - $FZF_CTRL_T_COMMAND
# - $FZF_CTRL_T_OPTS # - $FZF_CTRL_T_OPTS
# - $FZF_CTRL_R_COMMAND # - $FZF_CTRL_R_COMMAND
@@ -12,35 +11,29 @@
# - $FZF_ALT_C_COMMAND # - $FZF_ALT_C_COMMAND
# - $FZF_ALT_C_OPTS # - $FZF_ALT_C_OPTS
# Key bindings # Key bindings
# ------------ # ------------
# The oldest supported fish version is 3.1b1. To maintain compatibility, the
# command substitution syntax $(cmd) should never be used, even behind a version
# check, otherwise the source command will fail on fish versions older than 3.4.0.
function fzf_key_bindings function fzf_key_bindings
# Check fish version # The oldest supported fish version is 3.4.0. For this message being able to be
if set -l -- fish_ver (string match -r '^(\d+)\.(\d+)' $version 2>/dev/null) # displayed on older versions, the command substitution syntax $() should not
and test "$fish_ver[2]" -lt 3 -o "$fish_ver[2]" -eq 3 -a "$fish_ver[3]" -lt 1 # be used anywhere in the script, otherwise the source command will fail.
echo "This script requires fish version 3.1b1 or newer." >&2 if string match -qr -- '^[12]\\.|^3\\.[0-3]' $version
echo "fzf key bindings script requires fish version 3.4.0 or newer." >&2
return 1 return 1
else if not type -q fzf else if not command -q fzf
echo "fzf was not found in path." >&2 echo "fzf was not found in path." >&2
return 1 return 1
end end
#----BEGIN INCLUDE common.fish
# NOTE: Do not directly edit this section, which is copied from "common.fish".
# To modify it, one can edit "common.fish" and run "./update.sh" to apply
# the changes. See code comments in "common.fish" for the implementation details.
function __fzf_defaults function __fzf_defaults
# $argv[1]: Prepend to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
# $argv[2..]: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
test -n "$FZF_TMUX_HEIGHT"; or set -l FZF_TMUX_HEIGHT 40% test -n "$FZF_TMUX_HEIGHT"; or set -l FZF_TMUX_HEIGHT 40%
string join ' ' -- \ string join ' ' -- \
"--height $FZF_TMUX_HEIGHT --min-height=20+ --bind=ctrl-z:ignore" $argv[1] \ "--height $FZF_TMUX_HEIGHT --min-height=20+ --bind=ctrl-z:ignore" $argv[1] \
(test -r "$FZF_DEFAULT_OPTS_FILE"; and string join -- ' ' <$FZF_DEFAULT_OPTS_FILE) \ (test -r "$FZF_DEFAULT_OPTS_FILE"; and string join -- ' ' <$FZF_DEFAULT_OPTS_FILE) \
$FZF_DEFAULT_OPTS $argv[2..-1] $FZF_DEFAULT_OPTS $argv[2..]
end end
function __fzfcmd function __fzfcmd
@@ -54,107 +47,59 @@ function fzf_key_bindings
end end
end end
function __fzf_cmd_tokens -d 'Return command line tokens, skipping leading env assignments and command prefixes'
set -l tokens
if test (string match -r -- '^\d+' $version) -ge 4
set -- tokens (commandline -xpc)
else
set -- tokens (commandline -opc)
end
set -l -- var_count 0
for i in $tokens
if string match -qr -- '^[\w]+=' $i
set var_count (math $var_count + 1)
else
break
end
end
set -e -- tokens[0..$var_count]
while true
switch "$tokens[1]"
case builtin command
set -e -- tokens[1]
test "$tokens[1]" = "--"; and set -e -- tokens[1]
case env
set -e -- tokens[1]
test "$tokens[1]" = "--"; and set -e -- tokens[1]
while string match -qr -- '^[\w]+=' "$tokens[1]"
set -e -- tokens[1]
end
case '*'
break
end
end
string escape -n -- $tokens
end
function __fzf_parse_commandline -d 'Parse the current command line token and return split of existing filepath, fzf query, and optional -option= prefix' function __fzf_parse_commandline -d 'Parse the current command line token and return split of existing filepath, fzf query, and optional -option= prefix'
set -l fzf_query '' set -l fzf_query ''
set -l prefix '' set -l prefix ''
set -l dir '.' set -l dir '.'
set -l -- fish_major (string match -r -- '^\d+' $version) set -l -- match_regex '(?<fzf_query>[\\s\\S]*?(?=\\n?$)$)'
set -l -- fish_minor (string match -r -- '^\d+\.(\d+)' $version)[2] set -l -- prefix_regex '^-[^\\s=]+=|^-(?!-)\\S'
set -l -- match_regex '(?<fzf_query>[\s\S]*?(?=\n?$)$)' # Don't use option prefix if " -- " is preceded.
set -l -- prefix_regex '^-[^\s=]+=|^-(?!-)\S' string match -qv -- '* -- *' (string sub -l (commandline -Cp) -- (commandline -p))
if test "$fish_major" -eq 3 -a "$fish_minor" -lt 3 and set -- match_regex "(?<prefix>$prefix_regex)?$match_regex"
or string match -q -v -- '* -- *' (string sub -l (commandline -Cp) -- (commandline -p))
set -- match_regex "(?<prefix>$prefix_regex)?$match_regex"
end
if test "$fish_major" -ge 4 # Set $prefix and expanded $fzf_query with preserved trailing newlines.
if string match -qr -- '^\\d\\d+|^[4-9]' $version
# fish v4.0.0 and newer
string match -q -r -- $match_regex (commandline --current-token --tokens-expanded | string collect -N) string match -q -r -- $match_regex (commandline --current-token --tokens-expanded | string collect -N)
else if test "$fish_major" -eq 3 -a "$fish_minor" -ge 2
string match -q -r -- $match_regex (commandline --current-token --tokenize | string collect -N)
eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^\\\(?=~)|\\\(?=\$\w)' '')
else else
set -l -- cl_token (commandline --current-token --tokenize | string collect -N) string match -q -r -- $match_regex (commandline --current-token --tokenize | string collect -N)
set -- prefix (string match -r -- $prefix_regex $cl_token) eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^\\\\(?=~)|\\\\(?=\\$\\w)' '')
set -- fzf_query (string replace -- "$prefix" '' $cl_token | string collect -N)
eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^\\\(?=~)|\\\(?=\$\w)|\\\n\\\n$' '')
end end
if test -n "$fzf_query" if test -n "$fzf_query"
if test \( "$fish_major" -ge 4 \) -o \( "$fish_major" -eq 3 -a "$fish_minor" -ge 5 \) # Normalize path in $fzf_query, set $dir to the longest existing directory.
if string match -qr -- '^\\d\\d+|^4|^3\\.[5-9]' $version
# fish v3.5.0 and newer
set -- fzf_query (path normalize -- $fzf_query) set -- fzf_query (path normalize -- $fzf_query)
set -- dir $fzf_query set -- dir $fzf_query
while not path is -d $dir while not path is -d $dir
set -- dir (path dirname $dir) set -- dir (path dirname $dir)
end end
else else
if test "$fish_major" -eq 3 -a "$fish_minor" -ge 2 string match -q -r -- '(?<fzf_query>^[\\s\\S]*?(?=\\n?$)$)' \
string match -q -r -- '(?<fzf_query>^[\s\S]*?(?=\n?$)$)' \ (string replace -r -a -- '(?<=/)/|(?<!^)/+(?!\\n)$' '' $fzf_query | string collect -N)
(string replace -r -a -- '(?<=/)/|(?<!^)/+(?!\n)$' '' $fzf_query | string collect -N)
else
set -- fzf_query (string replace -r -a -- '(?<=/)/|(?<!^)/+(?!\n)$' '' $fzf_query | string collect -N)
eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r '\\\n$' '')
end
set -- dir $fzf_query set -- dir $fzf_query
while not test -d "$dir" while not test -d "$dir"
set -- dir (dirname -z -- "$dir" | string split0) set -- dir (dirname -z -- "$dir" | string split0)
end end
end end
if not string match -q -- '.' $dir; or string match -q -r -- '^\./|^\.$' $fzf_query if not string match -q -- '.' $dir; or string match -qr -- '^\\.(/|$)' $fzf_query
if test "$fish_major" -ge 4 # Strip $dir from $fzf_query - preserve trailing newlines.
string match -q -r -- '^'(string escape --style=regex -- $dir)'/?(?<fzf_query>[\s\S]*)' $fzf_query if string match -qr -- '^\\d\\d+|^[4-9]' $version
else if test "$fish_major" -eq 3 -a "$fish_minor" -ge 2 # fish v4.0.0 and newer
string match -q -r -- '^/?(?<fzf_query>[\s\S]*?(?=\n?$)$)' \ string match -q -r -- '^'(string escape --style=regex -- $dir)'/?(?<fzf_query>[\\s\\S]*)' $fzf_query
(string replace -- "$dir" '' $fzf_query | string collect -N)
else else
set -- fzf_query (string replace -- "$dir" '' $fzf_query | string collect -N) string match -q -r -- '^/?(?<fzf_query>[\\s\\S]*?(?=\\n?$)$)' \
eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^/?|\\\n$' '') (string replace -- "$dir" '' $fzf_query | string collect -N)
end end
end end
end end
string escape -n -- "$dir" "$fzf_query" "$prefix" string escape -n -- "$dir" "$fzf_query" "$prefix"
end end
#----END INCLUDE
# Store current token in $dir as root for the 'find' command # Store current token in $dir as root for the 'find' command
function fzf-file-widget -d "List files and folders" function fzf-file-widget -d "List files and folders"
@@ -171,7 +116,7 @@ function fzf_key_bindings
set -lx FZF_DEFAULT_OPTS_FILE set -lx FZF_DEFAULT_OPTS_FILE
set -l result (eval (__fzfcmd) --walker-root=$dir --query=$fzf_query | string split0) set -l result (eval (__fzfcmd) --walker-root=$dir --query=$fzf_query | string split0)
and commandline -rt -- (string join -- ' ' $prefix(string escape --no-quoted -- $result))' ' and commandline -rt -- (string join -- ' ' $prefix(string escape -n -- $result))' '
commandline -f repaint commandline -f repaint
end end
@@ -183,45 +128,33 @@ function fzf_key_bindings
set -l -- fzf_query (string escape -- $command_line[$current_line]) set -l -- fzf_query (string escape -- $command_line[$current_line])
set -lx -- FZF_DEFAULT_OPTS (__fzf_defaults '' \ set -lx -- FZF_DEFAULT_OPTS (__fzf_defaults '' \
'--nth=2..,.. --scheme=history --multi --no-multi-line --no-wrap --wrap-sign="\t\t\t↳ " --preview-wrap-sign="↳ "' \ '--with-nth=2.. --nth=2..,.. --scheme=history --multi --no-multi-line' \
'--bind=\'shift-delete:execute-silent(for i in (string split0 -- <{+f}); eval builtin history delete --exact --case-sensitive -- (string escape -n -- $i | string replace -r "^\d*\\\\\\t" ""); end)+reload(eval $FZF_DEFAULT_COMMAND)\'' \ '--no-wrap --wrap-sign="\t\t\t↳ " --preview-wrap-sign="↳ " --freeze-left=1' \
'--bind="alt-enter:become(string join0 -- (string collect -- {+2..} | fish_indent -i))"' \ '--bind="alt-enter:become(set -g fzf_temp {+sf3..}; string join0 -- (string split0 -- <$fzf_temp | fish_indent -i); unlink $fzf_temp &>/dev/null)"' \
'--bind="alt-t:change-with-nth(1,3..|3..|2..)"' \
'--bind="shift-delete:execute-silent(eval builtin history delete -Ce -- (string escape -n -- (string split0 -- <{+sf3..})))+reload(eval $FZF_DEFAULT_COMMAND)"' \
"--bind=ctrl-r:toggle-sort,alt-r:toggle-raw --highlight-line $FZF_CTRL_R_OPTS" \ "--bind=ctrl-r:toggle-sort,alt-r:toggle-raw --highlight-line $FZF_CTRL_R_OPTS" \
'--accept-nth=2.. --delimiter="\t" --tabstop=4 --read0 --print0 --with-shell='(status fish-path)\\ -c) '--accept-nth=3.. --delimiter="\t" --tabstop=4 --read0 --print0 --with-shell='(status fish-path)\\ -c)
# Add dynamic preview options if preview command isn't already set by user # Add dynamic preview options if preview command isn't already set by user
if string match -qvr -- '--preview[= ]' "$FZF_DEFAULT_OPTS" if string match -qvr -- '--preview[= ]' "$FZF_DEFAULT_OPTS"
# Convert the highlighted timestamp using the date command if available # Prepend the options to allow user overrides
set -l -- date_cmd '{1}'
if type -q date
if date -d @0 '+%s' 2>/dev/null | string match -q 0
# GNU date
set -- date_cmd '(date -d @{1} \\"+%F %a %T\\")'
else if date -r 0 '+%s' 2>/dev/null | string match -q 0
# BSD date
set -- date_cmd '(date -r {1} \\"+%F %a %T\\")'
end
end
# Prepend the options to allow user customizations
set -p -- FZF_DEFAULT_OPTS \ set -p -- FZF_DEFAULT_OPTS \
'--bind="focus,resize:bg-transform:if test \\"$FZF_COLUMNS\\" -gt 100 -a \\\\( \\"$FZF_SELECT_COUNT\\" -gt 0 -o \\\\( -z \\"$FZF_WRAP\\" -a (string length -- {}) -gt (math $FZF_COLUMNS - 4) \\\\) -o (string collect -- {2..} | fish_indent | count) -gt 1 \\\\); echo show-preview; else echo hide-preview; end"' \ '--bind="focus,multi,resize:bg-transform:if test \\"$FZF_COLUMNS\\" -gt 100 -a \\\\( \\"$FZF_SELECT_COUNT\\" -gt 0 -o \\\\( -z \\"$FZF_WRAP\\" -a (string join0 -- <{f3..} | string length) -gt (math $FZF_COLUMNS - (switch $FZF_WITH_NTH; case 2..; echo 13; case 1,3..; echo 25; case 3..; echo 1; end)) \\\\) -o (string split0 -- <{sf3..} | fish_indent | count) -gt 1 \\\\); echo show-preview; else; echo hide-preview; end"' \
'--preview="string collect -- (test \\"$FZF_SELECT_COUNT\\" -gt 0; and string collect -- {+2..}) \\"\\n# \\"'$date_cmd' {2..} | fish_indent --ansi"' \ '--preview="test \\"$FZF_SELECT_COUNT\\" -gt 0; and string split0 -- <{+sf3..} | fish_indent (string match -q -- 3.\\\\* $version; or echo -- --only-indent) --ansi; and echo -n \\\\n; string collect -- \\\\#\\\\ {1} (string split0 -- <{sf3..}) | fish_indent --ansi"' \
'--preview-window="right,50%,wrap-word,follow,info,hidden"' '--preview-window="right,50%,wrap-word,follow,info,hidden"'
end end
set -lx FZF_DEFAULT_OPTS_FILE set -lx FZF_DEFAULT_OPTS_FILE
set -lx -- FZF_DEFAULT_COMMAND 'builtin history -z --show-time="%s%t"' set -lx -- FZF_DEFAULT_COMMAND 'builtin history -z'
# Enable syntax highlighting colors on fish v4.3.3 and newer # Enable syntax highlighting colors on fish v4.3.3 and newer
if set -l -- v (string match -r -- '^(\d+)\.(\d+)(?:\.(\d+))?' $version) if string match -qr -- '^\\d\\d+|^4\\.[4-9]|^4\\.3\\.[3-9]' $version
and test "$v[2]" -gt 4 -o "$v[2]" -eq 4 -a \
\( "$v[3]" -gt 3 -o "$v[3]" -eq 3 -a \
\( -n "$v[4]" -a "$v[4]" -ge 3 \) \)
set -a -- FZF_DEFAULT_OPTS '--ansi' set -a -- FZF_DEFAULT_OPTS '--ansi'
set -a -- FZF_DEFAULT_COMMAND '--color=always' set -a -- FZF_DEFAULT_COMMAND '--color=always --show-time=(set_color $fish_color_comment)"%F %a %T%t%s%t"(set_color $fish_color_normal)'
else
set -a -- FZF_DEFAULT_COMMAND '--show-time="%F %a %T%t%s%t"'
end end
# Merge history from other sessions before searching # Merge history from other sessions before searching
-1
View File
@@ -4,7 +4,6 @@
# / __/ / /_/ __/ # / __/ / /_/ __/
# /_/ /___/_/ key-bindings.zsh # /_/ /___/_/ key-bindings.zsh
# #
# - $FZF_TMUX_OPTS
# - $FZF_CTRL_T_COMMAND # - $FZF_CTRL_T_COMMAND
# - $FZF_CTRL_T_OPTS # - $FZF_CTRL_T_OPTS
# - $FZF_CTRL_R_COMMAND # - $FZF_CTRL_R_COMMAND
+12 -14
View File
@@ -8,26 +8,24 @@ dir=${0%"${0##*/}"}
update() { update() {
{ {
sed -n "1,/^#----BEGIN INCLUDE $1/p" "$2" sed -n '1,/^#----BEGIN INCLUDE common\.sh/p' "$1"
cat << EOF cat << EOF
# NOTE: Do not directly edit this section, which is copied from "$1". # NOTE: Do not directly edit this section, which is copied from "common.sh".
# To modify it, one can edit "$1" and run "./update.sh" to apply # To modify it, one can edit "common.sh" and run "./update.sh" to apply
# the changes. See code comments in "$1" for the implementation details. # the changes. See code comments in "common.sh" for the implementation details.
EOF EOF
echo echo
grep -v '^[[:blank:]]*#' "$dir/$1" # remove code comments from the common file grep -v '^[[:blank:]]*#' "$dir/common.sh" # remove code comments in common.sh
sed -n '/^#----END INCLUDE/,$p' "$2" sed -n '/^#----END INCLUDE/,$p' "$1"
} > "$2.part" } > "$1.part"
mv -f "$2.part" "$2" mv -f "$1.part" "$1"
} }
update "common.sh" "$dir/completion.bash" update "$dir/completion.bash"
update "common.sh" "$dir/completion.zsh" update "$dir/completion.zsh"
update "common.sh" "$dir/key-bindings.bash" update "$dir/key-bindings.bash"
update "common.sh" "$dir/key-bindings.zsh" update "$dir/key-bindings.zsh"
update "common.fish" "$dir/completion.fish"
update "common.fish" "$dir/key-bindings.fish"
# Check if --check is in ARGV # Check if --check is in ARGV
check=0 check=0
+3
View File
@@ -56,6 +56,9 @@ func Run(opts *Options) (int, error) {
if opts.useTmux() { if opts.useTmux() {
return runTmux(os.Args, opts) return runTmux(os.Args, opts)
} }
if opts.useZellij() {
return runZellij(os.Args, opts)
}
if needWinpty(opts) { if needWinpty(opts) {
return runWinpty(os.Args, opts) return runWinpty(os.Args, opts)
+13 -11
View File
@@ -66,7 +66,7 @@ Usage: fzf [options]
--no-bold Do not use bold text --no-bold Do not use bold text
DISPLAY MODE DISPLAY MODE
--height=[~]HEIGHT[%] Display fzf window below the cursor with the given --height=[~][-]HEIGHT[%] Display fzf window below the cursor with the given
height instead of using fullscreen. height instead of using fullscreen.
A negative value is calculated as the terminal height A negative value is calculated as the terminal height
minus the given value. minus the given value.
@@ -75,9 +75,10 @@ Usage: fzf [options]
--min-height=HEIGHT[+] Minimum height when --height is given as a percentage. --min-height=HEIGHT[+] Minimum height when --height is given as a percentage.
Add '+' to automatically increase the value Add '+' to automatically increase the value
according to the other layout options (default: 10+). according to the other layout options (default: 10+).
--tmux[=OPTS] Start fzf in a tmux popup (requires tmux 3.3+) --popup[=OPTS] Start fzf in a popup window (requires tmux 3.3+ or Zellij 0.44+)
[center|top|bottom|left|right][,SIZE[%]][,SIZE[%]] [center|top|bottom|left|right][,SIZE[%]][,SIZE[%]]
[,border-native] (default: center,50%) [,border-native] (default: center,50%)
--tmux[=OPTS] Alias for --popup
LAYOUT LAYOUT
--layout=LAYOUT Choose layout: [default|reverse|reverse-list] --layout=LAYOUT Choose layout: [default|reverse|reverse-list]
@@ -417,7 +418,7 @@ func parseTmuxOptions(arg string, index int) (*tmuxOptions, error) {
var err error var err error
opts := defaultTmuxOptions(index) opts := defaultTmuxOptions(index)
tokens := splitRegexp.Split(arg, -1) tokens := splitRegexp.Split(arg, -1)
errorToReturn := errors.New("invalid tmux option: " + arg + " (expected: [center|top|bottom|left|right][,SIZE[%]][,SIZE[%][,border-native]])") errorToReturn := errors.New("invalid popup option: " + arg + " (expected: [center|top|bottom|left|right][,SIZE[%]][,SIZE[%][,border-native]])")
if len(tokens) == 0 || len(tokens) > 4 { if len(tokens) == 0 || len(tokens) > 4 {
return nil, errorToReturn return nil, errorToReturn
} }
@@ -869,7 +870,7 @@ func nthTransformer(str string) (func(Delimiter) func([]Token, int32) string, er
nth []Range nth []Range
} }
parts := make([]NthParts, len(indexes)) parts := make([]NthParts, 0, len(indexes))
idx := 0 idx := 0
for _, index := range indexes { for _, index := range indexes {
if idx < index[0] { if idx < index[0] {
@@ -2221,9 +2222,6 @@ func parseHeight(str string, index int) (heightSpec, error) {
str = str[1:] str = str[1:]
} }
if strings.HasPrefix(str, "-") { if strings.HasPrefix(str, "-") {
if heightSpec.auto {
return heightSpec, errors.New("negative(-) height is not compatible with adaptive(~) height")
}
heightSpec.inverse = true heightSpec.inverse = true
str = str[1:] str = str[1:]
} }
@@ -2636,7 +2634,7 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
opts.Version = true opts.Version = true
case "--no-winpty": case "--no-winpty":
opts.NoWinpty = true opts.NoWinpty = true
case "--tmux": case "--tmux", "--popup":
given, str := optionalNextString() given, str := optionalNextString()
if given { if given {
if opts.Tmux, err = parseTmuxOptions(str, index); err != nil { if opts.Tmux, err = parseTmuxOptions(str, index); err != nil {
@@ -2645,7 +2643,7 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
} else { } else {
opts.Tmux = defaultTmuxOptions(index) opts.Tmux = defaultTmuxOptions(index)
} }
case "--no-tmux": case "--no-tmux", "--no-popup":
opts.Tmux = nil opts.Tmux = nil
case "--tty-default": case "--tty-default":
if opts.TtyDefault, err = nextString("tty device name required"); err != nil { if opts.TtyDefault, err = nextString("tty device name required"); err != nil {
@@ -3151,7 +3149,7 @@ func parseOptions(index *int, opts *Options, allArgs []string) error {
} }
opts.PreviewWrapSign = &str opts.PreviewWrapSign = &str
case "--height": case "--height":
str, err := nextString("height required: [~]HEIGHT[%]") str, err := nextString("height required: [~][-]HEIGHT[%]")
if err != nil { if err != nil {
return err return err
} }
@@ -3593,7 +3591,7 @@ func validateOptions(opts *Options) error {
} }
} }
if opts.Height.auto { if opts.Height.auto && (opts.Tmux == nil || opts.Tmux.index < opts.Height.index) {
for _, s := range []sizeSpec{opts.Margin[0], opts.Margin[2]} { for _, s := range []sizeSpec{opts.Margin[0], opts.Margin[2]} {
if s.percent { if s.percent {
return errors.New("adaptive height is not compatible with top/bottom percent margin") return errors.New("adaptive height is not compatible with top/bottom percent margin")
@@ -3627,6 +3625,10 @@ func (opts *Options) useTmux() bool {
return opts.Tmux != nil && len(os.Getenv("TMUX")) > 0 && opts.Tmux.index >= opts.Height.index return opts.Tmux != nil && len(os.Getenv("TMUX")) > 0 && opts.Tmux.index >= opts.Height.index
} }
func (opts *Options) useZellij() bool {
return opts.Tmux != nil && len(os.Getenv("ZELLIJ")) > 0 && opts.Tmux.index >= opts.Height.index
}
func (opts *Options) noSeparatorLine() bool { func (opts *Options) noSeparatorLine() bool {
if opts.Inputless { if opts.Inputless {
return true return true
+26
View File
@@ -23,6 +23,32 @@ func escapeSingleQuote(str string) string {
return "'" + strings.ReplaceAll(str, "'", "'\\''") + "'" return "'" + strings.ReplaceAll(str, "'", "'\\''") + "'"
} }
func popupArgStr(args []string, opts *Options) (string, string) {
fzf, rest := args[0], args[1:]
args = []string{"--bind=ctrl-z:ignore"}
if !opts.Tmux.border && (opts.BorderShape == tui.BorderUndefined || opts.BorderShape == tui.BorderLine) {
if tui.DefaultBorderShape == tui.BorderRounded {
rest = append(rest, "--border=rounded")
} else {
rest = append(rest, "--border=sharp")
}
}
if opts.Tmux.border && opts.Margin == defaultMargin() {
args = append(args, "--margin=0,1")
}
argStr := escapeSingleQuote(fzf)
for _, arg := range append(args, rest...) {
argStr += " " + escapeSingleQuote(arg)
}
argStr += ` --no-popup --no-height`
dir, err := os.Getwd()
if err != nil {
dir = "."
}
return argStr, dir
}
func fifo(name string) (string, error) { func fifo(name string) (string, error) {
ns := time.Now().UnixNano() ns := time.Now().UnixNano()
output := filepath.Join(os.TempDir(), fmt.Sprintf("fzf-%s-%d", name, ns)) output := filepath.Join(os.TempDir(), fmt.Sprintf("fzf-%s-%d", name, ns))
+5 -6
View File
@@ -122,13 +122,12 @@ func startHttpServer(address listenAddress, actionChannel chan []*action, getHan
} }
} }
server := httpServer{
apiKey: []byte(apiKey),
actionChannel: actionChannel,
getHandler: getHandler,
}
go func() { go func() {
server := httpServer{
apiKey: []byte(apiKey),
actionChannel: actionChannel,
getHandler: getHandler,
}
for { for {
conn, err := listener.Accept() conn, err := listener.Accept()
if err != nil { if err != nil {
+1 -1
View File
@@ -2243,7 +2243,7 @@ func (t *Terminal) resizeWindows(forcePreview bool, redrawBorder bool) {
width := screenWidth - marginInt[1] - marginInt[3] width := screenWidth - marginInt[1] - marginInt[3]
height := screenHeight - marginInt[0] - marginInt[2] height := screenHeight - marginInt[0] - marginInt[2]
t.prevLines = make([]itemLine, screenHeight) t.prevLines = make([]itemLine, max(1, screenHeight))
if t.border != nil && redrawBorder { if t.border != nil && redrawBorder {
t.border = nil t.border = nil
} }
+1 -29
View File
@@ -1,39 +1,11 @@
package fzf package fzf
import ( import (
"os"
"os/exec" "os/exec"
"github.com/junegunn/fzf/src/tui"
) )
func runTmux(args []string, opts *Options) (int, error) { func runTmux(args []string, opts *Options) (int, error) {
// Prepare arguments argStr, dir := popupArgStr(args, opts)
fzf, rest := args[0], args[1:]
args = []string{"--bind=ctrl-z:ignore"}
if !opts.Tmux.border && (opts.BorderShape == tui.BorderUndefined || opts.BorderShape == tui.BorderLine) {
// We append --border option at the end, because `--style=full:STYLE`
// may have changed the default border style.
if tui.DefaultBorderShape == tui.BorderRounded {
rest = append(rest, "--border=rounded")
} else {
rest = append(rest, "--border=sharp")
}
}
if opts.Tmux.border && opts.Margin == defaultMargin() {
args = append(args, "--margin=0,1")
}
argStr := escapeSingleQuote(fzf)
for _, arg := range append(args, rest...) {
argStr += " " + escapeSingleQuote(arg)
}
argStr += ` --no-tmux --no-height`
// Get current directory
dir, err := os.Getwd()
if err != nil {
dir = "."
}
// Set tmux options for popup placement // Set tmux options for popup placement
// C Both The centre of the terminal // C Both The centre of the terminal
+44 -7
View File
@@ -11,6 +11,8 @@ import (
"strings" "strings"
"sync/atomic" "sync/atomic"
"syscall" "syscall"
"golang.org/x/sys/windows"
) )
type shellType int type shellType int
@@ -19,6 +21,7 @@ const (
shellTypeUnknown shellType = iota shellTypeUnknown shellType = iota
shellTypeCmd shellTypeCmd
shellTypePowerShell shellTypePowerShell
shellTypePwsh
) )
var escapeRegex = regexp.MustCompile(`[&|<>()^%!"]`) var escapeRegex = regexp.MustCompile(`[&|<>()^%!"]`)
@@ -46,7 +49,10 @@ func NewExecutor(withShell string) *Executor {
} else if strings.HasPrefix(basename, "cmd") { } else if strings.HasPrefix(basename, "cmd") {
shellType = shellTypeCmd shellType = shellTypeCmd
args = []string{"/s/c"} args = []string{"/s/c"}
} else if strings.HasPrefix(basename, "pwsh") || strings.HasPrefix(basename, "powershell") { } else if strings.HasPrefix(basename, "pwsh") {
shellType = shellTypePwsh
args = []string{"-NoProfile", "-Command"}
} else if strings.HasPrefix(basename, "powershell") {
shellType = shellTypePowerShell shellType = shellTypePowerShell
args = []string{"-NoProfile", "-Command"} args = []string{"-NoProfile", "-Command"}
} else { } else {
@@ -56,8 +62,12 @@ func NewExecutor(withShell string) *Executor {
} }
// ExecCommand executes the given command with $SHELL // ExecCommand executes the given command with $SHELL
// FIXME: setpgid is unused. We set it in the Unix implementation so that we //
// can kill preview process with its child processes at once. // On Windows, setpgid controls whether the spawned process is placed in a new
// process group (so that it can be signaled independently, e.g. for previews).
// However, we only do this for "pwsh" and non-standard shells, because cmd.exe
// and Windows PowerShell ("powershell.exe") don't always exit on Ctrl-Break.
//
// NOTE: For "powershell", we should ideally set output encoding to UTF8, // NOTE: For "powershell", we should ideally set output encoding to UTF8,
// but it is left as is now because no adverse effect has been observed. // but it is left as is now because no adverse effect has been observed.
func (x *Executor) ExecCommand(command string, setpgid bool) *exec.Cmd { func (x *Executor) ExecCommand(command string, setpgid bool) *exec.Cmd {
@@ -73,19 +83,31 @@ func (x *Executor) ExecCommand(command string, setpgid bool) *exec.Cmd {
} }
x.shellPath.Store(shell) x.shellPath.Store(shell)
} }
var creationFlags uint32
// Set new process group for pwsh (PowerShell 7+) and unknown/posix-ish shells
if setpgid && (x.shellType == shellTypePwsh || x.shellType == shellTypeUnknown) {
creationFlags = windows.CREATE_NEW_PROCESS_GROUP
}
var cmd *exec.Cmd var cmd *exec.Cmd
if x.shellType == shellTypeCmd { if x.shellType == shellTypeCmd {
cmd = exec.Command(shell) cmd = exec.Command(shell)
cmd.SysProcAttr = &syscall.SysProcAttr{ cmd.SysProcAttr = &syscall.SysProcAttr{
HideWindow: false, HideWindow: false,
CmdLine: fmt.Sprintf(`%s "%s"`, strings.Join(x.args, " "), command), CmdLine: fmt.Sprintf(`%s "%s"`, strings.Join(x.args, " "), command),
CreationFlags: 0, CreationFlags: creationFlags,
} }
} else { } else {
cmd = exec.Command(shell, append(x.args, command)...) args := x.args
if setpgid && x.shellType == shellTypePwsh {
// pwsh needs -NonInteractive flag to exit on Ctrl-Break
args = append([]string{"-NonInteractive"}, x.args...)
}
cmd = exec.Command(shell, append(args, command)...)
cmd.SysProcAttr = &syscall.SysProcAttr{ cmd.SysProcAttr = &syscall.SysProcAttr{
HideWindow: false, HideWindow: false,
CreationFlags: 0, CreationFlags: creationFlags,
} }
} }
return cmd return cmd
@@ -156,7 +178,7 @@ func (x *Executor) QuoteEntry(entry string) string {
fd -H --no-ignore -td -d 4 | fzf --preview ".\eza.exe --color=always --tree --level=3 --icons=always {}" --with-shell "powershell -NoProfile -Command" fd -H --no-ignore -td -d 4 | fzf --preview ".\eza.exe --color=always --tree --level=3 --icons=always {}" --with-shell "powershell -NoProfile -Command"
*/ */
return escapeArg(entry) return escapeArg(entry)
case shellTypePowerShell: case shellTypePowerShell, shellTypePwsh:
escaped := strings.ReplaceAll(entry, `"`, `\"`) escaped := strings.ReplaceAll(entry, `"`, `\"`)
return "'" + strings.ReplaceAll(escaped, "'", "''") + "'" return "'" + strings.ReplaceAll(escaped, "'", "''") + "'"
default: default:
@@ -166,6 +188,21 @@ func (x *Executor) QuoteEntry(entry string) string {
// KillCommand kills the process for the given command // KillCommand kills the process for the given command
func KillCommand(cmd *exec.Cmd) error { func KillCommand(cmd *exec.Cmd) error {
// Safely handle nil command or process.
if cmd == nil || cmd.Process == nil {
return nil
}
// If it has its own process group, we can send it Ctrl-Break
if cmd.SysProcAttr != nil && cmd.SysProcAttr.CreationFlags&windows.CREATE_NEW_PROCESS_GROUP != 0 {
if err := windows.GenerateConsoleCtrlEvent(windows.CTRL_BREAK_EVENT, uint32(cmd.Process.Pid)); err == nil {
return nil
}
}
// If it's the same process group, or if sending the console control event
// fails (e.g., no console, different console, or process already exited),
// fall back to a standard kill. This probably won't *help* if there's I/O
// going on, because Wait() will still hang until the I/O finishes unless we
// hard-kill the entire process group. But it doesn't hurt to try!
return cmd.Process.Kill() return cmd.Process.Kill()
} }
+41
View File
@@ -0,0 +1,41 @@
package fzf
import (
"os/exec"
)
func runZellij(args []string, opts *Options) (int, error) {
argStr, dir := popupArgStr(args, opts)
zellijArgs := []string{
"run", "--floating", "--close-on-exit", "--block-until-exit",
"--cwd", dir,
}
if !opts.Tmux.border {
zellijArgs = append(zellijArgs, "--borderless", "true")
}
switch opts.Tmux.position {
case posUp:
zellijArgs = append(zellijArgs, "-y", "0")
case posDown:
zellijArgs = append(zellijArgs, "-y", "9999")
case posLeft:
zellijArgs = append(zellijArgs, "-x", "0")
case posRight:
zellijArgs = append(zellijArgs, "-x", "9999")
case posCenter:
// Zellij centers floating panes by default
}
zellijArgs = append(zellijArgs, "--width", opts.Tmux.width.String())
zellijArgs = append(zellijArgs, "--height", opts.Tmux.height.String())
zellijArgs = append(zellijArgs, "--")
return runProxy(argStr, func(temp string, needBash bool) (*exec.Cmd, error) {
sh, err := sh(needBash)
if err != nil {
return nil, err
}
zellijArgs = append(zellijArgs, sh, temp)
return exec.Command("zellij", zellijArgs...), nil
}, opts, true)
}
+1 -2
View File
@@ -3,10 +3,9 @@ set -e FZF_DEFAULT_COMMAND FZF_DEFAULT_OPTS FZF_DEFAULT_OPTS_FILE FZF_TMUX FZF_T
set -e FZF_CTRL_T_COMMAND FZF_CTRL_T_OPTS FZF_ALT_C_COMMAND FZF_ALT_C_OPTS FZF_CTRL_R_OPTS set -e FZF_CTRL_T_COMMAND FZF_CTRL_T_OPTS FZF_ALT_C_COMMAND FZF_ALT_C_OPTS FZF_CTRL_R_OPTS
set -e FZF_API_KEY set -e FZF_API_KEY
# Unset completion-specific variables # Unset completion-specific variables
set -e FZF_COMPLETION_TRIGGER FZF_COMPLETION_OPTS set -e FZF_COMPLETION_OPTS FZF_EXPANSION_OPTS
set -gx FZF_DEFAULT_OPTS "--no-scrollbar --pointer '>' --marker '>'" set -gx FZF_DEFAULT_OPTS "--no-scrollbar --pointer '>' --marker '>'"
set -gx FZF_COMPLETION_TRIGGER '++'
set -gx fish_history fzf_test set -gx fish_history fzf_test
# Add fzf to PATH # Add fzf to PATH