mirror of
https://github.com/junegunn/fzf.git
synced 2025-08-22 07:53:49 -07:00
Compare commits
28 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
260a65b0fb | ||
|
835d2fb98c | ||
|
a9811addaa | ||
|
ee9d88b637 | ||
|
194a763c46 | ||
|
8d74446bef | ||
|
7ed6c7905c | ||
|
159a37fa37 | ||
|
f39ae0e7c1 | ||
|
4a68eac99b | ||
|
2665580120 | ||
|
a4391aeedd | ||
|
b86a967ee2 | ||
|
608232568b | ||
|
7f85beccb5 | ||
|
767f1255ab | ||
|
fddbfe7b0e | ||
|
4ab7fdc28e | ||
|
e352b68878 | ||
|
207deeadba | ||
|
d18d92f925 | ||
|
af3ce47c44 | ||
|
d8bfb6712d | ||
|
f864f8b5f7 | ||
|
31d72efba7 | ||
|
d169c951f3 | ||
|
90d7e38909 | ||
|
938f23e429 |
2
.github/workflows/typos.yml
vendored
2
.github/workflows/typos.yml
vendored
@@ -7,4 +7,4 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: crate-ci/typos@v1.19.0
|
- uses: crate-ci/typos@v1.20.10
|
||||||
|
@@ -12,6 +12,8 @@ builds:
|
|||||||
- darwin
|
- darwin
|
||||||
goarch:
|
goarch:
|
||||||
- amd64
|
- amd64
|
||||||
|
flags:
|
||||||
|
- -trimpath
|
||||||
ldflags:
|
ldflags:
|
||||||
- "-s -w -X main.version={{ .Version }} -X main.revision={{ .ShortCommit }}"
|
- "-s -w -X main.version={{ .Version }} -X main.revision={{ .ShortCommit }}"
|
||||||
hooks:
|
hooks:
|
||||||
@@ -19,7 +21,7 @@ builds:
|
|||||||
sh -c '
|
sh -c '
|
||||||
cat > /tmp/fzf-gon-amd64.hcl << EOF
|
cat > /tmp/fzf-gon-amd64.hcl << EOF
|
||||||
source = ["./dist/fzf-macos_darwin_amd64_v1/fzf"]
|
source = ["./dist/fzf-macos_darwin_amd64_v1/fzf"]
|
||||||
bundle_id = "kr.junegunn.fzf"
|
bundle_id = "junegunn.fzf"
|
||||||
sign {
|
sign {
|
||||||
application_identity = "Developer ID Application: Junegunn Choi (Y254DRW44Z)"
|
application_identity = "Developer ID Application: Junegunn Choi (Y254DRW44Z)"
|
||||||
}
|
}
|
||||||
@@ -36,6 +38,8 @@ builds:
|
|||||||
- darwin
|
- darwin
|
||||||
goarch:
|
goarch:
|
||||||
- arm64
|
- arm64
|
||||||
|
flags:
|
||||||
|
- -trimpath
|
||||||
ldflags:
|
ldflags:
|
||||||
- "-s -w -X main.version={{ .Version }} -X main.revision={{ .ShortCommit }}"
|
- "-s -w -X main.version={{ .Version }} -X main.revision={{ .ShortCommit }}"
|
||||||
hooks:
|
hooks:
|
||||||
@@ -43,7 +47,7 @@ builds:
|
|||||||
sh -c '
|
sh -c '
|
||||||
cat > /tmp/fzf-gon-arm64.hcl << EOF
|
cat > /tmp/fzf-gon-arm64.hcl << EOF
|
||||||
source = ["./dist/fzf-macos-arm_darwin_arm64/fzf"]
|
source = ["./dist/fzf-macos-arm_darwin_arm64/fzf"]
|
||||||
bundle_id = "kr.junegunn.fzf"
|
bundle_id = "junegunn.fzf"
|
||||||
sign {
|
sign {
|
||||||
application_identity = "Developer ID Application: Junegunn Choi (Y254DRW44Z)"
|
application_identity = "Developer ID Application: Junegunn Choi (Y254DRW44Z)"
|
||||||
}
|
}
|
||||||
@@ -71,6 +75,8 @@ builds:
|
|||||||
- 5
|
- 5
|
||||||
- 6
|
- 6
|
||||||
- 7
|
- 7
|
||||||
|
flags:
|
||||||
|
- -trimpath
|
||||||
ldflags:
|
ldflags:
|
||||||
- "-s -w -X main.version={{ .Version }} -X main.revision={{ .ShortCommit }}"
|
- "-s -w -X main.version={{ .Version }} -X main.revision={{ .ShortCommit }}"
|
||||||
ignore:
|
ignore:
|
||||||
|
30
CHANGELOG.md
30
CHANGELOG.md
@@ -1,6 +1,36 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
0.51.0
|
||||||
|
------
|
||||||
|
- Added a new environment variable `$FZF_POS` exported to the child processes. It's the vertical position of the cursor in the list starting from 1.
|
||||||
|
```sh
|
||||||
|
# Toggle selection to the top or to the bottom
|
||||||
|
seq 30 | fzf --multi --bind 'load:pos(10)' \
|
||||||
|
--bind 'shift-up:transform:for _ in $(seq $FZF_POS $FZF_MATCH_COUNT); do echo -n +toggle+up; done' \
|
||||||
|
--bind 'shift-down:transform:for _ in $(seq 1 $FZF_POS); do echo -n +toggle+down; done'
|
||||||
|
```
|
||||||
|
- Added `--with-shell` option to start child processes with a custom shell command and flags
|
||||||
|
```sh
|
||||||
|
gem list | fzf --with-shell 'ruby -e' \
|
||||||
|
--preview 'pp Gem::Specification.find_by_name({1})' \
|
||||||
|
--bind 'ctrl-o:execute-silent:
|
||||||
|
spec = Gem::Specification.find_by_name({1})
|
||||||
|
[spec.homepage, *spec.metadata.filter { _1.end_with?("uri") }.values].uniq.each do
|
||||||
|
system "open", _1
|
||||||
|
end
|
||||||
|
'
|
||||||
|
```
|
||||||
|
- Added `change-multi` action for dynamically changing `--multi` option
|
||||||
|
- `change-multi` - enable multi-select mode with no limit
|
||||||
|
- `change-multi(NUM)` - enable multi-select mode with a limit
|
||||||
|
- `change-multi(0)` - disable multi-select mode
|
||||||
|
- Windows improvements
|
||||||
|
- `become` action is now supported on Windows
|
||||||
|
- Unlike in *nix, this does not use `execve(2)`. Instead it spawns a new process and waits for it to finish, so the exact behavior may differ.
|
||||||
|
- Fixed argument escaping for Windows cmd.exe. No redundant escaping of backslashes.
|
||||||
|
- Bug fixes and improvements
|
||||||
|
|
||||||
0.50.0
|
0.50.0
|
||||||
------
|
------
|
||||||
- Search performance optimization. You can observe 50%+ improvement in some scenarios.
|
- Search performance optimization. You can observe 50%+ improvement in some scenarios.
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
FROM --platform=linux/amd64 ubuntu:22.04
|
FROM ubuntu:24.04
|
||||||
RUN apt-get update -y && apt install -y git make golang zsh fish ruby tmux
|
RUN apt-get update -y && apt install -y git make golang zsh fish ruby tmux
|
||||||
RUN gem install --no-document -v 5.14.2 minitest
|
RUN gem install --no-document -v 5.22.3 minitest
|
||||||
RUN echo '. /usr/share/bash-completion/completions/git' >> ~/.bashrc
|
RUN echo '. /usr/share/bash-completion/completions/git' >> ~/.bashrc
|
||||||
RUN echo '. ~/.bashrc' >> ~/.bash_profile
|
RUN echo '. ~/.bashrc' >> ~/.bash_profile
|
||||||
|
|
||||||
|
10
Makefile
10
Makefile
@@ -25,7 +25,7 @@ endif
|
|||||||
ifeq ($(REVISION),)
|
ifeq ($(REVISION),)
|
||||||
$(error Not on git repository; cannot determine $$FZF_REVISION)
|
$(error Not on git repository; cannot determine $$FZF_REVISION)
|
||||||
endif
|
endif
|
||||||
BUILD_FLAGS := -a -ldflags "-s -w -X main.version=$(VERSION) -X main.revision=$(REVISION)" -tags "$(TAGS)"
|
BUILD_FLAGS := -a -ldflags "-s -w -X main.version=$(VERSION) -X main.revision=$(REVISION)" -tags "$(TAGS)" -trimpath
|
||||||
|
|
||||||
BINARY32 := fzf-$(GOOS)_386
|
BINARY32 := fzf-$(GOOS)_386
|
||||||
BINARY64 := fzf-$(GOOS)_amd64
|
BINARY64 := fzf-$(GOOS)_amd64
|
||||||
@@ -174,12 +174,12 @@ bin/fzf: target/$(BINARY) | bin
|
|||||||
cp -f target/$(BINARY) bin/fzf
|
cp -f target/$(BINARY) bin/fzf
|
||||||
|
|
||||||
docker:
|
docker:
|
||||||
docker build -t fzf-arch .
|
docker build -t fzf-ubuntu .
|
||||||
docker run -it fzf-arch tmux
|
docker run -it fzf-ubuntu tmux
|
||||||
|
|
||||||
docker-test:
|
docker-test:
|
||||||
docker build -t fzf-arch .
|
docker build -t fzf-ubuntu .
|
||||||
docker run -it fzf-arch
|
docker run -it fzf-ubuntu
|
||||||
|
|
||||||
update:
|
update:
|
||||||
$(GO) get -u
|
$(GO) get -u
|
||||||
|
@@ -7,7 +7,7 @@ fail() {
|
|||||||
exit 2
|
exit 2
|
||||||
}
|
}
|
||||||
|
|
||||||
fzf="$(command -v fzf 2> /dev/null)" || fzf="$(dirname "$0")/fzf"
|
fzf="$(command which fzf)" || fzf="$(dirname "$0")/fzf"
|
||||||
[[ -x "$fzf" ]] || fail 'fzf executable not found'
|
[[ -x "$fzf" ]] || fail 'fzf executable not found'
|
||||||
|
|
||||||
args=()
|
args=()
|
||||||
|
2
install
2
install
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
set -u
|
set -u
|
||||||
|
|
||||||
version=0.50.0
|
version=0.51.0
|
||||||
auto_completion=
|
auto_completion=
|
||||||
key_bindings=
|
key_bindings=
|
||||||
update_config=2
|
update_config=2
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
$version="0.50.0"
|
$version="0.51.0"
|
||||||
|
|
||||||
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||||
|
|
||||||
|
2
main.go
2
main.go
@@ -9,7 +9,7 @@ import (
|
|||||||
"github.com/junegunn/fzf/src/protector"
|
"github.com/junegunn/fzf/src/protector"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version string = "0.50"
|
var version string = "0.51"
|
||||||
var revision string = "devel"
|
var revision string = "devel"
|
||||||
|
|
||||||
//go:embed shell/key-bindings.bash
|
//go:embed shell/key-bindings.bash
|
||||||
|
18
main_test.go
18
main_test.go
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"go/build"
|
"go/build"
|
||||||
@@ -10,6 +11,7 @@ import (
|
|||||||
"go/types"
|
"go/types"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -17,6 +19,20 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func loadPackages(t *testing.T) []*build.Package {
|
func loadPackages(t *testing.T) []*build.Package {
|
||||||
|
// If GOROOT is not set, use `go env GOROOT` to determine it since it
|
||||||
|
// performs more work than just runtime.GOROOT(). For context, running
|
||||||
|
// the tests with the "-trimpath" flag causes GOROOT to not be set.
|
||||||
|
ctxt := &build.Default
|
||||||
|
if ctxt.GOROOT == "" {
|
||||||
|
cmd := exec.Command("go", "env", "GOROOT")
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
out = bytes.TrimSpace(out)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error running command: %q: %v\n%s", cmd.Args, err, out)
|
||||||
|
}
|
||||||
|
ctxt.GOROOT = string(out)
|
||||||
|
}
|
||||||
|
|
||||||
wd, err := os.Getwd()
|
wd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -38,7 +54,7 @@ func loadPackages(t *testing.T) []*build.Package {
|
|||||||
if d.Type().IsRegular() && filepath.Ext(name) == ".go" && !strings.HasSuffix(name, "_test.go") {
|
if d.Type().IsRegular() && filepath.Ext(name) == ".go" && !strings.HasSuffix(name, "_test.go") {
|
||||||
dir := filepath.Dir(path)
|
dir := filepath.Dir(path)
|
||||||
if !seen[dir] {
|
if !seen[dir] {
|
||||||
pkg, err := build.ImportDir(dir, build.ImportComment)
|
pkg, err := ctxt.ImportDir(dir, build.ImportComment)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s: %s", dir, err)
|
return fmt.Errorf("%s: %s", dir, err)
|
||||||
}
|
}
|
||||||
|
@@ -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 "Apr 2024" "fzf 0.50.0" "fzf-tmux - open fzf in tmux split pane"
|
.TH fzf-tmux 1 "May 2024" "fzf 0.51.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
|
||||||
|
@@ -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 "Apr 2024" "fzf 0.50.0" "fzf - a command-line fuzzy finder"
|
.TH fzf 1 "May 2024" "fzf 0.51.0" "fzf - a command-line fuzzy finder"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf - a command-line fuzzy finder
|
fzf - a command-line fuzzy finder
|
||||||
@@ -812,12 +812,22 @@ e.g.
|
|||||||
.TP
|
.TP
|
||||||
.B "--sync"
|
.B "--sync"
|
||||||
Synchronous search for multi-staged filtering. If specified, fzf will launch
|
Synchronous search for multi-staged filtering. If specified, fzf will launch
|
||||||
ncurses finder only after the input stream is complete.
|
the finder only after the input stream is complete.
|
||||||
|
|
||||||
.RS
|
.RS
|
||||||
e.g. \fBfzf --multi | fzf --sync\fR
|
e.g. \fBfzf --multi | fzf --sync\fR
|
||||||
.RE
|
.RE
|
||||||
.TP
|
.TP
|
||||||
|
.B "--with-shell=STR"
|
||||||
|
Shell command and flags to start child processes with. On *nix Systems, the
|
||||||
|
default value is \fB$SHELL -c\fR if \fB$SHELL\fR is set, otherwise \fBsh -c\fR.
|
||||||
|
On Windows, the default value is \fBcmd /v:on/s/c\fR when \fB$SHELL\fR is not
|
||||||
|
set.
|
||||||
|
|
||||||
|
.RS
|
||||||
|
e.g. \fBgem list | fzf --with-shell 'ruby -e' --preview 'pp Gem::Specification.find_by_name({1})'\fR
|
||||||
|
.RE
|
||||||
|
.TP
|
||||||
.B "--listen[=[ADDR:]PORT]" "--listen-unsafe[=[ADDR:]PORT]"
|
.B "--listen[=[ADDR:]PORT]" "--listen-unsafe[=[ADDR:]PORT]"
|
||||||
Start HTTP server and listen on the given address. It allows external processes
|
Start HTTP server and listen on the given address. It allows external processes
|
||||||
to send actions to perform via POST method.
|
to send actions to perform via POST method.
|
||||||
@@ -932,6 +942,8 @@ you need to protect against DNS rebinding and privilege escalation attacks.
|
|||||||
.br
|
.br
|
||||||
.BR 2 " Error"
|
.BR 2 " Error"
|
||||||
.br
|
.br
|
||||||
|
.BR 127 " Invalid shell command for \fBbecome\fR action"
|
||||||
|
.br
|
||||||
.BR 130 " Interrupted with \fBCTRL-C\fR or \fBESC\fR"
|
.BR 130 " Interrupted with \fBCTRL-C\fR or \fBESC\fR"
|
||||||
|
|
||||||
.SH FIELD INDEX EXPRESSION
|
.SH FIELD INDEX EXPRESSION
|
||||||
@@ -972,6 +984,8 @@ fzf exports the following environment variables to its child processes.
|
|||||||
.br
|
.br
|
||||||
.BR FZF_SELECT_COUNT " Number of selected items"
|
.BR FZF_SELECT_COUNT " Number of selected items"
|
||||||
.br
|
.br
|
||||||
|
.BR FZF_POS " Vertical position of the cursor in the list starting from 1"
|
||||||
|
.br
|
||||||
.BR FZF_QUERY " Current query string"
|
.BR FZF_QUERY " Current query string"
|
||||||
.br
|
.br
|
||||||
.BR FZF_PROMPT " Prompt string"
|
.BR FZF_PROMPT " Prompt string"
|
||||||
@@ -1282,6 +1296,8 @@ A key or an event can be bound to one or more of the following actions.
|
|||||||
\fBcancel\fR (clear query string if not empty, abort fzf otherwise)
|
\fBcancel\fR (clear query string if not empty, abort fzf otherwise)
|
||||||
\fBchange-border-label(...)\fR (change \fB--border-label\fR to the given string)
|
\fBchange-border-label(...)\fR (change \fB--border-label\fR to the given string)
|
||||||
\fBchange-header(...)\fR (change header to the given string; doesn't affect \fB--header-lines\fR)
|
\fBchange-header(...)\fR (change header to the given string; doesn't affect \fB--header-lines\fR)
|
||||||
|
\fBchange-multi\fR (enable multi-select mode with no limit)
|
||||||
|
\fBchange-multi(...)\fR (enable multi-select mode with a limit or disable it with 0)
|
||||||
\fBchange-preview(...)\fR (change \fB--preview\fR option)
|
\fBchange-preview(...)\fR (change \fB--preview\fR option)
|
||||||
\fBchange-preview-label(...)\fR (change \fB--preview-label\fR to the given string)
|
\fBchange-preview-label(...)\fR (change \fB--preview-label\fR to the given string)
|
||||||
\fBchange-preview-window(...)\fR (change \fB--preview-window\fR option; rotate through the multiple option sets separated by '|')
|
\fBchange-preview-window(...)\fR (change \fB--preview-window\fR option; rotate through the multiple option sets separated by '|')
|
||||||
@@ -1439,8 +1455,6 @@ call.
|
|||||||
|
|
||||||
\fBfzf --bind "enter:become(vim {})"\fR
|
\fBfzf --bind "enter:become(vim {})"\fR
|
||||||
|
|
||||||
\fBbecome(...)\fR is not supported on Windows.
|
|
||||||
|
|
||||||
.SS RELOAD INPUT
|
.SS RELOAD INPUT
|
||||||
|
|
||||||
\fBreload(...)\fR action is used to dynamically update the input list
|
\fBreload(...)\fR action is used to dynamically update the input list
|
||||||
|
@@ -83,12 +83,28 @@ else
|
|||||||
endfunction
|
endfunction
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
let s:cmd_control_chars = ['&', '|', '<', '>', '(', ')', '@', '^', '!']
|
||||||
|
|
||||||
function! s:shellesc_cmd(arg)
|
function! s:shellesc_cmd(arg)
|
||||||
let escaped = substitute(a:arg, '[&|<>()@^]', '^&', 'g')
|
let e = '"'
|
||||||
let escaped = substitute(escaped, '%', '%%', 'g')
|
let slashes = 0
|
||||||
let escaped = substitute(escaped, '"', '\\^&', 'g')
|
for c in split(a:arg, '\zs')
|
||||||
let escaped = substitute(escaped, '\(\\\+\)\(\\^\)', '\1\1\2', 'g')
|
if c ==# '\'
|
||||||
return '^"'.substitute(escaped, '\(\\\+\)$', '\1\1', '').'^"'
|
let slashes += 1
|
||||||
|
elseif c ==# '"'
|
||||||
|
let e .= repeat('\', slashes + 1)
|
||||||
|
let slashes = 0
|
||||||
|
elseif c ==# '%'
|
||||||
|
let e .= '%'
|
||||||
|
elseif index(s:cmd_control_chars, c) >= 0
|
||||||
|
let e .= '^'
|
||||||
|
else
|
||||||
|
let slashes = 0
|
||||||
|
endif
|
||||||
|
let e .= c
|
||||||
|
endfor
|
||||||
|
let e .= repeat('\', slashes) .'"'
|
||||||
|
return e
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! fzf#shellescape(arg, ...)
|
function! fzf#shellescape(arg, ...)
|
||||||
|
@@ -32,6 +32,14 @@ if [[ $- =~ i ]]; then
|
|||||||
# To redraw line after fzf closes (printf '\e[5n')
|
# To redraw line after fzf closes (printf '\e[5n')
|
||||||
bind '"\e[0n": redraw-current-line' 2> /dev/null
|
bind '"\e[0n": redraw-current-line' 2> /dev/null
|
||||||
|
|
||||||
|
__fzf_defaults() {
|
||||||
|
# $1: Prepend to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
|
# $2: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
|
echo "--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore $1"
|
||||||
|
command cat "${FZF_DEFAULT_OPTS_FILE-}" 2> /dev/null
|
||||||
|
echo "${FZF_DEFAULT_OPTS-} $2"
|
||||||
|
}
|
||||||
|
|
||||||
__fzf_comprun() {
|
__fzf_comprun() {
|
||||||
if [[ "$(type -t _fzf_comprun 2>&1)" = function ]]; then
|
if [[ "$(type -t _fzf_comprun 2>&1)" = function ]]; then
|
||||||
_fzf_comprun "$@"
|
_fzf_comprun "$@"
|
||||||
@@ -92,128 +100,77 @@ _fzf_opts_completion() {
|
|||||||
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||||
opts="
|
opts="
|
||||||
-h --help
|
-h --help
|
||||||
-x --extended
|
|
||||||
-e --exact
|
-e --exact
|
||||||
--extended-exact
|
|
||||||
+x --no-extended
|
+x --no-extended
|
||||||
+e --no-exact
|
|
||||||
-q --query
|
-q --query
|
||||||
-f --filter
|
-f --filter
|
||||||
--literal
|
--literal
|
||||||
--no-literal
|
|
||||||
--algo
|
|
||||||
--scheme
|
--scheme
|
||||||
--expect
|
--expect
|
||||||
--no-expect
|
--disabled
|
||||||
--enabled --no-phony
|
|
||||||
--disabled --phony
|
|
||||||
--tiebreak
|
--tiebreak
|
||||||
--bind
|
--bind
|
||||||
--color
|
--color
|
||||||
--toggle-sort
|
|
||||||
-d --delimiter
|
-d --delimiter
|
||||||
-n --nth
|
-n --nth
|
||||||
--with-nth
|
--with-nth
|
||||||
-s --sort
|
|
||||||
+s --no-sort
|
+s --no-sort
|
||||||
--track
|
--track
|
||||||
--no-track
|
|
||||||
--tac
|
--tac
|
||||||
--no-tac
|
|
||||||
-i
|
-i
|
||||||
+i
|
+i
|
||||||
-m --multi
|
-m --multi
|
||||||
+m --no-multi
|
|
||||||
--ansi
|
--ansi
|
||||||
--no-ansi
|
|
||||||
--no-mouse
|
--no-mouse
|
||||||
+c --no-color
|
+c --no-color
|
||||||
+2 --no-256
|
|
||||||
--black
|
|
||||||
--no-black
|
|
||||||
--bold
|
|
||||||
--no-bold
|
--no-bold
|
||||||
--layout
|
--layout
|
||||||
--reverse
|
--reverse
|
||||||
--no-reverse
|
|
||||||
--cycle
|
--cycle
|
||||||
--no-cycle
|
|
||||||
--keep-right
|
--keep-right
|
||||||
--no-keep-right
|
|
||||||
--hscroll
|
|
||||||
--no-hscroll
|
--no-hscroll
|
||||||
--hscroll-off
|
--hscroll-off
|
||||||
--scroll-off
|
--scroll-off
|
||||||
--filepath-word
|
--filepath-word
|
||||||
--no-filepath-word
|
|
||||||
--info
|
--info
|
||||||
--no-info
|
|
||||||
--inline-info
|
|
||||||
--no-inline-info
|
|
||||||
--separator
|
--separator
|
||||||
--no-separator
|
--no-separator
|
||||||
--scrollbar
|
|
||||||
--no-scrollbar
|
--no-scrollbar
|
||||||
--jump-labels
|
--jump-labels
|
||||||
-1 --select-1
|
-1 --select-1
|
||||||
+1 --no-select-1
|
|
||||||
-0 --exit-0
|
-0 --exit-0
|
||||||
+0 --no-exit-0
|
|
||||||
--read0
|
--read0
|
||||||
--no-read0
|
|
||||||
--print0
|
--print0
|
||||||
--no-print0
|
|
||||||
--print-query
|
--print-query
|
||||||
--no-print-query
|
|
||||||
--prompt
|
--prompt
|
||||||
--pointer
|
--pointer
|
||||||
--marker
|
--marker
|
||||||
--sync
|
--sync
|
||||||
--no-sync
|
|
||||||
--async
|
|
||||||
--no-history
|
|
||||||
--history
|
--history
|
||||||
--history-size
|
--history-size
|
||||||
--no-header
|
|
||||||
--no-header-lines
|
|
||||||
--header
|
--header
|
||||||
--header-lines
|
--header-lines
|
||||||
--header-first
|
--header-first
|
||||||
--no-header-first
|
|
||||||
--ellipsis
|
--ellipsis
|
||||||
--preview
|
--preview
|
||||||
--no-preview
|
|
||||||
--preview-window
|
--preview-window
|
||||||
--height
|
--height
|
||||||
--min-height
|
--min-height
|
||||||
--no-height
|
|
||||||
--no-margin
|
|
||||||
--no-padding
|
|
||||||
--no-border
|
|
||||||
--border
|
--border
|
||||||
--no-border-label
|
|
||||||
--border-label
|
--border-label
|
||||||
--border-label-pos
|
--border-label-pos
|
||||||
--no-preview-label
|
|
||||||
--preview-label
|
--preview-label
|
||||||
--preview-label-pos
|
--preview-label-pos
|
||||||
--no-unicode
|
--no-unicode
|
||||||
--unicode
|
|
||||||
--margin
|
--margin
|
||||||
--padding
|
--padding
|
||||||
--tabstop
|
--tabstop
|
||||||
--listen
|
--listen
|
||||||
--no-listen
|
|
||||||
--clear
|
|
||||||
--no-clear
|
--no-clear
|
||||||
--version
|
--version
|
||||||
--"
|
--"
|
||||||
|
|
||||||
case "${prev}" in
|
case "${prev}" in
|
||||||
--algo)
|
|
||||||
COMPREPLY=( $(compgen -W "v1 v2" -- "$cur") )
|
|
||||||
return 0
|
|
||||||
;;
|
|
||||||
--scheme)
|
--scheme)
|
||||||
COMPREPLY=( $(compgen -W "default path history" -- "$cur") )
|
COMPREPLY=( $(compgen -W "default path history" -- "$cur") )
|
||||||
return 0
|
return 0
|
||||||
@@ -335,8 +292,8 @@ __fzf_generic_path_completion() {
|
|||||||
[[ -z "$dir" ]] && dir='.'
|
[[ -z "$dir" ]] && dir='.'
|
||||||
[[ "$dir" != "/" ]] && dir="${dir/%\//}"
|
[[ "$dir" != "/" ]] && dir="${dir/%\//}"
|
||||||
matches=$(
|
matches=$(
|
||||||
unset FZF_DEFAULT_COMMAND
|
export FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse --scheme=path" "${FZF_COMPLETION_OPTS-} $2")
|
||||||
export FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --scheme=path --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-} $2"
|
unset FZF_DEFAULT_COMMAND FZF_DEFAULT_OPTS_FILE
|
||||||
if declare -F "$1" > /dev/null; then
|
if declare -F "$1" > /dev/null; then
|
||||||
eval "$1 $(printf %q "$dir")" | __fzf_comprun "$4" -q "$leftover"
|
eval "$1 $(printf %q "$dir")" | __fzf_comprun "$4" -q "$leftover"
|
||||||
else
|
else
|
||||||
@@ -399,7 +356,10 @@ _fzf_complete() {
|
|||||||
if [[ "$cur" == *"$trigger" ]] && [[ $cur != *'$('* ]] && [[ $cur != *':='* ]] && [[ $cur != *'`'* ]]; then
|
if [[ "$cur" == *"$trigger" ]] && [[ $cur != *'$('* ]] && [[ $cur != *':='* ]] && [[ $cur != *'`'* ]]; then
|
||||||
cur=${cur:0:${#cur}-${#trigger}}
|
cur=${cur:0:${#cur}-${#trigger}}
|
||||||
|
|
||||||
selected=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-} $str_arg" __fzf_comprun "${rest[0]}" "${args[@]}" -q "$cur" | $post | command tr '\n' ' ')
|
selected=$(
|
||||||
|
FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse" "${FZF_COMPLETION_OPTS-} $str_arg") \
|
||||||
|
FZF_DEFAULT_OPTS_FILE='' \
|
||||||
|
__fzf_comprun "${rest[0]}" "${args[@]}" -q "$cur" | $post | command tr '\n' ' ')
|
||||||
selected=${selected% } # Strip trailing space not to repeat "-o nospace"
|
selected=${selected% } # Strip trailing space not to repeat "-o nospace"
|
||||||
if [[ -n "$selected" ]]; then
|
if [[ -n "$selected" ]]; then
|
||||||
COMPREPLY=("$selected")
|
COMPREPLY=("$selected")
|
||||||
@@ -503,8 +463,11 @@ complete -o default -F _fzf_opts_completion fzf
|
|||||||
# fzf-tmux specific options (like `-w WIDTH`) are left as a future patch.
|
# fzf-tmux specific options (like `-w WIDTH`) are left as a future patch.
|
||||||
complete -o default -F _fzf_opts_completion fzf-tmux
|
complete -o default -F _fzf_opts_completion fzf-tmux
|
||||||
|
|
||||||
d_cmds="${FZF_COMPLETION_DIR_COMMANDS:-cd pushd rmdir}"
|
d_cmds="${FZF_COMPLETION_DIR_COMMANDS-cd pushd rmdir}"
|
||||||
a_cmds="
|
|
||||||
|
# NOTE: $FZF_COMPLETION_PATH_COMMANDS and $FZF_COMPLETION_VAR_COMMANDS are
|
||||||
|
# undocumented and subject to change in the future.
|
||||||
|
a_cmds="${FZF_COMPLETION_PATH_COMMANDS-"
|
||||||
awk bat cat diff diff3
|
awk bat cat diff diff3
|
||||||
emacs emacsclient ex file ftp g++ gcc gvim head hg hx java
|
emacs emacsclient ex file ftp g++ gcc gvim head hg hx java
|
||||||
javac ld less more mvim nvim patch perl python ruby
|
javac ld less more mvim nvim patch perl python ruby
|
||||||
@@ -512,10 +475,11 @@ a_cmds="
|
|||||||
basename bunzip2 bzip2 chmod chown curl cp dirname du
|
basename bunzip2 bzip2 chmod chown curl cp dirname du
|
||||||
find git grep gunzip gzip hg jar
|
find git grep gunzip gzip hg jar
|
||||||
ln ls mv open rm rsync scp
|
ln ls mv open rm rsync scp
|
||||||
svn tar unzip zip"
|
svn tar unzip zip"}"
|
||||||
|
v_cmds="${FZF_COMPLETION_VAR_COMMANDS-export unset printenv}"
|
||||||
|
|
||||||
# Preserve existing completion
|
# Preserve existing completion
|
||||||
__fzf_orig_completion < <(complete -p $d_cmds $a_cmds ssh 2> /dev/null)
|
__fzf_orig_completion < <(complete -p $d_cmds $a_cmds $v_cmds unalias kill ssh 2> /dev/null)
|
||||||
|
|
||||||
if type _comp_load > /dev/null 2>&1; then
|
if type _comp_load > /dev/null 2>&1; then
|
||||||
# _comp_load was added in bash-completion 2.12 to replace _completion_loader.
|
# _comp_load was added in bash-completion 2.12 to replace _completion_loader.
|
||||||
@@ -551,10 +515,21 @@ for cmd in $d_cmds; do
|
|||||||
__fzf_defc "$cmd" _fzf_dir_completion "-o bashdefault -o nospace -o dirnames"
|
__fzf_defc "$cmd" _fzf_dir_completion "-o bashdefault -o nospace -o dirnames"
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# Variables
|
||||||
|
for cmd in $v_cmds; do
|
||||||
|
__fzf_defc "$cmd" _fzf_var_completion "-o default -o nospace -v"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Aliases
|
||||||
|
__fzf_defc unalias _fzf_alias_completion "-a"
|
||||||
|
|
||||||
|
# Processes
|
||||||
|
__fzf_defc kill _fzf_proc_completion "-o default -o bashdefault"
|
||||||
|
|
||||||
# ssh
|
# ssh
|
||||||
__fzf_defc ssh _fzf_complete_ssh "-o default -o bashdefault"
|
__fzf_defc ssh _fzf_complete_ssh "-o default -o bashdefault"
|
||||||
|
|
||||||
unset cmd d_cmds a_cmds
|
unset cmd d_cmds a_cmds v_cmds
|
||||||
|
|
||||||
_fzf_setup_completion() {
|
_fzf_setup_completion() {
|
||||||
local kind fn cmd
|
local kind fn cmd
|
||||||
@@ -576,10 +551,4 @@ _fzf_setup_completion() {
|
|||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
# Environment variables / Aliases / Hosts / Process
|
|
||||||
_fzf_setup_completion 'var' export unset printenv
|
|
||||||
_fzf_setup_completion 'alias' unalias
|
|
||||||
_fzf_setup_completion 'host' telnet
|
|
||||||
_fzf_setup_completion 'proc' kill
|
|
||||||
|
|
||||||
fi
|
fi
|
||||||
|
@@ -9,8 +9,6 @@
|
|||||||
# - $FZF_COMPLETION_TRIGGER (default: '**')
|
# - $FZF_COMPLETION_TRIGGER (default: '**')
|
||||||
# - $FZF_COMPLETION_OPTS (default: empty)
|
# - $FZF_COMPLETION_OPTS (default: empty)
|
||||||
|
|
||||||
if [[ -o interactive ]]; then
|
|
||||||
|
|
||||||
|
|
||||||
# Both branches of the following `if` do the same thing -- define
|
# Both branches of the following `if` do the same thing -- define
|
||||||
# __fzf_completion_options such that `eval $__fzf_completion_options` sets
|
# __fzf_completion_options such that `eval $__fzf_completion_options` sets
|
||||||
@@ -75,6 +73,9 @@ fi
|
|||||||
# This brace is the start of try-always block. The `always` part is like
|
# This brace is the start of try-always block. The `always` part is like
|
||||||
# `finally` in lesser languages. We use it to *always* restore user options.
|
# `finally` in lesser languages. We use it to *always* restore user options.
|
||||||
{
|
{
|
||||||
|
# The 'emulate' command should not be placed inside the interactive if check;
|
||||||
|
# placing it there fails to disable alias expansion. See #3731.
|
||||||
|
if [[ -o interactive ]]; then
|
||||||
|
|
||||||
# To use custom commands instead of find, override _fzf_compgen_{path,dir}
|
# To use custom commands instead of find, override _fzf_compgen_{path,dir}
|
||||||
#
|
#
|
||||||
@@ -93,6 +94,14 @@ fi
|
|||||||
|
|
||||||
###########################################################
|
###########################################################
|
||||||
|
|
||||||
|
__fzf_defaults() {
|
||||||
|
# $1: Prepend to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
|
# $2: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
|
echo "--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore $1"
|
||||||
|
command cat "${FZF_DEFAULT_OPTS_FILE-}" 2> /dev/null
|
||||||
|
echo "${FZF_DEFAULT_OPTS-} $2"
|
||||||
|
}
|
||||||
|
|
||||||
__fzf_comprun() {
|
__fzf_comprun() {
|
||||||
if [[ "$(type _fzf_comprun 2>&1)" =~ function ]]; then
|
if [[ "$(type _fzf_comprun 2>&1)" =~ function ]]; then
|
||||||
_fzf_comprun "$@"
|
_fzf_comprun "$@"
|
||||||
@@ -146,8 +155,8 @@ __fzf_generic_path_completion() {
|
|||||||
[ -z "$dir" ] && dir='.'
|
[ -z "$dir" ] && dir='.'
|
||||||
[ "$dir" != "/" ] && dir="${dir/%\//}"
|
[ "$dir" != "/" ] && dir="${dir/%\//}"
|
||||||
matches=$(
|
matches=$(
|
||||||
unset FZF_DEFAULT_COMMAND
|
export FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse --scheme=path" "${FZF_COMPLETION_OPTS-}")
|
||||||
export FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --scheme=path --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-}"
|
unset FZF_DEFAULT_COMMAND FZF_DEFAULT_OPTS_FILE
|
||||||
if declare -f "$compgen" > /dev/null; then
|
if declare -f "$compgen" > /dev/null; then
|
||||||
eval "$compgen $(printf %q "$dir")" | __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover"
|
eval "$compgen $(printf %q "$dir")" | __fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover"
|
||||||
else
|
else
|
||||||
@@ -217,7 +226,10 @@ _fzf_complete() {
|
|||||||
type $post > /dev/null 2>&1 || post=cat
|
type $post > /dev/null 2>&1 || post=cat
|
||||||
|
|
||||||
_fzf_feed_fifo "$fifo"
|
_fzf_feed_fifo "$fifo"
|
||||||
matches=$(FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_COMPLETION_OPTS-} $str_arg" __fzf_comprun "$cmd" "${args[@]}" -q "${(Q)prefix}" < "$fifo" | $post | tr '\n' ' ')
|
matches=$(
|
||||||
|
FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse" "${FZF_COMPLETION_OPTS-} $str_arg") \
|
||||||
|
FZF_DEFAULT_OPTS_FILE='' \
|
||||||
|
__fzf_comprun "$cmd" "${args[@]}" -q "${(Q)prefix}" < "$fifo" | $post | tr '\n' ' ')
|
||||||
if [ -n "$matches" ]; then
|
if [ -n "$matches" ]; then
|
||||||
LBUFFER="$lbuf$matches"
|
LBUFFER="$lbuf$matches"
|
||||||
fi
|
fi
|
||||||
@@ -315,7 +327,7 @@ fzf-completion() {
|
|||||||
|
|
||||||
# Trigger sequence given
|
# Trigger sequence given
|
||||||
if [ ${#tokens} -gt 1 -a "$tail" = "$trigger" ]; then
|
if [ ${#tokens} -gt 1 -a "$tail" = "$trigger" ]; then
|
||||||
d_cmds=(${=FZF_COMPLETION_DIR_COMMANDS:-cd pushd rmdir})
|
d_cmds=(${=FZF_COMPLETION_DIR_COMMANDS-cd pushd rmdir})
|
||||||
|
|
||||||
[ -z "$trigger" ] && prefix=${tokens[-1]} || prefix=${tokens[-1]:0:-${#trigger}}
|
[ -z "$trigger" ] && prefix=${tokens[-1]} || prefix=${tokens[-1]:0:-${#trigger}}
|
||||||
if [[ $prefix = *'$('* ]] || [[ $prefix = *'<('* ]] || [[ $prefix = *'>('* ]] || [[ $prefix = *':='* ]] || [[ $prefix = *'`'* ]]; then
|
if [[ $prefix = *'$('* ]] || [[ $prefix = *'<('* ]] || [[ $prefix = *'>('* ]] || [[ $prefix = *':='* ]] || [[ $prefix = *'`'* ]]; then
|
||||||
@@ -345,11 +357,10 @@ fzf-completion() {
|
|||||||
|
|
||||||
zle -N fzf-completion
|
zle -N fzf-completion
|
||||||
bindkey '^I' fzf-completion
|
bindkey '^I' fzf-completion
|
||||||
|
fi
|
||||||
|
|
||||||
} always {
|
} always {
|
||||||
# Restore the original options.
|
# Restore the original options.
|
||||||
eval $__fzf_completion_options
|
eval $__fzf_completion_options
|
||||||
'unset' '__fzf_completion_options'
|
'unset' '__fzf_completion_options'
|
||||||
}
|
}
|
||||||
|
|
||||||
fi
|
|
||||||
|
@@ -16,10 +16,19 @@ if [[ $- =~ i ]]; then
|
|||||||
|
|
||||||
# Key bindings
|
# Key bindings
|
||||||
# ------------
|
# ------------
|
||||||
|
|
||||||
|
__fzf_defaults() {
|
||||||
|
# $1: Prepend to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
|
# $2: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
|
echo "--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore $1"
|
||||||
|
command cat "${FZF_DEFAULT_OPTS_FILE-}" 2> /dev/null
|
||||||
|
echo "${FZF_DEFAULT_OPTS-} $2"
|
||||||
|
}
|
||||||
|
|
||||||
__fzf_select__() {
|
__fzf_select__() {
|
||||||
local opts
|
FZF_DEFAULT_COMMAND=${FZF_CTRL_T_COMMAND:-} \
|
||||||
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore --reverse --walker=file,dir,follow,hidden --scheme=path ${FZF_DEFAULT_OPTS-} ${FZF_CTRL_T_OPTS-} -m"
|
FZF_DEFAULT_OPTS=$(__fzf_defaults "--reverse --walker=file,dir,follow,hidden --scheme=path" "${FZF_CTRL_T_OPTS-} -m") \
|
||||||
FZF_DEFAULT_COMMAND=${FZF_CTRL_T_COMMAND:-} FZF_DEFAULT_OPTS="$opts" $(__fzfcmd) "$@" |
|
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd) "$@" |
|
||||||
while read -r item; do
|
while read -r item; do
|
||||||
printf '%q ' "$item" # escape special chars
|
printf '%q ' "$item" # escape special chars
|
||||||
done
|
done
|
||||||
@@ -37,23 +46,24 @@ fzf-file-widget() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
__fzf_cd__() {
|
__fzf_cd__() {
|
||||||
local opts dir
|
local dir
|
||||||
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore --reverse --walker=dir,follow,hidden --scheme=path ${FZF_DEFAULT_OPTS-} ${FZF_ALT_C_OPTS-} +m"
|
|
||||||
dir=$(
|
dir=$(
|
||||||
FZF_DEFAULT_COMMAND=${FZF_ALT_C_COMMAND:-} FZF_DEFAULT_OPTS="$opts" $(__fzfcmd)
|
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)
|
||||||
) && printf 'builtin cd -- %q' "$(builtin unset CDPATH && builtin cd -- "$dir" && builtin pwd)"
|
) && printf 'builtin cd -- %q' "$(builtin unset CDPATH && builtin cd -- "$dir" && builtin pwd)"
|
||||||
}
|
}
|
||||||
|
|
||||||
if command -v perl > /dev/null; then
|
if command -v perl > /dev/null; then
|
||||||
__fzf_history__() {
|
__fzf_history__() {
|
||||||
local output opts script
|
local output script
|
||||||
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} -n2..,.. --scheme=history --bind=ctrl-r:toggle-sort ${FZF_CTRL_R_OPTS-} +m --read0"
|
|
||||||
script='BEGIN { getc; $/ = "\n\t"; $HISTCOUNT = $ENV{last_hist} + 1 } s/^[ *]//; print $HISTCOUNT - $. . "\t$_" if !$seen{$_}++'
|
script='BEGIN { getc; $/ = "\n\t"; $HISTCOUNT = $ENV{last_hist} + 1 } s/^[ *]//; print $HISTCOUNT - $. . "\t$_" if !$seen{$_}++'
|
||||||
output=$(
|
output=$(
|
||||||
set +o pipefail
|
set +o pipefail
|
||||||
builtin fc -lnr -2147483648 |
|
builtin fc -lnr -2147483648 |
|
||||||
last_hist=$(HISTTIMEFORMAT='' builtin history 1) command perl -n -l0 -e "$script" |
|
last_hist=$(HISTTIMEFORMAT='' builtin history 1) command perl -n -l0 -e "$script" |
|
||||||
FZF_DEFAULT_OPTS="$opts" $(__fzfcmd) --query "$READLINE_LINE"
|
FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort ${FZF_CTRL_R_OPTS-} +m --read0") \
|
||||||
|
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd) --query "$READLINE_LINE"
|
||||||
) || return
|
) || return
|
||||||
READLINE_LINE=${output#*$'\t'}
|
READLINE_LINE=${output#*$'\t'}
|
||||||
if [[ -z "$READLINE_POINT" ]]; then
|
if [[ -z "$READLINE_POINT" ]]; then
|
||||||
@@ -64,14 +74,13 @@ if command -v perl > /dev/null; then
|
|||||||
}
|
}
|
||||||
else # awk - fallback for POSIX systems
|
else # awk - fallback for POSIX systems
|
||||||
__fzf_history__() {
|
__fzf_history__() {
|
||||||
local output opts script n x y z d
|
local output script n x y z d
|
||||||
if [[ -z $__fzf_awk ]]; then
|
if [[ -z $__fzf_awk ]]; then
|
||||||
__fzf_awk=awk
|
__fzf_awk=awk
|
||||||
# choose the faster mawk if: it's installed && build date >= 20230322 && version >= 1.3.4
|
# choose the faster mawk if: it's installed && build date >= 20230322 && version >= 1.3.4
|
||||||
IFS=' .' read n x y z d <<< $(command mawk -W version 2> /dev/null)
|
IFS=' .' read n x y z d <<< $(command mawk -W version 2> /dev/null)
|
||||||
[[ $n == mawk ]] && (( d >= 20230302 && (x *1000 +y) *1000 +z >= 1003004 )) && __fzf_awk=mawk
|
[[ $n == mawk ]] && (( d >= 20230302 && (x *1000 +y) *1000 +z >= 1003004 )) && __fzf_awk=mawk
|
||||||
fi
|
fi
|
||||||
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} -n2..,.. --scheme=history --bind=ctrl-r:toggle-sort ${FZF_CTRL_R_OPTS-} +m --read0"
|
|
||||||
[[ $(HISTTIMEFORMAT='' builtin history 1) =~ [[:digit:]]+ ]] # how many history entries
|
[[ $(HISTTIMEFORMAT='' builtin history 1) =~ [[:digit:]]+ ]] # how many history entries
|
||||||
script='function P(b) { ++n; sub(/^[ *]/, "", b); if (!seen[b]++) { printf "%d\t%s%c", '$((BASH_REMATCH + 1))' - n, b, 0 } }
|
script='function P(b) { ++n; sub(/^[ *]/, "", b); if (!seen[b]++) { printf "%d\t%s%c", '$((BASH_REMATCH + 1))' - n, b, 0 } }
|
||||||
NR==1 { b = substr($0, 2); next }
|
NR==1 { b = substr($0, 2); next }
|
||||||
@@ -82,7 +91,8 @@ else # awk - fallback for POSIX systems
|
|||||||
set +o pipefail
|
set +o pipefail
|
||||||
builtin fc -lnr -2147483648 2> /dev/null | # ( $'\t '<lines>$'\n' )* ; <lines> ::= [^\n]* ( $'\n'<lines> )*
|
builtin fc -lnr -2147483648 2> /dev/null | # ( $'\t '<lines>$'\n' )* ; <lines> ::= [^\n]* ( $'\n'<lines> )*
|
||||||
command $__fzf_awk "$script" | # ( <counter>$'\t'<lines>$'\000' )*
|
command $__fzf_awk "$script" | # ( <counter>$'\t'<lines>$'\000' )*
|
||||||
FZF_DEFAULT_OPTS="$opts" $(__fzfcmd) --query "$READLINE_LINE"
|
FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort ${FZF_CTRL_R_OPTS-} +m --read0") \
|
||||||
|
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd) --query "$READLINE_LINE"
|
||||||
) || return
|
) || return
|
||||||
READLINE_LINE=${output#*$'\t'}
|
READLINE_LINE=${output#*$'\t'}
|
||||||
if [[ -z "$READLINE_POINT" ]]; then
|
if [[ -z "$READLINE_POINT" ]]; then
|
||||||
|
@@ -18,6 +18,15 @@ status is-interactive; or exit 0
|
|||||||
# ------------
|
# ------------
|
||||||
function fzf_key_bindings
|
function fzf_key_bindings
|
||||||
|
|
||||||
|
function __fzf_defaults
|
||||||
|
# $1: Prepend to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
|
# $2: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
|
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
||||||
|
echo "--height $FZF_TMUX_HEIGHT --bind=ctrl-z:ignore" $argv[1]
|
||||||
|
command cat "$FZF_DEFAULT_OPTS_FILE" 2> /dev/null
|
||||||
|
echo $FZF_DEFAULT_OPTS $argv[2]
|
||||||
|
end
|
||||||
|
|
||||||
# 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"
|
||||||
set -l commandline (__fzf_parse_commandline)
|
set -l commandline (__fzf_parse_commandline)
|
||||||
@@ -27,8 +36,9 @@ function fzf_key_bindings
|
|||||||
|
|
||||||
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
||||||
begin
|
begin
|
||||||
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse --walker=file,dir,follow,hidden --walker-root='$dir' --scheme=path --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS"
|
set -lx FZF_DEFAULT_OPTS (__fzf_defaults "--reverse --walker=file,dir,follow,hidden --scheme=path --walker-root='$dir'" "$FZF_CTRL_T_OPTS")
|
||||||
set -lx FZF_DEFAULT_COMMAND "$FZF_CTRL_T_COMMAND"
|
set -lx FZF_DEFAULT_COMMAND "$FZF_CTRL_T_COMMAND"
|
||||||
|
set -lx FZF_DEFAULT_OPTS_FILE ''
|
||||||
eval (__fzfcmd)' -m --query "'$fzf_query'"' | while read -l r; set result $result $r; end
|
eval (__fzfcmd)' -m --query "'$fzf_query'"' | while read -l r; set result $result $r; end
|
||||||
end
|
end
|
||||||
if [ -z "$result" ]
|
if [ -z "$result" ]
|
||||||
@@ -49,7 +59,8 @@ function fzf_key_bindings
|
|||||||
function fzf-history-widget -d "Show command history"
|
function fzf-history-widget -d "Show command history"
|
||||||
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
||||||
begin
|
begin
|
||||||
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT $FZF_DEFAULT_OPTS --scheme=history --bind=ctrl-r:toggle-sort,ctrl-z:ignore $FZF_CTRL_R_OPTS +m"
|
set -lx FZF_DEFAULT_OPTS (__fzf_defaults "" "--scheme=history --bind=ctrl-r:toggle-sort $FZF_CTRL_R_OPTS +m")
|
||||||
|
set -lx FZF_DEFAULT_OPTS_FILE ''
|
||||||
|
|
||||||
set -l FISH_MAJOR (echo $version | cut -f1 -d.)
|
set -l FISH_MAJOR (echo $version | cut -f1 -d.)
|
||||||
set -l FISH_MINOR (echo $version | cut -f2 -d.)
|
set -l FISH_MINOR (echo $version | cut -f2 -d.)
|
||||||
@@ -76,7 +87,8 @@ function fzf_key_bindings
|
|||||||
|
|
||||||
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
||||||
begin
|
begin
|
||||||
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse --walker=dir,follow,hidden --walker-root='$dir' --scheme=path --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS"
|
set -lx FZF_DEFAULT_OPTS (__fzf_defaults "--reverse --walker=dir,follow,hidden --scheme=path --walker-root='$dir'" "$FZF_ALT_C_OPTS")
|
||||||
|
set -lx FZF_DEFAULT_OPTS_FILE ''
|
||||||
set -lx FZF_DEFAULT_COMMAND "$FZF_ALT_C_COMMAND"
|
set -lx FZF_DEFAULT_COMMAND "$FZF_ALT_C_COMMAND"
|
||||||
eval (__fzfcmd)' +m --query "'$fzf_query'"' | read -l result
|
eval (__fzfcmd)' +m --query "'$fzf_query'"' | read -l result
|
||||||
|
|
||||||
|
@@ -11,8 +11,6 @@
|
|||||||
# - $FZF_ALT_C_COMMAND
|
# - $FZF_ALT_C_COMMAND
|
||||||
# - $FZF_ALT_C_OPTS
|
# - $FZF_ALT_C_OPTS
|
||||||
|
|
||||||
if [[ -o interactive ]]; then
|
|
||||||
|
|
||||||
|
|
||||||
# Key bindings
|
# Key bindings
|
||||||
# ------------
|
# ------------
|
||||||
@@ -38,12 +36,23 @@ fi
|
|||||||
'builtin' 'emulate' 'zsh' && 'builtin' 'setopt' 'no_aliases'
|
'builtin' 'emulate' 'zsh' && 'builtin' 'setopt' 'no_aliases'
|
||||||
|
|
||||||
{
|
{
|
||||||
|
if [[ -o interactive ]]; then
|
||||||
|
|
||||||
|
__fzf_defaults() {
|
||||||
|
# $1: Prepend to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
|
# $2: Append to FZF_DEFAULT_OPTS_FILE and FZF_DEFAULT_OPTS
|
||||||
|
echo "--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore $1"
|
||||||
|
command cat "${FZF_DEFAULT_OPTS_FILE-}" 2> /dev/null
|
||||||
|
echo "${FZF_DEFAULT_OPTS-} $2"
|
||||||
|
}
|
||||||
|
|
||||||
# CTRL-T - Paste the selected file path(s) into the command line
|
# CTRL-T - Paste the selected file path(s) into the command line
|
||||||
__fsel() {
|
__fzf_select() {
|
||||||
setopt localoptions pipefail no_aliases 2> /dev/null
|
setopt localoptions pipefail no_aliases 2> /dev/null
|
||||||
local item
|
local item
|
||||||
FZF_DEFAULT_COMMAND=${FZF_CTRL_T_COMMAND:-} FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --walker=file,dir,follow,hidden --scheme=path --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_CTRL_T_OPTS-}" $(__fzfcmd) -m "$@" < /dev/tty | while read item; do
|
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) "$@" < /dev/tty | while read item; do
|
||||||
echo -n "${(q)item} "
|
echo -n "${(q)item} "
|
||||||
done
|
done
|
||||||
local ret=$?
|
local ret=$?
|
||||||
@@ -57,7 +66,7 @@ __fzfcmd() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fzf-file-widget() {
|
fzf-file-widget() {
|
||||||
LBUFFER="${LBUFFER}$(__fsel)"
|
LBUFFER="${LBUFFER}$(__fzf_select)"
|
||||||
local ret=$?
|
local ret=$?
|
||||||
zle reset-prompt
|
zle reset-prompt
|
||||||
return $ret
|
return $ret
|
||||||
@@ -72,7 +81,10 @@ fi
|
|||||||
# ALT-C - cd into the selected directory
|
# ALT-C - cd into the selected directory
|
||||||
fzf-cd-widget() {
|
fzf-cd-widget() {
|
||||||
setopt localoptions pipefail no_aliases 2> /dev/null
|
setopt localoptions pipefail no_aliases 2> /dev/null
|
||||||
local dir="$(FZF_DEFAULT_COMMAND=${FZF_ALT_C_COMMAND:-} FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse --walker=dir,follow,hidden --scheme=path --bind=ctrl-z:ignore ${FZF_DEFAULT_OPTS-} ${FZF_ALT_C_OPTS-}" $(__fzfcmd) +m < /dev/tty)"
|
local 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) < /dev/tty)"
|
||||||
if [[ -z "$dir" ]]; then
|
if [[ -z "$dir" ]]; then
|
||||||
zle redisplay
|
zle redisplay
|
||||||
return 0
|
return 0
|
||||||
@@ -97,7 +109,8 @@ fzf-history-widget() {
|
|||||||
local selected num
|
local selected num
|
||||||
setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases 2> /dev/null
|
setopt localoptions noglobsubst noposixbuiltins pipefail no_aliases 2> /dev/null
|
||||||
selected="$(fc -rl 1 | awk '{ cmd=$0; sub(/^[ \t]*[0-9]+\**[ \t]+/, "", cmd); if (!seen[cmd]++) print $0 }' |
|
selected="$(fc -rl 1 | awk '{ cmd=$0; sub(/^[ \t]*[0-9]+\**[ \t]+/, "", cmd); if (!seen[cmd]++) print $0 }' |
|
||||||
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} ${FZF_DEFAULT_OPTS-} -n2..,.. --scheme=history --bind=ctrl-r:toggle-sort,ctrl-z:ignore ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER} +m" $(__fzfcmd))"
|
FZF_DEFAULT_OPTS=$(__fzf_defaults "" "-n2..,.. --scheme=history --bind=ctrl-r:toggle-sort ${FZF_CTRL_R_OPTS-} --query=${(qqq)LBUFFER} +m") \
|
||||||
|
FZF_DEFAULT_OPTS_FILE='' $(__fzfcmd))"
|
||||||
local ret=$?
|
local ret=$?
|
||||||
if [ -n "$selected" ]; then
|
if [ -n "$selected" ]; then
|
||||||
num=$(awk '{print $1}' <<< "$selected")
|
num=$(awk '{print $1}' <<< "$selected")
|
||||||
@@ -114,10 +127,9 @@ zle -N fzf-history-widget
|
|||||||
bindkey -M emacs '^R' fzf-history-widget
|
bindkey -M emacs '^R' fzf-history-widget
|
||||||
bindkey -M vicmd '^R' fzf-history-widget
|
bindkey -M vicmd '^R' fzf-history-widget
|
||||||
bindkey -M viins '^R' fzf-history-widget
|
bindkey -M viins '^R' fzf-history-widget
|
||||||
|
fi
|
||||||
|
|
||||||
} always {
|
} always {
|
||||||
eval $__fzf_key_bindings_options
|
eval $__fzf_key_bindings_options
|
||||||
'unset' '__fzf_key_bindings_options'
|
'unset' '__fzf_key_bindings_options'
|
||||||
}
|
}
|
||||||
|
|
||||||
fi
|
|
||||||
|
@@ -26,102 +26,103 @@ func _() {
|
|||||||
_ = x[actCancel-15]
|
_ = x[actCancel-15]
|
||||||
_ = x[actChangeBorderLabel-16]
|
_ = x[actChangeBorderLabel-16]
|
||||||
_ = x[actChangeHeader-17]
|
_ = x[actChangeHeader-17]
|
||||||
_ = x[actChangePreviewLabel-18]
|
_ = x[actChangeMulti-18]
|
||||||
_ = x[actChangePrompt-19]
|
_ = x[actChangePreviewLabel-19]
|
||||||
_ = x[actChangeQuery-20]
|
_ = x[actChangePrompt-20]
|
||||||
_ = x[actClearScreen-21]
|
_ = x[actChangeQuery-21]
|
||||||
_ = x[actClearQuery-22]
|
_ = x[actClearScreen-22]
|
||||||
_ = x[actClearSelection-23]
|
_ = x[actClearQuery-23]
|
||||||
_ = x[actClose-24]
|
_ = x[actClearSelection-24]
|
||||||
_ = x[actDeleteChar-25]
|
_ = x[actClose-25]
|
||||||
_ = x[actDeleteCharEof-26]
|
_ = x[actDeleteChar-26]
|
||||||
_ = x[actEndOfLine-27]
|
_ = x[actDeleteCharEof-27]
|
||||||
_ = x[actForwardChar-28]
|
_ = x[actEndOfLine-28]
|
||||||
_ = x[actForwardWord-29]
|
_ = x[actForwardChar-29]
|
||||||
_ = x[actKillLine-30]
|
_ = x[actForwardWord-30]
|
||||||
_ = x[actKillWord-31]
|
_ = x[actKillLine-31]
|
||||||
_ = x[actUnixLineDiscard-32]
|
_ = x[actKillWord-32]
|
||||||
_ = x[actUnixWordRubout-33]
|
_ = x[actUnixLineDiscard-33]
|
||||||
_ = x[actYank-34]
|
_ = x[actUnixWordRubout-34]
|
||||||
_ = x[actBackwardKillWord-35]
|
_ = x[actYank-35]
|
||||||
_ = x[actSelectAll-36]
|
_ = x[actBackwardKillWord-36]
|
||||||
_ = x[actDeselectAll-37]
|
_ = x[actSelectAll-37]
|
||||||
_ = x[actToggle-38]
|
_ = x[actDeselectAll-38]
|
||||||
_ = x[actToggleSearch-39]
|
_ = x[actToggle-39]
|
||||||
_ = x[actToggleAll-40]
|
_ = x[actToggleSearch-40]
|
||||||
_ = x[actToggleDown-41]
|
_ = x[actToggleAll-41]
|
||||||
_ = x[actToggleUp-42]
|
_ = x[actToggleDown-42]
|
||||||
_ = x[actToggleIn-43]
|
_ = x[actToggleUp-43]
|
||||||
_ = x[actToggleOut-44]
|
_ = x[actToggleIn-44]
|
||||||
_ = x[actToggleTrack-45]
|
_ = x[actToggleOut-45]
|
||||||
_ = x[actToggleTrackCurrent-46]
|
_ = x[actToggleTrack-46]
|
||||||
_ = x[actToggleHeader-47]
|
_ = x[actToggleTrackCurrent-47]
|
||||||
_ = x[actTrackCurrent-48]
|
_ = x[actToggleHeader-48]
|
||||||
_ = x[actUntrackCurrent-49]
|
_ = x[actTrackCurrent-49]
|
||||||
_ = x[actDown-50]
|
_ = x[actUntrackCurrent-50]
|
||||||
_ = x[actUp-51]
|
_ = x[actDown-51]
|
||||||
_ = x[actPageUp-52]
|
_ = x[actUp-52]
|
||||||
_ = x[actPageDown-53]
|
_ = x[actPageUp-53]
|
||||||
_ = x[actPosition-54]
|
_ = x[actPageDown-54]
|
||||||
_ = x[actHalfPageUp-55]
|
_ = x[actPosition-55]
|
||||||
_ = x[actHalfPageDown-56]
|
_ = x[actHalfPageUp-56]
|
||||||
_ = x[actOffsetUp-57]
|
_ = x[actHalfPageDown-57]
|
||||||
_ = x[actOffsetDown-58]
|
_ = x[actOffsetUp-58]
|
||||||
_ = x[actJump-59]
|
_ = x[actOffsetDown-59]
|
||||||
_ = x[actJumpAccept-60]
|
_ = x[actJump-60]
|
||||||
_ = x[actPrintQuery-61]
|
_ = x[actJumpAccept-61]
|
||||||
_ = x[actRefreshPreview-62]
|
_ = x[actPrintQuery-62]
|
||||||
_ = x[actReplaceQuery-63]
|
_ = x[actRefreshPreview-63]
|
||||||
_ = x[actToggleSort-64]
|
_ = x[actReplaceQuery-64]
|
||||||
_ = x[actShowPreview-65]
|
_ = x[actToggleSort-65]
|
||||||
_ = x[actHidePreview-66]
|
_ = x[actShowPreview-66]
|
||||||
_ = x[actTogglePreview-67]
|
_ = x[actHidePreview-67]
|
||||||
_ = x[actTogglePreviewWrap-68]
|
_ = x[actTogglePreview-68]
|
||||||
_ = x[actTransform-69]
|
_ = x[actTogglePreviewWrap-69]
|
||||||
_ = x[actTransformBorderLabel-70]
|
_ = x[actTransform-70]
|
||||||
_ = x[actTransformHeader-71]
|
_ = x[actTransformBorderLabel-71]
|
||||||
_ = x[actTransformPreviewLabel-72]
|
_ = x[actTransformHeader-72]
|
||||||
_ = x[actTransformPrompt-73]
|
_ = x[actTransformPreviewLabel-73]
|
||||||
_ = x[actTransformQuery-74]
|
_ = x[actTransformPrompt-74]
|
||||||
_ = x[actPreview-75]
|
_ = x[actTransformQuery-75]
|
||||||
_ = x[actChangePreview-76]
|
_ = x[actPreview-76]
|
||||||
_ = x[actChangePreviewWindow-77]
|
_ = x[actChangePreview-77]
|
||||||
_ = x[actPreviewTop-78]
|
_ = x[actChangePreviewWindow-78]
|
||||||
_ = x[actPreviewBottom-79]
|
_ = x[actPreviewTop-79]
|
||||||
_ = x[actPreviewUp-80]
|
_ = x[actPreviewBottom-80]
|
||||||
_ = x[actPreviewDown-81]
|
_ = x[actPreviewUp-81]
|
||||||
_ = x[actPreviewPageUp-82]
|
_ = x[actPreviewDown-82]
|
||||||
_ = x[actPreviewPageDown-83]
|
_ = x[actPreviewPageUp-83]
|
||||||
_ = x[actPreviewHalfPageUp-84]
|
_ = x[actPreviewPageDown-84]
|
||||||
_ = x[actPreviewHalfPageDown-85]
|
_ = x[actPreviewHalfPageUp-85]
|
||||||
_ = x[actPrevHistory-86]
|
_ = x[actPreviewHalfPageDown-86]
|
||||||
_ = x[actPrevSelected-87]
|
_ = x[actPrevHistory-87]
|
||||||
_ = x[actPut-88]
|
_ = x[actPrevSelected-88]
|
||||||
_ = x[actNextHistory-89]
|
_ = x[actPut-89]
|
||||||
_ = x[actNextSelected-90]
|
_ = x[actNextHistory-90]
|
||||||
_ = x[actExecute-91]
|
_ = x[actNextSelected-91]
|
||||||
_ = x[actExecuteSilent-92]
|
_ = x[actExecute-92]
|
||||||
_ = x[actExecuteMulti-93]
|
_ = x[actExecuteSilent-93]
|
||||||
_ = x[actSigStop-94]
|
_ = x[actExecuteMulti-94]
|
||||||
_ = x[actFirst-95]
|
_ = x[actSigStop-95]
|
||||||
_ = x[actLast-96]
|
_ = x[actFirst-96]
|
||||||
_ = x[actReload-97]
|
_ = x[actLast-97]
|
||||||
_ = x[actReloadSync-98]
|
_ = x[actReload-98]
|
||||||
_ = x[actDisableSearch-99]
|
_ = x[actReloadSync-99]
|
||||||
_ = x[actEnableSearch-100]
|
_ = x[actDisableSearch-100]
|
||||||
_ = x[actSelect-101]
|
_ = x[actEnableSearch-101]
|
||||||
_ = x[actDeselect-102]
|
_ = x[actSelect-102]
|
||||||
_ = x[actUnbind-103]
|
_ = x[actDeselect-103]
|
||||||
_ = x[actRebind-104]
|
_ = x[actUnbind-104]
|
||||||
_ = x[actBecome-105]
|
_ = x[actRebind-105]
|
||||||
_ = x[actResponse-106]
|
_ = x[actBecome-106]
|
||||||
_ = x[actShowHeader-107]
|
_ = x[actResponse-107]
|
||||||
_ = x[actHideHeader-108]
|
_ = x[actShowHeader-108]
|
||||||
|
_ = x[actHideHeader-109]
|
||||||
}
|
}
|
||||||
|
|
||||||
const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeHeaderactChangePreviewLabelactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactTrackCurrentactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformHeaderactTransformPreviewLabelactTransformPromptactTransformQueryactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactBecomeactResponseactShowHeaderactHideHeader"
|
const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeHeaderactChangeMultiactChangePreviewLabelactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactTrackCurrentactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformHeaderactTransformPreviewLabelactTransformPromptactTransformQueryactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactBecomeactResponseactShowHeaderactHideHeader"
|
||||||
|
|
||||||
var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 42, 50, 68, 76, 85, 102, 123, 138, 159, 183, 198, 207, 227, 242, 263, 278, 292, 306, 319, 336, 344, 357, 373, 385, 399, 413, 424, 435, 453, 470, 477, 496, 508, 522, 531, 546, 558, 571, 582, 593, 605, 619, 640, 655, 670, 687, 694, 699, 708, 719, 730, 743, 758, 769, 782, 789, 802, 815, 832, 847, 860, 874, 888, 904, 924, 936, 959, 977, 1001, 1019, 1036, 1046, 1062, 1084, 1097, 1113, 1125, 1139, 1155, 1173, 1193, 1215, 1229, 1244, 1250, 1264, 1279, 1289, 1305, 1320, 1330, 1338, 1345, 1354, 1367, 1383, 1398, 1407, 1418, 1427, 1436, 1445, 1456, 1469, 1482}
|
var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 42, 50, 68, 76, 85, 102, 123, 138, 159, 183, 198, 207, 227, 242, 256, 277, 292, 306, 320, 333, 350, 358, 371, 387, 399, 413, 427, 438, 449, 467, 484, 491, 510, 522, 536, 545, 560, 572, 585, 596, 607, 619, 633, 654, 669, 684, 701, 708, 713, 722, 733, 744, 757, 772, 783, 796, 803, 816, 829, 846, 861, 874, 888, 902, 918, 938, 950, 973, 991, 1015, 1033, 1050, 1060, 1076, 1098, 1111, 1127, 1139, 1153, 1169, 1187, 1207, 1229, 1243, 1258, 1264, 1278, 1293, 1303, 1319, 1334, 1344, 1352, 1359, 1368, 1381, 1397, 1412, 1421, 1432, 1441, 1450, 1459, 1470, 1483, 1496}
|
||||||
|
|
||||||
func (i actionType) String() string {
|
func (i actionType) String() string {
|
||||||
if i < 0 || i >= actionType(len(_actionType_index)-1) {
|
if i < 0 || i >= actionType(len(_actionType_index)-1) {
|
||||||
|
14
src/core.go
14
src/core.go
@@ -121,13 +121,16 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Process executor
|
||||||
|
executor := util.NewExecutor(opts.WithShell)
|
||||||
|
|
||||||
// Reader
|
// Reader
|
||||||
streamingFilter := opts.Filter != nil && !sort && !opts.Tac && !opts.Sync
|
streamingFilter := opts.Filter != nil && !sort && !opts.Tac && !opts.Sync
|
||||||
var reader *Reader
|
var reader *Reader
|
||||||
if !streamingFilter {
|
if !streamingFilter {
|
||||||
reader = NewReader(func(data []byte) bool {
|
reader = NewReader(func(data []byte) bool {
|
||||||
return chunkList.Push(data)
|
return chunkList.Push(data)
|
||||||
}, eventBox, opts.ReadZero, opts.Filter == nil)
|
}, eventBox, executor, opts.ReadZero, opts.Filter == nil)
|
||||||
go reader.ReadSource(opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip)
|
go reader.ReadSource(opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,7 +181,7 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
mutex.Unlock()
|
mutex.Unlock()
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}, eventBox, opts.ReadZero, false)
|
}, eventBox, executor, opts.ReadZero, false)
|
||||||
reader.ReadSource(opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip)
|
reader.ReadSource(opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip)
|
||||||
} else {
|
} else {
|
||||||
eventBox.Unwatch(EvtReadNew)
|
eventBox.Unwatch(EvtReadNew)
|
||||||
@@ -209,7 +212,7 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
go matcher.Loop()
|
go matcher.Loop()
|
||||||
|
|
||||||
// Terminal I/O
|
// Terminal I/O
|
||||||
terminal := NewTerminal(opts, eventBox)
|
terminal := NewTerminal(opts, eventBox, executor)
|
||||||
maxFit := 0 // Maximum number of items that can fit on screen
|
maxFit := 0 // Maximum number of items that can fit on screen
|
||||||
padHeight := 0
|
padHeight := 0
|
||||||
heightUnknown := opts.Height.auto
|
heightUnknown := opts.Height.auto
|
||||||
@@ -298,8 +301,7 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
total = count
|
total = count
|
||||||
terminal.UpdateCount(total, !reading, value.(*string))
|
terminal.UpdateCount(total, !reading, value.(*string))
|
||||||
if opts.Sync {
|
if opts.Sync {
|
||||||
opts.Sync = false
|
terminal.UpdateList(PassMerger(&snapshot, opts.Tac, snapshotRevision), false)
|
||||||
terminal.UpdateList(PassMerger(&snapshot, opts.Tac, snapshotRevision))
|
|
||||||
}
|
}
|
||||||
if heightUnknown && !deferred {
|
if heightUnknown && !deferred {
|
||||||
determine(!reading)
|
determine(!reading)
|
||||||
@@ -384,7 +386,7 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
determine(val.final)
|
determine(val.final)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
terminal.UpdateList(val)
|
terminal.UpdateList(val, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -57,7 +57,7 @@ const usage = `usage: fzf [options]
|
|||||||
Layout
|
Layout
|
||||||
--height=[~]HEIGHT[%] Display fzf window below the cursor with the given
|
--height=[~]HEIGHT[%] Display fzf window below the cursor with the given
|
||||||
height instead of using fullscreen.
|
height instead of using fullscreen.
|
||||||
A negative value is calcalated as the terminal height
|
A negative value is calculated as the terminal height
|
||||||
minus the given value.
|
minus the given value.
|
||||||
If prefixed with '~', fzf will determine the height
|
If prefixed with '~', fzf will determine the height
|
||||||
according to the input size.
|
according to the input size.
|
||||||
@@ -120,6 +120,7 @@ const usage = `usage: fzf [options]
|
|||||||
--read0 Read input delimited by ASCII NUL characters
|
--read0 Read input delimited by ASCII NUL characters
|
||||||
--print0 Print output delimited by ASCII NUL characters
|
--print0 Print output delimited by ASCII NUL characters
|
||||||
--sync Synchronous search for multi-staged filtering
|
--sync Synchronous search for multi-staged filtering
|
||||||
|
--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 (POST /)
|
||||||
(To allow remote process execution, use --listen-unsafe)
|
(To allow remote process execution, use --listen-unsafe)
|
||||||
--version Display version information and exit
|
--version Display version information and exit
|
||||||
@@ -356,6 +357,7 @@ type Options struct {
|
|||||||
Unicode bool
|
Unicode bool
|
||||||
Ambidouble bool
|
Ambidouble bool
|
||||||
Tabstop int
|
Tabstop int
|
||||||
|
WithShell string
|
||||||
ListenAddr *listenAddress
|
ListenAddr *listenAddress
|
||||||
Unsafe bool
|
Unsafe bool
|
||||||
ClearOnExit bool
|
ClearOnExit bool
|
||||||
@@ -1055,7 +1057,7 @@ const (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
executeRegexp = regexp.MustCompile(
|
executeRegexp = regexp.MustCompile(
|
||||||
`(?si)[:+](become|execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|transform)-(?:header|query|prompt|border-label|preview-label)|transform|change-preview-window|change-preview|(?:re|un)bind|pos|put)`)
|
`(?si)[:+](become|execute(?:-multi|-silent)?|reload(?:-sync)?|preview|(?:change|transform)-(?:header|query|prompt|border-label|preview-label)|transform|change-(?:preview-window|preview|multi)|(?:re|un)bind|pos|put)`)
|
||||||
splitRegexp = regexp.MustCompile("[,:]+")
|
splitRegexp = regexp.MustCompile("[,:]+")
|
||||||
actionNameRegexp = regexp.MustCompile("(?i)^[a-z-]+")
|
actionNameRegexp = regexp.MustCompile("(?i)^[a-z-]+")
|
||||||
}
|
}
|
||||||
@@ -1306,6 +1308,8 @@ func parseActionList(masked string, original string, prevActions []*action, putA
|
|||||||
if t == actIgnore {
|
if t == actIgnore {
|
||||||
if specIndex == 0 && specLower == "" {
|
if specIndex == 0 && specLower == "" {
|
||||||
actions = append(prevActions, actions...)
|
actions = append(prevActions, actions...)
|
||||||
|
} else if specLower == "change-multi" {
|
||||||
|
appendAction(actChangeMulti)
|
||||||
} else {
|
} else {
|
||||||
exit("unknown action: " + spec)
|
exit("unknown action: " + spec)
|
||||||
}
|
}
|
||||||
@@ -1325,10 +1329,6 @@ func parseActionList(masked string, original string, prevActions []*action, putA
|
|||||||
actions = append(actions, &action{t: t, a: actionArg})
|
actions = append(actions, &action{t: t, a: actionArg})
|
||||||
}
|
}
|
||||||
switch t {
|
switch t {
|
||||||
case actBecome:
|
|
||||||
if util.IsWindows() {
|
|
||||||
exit("become action is not supported on Windows")
|
|
||||||
}
|
|
||||||
case actUnbind, actRebind:
|
case actUnbind, actRebind:
|
||||||
parseKeyChordsImpl(actionArg, spec[0:offset]+" target required", exit)
|
parseKeyChordsImpl(actionArg, spec[0:offset]+" target required", exit)
|
||||||
case actChangePreviewWindow:
|
case actChangePreviewWindow:
|
||||||
@@ -1407,6 +1407,8 @@ func isExecuteAction(str string) actionType {
|
|||||||
return actChangePrompt
|
return actChangePrompt
|
||||||
case "change-query":
|
case "change-query":
|
||||||
return actChangeQuery
|
return actChangeQuery
|
||||||
|
case "change-multi":
|
||||||
|
return actChangeMulti
|
||||||
case "pos":
|
case "pos":
|
||||||
return actPosition
|
return actPosition
|
||||||
case "execute":
|
case "execute":
|
||||||
@@ -1876,9 +1878,7 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.Marker = firstLine(nextString(allArgs, &i, "selected sign string required"))
|
opts.Marker = firstLine(nextString(allArgs, &i, "selected sign string required"))
|
||||||
case "--sync":
|
case "--sync":
|
||||||
opts.Sync = true
|
opts.Sync = true
|
||||||
case "--no-sync":
|
case "--no-sync", "--async":
|
||||||
opts.Sync = false
|
|
||||||
case "--async":
|
|
||||||
opts.Sync = false
|
opts.Sync = false
|
||||||
case "--no-history":
|
case "--no-history":
|
||||||
opts.History = nil
|
opts.History = nil
|
||||||
@@ -1955,6 +1955,8 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
nextString(allArgs, &i, "padding required (TRBL / TB,RL / T,RL,B / T,R,B,L)"))
|
nextString(allArgs, &i, "padding required (TRBL / TB,RL / T,RL,B / T,R,B,L)"))
|
||||||
case "--tabstop":
|
case "--tabstop":
|
||||||
opts.Tabstop = nextInt(allArgs, &i, "tab stop required")
|
opts.Tabstop = nextInt(allArgs, &i, "tab stop required")
|
||||||
|
case "--with-shell":
|
||||||
|
opts.WithShell = nextString(allArgs, &i, "shell command and flags required")
|
||||||
case "--listen", "--listen-unsafe":
|
case "--listen", "--listen-unsafe":
|
||||||
given, str := optionalNextString(allArgs, &i)
|
given, str := optionalNextString(allArgs, &i)
|
||||||
addr := defaultListenAddr
|
addr := defaultListenAddr
|
||||||
@@ -2071,6 +2073,8 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.Padding = parseMargin("padding", value)
|
opts.Padding = parseMargin("padding", value)
|
||||||
} else if match, value := optString(arg, "--tabstop="); match {
|
} else if match, value := optString(arg, "--tabstop="); match {
|
||||||
opts.Tabstop = atoi(value)
|
opts.Tabstop = atoi(value)
|
||||||
|
} else if match, value := optString(arg, "--with-shell="); match {
|
||||||
|
opts.WithShell = value
|
||||||
} else if match, value := optString(arg, "--listen="); match {
|
} else if match, value := optString(arg, "--listen="); match {
|
||||||
addr, err := parseListenAddress(value)
|
addr, err := parseListenAddress(value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@@ -18,6 +18,7 @@ import (
|
|||||||
// Reader reads from command or standard input
|
// Reader reads from command or standard input
|
||||||
type Reader struct {
|
type Reader struct {
|
||||||
pusher func([]byte) bool
|
pusher func([]byte) bool
|
||||||
|
executor *util.Executor
|
||||||
eventBox *util.EventBox
|
eventBox *util.EventBox
|
||||||
delimNil bool
|
delimNil bool
|
||||||
event int32
|
event int32
|
||||||
@@ -30,8 +31,8 @@ type Reader struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewReader returns new Reader object
|
// NewReader returns new Reader object
|
||||||
func NewReader(pusher func([]byte) bool, eventBox *util.EventBox, delimNil bool, wait bool) *Reader {
|
func NewReader(pusher func([]byte) bool, eventBox *util.EventBox, executor *util.Executor, delimNil bool, wait bool) *Reader {
|
||||||
return &Reader{pusher, eventBox, delimNil, int32(EvtReady), make(chan bool, 1), sync.Mutex{}, nil, nil, false, wait}
|
return &Reader{pusher, executor, eventBox, delimNil, int32(EvtReady), make(chan bool, 1), sync.Mutex{}, nil, nil, false, wait}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) startEventPoller() {
|
func (r *Reader) startEventPoller() {
|
||||||
@@ -147,7 +148,7 @@ func (r *Reader) feed(src io.Reader) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// We're not making any progress after 100 tries. Stop.
|
// We're not making any progress after 100 tries. Stop.
|
||||||
if n == 0 && err == nil {
|
if n == 0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,7 +243,7 @@ func (r *Reader) readFromCommand(command string, environ []string) bool {
|
|||||||
r.mutex.Lock()
|
r.mutex.Lock()
|
||||||
r.killed = false
|
r.killed = false
|
||||||
r.command = &command
|
r.command = &command
|
||||||
r.exec = util.ExecCommand(command, true)
|
r.exec = r.executor.ExecCommand(command, true)
|
||||||
if environ != nil {
|
if environ != nil {
|
||||||
r.exec.Env = environ
|
r.exec.Env = environ
|
||||||
}
|
}
|
||||||
|
@@ -10,9 +10,10 @@ import (
|
|||||||
func TestReadFromCommand(t *testing.T) {
|
func TestReadFromCommand(t *testing.T) {
|
||||||
strs := []string{}
|
strs := []string{}
|
||||||
eb := util.NewEventBox()
|
eb := util.NewEventBox()
|
||||||
|
exec := util.NewExecutor("")
|
||||||
reader := NewReader(
|
reader := NewReader(
|
||||||
func(s []byte) bool { strs = append(strs, string(s)); return true },
|
func(s []byte) bool { strs = append(strs, string(s)); return true },
|
||||||
eb, false, true)
|
eb, exec, false, true)
|
||||||
|
|
||||||
reader.startEventPoller()
|
reader.startEventPoller()
|
||||||
|
|
||||||
|
@@ -78,7 +78,7 @@ func startHttpServer(address listenAddress, actionChannel chan []*action, respon
|
|||||||
port := address.port
|
port := address.port
|
||||||
apiKey := os.Getenv("FZF_API_KEY")
|
apiKey := os.Getenv("FZF_API_KEY")
|
||||||
if !address.IsLocal() && len(apiKey) == 0 {
|
if !address.IsLocal() && len(apiKey) == 0 {
|
||||||
return port, fmt.Errorf("FZF_API_KEY is required to allow remote access")
|
return port, errors.New("FZF_API_KEY is required to allow remote access")
|
||||||
}
|
}
|
||||||
addrStr := fmt.Sprintf("%s:%d", host, port)
|
addrStr := fmt.Sprintf("%s:%d", host, port)
|
||||||
listener, err := net.Listen("tcp", addrStr)
|
listener, err := net.Listen("tcp", addrStr)
|
||||||
|
@@ -7,7 +7,6 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
@@ -245,6 +244,7 @@ type Terminal struct {
|
|||||||
listenUnsafe bool
|
listenUnsafe bool
|
||||||
borderShape tui.BorderShape
|
borderShape tui.BorderShape
|
||||||
cleanExit bool
|
cleanExit bool
|
||||||
|
executor *util.Executor
|
||||||
paused bool
|
paused bool
|
||||||
border tui.Window
|
border tui.Window
|
||||||
window tui.Window
|
window tui.Window
|
||||||
@@ -367,6 +367,7 @@ const (
|
|||||||
actCancel
|
actCancel
|
||||||
actChangeBorderLabel
|
actChangeBorderLabel
|
||||||
actChangeHeader
|
actChangeHeader
|
||||||
|
actChangeMulti
|
||||||
actChangePreviewLabel
|
actChangePreviewLabel
|
||||||
actChangePrompt
|
actChangePrompt
|
||||||
actChangeQuery
|
actChangeQuery
|
||||||
@@ -639,7 +640,7 @@ func evaluateHeight(opts *Options, termHeight int) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewTerminal returns new Terminal object
|
// NewTerminal returns new Terminal object
|
||||||
func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor) *Terminal {
|
||||||
input := trimQuery(opts.Query)
|
input := trimQuery(opts.Query)
|
||||||
var delay time.Duration
|
var delay time.Duration
|
||||||
if opts.Tac {
|
if opts.Tac {
|
||||||
@@ -735,6 +736,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
previewLabel: nil,
|
previewLabel: nil,
|
||||||
previewLabelOpts: opts.PreviewLabel,
|
previewLabelOpts: opts.PreviewLabel,
|
||||||
cleanExit: opts.ClearOnExit,
|
cleanExit: opts.ClearOnExit,
|
||||||
|
executor: executor,
|
||||||
paused: opts.Phony,
|
paused: opts.Phony,
|
||||||
cycle: opts.Cycle,
|
cycle: opts.Cycle,
|
||||||
headerVisible: true,
|
headerVisible: true,
|
||||||
@@ -854,6 +856,7 @@ func (t *Terminal) environ() []string {
|
|||||||
env = append(env, fmt.Sprintf("FZF_SELECT_COUNT=%d", len(t.selected)))
|
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_LINES=%d", t.areaLines))
|
||||||
env = append(env, fmt.Sprintf("FZF_COLUMNS=%d", t.areaColumns))
|
env = append(env, fmt.Sprintf("FZF_COLUMNS=%d", t.areaColumns))
|
||||||
|
env = append(env, fmt.Sprintf("FZF_POS=%d", util.Min(t.merger.Length(), t.cy+1)))
|
||||||
return env
|
return env
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1067,7 +1070,7 @@ func (t *Terminal) UpdateProgress(progress float32) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UpdateList updates Merger to display the list
|
// UpdateList updates Merger to display the list
|
||||||
func (t *Terminal) UpdateList(merger *Merger) {
|
func (t *Terminal) UpdateList(merger *Merger, triggerResultEvent bool) {
|
||||||
t.mutex.Lock()
|
t.mutex.Lock()
|
||||||
prevIndex := minItem.Index()
|
prevIndex := minItem.Index()
|
||||||
reset := t.revision != merger.Revision()
|
reset := t.revision != merger.Revision()
|
||||||
@@ -1118,7 +1121,7 @@ func (t *Terminal) UpdateList(merger *Merger) {
|
|||||||
t.eventChan <- one
|
t.eventChan <- one
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if t.hasResultActions {
|
if triggerResultEvent && t.hasResultActions {
|
||||||
t.eventChan <- tui.Result.AsEvent()
|
t.eventChan <- tui.Result.AsEvent()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2521,6 +2524,7 @@ type replacePlaceholderParams struct {
|
|||||||
allItems []*Item
|
allItems []*Item
|
||||||
lastAction actionType
|
lastAction actionType
|
||||||
prompt string
|
prompt string
|
||||||
|
executor *util.Executor
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) replacePlaceholder(template string, forcePlus bool, input string, list []*Item) string {
|
func (t *Terminal) replacePlaceholder(template string, forcePlus bool, input string, list []*Item) string {
|
||||||
@@ -2534,6 +2538,7 @@ func (t *Terminal) replacePlaceholder(template string, forcePlus bool, input str
|
|||||||
allItems: list,
|
allItems: list,
|
||||||
lastAction: t.lastAction,
|
lastAction: t.lastAction,
|
||||||
prompt: t.promptString,
|
prompt: t.promptString,
|
||||||
|
executor: t.executor,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2594,7 +2599,7 @@ func replacePlaceholder(params replacePlaceholderParams) string {
|
|||||||
case escaped:
|
case escaped:
|
||||||
return match
|
return match
|
||||||
case match == "{q}" || match == "{fzf:query}":
|
case match == "{q}" || match == "{fzf:query}":
|
||||||
return quoteEntry(params.query)
|
return params.executor.QuoteEntry(params.query)
|
||||||
case match == "{}":
|
case match == "{}":
|
||||||
replace = func(item *Item) string {
|
replace = func(item *Item) string {
|
||||||
switch {
|
switch {
|
||||||
@@ -2607,13 +2612,13 @@ func replacePlaceholder(params replacePlaceholderParams) string {
|
|||||||
case flags.file:
|
case flags.file:
|
||||||
return item.AsString(params.stripAnsi)
|
return item.AsString(params.stripAnsi)
|
||||||
default:
|
default:
|
||||||
return quoteEntry(item.AsString(params.stripAnsi))
|
return params.executor.QuoteEntry(item.AsString(params.stripAnsi))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case match == "{fzf:action}":
|
case match == "{fzf:action}":
|
||||||
return params.lastAction.Name()
|
return params.lastAction.Name()
|
||||||
case match == "{fzf:prompt}":
|
case match == "{fzf:prompt}":
|
||||||
return quoteEntry(params.prompt)
|
return params.executor.QuoteEntry(params.prompt)
|
||||||
default:
|
default:
|
||||||
// token type and also failover (below)
|
// token type and also failover (below)
|
||||||
rangeExpressions := strings.Split(match[1:len(match)-1], ",")
|
rangeExpressions := strings.Split(match[1:len(match)-1], ",")
|
||||||
@@ -2647,7 +2652,7 @@ func replacePlaceholder(params replacePlaceholderParams) string {
|
|||||||
str = strings.TrimSpace(str)
|
str = strings.TrimSpace(str)
|
||||||
}
|
}
|
||||||
if !flags.file {
|
if !flags.file {
|
||||||
str = quoteEntry(str)
|
str = params.executor.QuoteEntry(str)
|
||||||
}
|
}
|
||||||
return str
|
return str
|
||||||
}
|
}
|
||||||
@@ -2687,7 +2692,7 @@ func (t *Terminal) executeCommand(template string, forcePlus bool, background bo
|
|||||||
return line
|
return line
|
||||||
}
|
}
|
||||||
command := t.replacePlaceholder(template, forcePlus, string(t.input), list)
|
command := t.replacePlaceholder(template, forcePlus, string(t.input), list)
|
||||||
cmd := util.ExecCommand(command, false)
|
cmd := t.executor.ExecCommand(command, false)
|
||||||
cmd.Env = t.environ()
|
cmd.Env = t.environ()
|
||||||
t.executing.Set(true)
|
t.executing.Set(true)
|
||||||
if !background {
|
if !background {
|
||||||
@@ -2964,7 +2969,7 @@ func (t *Terminal) Loop() {
|
|||||||
if items[0] != nil {
|
if items[0] != nil {
|
||||||
_, query := t.Input()
|
_, query := t.Input()
|
||||||
command := t.replacePlaceholder(commandTemplate, false, string(query), items)
|
command := t.replacePlaceholder(commandTemplate, false, string(query), items)
|
||||||
cmd := util.ExecCommand(command, true)
|
cmd := t.executor.ExecCommand(command, true)
|
||||||
env := t.environ()
|
env := t.environ()
|
||||||
if pwindowSize.Lines > 0 {
|
if pwindowSize.Lines > 0 {
|
||||||
lines := fmt.Sprintf("LINES=%d", pwindowSize.Lines)
|
lines := fmt.Sprintf("LINES=%d", pwindowSize.Lines)
|
||||||
@@ -3371,27 +3376,21 @@ func (t *Terminal) Loop() {
|
|||||||
valid, list := t.buildPlusList(a.a, false)
|
valid, list := t.buildPlusList(a.a, false)
|
||||||
if valid {
|
if valid {
|
||||||
command := t.replacePlaceholder(a.a, false, string(t.input), list)
|
command := t.replacePlaceholder(a.a, false, string(t.input), list)
|
||||||
shell := os.Getenv("SHELL")
|
|
||||||
if len(shell) == 0 {
|
|
||||||
shell = "sh"
|
|
||||||
}
|
|
||||||
shellPath, err := exec.LookPath(shell)
|
|
||||||
if err == nil {
|
|
||||||
t.tui.Close()
|
t.tui.Close()
|
||||||
if t.history != nil {
|
if t.history != nil {
|
||||||
t.history.append(string(t.input))
|
t.history.append(string(t.input))
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
FIXME: It is not at all clear why this is required.
|
FIXME: It is not at all clear why this is required.
|
||||||
The following command will report 'not a tty', unless we open
|
The following command will report 'not a tty', unless we open
|
||||||
/dev/tty *twice* after closing the standard input for 'reload'
|
/dev/tty *twice* after closing the standard input for 'reload'
|
||||||
in Reader.terminate().
|
in Reader.terminate().
|
||||||
: | fzf --bind 'start:reload:ls' --bind 'enter:become:tty'
|
|
||||||
|
while : | fzf --bind 'start:reload:ls' --bind 'load:become:tty'; do echo; done
|
||||||
*/
|
*/
|
||||||
tui.TtyIn()
|
tui.TtyIn()
|
||||||
util.SetStdin(tui.TtyIn())
|
t.executor.Become(tui.TtyIn(), t.environ(), command)
|
||||||
syscall.Exec(shellPath, []string{shell, "-c", command}, os.Environ())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
case actExecute, actExecuteSilent:
|
case actExecute, actExecuteSilent:
|
||||||
t.executeCommand(a.a, false, a.t == actExecuteSilent, false, false)
|
t.executeCommand(a.a, false, a.t == actExecuteSilent, false, false)
|
||||||
@@ -3489,6 +3488,19 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
case actPrintQuery:
|
case actPrintQuery:
|
||||||
req(reqPrintQuery)
|
req(reqPrintQuery)
|
||||||
|
case actChangeMulti:
|
||||||
|
multi := t.multi
|
||||||
|
if a.a == "" {
|
||||||
|
multi = maxMulti
|
||||||
|
} else if n, e := strconv.Atoi(a.a); e == nil && n >= 0 {
|
||||||
|
multi = n
|
||||||
|
}
|
||||||
|
if t.multi > 0 && multi != t.multi {
|
||||||
|
t.selected = make(map[int32]selectedItem)
|
||||||
|
t.version++
|
||||||
|
}
|
||||||
|
t.multi = multi
|
||||||
|
req(reqList, reqInfo)
|
||||||
case actChangeQuery:
|
case actChangeQuery:
|
||||||
t.input = []rune(a.a)
|
t.input = []rune(a.a)
|
||||||
t.cx = len(t.input)
|
t.cx = len(t.input)
|
||||||
|
@@ -23,6 +23,7 @@ func replacePlaceholderTest(template string, stripAnsi bool, delimiter Delimiter
|
|||||||
allItems: allItems,
|
allItems: allItems,
|
||||||
lastAction: actBackwardDeleteCharEof,
|
lastAction: actBackwardDeleteCharEof,
|
||||||
prompt: "prompt",
|
prompt: "prompt",
|
||||||
|
executor: util.NewExecutor(""),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,6 +245,7 @@ func TestQuoteEntry(t *testing.T) {
|
|||||||
unixStyle := quotes{``, `'`, `'\''`, `"`, `\`}
|
unixStyle := quotes{``, `'`, `'\''`, `"`, `\`}
|
||||||
windowsStyle := quotes{`^`, `^"`, `'`, `\^"`, `\\`}
|
windowsStyle := quotes{`^`, `^"`, `'`, `\^"`, `\\`}
|
||||||
var effectiveStyle quotes
|
var effectiveStyle quotes
|
||||||
|
exec := util.NewExecutor("")
|
||||||
|
|
||||||
if util.IsWindows() {
|
if util.IsWindows() {
|
||||||
effectiveStyle = windowsStyle
|
effectiveStyle = windowsStyle
|
||||||
@@ -278,7 +280,7 @@ func TestQuoteEntry(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for input, expected := range tests {
|
for input, expected := range tests {
|
||||||
escaped := quoteEntry(input)
|
escaped := exec.QuoteEntry(input)
|
||||||
expected = templateToString(expected, effectiveStyle)
|
expected = templateToString(expected, effectiveStyle)
|
||||||
if escaped != expected {
|
if escaped != expected {
|
||||||
t.Errorf("Input: %s, expected: %s, actual %s", input, expected, escaped)
|
t.Errorf("Input: %s, expected: %s, actual %s", input, expected, escaped)
|
||||||
@@ -317,9 +319,9 @@ func TestUnixCommands(t *testing.T) {
|
|||||||
|
|
||||||
// purpose of this test is to demonstrate some shortcomings of fzf's templating system on Windows
|
// purpose of this test is to demonstrate some shortcomings of fzf's templating system on Windows
|
||||||
func TestWindowsCommands(t *testing.T) {
|
func TestWindowsCommands(t *testing.T) {
|
||||||
if !util.IsWindows() {
|
// XXX Deprecated
|
||||||
t.SkipNow()
|
t.SkipNow()
|
||||||
}
|
|
||||||
tests := []testCase{
|
tests := []testCase{
|
||||||
// reference: give{template, query, items}, want{output OR match}
|
// reference: give{template, query, items}, want{output OR match}
|
||||||
|
|
||||||
|
@@ -5,26 +5,11 @@ package fzf
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"strings"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
var escaper *strings.Replacer
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
tokens := strings.Split(os.Getenv("SHELL"), "/")
|
|
||||||
if tokens[len(tokens)-1] == "fish" {
|
|
||||||
// https://fishshell.com/docs/current/language.html#quotes
|
|
||||||
// > The only meaningful escape sequences in single quotes are \', which
|
|
||||||
// > escapes a single quote and \\, which escapes the backslash symbol.
|
|
||||||
escaper = strings.NewReplacer("\\", "\\\\", "'", "\\'")
|
|
||||||
} else {
|
|
||||||
escaper = strings.NewReplacer("'", "'\\''")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func notifyOnResize(resizeChan chan<- os.Signal) {
|
func notifyOnResize(resizeChan chan<- os.Signal) {
|
||||||
signal.Notify(resizeChan, syscall.SIGWINCH)
|
signal.Notify(resizeChan, syscall.SIGWINCH)
|
||||||
}
|
}
|
||||||
@@ -41,7 +26,3 @@ func notifyStop(p *os.Process) {
|
|||||||
func notifyOnCont(resizeChan chan<- os.Signal) {
|
func notifyOnCont(resizeChan chan<- os.Signal) {
|
||||||
signal.Notify(resizeChan, syscall.SIGCONT)
|
signal.Notify(resizeChan, syscall.SIGCONT)
|
||||||
}
|
}
|
||||||
|
|
||||||
func quoteEntry(entry string) string {
|
|
||||||
return "'" + escaper.Replace(entry) + "'"
|
|
||||||
}
|
|
||||||
|
@@ -4,8 +4,6 @@ package fzf
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func notifyOnResize(resizeChan chan<- os.Signal) {
|
func notifyOnResize(resizeChan chan<- os.Signal) {
|
||||||
@@ -19,27 +17,3 @@ func notifyStop(p *os.Process) {
|
|||||||
func notifyOnCont(resizeChan chan<- os.Signal) {
|
func notifyOnCont(resizeChan chan<- os.Signal) {
|
||||||
// NOOP
|
// NOOP
|
||||||
}
|
}
|
||||||
|
|
||||||
func quoteEntry(entry string) string {
|
|
||||||
shell := os.Getenv("SHELL")
|
|
||||||
if len(shell) == 0 {
|
|
||||||
shell = "cmd"
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.Contains(shell, "cmd") {
|
|
||||||
// backslash escaping is done here for applications
|
|
||||||
// (see ripgrep test case in terminal_test.go#TestWindowsCommands)
|
|
||||||
escaped := strings.Replace(entry, `\`, `\\`, -1)
|
|
||||||
escaped = `"` + strings.Replace(escaped, `"`, `\"`, -1) + `"`
|
|
||||||
// caret is the escape character for cmd shell
|
|
||||||
r, _ := regexp.Compile(`[&|<>()@^%!"]`)
|
|
||||||
return r.ReplaceAllStringFunc(escaped, func(match string) string {
|
|
||||||
return "^" + match
|
|
||||||
})
|
|
||||||
} else if strings.Contains(shell, "pwsh") || strings.Contains(shell, "powershell") {
|
|
||||||
escaped := strings.Replace(entry, `"`, `\"`, -1)
|
|
||||||
return "'" + strings.Replace(escaped, "'", "''", -1) + "'"
|
|
||||||
} else {
|
|
||||||
return "'" + strings.Replace(entry, "'", "'\\''", -1) + "'"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@@ -3,31 +3,71 @@
|
|||||||
package util
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ExecCommand executes the given command with $SHELL
|
type Executor struct {
|
||||||
func ExecCommand(command string, setpgid bool) *exec.Cmd {
|
shell string
|
||||||
|
args []string
|
||||||
|
escaper *strings.Replacer
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewExecutor(withShell string) *Executor {
|
||||||
shell := os.Getenv("SHELL")
|
shell := os.Getenv("SHELL")
|
||||||
|
args := strings.Fields(withShell)
|
||||||
|
if len(args) > 0 {
|
||||||
|
shell = args[0]
|
||||||
|
args = args[1:]
|
||||||
|
} else {
|
||||||
if len(shell) == 0 {
|
if len(shell) == 0 {
|
||||||
shell = "sh"
|
shell = "sh"
|
||||||
}
|
}
|
||||||
return ExecCommandWith(shell, command, setpgid)
|
args = []string{"-c"}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecCommandWith executes the given command with the specified shell
|
var escaper *strings.Replacer
|
||||||
func ExecCommandWith(shell string, command string, setpgid bool) *exec.Cmd {
|
tokens := strings.Split(shell, "/")
|
||||||
cmd := exec.Command(shell, "-c", command)
|
if tokens[len(tokens)-1] == "fish" {
|
||||||
|
// https://fishshell.com/docs/current/language.html#quotes
|
||||||
|
// > The only meaningful escape sequences in single quotes are \', which
|
||||||
|
// > escapes a single quote and \\, which escapes the backslash symbol.
|
||||||
|
escaper = strings.NewReplacer("\\", "\\\\", "'", "\\'")
|
||||||
|
} else {
|
||||||
|
escaper = strings.NewReplacer("'", "'\\''")
|
||||||
|
}
|
||||||
|
return &Executor{shell, args, escaper}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecCommand executes the given command with $SHELL
|
||||||
|
func (x *Executor) ExecCommand(command string, setpgid bool) *exec.Cmd {
|
||||||
|
cmd := exec.Command(x.shell, append(x.args, command)...)
|
||||||
if setpgid {
|
if setpgid {
|
||||||
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
||||||
}
|
}
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *Executor) QuoteEntry(entry string) string {
|
||||||
|
return "'" + x.escaper.Replace(entry) + "'"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Executor) Become(stdin *os.File, environ []string, command string) {
|
||||||
|
shellPath, err := exec.LookPath(x.shell)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "fzf (become): %s\n", err.Error())
|
||||||
|
Exit(127)
|
||||||
|
}
|
||||||
|
args := append([]string{shellPath}, append(x.args, command)...)
|
||||||
|
SetStdin(stdin)
|
||||||
|
syscall.Exec(shellPath, args, environ)
|
||||||
|
}
|
||||||
|
|
||||||
// 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 {
|
||||||
return syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
|
return syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
|
||||||
|
@@ -6,62 +6,161 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"syscall"
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
var shellPath atomic.Value
|
type shellType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
shellTypeUnknown shellType = iota
|
||||||
|
shellTypeCmd
|
||||||
|
shellTypePowerShell
|
||||||
|
)
|
||||||
|
|
||||||
|
type Executor struct {
|
||||||
|
shell string
|
||||||
|
shellType shellType
|
||||||
|
args []string
|
||||||
|
shellPath atomic.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewExecutor(withShell string) *Executor {
|
||||||
|
shell := os.Getenv("SHELL")
|
||||||
|
args := strings.Fields(withShell)
|
||||||
|
if len(args) > 0 {
|
||||||
|
shell = args[0]
|
||||||
|
} else if len(shell) == 0 {
|
||||||
|
shell = "cmd"
|
||||||
|
}
|
||||||
|
|
||||||
|
shellType := shellTypeUnknown
|
||||||
|
basename := filepath.Base(shell)
|
||||||
|
if len(args) > 0 {
|
||||||
|
args = args[1:]
|
||||||
|
} else if strings.HasPrefix(basename, "cmd") {
|
||||||
|
shellType = shellTypeCmd
|
||||||
|
args = []string{"/v:on/s/c"}
|
||||||
|
} else if strings.HasPrefix(basename, "pwsh") || strings.HasPrefix(basename, "powershell") {
|
||||||
|
shellType = shellTypePowerShell
|
||||||
|
args = []string{"-NoProfile", "-Command"}
|
||||||
|
} else {
|
||||||
|
args = []string{"-c"}
|
||||||
|
}
|
||||||
|
return &Executor{shell: shell, shellType: shellType, args: args}
|
||||||
|
}
|
||||||
|
|
||||||
// ExecCommand executes the given command with $SHELL
|
// ExecCommand executes the given command with $SHELL
|
||||||
func ExecCommand(command string, setpgid bool) *exec.Cmd {
|
// FIXME: setpgid is unused. We set it in the Unix implementation so that we
|
||||||
var shell string
|
// can kill preview process with its child processes at once.
|
||||||
if cached := shellPath.Load(); cached != nil {
|
// 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.
|
||||||
|
func (x *Executor) ExecCommand(command string, setpgid bool) *exec.Cmd {
|
||||||
|
shell := x.shell
|
||||||
|
if cached := x.shellPath.Load(); cached != nil {
|
||||||
shell = cached.(string)
|
shell = cached.(string)
|
||||||
} else {
|
} else {
|
||||||
shell = os.Getenv("SHELL")
|
if strings.Contains(shell, "/") {
|
||||||
if len(shell) == 0 {
|
|
||||||
shell = "cmd"
|
|
||||||
} else if strings.Contains(shell, "/") {
|
|
||||||
out, err := exec.Command("cygpath", "-w", shell).Output()
|
out, err := exec.Command("cygpath", "-w", shell).Output()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
shell = strings.Trim(string(out), "\n")
|
shell = strings.Trim(string(out), "\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
shellPath.Store(shell)
|
x.shellPath.Store(shell)
|
||||||
}
|
}
|
||||||
return ExecCommandWith(shell, command, setpgid)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExecCommandWith executes the given command with the specified 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.
|
|
||||||
// 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.
|
|
||||||
func ExecCommandWith(shell string, command string, setpgid bool) *exec.Cmd {
|
|
||||||
var cmd *exec.Cmd
|
var cmd *exec.Cmd
|
||||||
if strings.Contains(shell, "cmd") {
|
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(` /v:on/s/c "%s"`, command),
|
CmdLine: fmt.Sprintf(`%s "%s"`, strings.Join(x.args, " "), command),
|
||||||
CreationFlags: 0,
|
CreationFlags: 0,
|
||||||
}
|
}
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.Contains(shell, "pwsh") || strings.Contains(shell, "powershell") {
|
|
||||||
cmd = exec.Command(shell, "-NoProfile", "-Command", command)
|
|
||||||
} else {
|
} else {
|
||||||
cmd = exec.Command(shell, "-c", command)
|
cmd = exec.Command(shell, append(x.args, command)...)
|
||||||
}
|
|
||||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||||
HideWindow: false,
|
HideWindow: false,
|
||||||
CreationFlags: 0,
|
CreationFlags: 0,
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *Executor) Become(stdin *os.File, environ []string, command string) {
|
||||||
|
cmd := x.ExecCommand(command, false)
|
||||||
|
cmd.Stdin = stdin
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
cmd.Env = environ
|
||||||
|
err := cmd.Start()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "fzf (become): %s\n", err.Error())
|
||||||
|
Exit(127)
|
||||||
|
}
|
||||||
|
err = cmd.Wait()
|
||||||
|
if err != nil {
|
||||||
|
if exitError, ok := err.(*exec.ExitError); ok {
|
||||||
|
Exit(exitError.ExitCode())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func escapeArg(s string) string {
|
||||||
|
b := make([]byte, 0, len(s)+2)
|
||||||
|
b = append(b, '"')
|
||||||
|
slashes := 0
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
c := s[i]
|
||||||
|
switch c {
|
||||||
|
default:
|
||||||
|
slashes = 0
|
||||||
|
case '\\':
|
||||||
|
slashes++
|
||||||
|
case '&', '|', '<', '>', '(', ')', '@', '^', '%', '!':
|
||||||
|
b = append(b, '^')
|
||||||
|
case '"':
|
||||||
|
for ; slashes > 0; slashes-- {
|
||||||
|
b = append(b, '\\')
|
||||||
|
}
|
||||||
|
b = append(b, '\\')
|
||||||
|
}
|
||||||
|
b = append(b, c)
|
||||||
|
}
|
||||||
|
for ; slashes > 0; slashes-- {
|
||||||
|
b = append(b, '\\')
|
||||||
|
}
|
||||||
|
b = append(b, '"')
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Executor) QuoteEntry(entry string) string {
|
||||||
|
switch x.shellType {
|
||||||
|
case shellTypeCmd:
|
||||||
|
/* Manually tested with the following commands:
|
||||||
|
fzf --preview "echo {}"
|
||||||
|
fzf --preview "type {}"
|
||||||
|
echo .git\refs\| fzf --preview "dir {}"
|
||||||
|
echo .git\refs\\| fzf --preview "dir {}"
|
||||||
|
echo .git\refs\\\| fzf --preview "dir {}"
|
||||||
|
reg query HKCU | fzf --reverse --bind "enter:reload(reg query {})"
|
||||||
|
fzf --disabled --preview "echo {q} {n} {}" --query "&|<>()@^%!"
|
||||||
|
fd -H --no-ignore -td -d 4 | fzf --preview "dir {}"
|
||||||
|
fd -H --no-ignore -td -d 4 | fzf --preview "eza {}" --preview-window up
|
||||||
|
fd -H --no-ignore -td -d 4 | fzf --preview "eza --color=always --tree --level=3 --icons=always {}"
|
||||||
|
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)
|
||||||
|
case shellTypePowerShell:
|
||||||
|
escaped := strings.Replace(entry, `"`, `\"`, -1)
|
||||||
|
return "'" + strings.Replace(escaped, "'", "''", -1) + "'"
|
||||||
|
default:
|
||||||
|
return "'" + strings.Replace(entry, "'", "'\\''", -1) + "'"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 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 {
|
||||||
return cmd.Process.Kill()
|
return cmd.Process.Kill()
|
||||||
|
@@ -425,6 +425,25 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_multi_action
|
||||||
|
tmux.send_keys "seq 10 | #{FZF} --bind 'a:change-multi,b:change-multi(3),c:change-multi(xxx),d:change-multi(0)'", :Enter
|
||||||
|
tmux.until { |lines| assert_equal 10, lines.item_count }
|
||||||
|
tmux.until { |lines| assert lines[-2]&.start_with?(' 10/10 ') }
|
||||||
|
tmux.send_keys 'a'
|
||||||
|
tmux.until { |lines| assert lines[-2]&.start_with?(' 10/10 (0)') }
|
||||||
|
tmux.send_keys 'b'
|
||||||
|
tmux.until { |lines| assert lines[-2]&.start_with?(' 10/10 (0/3)') }
|
||||||
|
tmux.send_keys :BTab
|
||||||
|
tmux.until { |lines| assert lines[-2]&.start_with?(' 10/10 (1/3)') }
|
||||||
|
tmux.send_keys 'c'
|
||||||
|
tmux.send_keys :BTab
|
||||||
|
tmux.until { |lines| assert lines[-2]&.start_with?(' 10/10 (2/3)') }
|
||||||
|
tmux.send_keys 'd'
|
||||||
|
tmux.until do |lines|
|
||||||
|
assert lines[-2]&.start_with?(' 10/10 ') && !lines[-2]&.include?('(')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_with_nth
|
def test_with_nth
|
||||||
[true, false].each do |multi|
|
[true, false].each do |multi|
|
||||||
tmux.send_keys "(echo ' 1st 2nd 3rd/';
|
tmux.send_keys "(echo ' 1st 2nd 3rd/';
|
||||||
@@ -1955,6 +1974,11 @@ class TestGoFZF < TestBase
|
|||||||
tmux.until { |lines| assert_equal 10, lines.item_count }
|
tmux.until { |lines| assert_equal 10, lines.item_count }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_reload_should_terminate_standard_input_stream
|
||||||
|
tmux.send_keys %(ruby -e "STDOUT.sync = true; loop { puts 1; sleep 0.1 }" | fzf --bind 'start:reload(seq 100)'), :Enter
|
||||||
|
tmux.until { |lines| assert_equal 100, lines.item_count }
|
||||||
|
end
|
||||||
|
|
||||||
def test_clear_list_when_header_lines_changed_due_to_reload
|
def test_clear_list_when_header_lines_changed_due_to_reload
|
||||||
tmux.send_keys %(seq 10 | #{FZF} --header 0 --header-lines 3 --bind 'space:reload(seq 1)'), :Enter
|
tmux.send_keys %(seq 10 | #{FZF} --header 0 --header-lines 3 --bind 'space:reload(seq 1)'), :Enter
|
||||||
tmux.until { |lines| assert_includes lines, ' 9' }
|
tmux.until { |lines| assert_includes lines, ' 9' }
|
||||||
@@ -3226,6 +3250,17 @@ class TestGoFZF < TestBase
|
|||||||
tmux.send_keys :Up
|
tmux.send_keys :Up
|
||||||
tmux.until { |lines| assert_includes lines, '> 2' }
|
tmux.until { |lines| assert_includes lines, '> 2' }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_fzf_pos
|
||||||
|
tmux.send_keys "seq 100 | #{FZF} --preview 'echo $FZF_POS / $FZF_MATCH_COUNT'", :Enter
|
||||||
|
tmux.until { |lines| assert(lines.any? { |line| line.include?('1 / 100') }) }
|
||||||
|
tmux.send_keys :Up
|
||||||
|
tmux.until { |lines| assert(lines.any? { |line| line.include?('2 / 100') }) }
|
||||||
|
tmux.send_keys '99'
|
||||||
|
tmux.until { |lines| assert(lines.any? { |line| line.include?('1 / 1') }) }
|
||||||
|
tmux.send_keys '99'
|
||||||
|
tmux.until { |lines| assert(lines.any? { |line| line.include?('0 / 0') }) }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
module TestShell
|
module TestShell
|
||||||
@@ -3457,7 +3492,11 @@ module CompletionTest
|
|||||||
tmux.until { |lines| assert_operator lines.match_count, :>, 0 }
|
tmux.until { |lines| assert_operator lines.match_count, :>, 0 }
|
||||||
tmux.send_keys :Tab, :Tab # Tab does not work here
|
tmux.send_keys :Tab, :Tab # Tab does not work here
|
||||||
tmux.send_keys 55
|
tmux.send_keys 55
|
||||||
tmux.until { |lines| assert_equal 1, lines.match_count }
|
tmux.until do |lines|
|
||||||
|
assert_equal 1, lines.match_count
|
||||||
|
assert_includes lines, '> 55'
|
||||||
|
assert_includes lines, '> /tmp/fzf-test/d55'
|
||||||
|
end
|
||||||
tmux.send_keys :Enter
|
tmux.send_keys :Enter
|
||||||
tmux.until(true) { |lines| assert_equal 'cd /tmp/fzf-test/d55/', lines[-1] }
|
tmux.until(true) { |lines| assert_equal 'cd /tmp/fzf-test/d55/', lines[-1] }
|
||||||
tmux.send_keys :xx
|
tmux.send_keys :xx
|
||||||
|
Reference in New Issue
Block a user