mirror of
https://github.com/junegunn/fzf.git
synced 2025-11-11 12:53:48 -05:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bcda25a513 | ||
|
|
8256fcde15 | ||
|
|
af65aa298a | ||
|
|
6834d17844 | ||
|
|
ed511d7867 | ||
|
|
cd8d736a9f | ||
|
|
0952b2dfd4 | ||
|
|
4bedd33c59 | ||
|
|
c5fb0c43f9 | ||
|
|
9e4780510e | ||
|
|
e8405f40fe | ||
|
|
065b9e6fb2 | ||
|
|
98141ca7d8 | ||
|
|
501577ab28 | ||
|
|
5669f48343 | ||
|
|
24ff66d4a9 | ||
|
|
bf184449bc | ||
|
|
7b98c2c653 | ||
|
|
b6add2a257 | ||
|
|
2bd41f1330 | ||
|
|
c37cd11ca5 | ||
|
|
9dee8edc0c | ||
|
|
f6aa28c380 | ||
|
|
dba1644518 |
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.20.10
|
- uses: crate-ci/typos@v1.21.0
|
||||||
|
|||||||
28
CHANGELOG.md
28
CHANGELOG.md
@@ -1,6 +1,34 @@
|
|||||||
CHANGELOG
|
CHANGELOG
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
0.52.0
|
||||||
|
------
|
||||||
|
- Added `--highlight-line` to highlight the whole current line (à la `set cursorline` of Vim)
|
||||||
|
- Added color names for selected lines: `selected-fg`, `selected-bg`, and `selected-hl`
|
||||||
|
```sh
|
||||||
|
fzf --border --multi --info inline-right --layout reverse --marker ▏ --pointer ▌ --prompt '▌ ' \
|
||||||
|
--highlight-line --color gutter:-1,selected-bg:238,selected-fg:146,current-fg:189
|
||||||
|
```
|
||||||
|
- Added `click-header` event that is triggered when the header section is clicked. When the event is triggered, `$FZF_CLICK_HEADER_COLUMN` and `$FZF_CLICK_HEADER_LINE` are set.
|
||||||
|
```sh
|
||||||
|
fd --type f |
|
||||||
|
fzf --header $'[Files] [Directories]' --header-first \
|
||||||
|
--bind 'click-header:transform:
|
||||||
|
(( FZF_CLICK_HEADER_COLUMN <= 7 )) && echo "reload(fd --type f)"
|
||||||
|
(( FZF_CLICK_HEADER_COLUMN >= 9 )) && echo "reload(fd --type d)"
|
||||||
|
'
|
||||||
|
```
|
||||||
|
- Add `$FZF_COMPLETION_{DIR,PATH}_OPTS` for separately customizing the behavior of fuzzy completion
|
||||||
|
```sh
|
||||||
|
# Set --walker options without 'follow' not to follow symbolic links
|
||||||
|
FZF_COMPLETION_PATH_OPTS="--walker=file,dir,hidden"
|
||||||
|
FZF_COMPLETION_DIR_OPTS="--walker=dir,hidden"
|
||||||
|
```
|
||||||
|
- Fixed Windows argument escaping
|
||||||
|
- Bug fixes and improvements
|
||||||
|
- The code was heavily refactored to allow using fzf as a library in Go programs. The API is still experimental and subject to change.
|
||||||
|
- https://gist.github.com/junegunn/193990b65be48a38aac6ac49d5669170
|
||||||
|
|
||||||
0.51.0
|
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.
|
- 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.
|
||||||
|
|||||||
3
Makefile
3
Makefile
@@ -1,6 +1,6 @@
|
|||||||
SHELL := bash
|
SHELL := bash
|
||||||
GO ?= go
|
GO ?= go
|
||||||
GOOS ?= $(word 1, $(subst /, " ", $(word 4, $(shell go version))))
|
GOOS ?= $(shell $(GO) env GOOS)
|
||||||
|
|
||||||
MAKEFILE := $(realpath $(lastword $(MAKEFILE_LIST)))
|
MAKEFILE := $(realpath $(lastword $(MAKEFILE_LIST)))
|
||||||
ROOT_DIR := $(shell dirname $(MAKEFILE))
|
ROOT_DIR := $(shell dirname $(MAKEFILE))
|
||||||
@@ -79,7 +79,6 @@ all: target/$(BINARY)
|
|||||||
test: $(SOURCES)
|
test: $(SOURCES)
|
||||||
[ -z "$$(gofmt -s -d src)" ] || (gofmt -s -d src; exit 1)
|
[ -z "$$(gofmt -s -d src)" ] || (gofmt -s -d src; exit 1)
|
||||||
SHELL=/bin/sh GOOS= $(GO) test -v -tags "$(TAGS)" \
|
SHELL=/bin/sh GOOS= $(GO) test -v -tags "$(TAGS)" \
|
||||||
github.com/junegunn/fzf \
|
|
||||||
github.com/junegunn/fzf/src \
|
github.com/junegunn/fzf/src \
|
||||||
github.com/junegunn/fzf/src/algo \
|
github.com/junegunn/fzf/src/algo \
|
||||||
github.com/junegunn/fzf/src/tui \
|
github.com/junegunn/fzf/src/tui \
|
||||||
|
|||||||
4
go.mod
4
go.mod
@@ -6,8 +6,8 @@ require (
|
|||||||
github.com/mattn/go-isatty v0.0.20
|
github.com/mattn/go-isatty v0.0.20
|
||||||
github.com/mattn/go-shellwords v1.0.12
|
github.com/mattn/go-shellwords v1.0.12
|
||||||
github.com/rivo/uniseg v0.4.7
|
github.com/rivo/uniseg v0.4.7
|
||||||
golang.org/x/sys v0.19.0
|
golang.org/x/sys v0.20.0
|
||||||
golang.org/x/term v0.19.0
|
golang.org/x/term v0.20.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|||||||
8
go.sum
8
go.sum
@@ -36,14 +36,14 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||||
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
|
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
|
||||||
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
|
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
|||||||
6
install
6
install
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
set -u
|
set -u
|
||||||
|
|
||||||
version=0.51.0
|
version=0.52.0
|
||||||
auto_completion=
|
auto_completion=
|
||||||
key_bindings=
|
key_bindings=
|
||||||
update_config=2
|
update_config=2
|
||||||
@@ -115,7 +115,7 @@ link_fzf_in_path() {
|
|||||||
try_curl() {
|
try_curl() {
|
||||||
command -v curl > /dev/null &&
|
command -v curl > /dev/null &&
|
||||||
if [[ $1 =~ tar.gz$ ]]; then
|
if [[ $1 =~ tar.gz$ ]]; then
|
||||||
curl -fL $1 | tar -xzf -
|
curl -fL $1 | tar --no-same-owner -xzf -
|
||||||
else
|
else
|
||||||
local temp=${TMPDIR:-/tmp}/fzf.zip
|
local temp=${TMPDIR:-/tmp}/fzf.zip
|
||||||
curl -fLo "$temp" $1 && unzip -o "$temp" && rm -f "$temp"
|
curl -fLo "$temp" $1 && unzip -o "$temp" && rm -f "$temp"
|
||||||
@@ -125,7 +125,7 @@ try_curl() {
|
|||||||
try_wget() {
|
try_wget() {
|
||||||
command -v wget > /dev/null &&
|
command -v wget > /dev/null &&
|
||||||
if [[ $1 =~ tar.gz$ ]]; then
|
if [[ $1 =~ tar.gz$ ]]; then
|
||||||
wget -O - $1 | tar -xzf -
|
wget -O - $1 | tar --no-same-owner -xzf -
|
||||||
else
|
else
|
||||||
local temp=${TMPDIR:-/tmp}/fzf.zip
|
local temp=${TMPDIR:-/tmp}/fzf.zip
|
||||||
wget -O "$temp" $1 && unzip -o "$temp" && rm -f "$temp"
|
wget -O "$temp" $1 && unzip -o "$temp" && rm -f "$temp"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
$version="0.51.0"
|
$version="0.52.0"
|
||||||
|
|
||||||
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||||
|
|
||||||
|
|||||||
35
main.go
35
main.go
@@ -3,14 +3,15 @@ package main
|
|||||||
import (
|
import (
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
fzf "github.com/junegunn/fzf/src"
|
fzf "github.com/junegunn/fzf/src"
|
||||||
"github.com/junegunn/fzf/src/protector"
|
"github.com/junegunn/fzf/src/protector"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version string = "0.51"
|
var version = "0.52"
|
||||||
var revision string = "devel"
|
var revision = "devel"
|
||||||
|
|
||||||
//go:embed shell/key-bindings.bash
|
//go:embed shell/key-bindings.bash
|
||||||
var bashKeyBindings []byte
|
var bashKeyBindings []byte
|
||||||
@@ -33,9 +34,21 @@ func printScript(label string, content []byte) {
|
|||||||
fmt.Println("### end: " + label + " ###")
|
fmt.Println("### end: " + label + " ###")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func exit(code int, err error) {
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err.Error())
|
||||||
|
}
|
||||||
|
os.Exit(code)
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
protector.Protect()
|
protector.Protect()
|
||||||
options := fzf.ParseOptions()
|
|
||||||
|
options, err := fzf.ParseOptions(true, os.Args[1:])
|
||||||
|
if err != nil {
|
||||||
|
exit(fzf.ExitError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
if options.Bash {
|
if options.Bash {
|
||||||
printScript("key-bindings.bash", bashKeyBindings)
|
printScript("key-bindings.bash", bashKeyBindings)
|
||||||
printScript("completion.bash", bashCompletion)
|
printScript("completion.bash", bashCompletion)
|
||||||
@@ -51,5 +64,19 @@ func main() {
|
|||||||
fmt.Println("fzf_key_bindings")
|
fmt.Println("fzf_key_bindings")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fzf.Run(options, version, revision)
|
if options.Help {
|
||||||
|
fmt.Print(fzf.Usage)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if options.Version {
|
||||||
|
if len(revision) > 0 {
|
||||||
|
fmt.Printf("%s (%s)\n", version, revision)
|
||||||
|
} else {
|
||||||
|
fmt.Println(version)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
code, err := fzf.Run(options)
|
||||||
|
exit(code, err)
|
||||||
}
|
}
|
||||||
|
|||||||
174
main_test.go
174
main_test.go
@@ -1,174 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"go/ast"
|
|
||||||
"go/build"
|
|
||||||
"go/importer"
|
|
||||||
"go/parser"
|
|
||||||
"go/token"
|
|
||||||
"go/types"
|
|
||||||
"io/fs"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
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()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var pkgs []*build.Package
|
|
||||||
seen := make(map[string]bool)
|
|
||||||
err = filepath.WalkDir(wd, func(path string, d fs.DirEntry, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
name := d.Name()
|
|
||||||
if d.IsDir() {
|
|
||||||
if name == "" || name[0] == '.' || name[0] == '_' || name == "vendor" || name == "tmp" {
|
|
||||||
return filepath.SkipDir
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if d.Type().IsRegular() && filepath.Ext(name) == ".go" && !strings.HasSuffix(name, "_test.go") {
|
|
||||||
dir := filepath.Dir(path)
|
|
||||||
if !seen[dir] {
|
|
||||||
pkg, err := ctxt.ImportDir(dir, build.ImportComment)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("%s: %s", dir, err)
|
|
||||||
}
|
|
||||||
if pkg.ImportPath == "" || pkg.ImportPath == "." {
|
|
||||||
importPath, err := filepath.Rel(wd, dir)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
pkg.ImportPath = filepath.ToSlash(filepath.Join("github.com/junegunn/fzf", importPath))
|
|
||||||
}
|
|
||||||
|
|
||||||
pkgs = append(pkgs, pkg)
|
|
||||||
seen[dir] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Slice(pkgs, func(i, j int) bool {
|
|
||||||
return pkgs[i].ImportPath < pkgs[j].ImportPath
|
|
||||||
})
|
|
||||||
return pkgs
|
|
||||||
}
|
|
||||||
|
|
||||||
var sourceImporter = importer.ForCompiler(token.NewFileSet(), "source", nil)
|
|
||||||
|
|
||||||
func checkPackageForOsExit(t *testing.T, bpkg *build.Package, allowed map[string]int) (errOsExit bool) {
|
|
||||||
var files []*ast.File
|
|
||||||
fset := token.NewFileSet()
|
|
||||||
for _, name := range bpkg.GoFiles {
|
|
||||||
filename := filepath.Join(bpkg.Dir, name)
|
|
||||||
af, err := parser.ParseFile(fset, filename, nil, parser.ParseComments)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
files = append(files, af)
|
|
||||||
}
|
|
||||||
|
|
||||||
info := types.Info{
|
|
||||||
Uses: make(map[*ast.Ident]types.Object),
|
|
||||||
}
|
|
||||||
conf := types.Config{
|
|
||||||
Importer: sourceImporter,
|
|
||||||
}
|
|
||||||
_, err := conf.Check(bpkg.Name, fset, files, &info)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
wd, err := os.Getwd()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for id, obj := range info.Uses {
|
|
||||||
if obj.Pkg() != nil && obj.Pkg().Name() == "os" && obj.Name() == "Exit" {
|
|
||||||
pos := fset.Position(id.Pos())
|
|
||||||
|
|
||||||
name, err := filepath.Rel(wd, pos.Filename)
|
|
||||||
if err != nil {
|
|
||||||
t.Log(err)
|
|
||||||
name = pos.Filename
|
|
||||||
}
|
|
||||||
name = filepath.ToSlash(name)
|
|
||||||
|
|
||||||
// Check if the usage is allowed
|
|
||||||
if allowed[name] > 0 {
|
|
||||||
allowed[name]--
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Errorf("os.Exit referenced at: %s:%d:%d", name, pos.Line, pos.Column)
|
|
||||||
errOsExit = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return errOsExit
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enforce that src/util.Exit() is used instead of os.Exit by prohibiting
|
|
||||||
// references to it anywhere else in the fzf code base.
|
|
||||||
func TestOSExitNotAllowed(t *testing.T) {
|
|
||||||
if testing.Short() {
|
|
||||||
t.Skip("skipping: short test")
|
|
||||||
}
|
|
||||||
allowed := map[string]int{
|
|
||||||
"src/util/atexit.go": 1, // os.Exit allowed 1 time in "atexit.go"
|
|
||||||
}
|
|
||||||
var errOsExit bool
|
|
||||||
for _, pkg := range loadPackages(t) {
|
|
||||||
t.Run(pkg.ImportPath, func(t *testing.T) {
|
|
||||||
if checkPackageForOsExit(t, pkg, allowed) {
|
|
||||||
errOsExit = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if t.Failed() && errOsExit {
|
|
||||||
var names []string
|
|
||||||
for name := range allowed {
|
|
||||||
names = append(names, fmt.Sprintf("%q", name))
|
|
||||||
}
|
|
||||||
sort.Strings(names)
|
|
||||||
|
|
||||||
const errMsg = `
|
|
||||||
Test failed because os.Exit was referenced outside of the following files:
|
|
||||||
|
|
||||||
%s
|
|
||||||
|
|
||||||
Use github.com/junegunn/fzf/src/util.Exit() instead to exit the program.
|
|
||||||
This is enforced because calling os.Exit() prevents the functions
|
|
||||||
registered with util.AtExit() from running.`
|
|
||||||
|
|
||||||
t.Errorf(errMsg, strings.Join(names, "\n "))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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 "May 2024" "fzf 0.51.0" "fzf-tmux - open fzf in tmux split pane"
|
.TH fzf-tmux 1 "May 2024" "fzf 0.52.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 "May 2024" "fzf 0.51.0" "fzf - a command-line fuzzy finder"
|
.TH fzf 1 "May 2024" "fzf 0.52.0" "fzf - a command-line fuzzy finder"
|
||||||
|
|
||||||
.SH NAME
|
.SH NAME
|
||||||
fzf - a command-line fuzzy finder
|
fzf - a command-line fuzzy finder
|
||||||
@@ -464,14 +464,17 @@ color mappings.
|
|||||||
|
|
||||||
.B COLOR NAMES:
|
.B COLOR NAMES:
|
||||||
\fBfg \fRText
|
\fBfg \fRText
|
||||||
|
\fBselected-fg \fRSelected line text
|
||||||
\fBpreview-fg \fRPreview window text
|
\fBpreview-fg \fRPreview window text
|
||||||
\fBbg \fRBackground
|
\fBbg \fRBackground
|
||||||
|
\fBselected-bg \fRSelected line background
|
||||||
\fBpreview-bg \fRPreview window background
|
\fBpreview-bg \fRPreview window background
|
||||||
\fBhl \fRHighlighted substrings
|
\fBhl \fRHighlighted substrings
|
||||||
\fBfg+ \fRText (current line)
|
\fBselected-hl \fRHighlighted substrings in the selected line
|
||||||
\fBbg+ \fRBackground (current line)
|
\fBcurrent-fg (fg+) \fRText (current line)
|
||||||
|
\fBcurrent-bg (bg+) \fRBackground (current line)
|
||||||
\fBgutter \fRGutter on the left
|
\fBgutter \fRGutter on the left
|
||||||
\fBhl+ \fRHighlighted substrings (current line)
|
\fBcurrent-hl (hl+) \fRHighlighted substrings (current line)
|
||||||
\fBquery \fRQuery string
|
\fBquery \fRQuery string
|
||||||
\fBdisabled \fRQuery string when search is disabled (\fB--disabled\fR)
|
\fBdisabled \fRQuery string when search is disabled (\fB--disabled\fR)
|
||||||
\fBinfo \fRInfo line (match counters)
|
\fBinfo \fRInfo line (match counters)
|
||||||
@@ -534,6 +537,9 @@ color mappings.
|
|||||||
--color='pointer:#E12672,marker:#E17899,prompt:#98BEDE,hl+:#98BC99'\fR
|
--color='pointer:#E12672,marker:#E17899,prompt:#98BEDE,hl+:#98BC99'\fR
|
||||||
.RE
|
.RE
|
||||||
.TP
|
.TP
|
||||||
|
.B "--highlight-line"
|
||||||
|
Highlight the whole current line
|
||||||
|
.TP
|
||||||
.B "--no-bold"
|
.B "--no-bold"
|
||||||
Do not use bold text
|
Do not use bold text
|
||||||
.TP
|
.TP
|
||||||
@@ -821,7 +827,7 @@ e.g. \fBfzf --multi | fzf --sync\fR
|
|||||||
.B "--with-shell=STR"
|
.B "--with-shell=STR"
|
||||||
Shell command and flags to start child processes with. On *nix Systems, the
|
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.
|
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
|
On Windows, the default value is \fBcmd /s/c\fR when \fB$SHELL\fR is not
|
||||||
set.
|
set.
|
||||||
|
|
||||||
.RS
|
.RS
|
||||||
@@ -1278,6 +1284,15 @@ e.g.
|
|||||||
\fBfzf --bind space:jump,jump:accept,jump-cancel:abort\fR
|
\fBfzf --bind space:jump,jump:accept,jump-cancel:abort\fR
|
||||||
.RE
|
.RE
|
||||||
|
|
||||||
|
\fIclick-header\fR
|
||||||
|
.RS
|
||||||
|
Triggered when a mouse click occurs within the header. Sets \fBFZF_CLICK_HEADER_LINE\fR and \fBFZF_CLICK_HEADER_COLUMN\fR environment variables starting from 1.
|
||||||
|
|
||||||
|
e.g.
|
||||||
|
\fBprintf "head1\\nhead2" | fzf --header-lines=2 --bind 'click-header:transform-prompt:printf ${FZF_CLICK_HEADER_LINE}x${FZF_CLICK_HEADER_COLUMN}'\fR
|
||||||
|
|
||||||
|
.RE
|
||||||
|
|
||||||
.SS AVAILABLE ACTIONS:
|
.SS AVAILABLE ACTIONS:
|
||||||
A key or an event can be bound to one or more of the following actions.
|
A key or an event can be bound to one or more of the following actions.
|
||||||
|
|
||||||
|
|||||||
@@ -59,12 +59,9 @@ if s:is_win
|
|||||||
return iconv(a:str, &encoding, 'cp'.s:codepage)
|
return iconv(a:str, &encoding, 'cp'.s:codepage)
|
||||||
endfunction
|
endfunction
|
||||||
function! s:wrap_cmds(cmds)
|
function! s:wrap_cmds(cmds)
|
||||||
return map([
|
return map(['@echo off']
|
||||||
\ '@echo off',
|
|
||||||
\ 'setlocal enabledelayedexpansion']
|
|
||||||
\ + (has('gui_running') ? ['set TERM= > nul'] : [])
|
\ + (has('gui_running') ? ['set TERM= > nul'] : [])
|
||||||
\ + (type(a:cmds) == type([]) ? a:cmds : [a:cmds])
|
\ + (type(a:cmds) == type([]) ? a:cmds : [a:cmds]),
|
||||||
\ + ['endlocal'],
|
|
||||||
\ '<SID>enc_to_cp(v:val."\r")')
|
\ '<SID>enc_to_cp(v:val."\r")')
|
||||||
endfunction
|
endfunction
|
||||||
else
|
else
|
||||||
@@ -83,8 +80,6 @@ else
|
|||||||
endfunction
|
endfunction
|
||||||
endif
|
endif
|
||||||
|
|
||||||
let s:cmd_control_chars = ['&', '|', '<', '>', '(', ')', '@', '^', '!']
|
|
||||||
|
|
||||||
function! s:shellesc_cmd(arg)
|
function! s:shellesc_cmd(arg)
|
||||||
let e = '"'
|
let e = '"'
|
||||||
let slashes = 0
|
let slashes = 0
|
||||||
@@ -94,10 +89,6 @@ function! s:shellesc_cmd(arg)
|
|||||||
elseif c ==# '"'
|
elseif c ==# '"'
|
||||||
let e .= repeat('\', slashes + 1)
|
let e .= repeat('\', slashes + 1)
|
||||||
let slashes = 0
|
let slashes = 0
|
||||||
elseif c ==# '%'
|
|
||||||
let e .= '%'
|
|
||||||
elseif index(s:cmd_control_chars, c) >= 0
|
|
||||||
let e .= '^'
|
|
||||||
else
|
else
|
||||||
let slashes = 0
|
let slashes = 0
|
||||||
endif
|
endif
|
||||||
@@ -517,19 +508,19 @@ try
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
if has_key(dict, 'source')
|
if has_key(dict, 'source')
|
||||||
let source = remove(dict, 'source')
|
let source = dict.source
|
||||||
let type = type(source)
|
let type = type(source)
|
||||||
if type == 1
|
if type == 1
|
||||||
let source_command = source
|
let prefix = '('.source.')|'
|
||||||
elseif type == 3
|
elseif type == 3
|
||||||
let temps.input = s:fzf_tempname()
|
let temps.input = s:fzf_tempname()
|
||||||
call s:writefile(source, temps.input)
|
call s:writefile(source, temps.input)
|
||||||
let source_command = (s:is_win ? 'type ' : 'cat ').fzf#shellescape(temps.input)
|
let prefix = (s:is_win ? 'type ' : 'command cat ').fzf#shellescape(temps.input).'|'
|
||||||
else
|
else
|
||||||
throw 'Invalid source type'
|
throw 'Invalid source type'
|
||||||
endif
|
endif
|
||||||
else
|
else
|
||||||
let source_command = ''
|
let prefix = ''
|
||||||
endif
|
endif
|
||||||
|
|
||||||
let prefer_tmux = get(g:, 'fzf_prefer_tmux', 0) || has_key(dict, 'tmux')
|
let prefer_tmux = get(g:, 'fzf_prefer_tmux', 0) || has_key(dict, 'tmux')
|
||||||
@@ -553,11 +544,7 @@ try
|
|||||||
endif
|
endif
|
||||||
" Respect --border option given in $FZF_DEFAULT_OPTS and 'options'
|
" Respect --border option given in $FZF_DEFAULT_OPTS and 'options'
|
||||||
let optstr = join([s:border_opt(get(dict, 'window', 0)), s:extract_option($FZF_DEFAULT_OPTS, 'border'), optstr])
|
let optstr = join([s:border_opt(get(dict, 'window', 0)), s:extract_option($FZF_DEFAULT_OPTS, 'border'), optstr])
|
||||||
let prev_default_command = $FZF_DEFAULT_COMMAND
|
let command = prefix.(use_tmux ? s:fzf_tmux(dict) : fzf_exec).' '.optstr.' > '.temps.result
|
||||||
if len(source_command)
|
|
||||||
let $FZF_DEFAULT_COMMAND = source_command
|
|
||||||
endif
|
|
||||||
let command = (use_tmux ? s:fzf_tmux(dict) : fzf_exec).' '.optstr.' > '.temps.result
|
|
||||||
|
|
||||||
if use_term
|
if use_term
|
||||||
return s:execute_term(dict, command, temps)
|
return s:execute_term(dict, command, temps)
|
||||||
@@ -568,14 +555,6 @@ try
|
|||||||
call s:callback(dict, lines)
|
call s:callback(dict, lines)
|
||||||
return lines
|
return lines
|
||||||
finally
|
finally
|
||||||
if exists('source_command') && len(source_command)
|
|
||||||
if len(prev_default_command)
|
|
||||||
let $FZF_DEFAULT_COMMAND = prev_default_command
|
|
||||||
else
|
|
||||||
let $FZF_DEFAULT_COMMAND = ''
|
|
||||||
silent! execute 'unlet $FZF_DEFAULT_COMMAND'
|
|
||||||
endif
|
|
||||||
endif
|
|
||||||
let [&shell, &shellslash, &shellcmdflag, &shellxquote] = [shell, shellslash, shellcmdflag, shellxquote]
|
let [&shell, &shellslash, &shellcmdflag, &shellxquote] = [shell, shellslash, shellcmdflag, shellxquote]
|
||||||
endtry
|
endtry
|
||||||
endfunction
|
endfunction
|
||||||
@@ -605,8 +584,8 @@ function! s:fzf_tmux(dict)
|
|||||||
endif
|
endif
|
||||||
endfor
|
endfor
|
||||||
endif
|
endif
|
||||||
return printf('LINES=%d COLUMNS=%d %s %s - --',
|
return printf('LINES=%d COLUMNS=%d %s %s %s --',
|
||||||
\ &lines, &columns, fzf#shellescape(s:fzf_tmux), size)
|
\ &lines, &columns, fzf#shellescape(s:fzf_tmux), size, (has_key(a:dict, 'source') ? '' : '-'))
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:splittable(dict)
|
function! s:splittable(dict)
|
||||||
@@ -736,7 +715,8 @@ function! s:execute(dict, command, use_height, temps) abort
|
|||||||
let a:temps.shellscript = shellscript
|
let a:temps.shellscript = shellscript
|
||||||
endif
|
endif
|
||||||
if a:use_height
|
if a:use_height
|
||||||
call system(printf('tput cup %d > /dev/tty; tput cnorm > /dev/tty; %s < /dev/tty 2> /dev/tty', &lines, command))
|
let stdin = has_key(a:dict, 'source') ? '' : '< /dev/tty'
|
||||||
|
call system(printf('tput cup %d > /dev/tty; tput cnorm > /dev/tty; %s %s 2> /dev/tty', &lines, command, stdin))
|
||||||
else
|
else
|
||||||
execute 'silent !'.command
|
execute 'silent !'.command
|
||||||
endif
|
endif
|
||||||
|
|||||||
@@ -4,10 +4,12 @@
|
|||||||
# / __/ / /_/ __/
|
# / __/ / /_/ __/
|
||||||
# /_/ /___/_/ completion.bash
|
# /_/ /___/_/ completion.bash
|
||||||
#
|
#
|
||||||
# - $FZF_TMUX (default: 0)
|
# - $FZF_TMUX (default: 0)
|
||||||
# - $FZF_TMUX_OPTS (default: empty)
|
# - $FZF_TMUX_OPTS (default: empty)
|
||||||
# - $FZF_COMPLETION_TRIGGER (default: '**')
|
# - $FZF_COMPLETION_TRIGGER (default: '**')
|
||||||
# - $FZF_COMPLETION_OPTS (default: empty)
|
# - $FZF_COMPLETION_OPTS (default: empty)
|
||||||
|
# - $FZF_COMPLETION_PATH_OPTS (default: empty)
|
||||||
|
# - $FZF_COMPLETION_DIR_OPTS (default: empty)
|
||||||
|
|
||||||
if [[ $- =~ i ]]; then
|
if [[ $- =~ i ]]; then
|
||||||
|
|
||||||
@@ -297,8 +299,14 @@ __fzf_generic_path_completion() {
|
|||||||
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
|
||||||
[[ $1 =~ dir ]] && walker=dir,follow || walker=file,dir,follow,hidden
|
if [[ $1 =~ dir ]]; then
|
||||||
__fzf_comprun "$4" -q "$leftover" --walker "$walker" --walker-root="$dir"
|
walker=dir,follow
|
||||||
|
rest=${FZF_COMPLETION_DIR_OPTS-}
|
||||||
|
else
|
||||||
|
walker=file,dir,follow,hidden
|
||||||
|
rest=${FZF_COMPLETION_PATH_OPTS-}
|
||||||
|
fi
|
||||||
|
__fzf_comprun "$4" -q "$leftover" --walker "$walker" --walker-root="$dir" $rest
|
||||||
fi | while read -r item; do
|
fi | while read -r item; do
|
||||||
printf "%q " "${item%$3}$3"
|
printf "%q " "${item%$3}$3"
|
||||||
done
|
done
|
||||||
|
|||||||
@@ -4,10 +4,12 @@
|
|||||||
# / __/ / /_/ __/
|
# / __/ / /_/ __/
|
||||||
# /_/ /___/_/ completion.zsh
|
# /_/ /___/_/ completion.zsh
|
||||||
#
|
#
|
||||||
# - $FZF_TMUX (default: 0)
|
# - $FZF_TMUX (default: 0)
|
||||||
# - $FZF_TMUX_OPTS (default: '-d 40%')
|
# - $FZF_TMUX_OPTS (default: empty)
|
||||||
# - $FZF_COMPLETION_TRIGGER (default: '**')
|
# - $FZF_COMPLETION_TRIGGER (default: '**')
|
||||||
# - $FZF_COMPLETION_OPTS (default: empty)
|
# - $FZF_COMPLETION_OPTS (default: empty)
|
||||||
|
# - $FZF_COMPLETION_PATH_OPTS (default: empty)
|
||||||
|
# - $FZF_COMPLETION_DIR_OPTS (default: empty)
|
||||||
|
|
||||||
|
|
||||||
# Both branches of the following `if` do the same thing -- define
|
# Both branches of the following `if` do the same thing -- define
|
||||||
@@ -160,8 +162,14 @@ __fzf_generic_path_completion() {
|
|||||||
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
|
||||||
[[ $compgen =~ dir ]] && walker=dir,follow || walker=file,dir,follow,hidden
|
if [[ $compgen =~ dir ]]; then
|
||||||
__fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" --walker "$walker" --walker-root="$dir" < /dev/tty
|
walker=dir,follow
|
||||||
|
rest=${FZF_COMPLETION_DIR_OPTS-}
|
||||||
|
else
|
||||||
|
walker=file,dir,follow,hidden
|
||||||
|
rest=${FZF_COMPLETION_PATH_OPTS-}
|
||||||
|
fi
|
||||||
|
__fzf_comprun "$cmd" ${(Q)${(Z+n+)fzf_opts}} -q "$leftover" --walker "$walker" --walker-root="$dir" ${(Q)${(Z+n+)rest}} < /dev/tty
|
||||||
fi | while read item; do
|
fi | while read item; do
|
||||||
item="${item%$suffix}$suffix"
|
item="${item%$suffix}$suffix"
|
||||||
echo -n "${(q)item} "
|
echo -n "${(q)item} "
|
||||||
|
|||||||
@@ -37,92 +37,93 @@ func _() {
|
|||||||
_ = x[actDeleteChar-26]
|
_ = x[actDeleteChar-26]
|
||||||
_ = x[actDeleteCharEof-27]
|
_ = x[actDeleteCharEof-27]
|
||||||
_ = x[actEndOfLine-28]
|
_ = x[actEndOfLine-28]
|
||||||
_ = x[actForwardChar-29]
|
_ = x[actFatal-29]
|
||||||
_ = x[actForwardWord-30]
|
_ = x[actForwardChar-30]
|
||||||
_ = x[actKillLine-31]
|
_ = x[actForwardWord-31]
|
||||||
_ = x[actKillWord-32]
|
_ = x[actKillLine-32]
|
||||||
_ = x[actUnixLineDiscard-33]
|
_ = x[actKillWord-33]
|
||||||
_ = x[actUnixWordRubout-34]
|
_ = x[actUnixLineDiscard-34]
|
||||||
_ = x[actYank-35]
|
_ = x[actUnixWordRubout-35]
|
||||||
_ = x[actBackwardKillWord-36]
|
_ = x[actYank-36]
|
||||||
_ = x[actSelectAll-37]
|
_ = x[actBackwardKillWord-37]
|
||||||
_ = x[actDeselectAll-38]
|
_ = x[actSelectAll-38]
|
||||||
_ = x[actToggle-39]
|
_ = x[actDeselectAll-39]
|
||||||
_ = x[actToggleSearch-40]
|
_ = x[actToggle-40]
|
||||||
_ = x[actToggleAll-41]
|
_ = x[actToggleSearch-41]
|
||||||
_ = x[actToggleDown-42]
|
_ = x[actToggleAll-42]
|
||||||
_ = x[actToggleUp-43]
|
_ = x[actToggleDown-43]
|
||||||
_ = x[actToggleIn-44]
|
_ = x[actToggleUp-44]
|
||||||
_ = x[actToggleOut-45]
|
_ = x[actToggleIn-45]
|
||||||
_ = x[actToggleTrack-46]
|
_ = x[actToggleOut-46]
|
||||||
_ = x[actToggleTrackCurrent-47]
|
_ = x[actToggleTrack-47]
|
||||||
_ = x[actToggleHeader-48]
|
_ = x[actToggleTrackCurrent-48]
|
||||||
_ = x[actTrackCurrent-49]
|
_ = x[actToggleHeader-49]
|
||||||
_ = x[actUntrackCurrent-50]
|
_ = x[actTrackCurrent-50]
|
||||||
_ = x[actDown-51]
|
_ = x[actUntrackCurrent-51]
|
||||||
_ = x[actUp-52]
|
_ = x[actDown-52]
|
||||||
_ = x[actPageUp-53]
|
_ = x[actUp-53]
|
||||||
_ = x[actPageDown-54]
|
_ = x[actPageUp-54]
|
||||||
_ = x[actPosition-55]
|
_ = x[actPageDown-55]
|
||||||
_ = x[actHalfPageUp-56]
|
_ = x[actPosition-56]
|
||||||
_ = x[actHalfPageDown-57]
|
_ = x[actHalfPageUp-57]
|
||||||
_ = x[actOffsetUp-58]
|
_ = x[actHalfPageDown-58]
|
||||||
_ = x[actOffsetDown-59]
|
_ = x[actOffsetUp-59]
|
||||||
_ = x[actJump-60]
|
_ = x[actOffsetDown-60]
|
||||||
_ = x[actJumpAccept-61]
|
_ = x[actJump-61]
|
||||||
_ = x[actPrintQuery-62]
|
_ = x[actJumpAccept-62]
|
||||||
_ = x[actRefreshPreview-63]
|
_ = x[actPrintQuery-63]
|
||||||
_ = x[actReplaceQuery-64]
|
_ = x[actRefreshPreview-64]
|
||||||
_ = x[actToggleSort-65]
|
_ = x[actReplaceQuery-65]
|
||||||
_ = x[actShowPreview-66]
|
_ = x[actToggleSort-66]
|
||||||
_ = x[actHidePreview-67]
|
_ = x[actShowPreview-67]
|
||||||
_ = x[actTogglePreview-68]
|
_ = x[actHidePreview-68]
|
||||||
_ = x[actTogglePreviewWrap-69]
|
_ = x[actTogglePreview-69]
|
||||||
_ = x[actTransform-70]
|
_ = x[actTogglePreviewWrap-70]
|
||||||
_ = x[actTransformBorderLabel-71]
|
_ = x[actTransform-71]
|
||||||
_ = x[actTransformHeader-72]
|
_ = x[actTransformBorderLabel-72]
|
||||||
_ = x[actTransformPreviewLabel-73]
|
_ = x[actTransformHeader-73]
|
||||||
_ = x[actTransformPrompt-74]
|
_ = x[actTransformPreviewLabel-74]
|
||||||
_ = x[actTransformQuery-75]
|
_ = x[actTransformPrompt-75]
|
||||||
_ = x[actPreview-76]
|
_ = x[actTransformQuery-76]
|
||||||
_ = x[actChangePreview-77]
|
_ = x[actPreview-77]
|
||||||
_ = x[actChangePreviewWindow-78]
|
_ = x[actChangePreview-78]
|
||||||
_ = x[actPreviewTop-79]
|
_ = x[actChangePreviewWindow-79]
|
||||||
_ = x[actPreviewBottom-80]
|
_ = x[actPreviewTop-80]
|
||||||
_ = x[actPreviewUp-81]
|
_ = x[actPreviewBottom-81]
|
||||||
_ = x[actPreviewDown-82]
|
_ = x[actPreviewUp-82]
|
||||||
_ = x[actPreviewPageUp-83]
|
_ = x[actPreviewDown-83]
|
||||||
_ = x[actPreviewPageDown-84]
|
_ = x[actPreviewPageUp-84]
|
||||||
_ = x[actPreviewHalfPageUp-85]
|
_ = x[actPreviewPageDown-85]
|
||||||
_ = x[actPreviewHalfPageDown-86]
|
_ = x[actPreviewHalfPageUp-86]
|
||||||
_ = x[actPrevHistory-87]
|
_ = x[actPreviewHalfPageDown-87]
|
||||||
_ = x[actPrevSelected-88]
|
_ = x[actPrevHistory-88]
|
||||||
_ = x[actPut-89]
|
_ = x[actPrevSelected-89]
|
||||||
_ = x[actNextHistory-90]
|
_ = x[actPut-90]
|
||||||
_ = x[actNextSelected-91]
|
_ = x[actNextHistory-91]
|
||||||
_ = x[actExecute-92]
|
_ = x[actNextSelected-92]
|
||||||
_ = x[actExecuteSilent-93]
|
_ = x[actExecute-93]
|
||||||
_ = x[actExecuteMulti-94]
|
_ = x[actExecuteSilent-94]
|
||||||
_ = x[actSigStop-95]
|
_ = x[actExecuteMulti-95]
|
||||||
_ = x[actFirst-96]
|
_ = x[actSigStop-96]
|
||||||
_ = x[actLast-97]
|
_ = x[actFirst-97]
|
||||||
_ = x[actReload-98]
|
_ = x[actLast-98]
|
||||||
_ = x[actReloadSync-99]
|
_ = x[actReload-99]
|
||||||
_ = x[actDisableSearch-100]
|
_ = x[actReloadSync-100]
|
||||||
_ = x[actEnableSearch-101]
|
_ = x[actDisableSearch-101]
|
||||||
_ = x[actSelect-102]
|
_ = x[actEnableSearch-102]
|
||||||
_ = x[actDeselect-103]
|
_ = x[actSelect-103]
|
||||||
_ = x[actUnbind-104]
|
_ = x[actDeselect-104]
|
||||||
_ = x[actRebind-105]
|
_ = x[actUnbind-105]
|
||||||
_ = x[actBecome-106]
|
_ = x[actRebind-106]
|
||||||
_ = x[actResponse-107]
|
_ = x[actBecome-107]
|
||||||
_ = x[actShowHeader-108]
|
_ = x[actResponse-108]
|
||||||
_ = x[actHideHeader-109]
|
_ = x[actShowHeader-109]
|
||||||
|
_ = x[actHideHeader-110]
|
||||||
}
|
}
|
||||||
|
|
||||||
const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeHeaderactChangeMultiactChangePreviewLabelactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactTrackCurrentactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformHeaderactTransformPreviewLabelactTransformPromptactTransformQueryactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactBecomeactResponseactShowHeaderactHideHeader"
|
const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeHeaderactChangeMultiactChangePreviewLabelactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactFatalactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactTrackCurrentactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformHeaderactTransformPreviewLabelactTransformPromptactTransformQueryactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactBecomeactResponseactShowHeaderactHideHeader"
|
||||||
|
|
||||||
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}
|
var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 42, 50, 68, 76, 85, 102, 123, 138, 159, 183, 198, 207, 227, 242, 256, 277, 292, 306, 320, 333, 350, 358, 371, 387, 399, 407, 421, 435, 446, 457, 475, 492, 499, 518, 530, 544, 553, 568, 580, 593, 604, 615, 627, 641, 662, 677, 692, 709, 716, 721, 730, 741, 752, 765, 780, 791, 804, 811, 824, 837, 854, 869, 882, 896, 910, 926, 946, 958, 981, 999, 1023, 1041, 1058, 1068, 1084, 1106, 1119, 1135, 1147, 1161, 1177, 1195, 1215, 1237, 1251, 1266, 1272, 1286, 1301, 1311, 1327, 1342, 1352, 1360, 1367, 1376, 1389, 1405, 1420, 1429, 1440, 1449, 1458, 1467, 1478, 1491, 1504}
|
||||||
|
|
||||||
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) {
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ var (
|
|||||||
// Extra bonus for word boundary after slash, colon, semi-colon, and comma
|
// Extra bonus for word boundary after slash, colon, semi-colon, and comma
|
||||||
bonusBoundaryDelimiter int16 = bonusBoundary + 1
|
bonusBoundaryDelimiter int16 = bonusBoundary + 1
|
||||||
|
|
||||||
initialCharClass charClass = charWhite
|
initialCharClass = charWhite
|
||||||
|
|
||||||
// A minor optimization that can give 15%+ performance boost
|
// A minor optimization that can give 15%+ performance boost
|
||||||
asciiCharClasses [unicode.MaxASCII + 1]charClass
|
asciiCharClasses [unicode.MaxASCII + 1]charClass
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
package algo
|
package algo
|
||||||
|
|
||||||
var normalized map[rune]rune = map[rune]rune{
|
var normalized = map[rune]rune{
|
||||||
0x00E1: 'a', // WITH ACUTE, LATIN SMALL LETTER
|
0x00E1: 'a', // WITH ACUTE, LATIN SMALL LETTER
|
||||||
0x0103: 'a', // WITH BREVE, LATIN SMALL LETTER
|
0x0103: 'a', // WITH BREVE, LATIN SMALL LETTER
|
||||||
0x01CE: 'a', // WITH CARON, LATIN SMALL LETTER
|
0x01CE: 'a', // WITH CARON, LATIN SMALL LETTER
|
||||||
|
|||||||
@@ -292,7 +292,7 @@ func extractColor(str string, state *ansiState, proc func(string, *ansiState) bo
|
|||||||
|
|
||||||
func parseAnsiCode(s string, delimiter byte) (int, byte, string) {
|
func parseAnsiCode(s string, delimiter byte) (int, byte, string) {
|
||||||
var remaining string
|
var remaining string
|
||||||
i := -1
|
var i int
|
||||||
if delimiter == 0 {
|
if delimiter == 0 {
|
||||||
// Faster than strings.IndexAny(";:")
|
// Faster than strings.IndexAny(";:")
|
||||||
i = strings.IndexByte(s, ';')
|
i = strings.IndexByte(s, ';')
|
||||||
@@ -312,7 +312,7 @@ func parseAnsiCode(s string, delimiter byte) (int, byte, string) {
|
|||||||
// Inlined version of strconv.Atoi() that only handles positive
|
// Inlined version of strconv.Atoi() that only handles positive
|
||||||
// integers and does not allocate on error.
|
// integers and does not allocate on error.
|
||||||
code := 0
|
code := 0
|
||||||
for _, ch := range sbytes(s) {
|
for _, ch := range stringBytes(s) {
|
||||||
ch -= '0'
|
ch -= '0'
|
||||||
if ch > 9 {
|
if ch > 9 {
|
||||||
return -1, delimiter, remaining
|
return -1, delimiter, remaining
|
||||||
@@ -350,7 +350,7 @@ func interpretCode(ansiCode string, prevState *ansiState) ansiState {
|
|||||||
state256 := 0
|
state256 := 0
|
||||||
ptr := &state.fg
|
ptr := &state.fg
|
||||||
|
|
||||||
var delimiter byte = 0
|
var delimiter byte
|
||||||
count := 0
|
count := 0
|
||||||
for len(ansiCode) != 0 {
|
for len(ansiCode) != 0 {
|
||||||
var num int
|
var num int
|
||||||
|
|||||||
10
src/cache.go
10
src/cache.go
@@ -12,8 +12,14 @@ type ChunkCache struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewChunkCache returns a new ChunkCache
|
// NewChunkCache returns a new ChunkCache
|
||||||
func NewChunkCache() ChunkCache {
|
func NewChunkCache() *ChunkCache {
|
||||||
return ChunkCache{sync.Mutex{}, make(map[*Chunk]*queryCache)}
|
return &ChunkCache{sync.Mutex{}, make(map[*Chunk]*queryCache)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cc *ChunkCache) Clear() {
|
||||||
|
cc.mutex.Lock()
|
||||||
|
cc.cache = make(map[*Chunk]*queryCache)
|
||||||
|
cc.mutex.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add adds the list to the cache
|
// Add adds the list to the cache
|
||||||
|
|||||||
@@ -67,9 +67,9 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
exitCancel = -1
|
ExitCancel = -1
|
||||||
exitOk = 0
|
ExitOk = 0
|
||||||
exitNoMatch = 1
|
ExitNoMatch = 1
|
||||||
exitError = 2
|
ExitError = 2
|
||||||
exitInterrupt = 130
|
ExitInterrupt = 130
|
||||||
)
|
)
|
||||||
|
|||||||
92
src/core.go
92
src/core.go
@@ -2,10 +2,8 @@
|
|||||||
package fzf
|
package fzf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
)
|
)
|
||||||
@@ -19,30 +17,24 @@ Matcher -> EvtSearchFin -> Terminal (update list)
|
|||||||
Matcher -> EvtHeader -> Terminal (update header)
|
Matcher -> EvtHeader -> Terminal (update header)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
func ustring(data []byte) string {
|
|
||||||
return unsafe.String(unsafe.SliceData(data), len(data))
|
|
||||||
}
|
|
||||||
|
|
||||||
func sbytes(data string) []byte {
|
|
||||||
return unsafe.Slice(unsafe.StringData(data), len(data))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run starts fzf
|
// Run starts fzf
|
||||||
func Run(opts *Options, version string, revision string) {
|
func Run(opts *Options) (int, error) {
|
||||||
|
if err := postProcessOptions(opts); err != nil {
|
||||||
|
return ExitError, err
|
||||||
|
}
|
||||||
|
|
||||||
defer util.RunAtExitFuncs()
|
defer util.RunAtExitFuncs()
|
||||||
|
|
||||||
|
// Output channel given
|
||||||
|
if opts.Output != nil {
|
||||||
|
opts.Printer = func(str string) {
|
||||||
|
opts.Output <- str
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sort := opts.Sort > 0
|
sort := opts.Sort > 0
|
||||||
sortCriteria = opts.Criteria
|
sortCriteria = opts.Criteria
|
||||||
|
|
||||||
if opts.Version {
|
|
||||||
if len(revision) > 0 {
|
|
||||||
fmt.Printf("%s (%s)\n", version, revision)
|
|
||||||
} else {
|
|
||||||
fmt.Println(version)
|
|
||||||
}
|
|
||||||
util.Exit(exitOk)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Event channel
|
// Event channel
|
||||||
eventBox := util.NewEventBox()
|
eventBox := util.NewEventBox()
|
||||||
|
|
||||||
@@ -56,16 +48,16 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
if opts.Theme.Colored {
|
if opts.Theme.Colored {
|
||||||
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
|
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
|
||||||
prevLineAnsiState = lineAnsiState
|
prevLineAnsiState = lineAnsiState
|
||||||
trimmed, offsets, newState := extractColor(ustring(data), lineAnsiState, nil)
|
trimmed, offsets, newState := extractColor(byteString(data), lineAnsiState, nil)
|
||||||
lineAnsiState = newState
|
lineAnsiState = newState
|
||||||
return util.ToChars(sbytes(trimmed)), offsets
|
return util.ToChars(stringBytes(trimmed)), offsets
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// When color is disabled but ansi option is given,
|
// When color is disabled but ansi option is given,
|
||||||
// we simply strip out ANSI codes from the input
|
// we simply strip out ANSI codes from the input
|
||||||
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
|
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
|
||||||
trimmed, _, _ := extractColor(ustring(data), nil, nil)
|
trimmed, _, _ := extractColor(byteString(data), nil, nil)
|
||||||
return util.ToChars(sbytes(trimmed)), nil
|
return util.ToChars(stringBytes(trimmed)), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -77,7 +69,7 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
if len(opts.WithNth) == 0 {
|
if len(opts.WithNth) == 0 {
|
||||||
chunkList = NewChunkList(func(item *Item, data []byte) bool {
|
chunkList = NewChunkList(func(item *Item, data []byte) bool {
|
||||||
if len(header) < opts.HeaderLines {
|
if len(header) < opts.HeaderLines {
|
||||||
header = append(header, ustring(data))
|
header = append(header, byteString(data))
|
||||||
eventBox.Set(EvtHeader, header)
|
eventBox.Set(EvtHeader, header)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -88,7 +80,7 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
chunkList = NewChunkList(func(item *Item, data []byte) bool {
|
chunkList = NewChunkList(func(item *Item, data []byte) bool {
|
||||||
tokens := Tokenize(ustring(data), opts.Delimiter)
|
tokens := Tokenize(byteString(data), opts.Delimiter)
|
||||||
if opts.Ansi && opts.Theme.Colored && len(tokens) > 1 {
|
if opts.Ansi && opts.Theme.Colored && len(tokens) > 1 {
|
||||||
var ansiState *ansiState
|
var ansiState *ansiState
|
||||||
if prevLineAnsiState != nil {
|
if prevLineAnsiState != nil {
|
||||||
@@ -112,7 +104,7 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
eventBox.Set(EvtHeader, header)
|
eventBox.Set(EvtHeader, header)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
item.text, item.colors = ansiProcessor(sbytes(transformed))
|
item.text, item.colors = ansiProcessor(stringBytes(transformed))
|
||||||
item.text.TrimTrailingWhitespaces()
|
item.text.TrimTrailingWhitespaces()
|
||||||
item.text.Index = itemIndex
|
item.text.Index = itemIndex
|
||||||
item.origText = &data
|
item.origText = &data
|
||||||
@@ -131,7 +123,7 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
reader = NewReader(func(data []byte) bool {
|
reader = NewReader(func(data []byte) bool {
|
||||||
return chunkList.Push(data)
|
return chunkList.Push(data)
|
||||||
}, eventBox, executor, 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.Input, opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Matcher
|
// Matcher
|
||||||
@@ -147,14 +139,16 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
forward = true
|
forward = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
cache := NewChunkCache()
|
||||||
|
patternCache := make(map[string]*Pattern)
|
||||||
patternBuilder := func(runes []rune) *Pattern {
|
patternBuilder := func(runes []rune) *Pattern {
|
||||||
return BuildPattern(
|
return BuildPattern(cache, patternCache,
|
||||||
opts.Fuzzy, opts.FuzzyAlgo, opts.Extended, opts.Case, opts.Normalize, forward, withPos,
|
opts.Fuzzy, opts.FuzzyAlgo, opts.Extended, opts.Case, opts.Normalize, forward, withPos,
|
||||||
opts.Filter == nil, opts.Nth, opts.Delimiter, runes)
|
opts.Filter == nil, opts.Nth, opts.Delimiter, runes)
|
||||||
}
|
}
|
||||||
inputRevision := 0
|
inputRevision := 0
|
||||||
snapshotRevision := 0
|
snapshotRevision := 0
|
||||||
matcher := NewMatcher(patternBuilder, sort, opts.Tac, eventBox, inputRevision)
|
matcher := NewMatcher(cache, patternBuilder, sort, opts.Tac, eventBox, inputRevision)
|
||||||
|
|
||||||
// Filtering mode
|
// Filtering mode
|
||||||
if opts.Filter != nil {
|
if opts.Filter != nil {
|
||||||
@@ -182,7 +176,7 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}, eventBox, executor, opts.ReadZero, false)
|
}, eventBox, executor, opts.ReadZero, false)
|
||||||
reader.ReadSource(opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip)
|
reader.ReadSource(opts.Input, opts.WalkerRoot, opts.WalkerOpts, opts.WalkerSkip)
|
||||||
} else {
|
} else {
|
||||||
eventBox.Unwatch(EvtReadNew)
|
eventBox.Unwatch(EvtReadNew)
|
||||||
eventBox.WaitFor(EvtReadFin)
|
eventBox.WaitFor(EvtReadFin)
|
||||||
@@ -197,9 +191,9 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if found {
|
if found {
|
||||||
util.Exit(exitOk)
|
return ExitOk, nil
|
||||||
}
|
}
|
||||||
util.Exit(exitNoMatch)
|
return ExitNoMatch, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Synchronous search
|
// Synchronous search
|
||||||
@@ -210,9 +204,13 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
|
|
||||||
// Go interactive
|
// Go interactive
|
||||||
go matcher.Loop()
|
go matcher.Loop()
|
||||||
|
defer matcher.Stop()
|
||||||
|
|
||||||
// Terminal I/O
|
// Terminal I/O
|
||||||
terminal := NewTerminal(opts, eventBox, executor)
|
terminal, err := NewTerminal(opts, eventBox, executor)
|
||||||
|
if err != nil {
|
||||||
|
return ExitError, err
|
||||||
|
}
|
||||||
maxFit := 0 // Maximum number of items that can fit on screen
|
maxFit := 0 // Maximum number of items that can fit on screen
|
||||||
padHeight := 0
|
padHeight := 0
|
||||||
heightUnknown := opts.Height.auto
|
heightUnknown := opts.Height.auto
|
||||||
@@ -229,7 +227,7 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
// Event coordination
|
// Event coordination
|
||||||
reading := true
|
reading := true
|
||||||
ticks := 0
|
ticks := 0
|
||||||
var nextCommand *string
|
var nextCommand *commandSpec
|
||||||
var nextEnviron []string
|
var nextEnviron []string
|
||||||
eventBox.Watch(EvtReadNew)
|
eventBox.Watch(EvtReadNew)
|
||||||
total := 0
|
total := 0
|
||||||
@@ -250,7 +248,7 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
useSnapshot := false
|
useSnapshot := false
|
||||||
var snapshot []*Chunk
|
var snapshot []*Chunk
|
||||||
var count int
|
var count int
|
||||||
restart := func(command string, environ []string) {
|
restart := func(command commandSpec, environ []string) {
|
||||||
reading = true
|
reading = true
|
||||||
chunkList.Clear()
|
chunkList.Clear()
|
||||||
itemIndex = 0
|
itemIndex = 0
|
||||||
@@ -258,6 +256,9 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
header = make([]string, 0, opts.HeaderLines)
|
header = make([]string, 0, opts.HeaderLines)
|
||||||
go reader.restart(command, environ)
|
go reader.restart(command, environ)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exitCode := ExitOk
|
||||||
|
stop := false
|
||||||
for {
|
for {
|
||||||
delay := true
|
delay := true
|
||||||
ticks++
|
ticks++
|
||||||
@@ -278,7 +279,11 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
if reading {
|
if reading {
|
||||||
reader.terminate()
|
reader.terminate()
|
||||||
}
|
}
|
||||||
util.Exit(value.(int))
|
quitSignal := value.(quitSignal)
|
||||||
|
exitCode = quitSignal.code
|
||||||
|
err = quitSignal.err
|
||||||
|
stop = true
|
||||||
|
return
|
||||||
case EvtReadNew, EvtReadFin:
|
case EvtReadNew, EvtReadFin:
|
||||||
if evt == EvtReadFin && nextCommand != nil {
|
if evt == EvtReadFin && nextCommand != nil {
|
||||||
restart(*nextCommand, nextEnviron)
|
restart(*nextCommand, nextEnviron)
|
||||||
@@ -309,7 +314,7 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
matcher.Reset(snapshot, input(), false, !reading, sort, snapshotRevision)
|
matcher.Reset(snapshot, input(), false, !reading, sort, snapshotRevision)
|
||||||
|
|
||||||
case EvtSearchNew:
|
case EvtSearchNew:
|
||||||
var command *string
|
var command *commandSpec
|
||||||
var environ []string
|
var environ []string
|
||||||
var changed bool
|
var changed bool
|
||||||
switch val := value.(type) {
|
switch val := value.(type) {
|
||||||
@@ -378,10 +383,11 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
for i := 0; i < count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
opts.Printer(val.Get(i).item.AsString(opts.Ansi))
|
opts.Printer(val.Get(i).item.AsString(opts.Ansi))
|
||||||
}
|
}
|
||||||
if count > 0 {
|
if count == 0 {
|
||||||
util.Exit(exitOk)
|
exitCode = ExitNoMatch
|
||||||
}
|
}
|
||||||
util.Exit(exitNoMatch)
|
stop = true
|
||||||
|
return
|
||||||
}
|
}
|
||||||
determine(val.final)
|
determine(val.final)
|
||||||
}
|
}
|
||||||
@@ -392,6 +398,9 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
}
|
}
|
||||||
events.Clear()
|
events.Clear()
|
||||||
})
|
})
|
||||||
|
if stop {
|
||||||
|
break
|
||||||
|
}
|
||||||
if delay && reading {
|
if delay && reading {
|
||||||
dur := util.DurWithin(
|
dur := util.DurWithin(
|
||||||
time.Duration(ticks)*coordinatorDelayStep,
|
time.Duration(ticks)*coordinatorDelayStep,
|
||||||
@@ -399,4 +408,5 @@ func Run(opts *Options, version string, revision string) {
|
|||||||
time.Sleep(dur)
|
time.Sleep(dur)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return exitCode, err
|
||||||
}
|
}
|
||||||
|
|||||||
35
src/functions.go
Normal file
35
src/functions.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package fzf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func writeTemporaryFile(data []string, printSep string) string {
|
||||||
|
f, err := os.CreateTemp("", "fzf-preview-*")
|
||||||
|
if err != nil {
|
||||||
|
// Unable to create temporary file
|
||||||
|
// FIXME: Should we terminate the program?
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
f.WriteString(strings.Join(data, printSep))
|
||||||
|
f.WriteString(printSep)
|
||||||
|
return f.Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeFiles(files []string) {
|
||||||
|
for _, filename := range files {
|
||||||
|
os.Remove(filename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func stringBytes(data string) []byte {
|
||||||
|
return unsafe.Slice(unsafe.StringData(data), len(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
func byteString(data []byte) string {
|
||||||
|
return unsafe.String(unsafe.SliceData(data), len(data))
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@ type MatchRequest struct {
|
|||||||
|
|
||||||
// Matcher is responsible for performing search
|
// Matcher is responsible for performing search
|
||||||
type Matcher struct {
|
type Matcher struct {
|
||||||
|
cache *ChunkCache
|
||||||
patternBuilder func([]rune) *Pattern
|
patternBuilder func([]rune) *Pattern
|
||||||
sort bool
|
sort bool
|
||||||
tac bool
|
tac bool
|
||||||
@@ -38,10 +39,11 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// NewMatcher returns a new Matcher
|
// NewMatcher returns a new Matcher
|
||||||
func NewMatcher(patternBuilder func([]rune) *Pattern,
|
func NewMatcher(cache *ChunkCache, patternBuilder func([]rune) *Pattern,
|
||||||
sort bool, tac bool, eventBox *util.EventBox, revision int) *Matcher {
|
sort bool, tac bool, eventBox *util.EventBox, revision int) *Matcher {
|
||||||
partitions := util.Min(numPartitionsMultiplier*runtime.NumCPU(), maxPartitions)
|
partitions := util.Min(numPartitionsMultiplier*runtime.NumCPU(), maxPartitions)
|
||||||
return &Matcher{
|
return &Matcher{
|
||||||
|
cache: cache,
|
||||||
patternBuilder: patternBuilder,
|
patternBuilder: patternBuilder,
|
||||||
sort: sort,
|
sort: sort,
|
||||||
tac: tac,
|
tac: tac,
|
||||||
@@ -60,8 +62,13 @@ func (m *Matcher) Loop() {
|
|||||||
for {
|
for {
|
||||||
var request MatchRequest
|
var request MatchRequest
|
||||||
|
|
||||||
|
stop := false
|
||||||
m.reqBox.Wait(func(events *util.Events) {
|
m.reqBox.Wait(func(events *util.Events) {
|
||||||
for _, val := range *events {
|
for t, val := range *events {
|
||||||
|
if t == reqQuit {
|
||||||
|
stop = true
|
||||||
|
return
|
||||||
|
}
|
||||||
switch val := val.(type) {
|
switch val := val.(type) {
|
||||||
case MatchRequest:
|
case MatchRequest:
|
||||||
request = val
|
request = val
|
||||||
@@ -71,12 +78,15 @@ func (m *Matcher) Loop() {
|
|||||||
}
|
}
|
||||||
events.Clear()
|
events.Clear()
|
||||||
})
|
})
|
||||||
|
if stop {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
if request.sort != m.sort || request.revision != m.revision {
|
if request.sort != m.sort || request.revision != m.revision {
|
||||||
m.sort = request.sort
|
m.sort = request.sort
|
||||||
m.revision = request.revision
|
m.revision = request.revision
|
||||||
m.mergerCache = make(map[string]*Merger)
|
m.mergerCache = make(map[string]*Merger)
|
||||||
clearChunkCache()
|
m.cache.Clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restart search
|
// Restart search
|
||||||
@@ -236,3 +246,7 @@ func (m *Matcher) Reset(chunks []*Chunk, patternRunes []rune, cancel bool, final
|
|||||||
}
|
}
|
||||||
m.reqBox.Set(event, MatchRequest{chunks, pattern, final, sort && pattern.sortable, revision})
|
m.reqBox.Set(event, MatchRequest{chunks, pattern, final, sort && pattern.sortable, revision})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Matcher) Stop() {
|
||||||
|
m.reqBox.Set(reqQuit, nil)
|
||||||
|
}
|
||||||
|
|||||||
991
src/options.go
991
src/options.go
File diff suppressed because it is too large
Load Diff
@@ -3,9 +3,11 @@
|
|||||||
|
|
||||||
package fzf
|
package fzf
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
func (o *Options) initProfiling() error {
|
func (o *Options) initProfiling() error {
|
||||||
if o.CPUProfile != "" || o.MEMProfile != "" || o.BlockProfile != "" || o.MutexProfile != "" {
|
if o.CPUProfile != "" || o.MEMProfile != "" || o.BlockProfile != "" || o.MutexProfile != "" {
|
||||||
errorExit("error: profiling not supported: FZF must be built with '-tags=pprof' to enable profiling")
|
return errors.New("error: profiling not supported: FZF must be built with '-tags=pprof' to enable profiling")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ func TestDelimiterRegexRegexCaret(t *testing.T) {
|
|||||||
|
|
||||||
func TestSplitNth(t *testing.T) {
|
func TestSplitNth(t *testing.T) {
|
||||||
{
|
{
|
||||||
ranges := splitNth("..")
|
ranges, _ := splitNth("..")
|
||||||
if len(ranges) != 1 ||
|
if len(ranges) != 1 ||
|
||||||
ranges[0].begin != rangeEllipsis ||
|
ranges[0].begin != rangeEllipsis ||
|
||||||
ranges[0].end != rangeEllipsis {
|
ranges[0].end != rangeEllipsis {
|
||||||
@@ -88,7 +88,7 @@ func TestSplitNth(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
ranges := splitNth("..3,1..,2..3,4..-1,-3..-2,..,2,-2,2..-2,1..-1")
|
ranges, _ := splitNth("..3,1..,2..3,4..-1,-3..-2,..,2,-2,2..-2,1..-1")
|
||||||
if len(ranges) != 10 ||
|
if len(ranges) != 10 ||
|
||||||
ranges[0].begin != rangeEllipsis || ranges[0].end != 3 ||
|
ranges[0].begin != rangeEllipsis || ranges[0].end != 3 ||
|
||||||
ranges[1].begin != rangeEllipsis || ranges[1].end != rangeEllipsis ||
|
ranges[1].begin != rangeEllipsis || ranges[1].end != rangeEllipsis ||
|
||||||
@@ -137,7 +137,7 @@ func TestIrrelevantNth(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestParseKeys(t *testing.T) {
|
func TestParseKeys(t *testing.T) {
|
||||||
pairs := parseKeyChords("ctrl-z,alt-z,f2,@,Alt-a,!,ctrl-G,J,g,ctrl-alt-a,ALT-enter,alt-SPACE", "")
|
pairs, _ := parseKeyChords("ctrl-z,alt-z,f2,@,Alt-a,!,ctrl-G,J,g,ctrl-alt-a,ALT-enter,alt-SPACE", "")
|
||||||
checkEvent := func(e tui.Event, s string) {
|
checkEvent := func(e tui.Event, s string) {
|
||||||
if pairs[e] != s {
|
if pairs[e] != s {
|
||||||
t.Errorf("%s != %s", pairs[e], s)
|
t.Errorf("%s != %s", pairs[e], s)
|
||||||
@@ -163,7 +163,7 @@ func TestParseKeys(t *testing.T) {
|
|||||||
checkEvent(tui.AltKey(' '), "alt-SPACE")
|
checkEvent(tui.AltKey(' '), "alt-SPACE")
|
||||||
|
|
||||||
// Synonyms
|
// Synonyms
|
||||||
pairs = parseKeyChords("enter,Return,space,tab,btab,esc,up,down,left,right", "")
|
pairs, _ = parseKeyChords("enter,Return,space,tab,btab,esc,up,down,left,right", "")
|
||||||
if len(pairs) != 9 {
|
if len(pairs) != 9 {
|
||||||
t.Error(9)
|
t.Error(9)
|
||||||
}
|
}
|
||||||
@@ -177,7 +177,7 @@ func TestParseKeys(t *testing.T) {
|
|||||||
check(tui.Left, "left")
|
check(tui.Left, "left")
|
||||||
check(tui.Right, "right")
|
check(tui.Right, "right")
|
||||||
|
|
||||||
pairs = parseKeyChords("Tab,Ctrl-I,PgUp,page-up,pgdn,Page-Down,Home,End,Alt-BS,Alt-BSpace,shift-left,shift-right,btab,shift-tab,return,Enter,bspace", "")
|
pairs, _ = parseKeyChords("Tab,Ctrl-I,PgUp,page-up,pgdn,Page-Down,Home,End,Alt-BS,Alt-BSpace,shift-left,shift-right,btab,shift-tab,return,Enter,bspace", "")
|
||||||
if len(pairs) != 11 {
|
if len(pairs) != 11 {
|
||||||
t.Error(11)
|
t.Error(11)
|
||||||
}
|
}
|
||||||
@@ -206,40 +206,40 @@ func TestParseKeysWithComma(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pairs := parseKeyChords(",", "")
|
pairs, _ := parseKeyChords(",", "")
|
||||||
checkN(len(pairs), 1)
|
checkN(len(pairs), 1)
|
||||||
check(pairs, tui.Key(','), ",")
|
check(pairs, tui.Key(','), ",")
|
||||||
|
|
||||||
pairs = parseKeyChords(",,a,b", "")
|
pairs, _ = parseKeyChords(",,a,b", "")
|
||||||
checkN(len(pairs), 3)
|
checkN(len(pairs), 3)
|
||||||
check(pairs, tui.Key('a'), "a")
|
check(pairs, tui.Key('a'), "a")
|
||||||
check(pairs, tui.Key('b'), "b")
|
check(pairs, tui.Key('b'), "b")
|
||||||
check(pairs, tui.Key(','), ",")
|
check(pairs, tui.Key(','), ",")
|
||||||
|
|
||||||
pairs = parseKeyChords("a,b,,", "")
|
pairs, _ = parseKeyChords("a,b,,", "")
|
||||||
checkN(len(pairs), 3)
|
checkN(len(pairs), 3)
|
||||||
check(pairs, tui.Key('a'), "a")
|
check(pairs, tui.Key('a'), "a")
|
||||||
check(pairs, tui.Key('b'), "b")
|
check(pairs, tui.Key('b'), "b")
|
||||||
check(pairs, tui.Key(','), ",")
|
check(pairs, tui.Key(','), ",")
|
||||||
|
|
||||||
pairs = parseKeyChords("a,,,b", "")
|
pairs, _ = parseKeyChords("a,,,b", "")
|
||||||
checkN(len(pairs), 3)
|
checkN(len(pairs), 3)
|
||||||
check(pairs, tui.Key('a'), "a")
|
check(pairs, tui.Key('a'), "a")
|
||||||
check(pairs, tui.Key('b'), "b")
|
check(pairs, tui.Key('b'), "b")
|
||||||
check(pairs, tui.Key(','), ",")
|
check(pairs, tui.Key(','), ",")
|
||||||
|
|
||||||
pairs = parseKeyChords("a,,,b,c", "")
|
pairs, _ = parseKeyChords("a,,,b,c", "")
|
||||||
checkN(len(pairs), 4)
|
checkN(len(pairs), 4)
|
||||||
check(pairs, tui.Key('a'), "a")
|
check(pairs, tui.Key('a'), "a")
|
||||||
check(pairs, tui.Key('b'), "b")
|
check(pairs, tui.Key('b'), "b")
|
||||||
check(pairs, tui.Key('c'), "c")
|
check(pairs, tui.Key('c'), "c")
|
||||||
check(pairs, tui.Key(','), ",")
|
check(pairs, tui.Key(','), ",")
|
||||||
|
|
||||||
pairs = parseKeyChords(",,,", "")
|
pairs, _ = parseKeyChords(",,,", "")
|
||||||
checkN(len(pairs), 1)
|
checkN(len(pairs), 1)
|
||||||
check(pairs, tui.Key(','), ",")
|
check(pairs, tui.Key(','), ",")
|
||||||
|
|
||||||
pairs = parseKeyChords(",ALT-,,", "")
|
pairs, _ = parseKeyChords(",ALT-,,", "")
|
||||||
checkN(len(pairs), 1)
|
checkN(len(pairs), 1)
|
||||||
check(pairs, tui.AltKey(','), "ALT-,")
|
check(pairs, tui.AltKey(','), "ALT-,")
|
||||||
}
|
}
|
||||||
@@ -262,17 +262,13 @@ func TestBind(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
check(tui.CtrlA.AsEvent(), "", actBeginningOfLine)
|
check(tui.CtrlA.AsEvent(), "", actBeginningOfLine)
|
||||||
errorString := ""
|
|
||||||
errorFn := func(e string) {
|
|
||||||
errorString = e
|
|
||||||
}
|
|
||||||
parseKeymap(keymap,
|
parseKeymap(keymap,
|
||||||
"ctrl-a:kill-line,ctrl-b:toggle-sort+up+down,c:page-up,alt-z:page-down,"+
|
"ctrl-a:kill-line,ctrl-b:toggle-sort+up+down,c:page-up,alt-z:page-down,"+
|
||||||
"f1:execute(ls {+})+abort+execute(echo \n{+})+select-all,f2:execute/echo {}, {}, {}/,f3:execute[echo '({})'],f4:execute;less {};,"+
|
"f1:execute(ls {+})+abort+execute(echo \n{+})+select-all,f2:execute/echo {}, {}, {}/,f3:execute[echo '({})'],f4:execute;less {};,"+
|
||||||
"alt-a:execute-Multi@echo (,),[,],/,:,;,%,{}@,alt-b:execute;echo (,),[,],/,:,@,%,{};,"+
|
"alt-a:execute-Multi@echo (,),[,],/,:,;,%,{}@,alt-b:execute;echo (,),[,],/,:,@,%,{};,"+
|
||||||
"x:Execute(foo+bar),X:execute/bar+baz/"+
|
"x:Execute(foo+bar),X:execute/bar+baz/"+
|
||||||
",f1:+first,f1:+top"+
|
",f1:+first,f1:+top"+
|
||||||
",,:abort,::accept,+:execute:++\nfoobar,Y:execute(baz)+up", errorFn)
|
",,:abort,::accept,+:execute:++\nfoobar,Y:execute(baz)+up")
|
||||||
check(tui.CtrlA.AsEvent(), "", actKillLine)
|
check(tui.CtrlA.AsEvent(), "", actKillLine)
|
||||||
check(tui.CtrlB.AsEvent(), "", actToggleSort, actUp, actDown)
|
check(tui.CtrlB.AsEvent(), "", actToggleSort, actUp, actDown)
|
||||||
check(tui.Key('c'), "", actPageUp)
|
check(tui.Key('c'), "", actPageUp)
|
||||||
@@ -290,20 +286,17 @@ func TestBind(t *testing.T) {
|
|||||||
check(tui.Key('+'), "++\nfoobar,Y:execute(baz)+up", actExecute)
|
check(tui.Key('+'), "++\nfoobar,Y:execute(baz)+up", actExecute)
|
||||||
|
|
||||||
for idx, char := range []rune{'~', '!', '@', '#', '$', '%', '^', '&', '*', '|', ';', '/'} {
|
for idx, char := range []rune{'~', '!', '@', '#', '$', '%', '^', '&', '*', '|', ';', '/'} {
|
||||||
parseKeymap(keymap, fmt.Sprintf("%d:execute%cfoobar%c", idx%10, char, char), errorFn)
|
parseKeymap(keymap, fmt.Sprintf("%d:execute%cfoobar%c", idx%10, char, char))
|
||||||
check(tui.Key([]rune(fmt.Sprintf("%d", idx%10))[0]), "foobar", actExecute)
|
check(tui.Key([]rune(fmt.Sprintf("%d", idx%10))[0]), "foobar", actExecute)
|
||||||
}
|
}
|
||||||
|
|
||||||
parseKeymap(keymap, "f1:abort", errorFn)
|
parseKeymap(keymap, "f1:abort")
|
||||||
check(tui.F1.AsEvent(), "", actAbort)
|
check(tui.F1.AsEvent(), "", actAbort)
|
||||||
if len(errorString) > 0 {
|
|
||||||
t.Errorf("error parsing keymap: %s", errorString)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestColorSpec(t *testing.T) {
|
func TestColorSpec(t *testing.T) {
|
||||||
theme := tui.Dark256
|
theme := tui.Dark256
|
||||||
dark := parseTheme(theme, "dark")
|
dark, _ := parseTheme(theme, "dark")
|
||||||
if *dark != *theme {
|
if *dark != *theme {
|
||||||
t.Errorf("colors should be equivalent")
|
t.Errorf("colors should be equivalent")
|
||||||
}
|
}
|
||||||
@@ -311,7 +304,7 @@ func TestColorSpec(t *testing.T) {
|
|||||||
t.Errorf("point should not be equivalent")
|
t.Errorf("point should not be equivalent")
|
||||||
}
|
}
|
||||||
|
|
||||||
light := parseTheme(theme, "dark,light")
|
light, _ := parseTheme(theme, "dark,light")
|
||||||
if *light == *theme {
|
if *light == *theme {
|
||||||
t.Errorf("should not be equivalent")
|
t.Errorf("should not be equivalent")
|
||||||
}
|
}
|
||||||
@@ -322,7 +315,7 @@ func TestColorSpec(t *testing.T) {
|
|||||||
t.Errorf("point should not be equivalent")
|
t.Errorf("point should not be equivalent")
|
||||||
}
|
}
|
||||||
|
|
||||||
customized := parseTheme(theme, "fg:231,bg:232")
|
customized, _ := parseTheme(theme, "fg:231,bg:232")
|
||||||
if customized.Fg.Color != 231 || customized.Bg.Color != 232 {
|
if customized.Fg.Color != 231 || customized.Bg.Color != 232 {
|
||||||
t.Errorf("color not customized")
|
t.Errorf("color not customized")
|
||||||
}
|
}
|
||||||
@@ -335,7 +328,7 @@ func TestColorSpec(t *testing.T) {
|
|||||||
t.Errorf("colors should now be equivalent: %v, %v", tui.Dark256, customized)
|
t.Errorf("colors should now be equivalent: %v, %v", tui.Dark256, customized)
|
||||||
}
|
}
|
||||||
|
|
||||||
customized = parseTheme(theme, "fg:231,dark,bg:232")
|
customized, _ = parseTheme(theme, "fg:231,dark,bg:232")
|
||||||
if customized.Fg != tui.Dark256.Fg || customized.Bg == tui.Dark256.Bg {
|
if customized.Fg != tui.Dark256.Fg || customized.Bg == tui.Dark256.Bg {
|
||||||
t.Errorf("color not customized")
|
t.Errorf("color not customized")
|
||||||
}
|
}
|
||||||
@@ -475,7 +468,7 @@ func TestValidateSign(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestParseSingleActionList(t *testing.T) {
|
func TestParseSingleActionList(t *testing.T) {
|
||||||
actions := parseSingleActionList("Execute@foo+bar,baz@+up+up+reload:down+down", func(string) {})
|
actions, _ := parseSingleActionList("Execute@foo+bar,baz@+up+up+reload:down+down")
|
||||||
if len(actions) != 4 {
|
if len(actions) != 4 {
|
||||||
t.Errorf("Invalid number of actions parsed:%d", len(actions))
|
t.Errorf("Invalid number of actions parsed:%d", len(actions))
|
||||||
}
|
}
|
||||||
@@ -491,11 +484,8 @@ func TestParseSingleActionList(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestParseSingleActionListError(t *testing.T) {
|
func TestParseSingleActionListError(t *testing.T) {
|
||||||
err := ""
|
_, err := parseSingleActionList("change-query(foobar)baz")
|
||||||
parseSingleActionList("change-query(foobar)baz", func(e string) {
|
if err == nil {
|
||||||
err = e
|
|
||||||
})
|
|
||||||
if len(err) == 0 {
|
|
||||||
t.Errorf("Failed to detect error")
|
t.Errorf("Failed to detect error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,32 +60,17 @@ type Pattern struct {
|
|||||||
delimiter Delimiter
|
delimiter Delimiter
|
||||||
nth []Range
|
nth []Range
|
||||||
procFun map[termType]algo.Algo
|
procFun map[termType]algo.Algo
|
||||||
|
cache *ChunkCache
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var _splitRegex *regexp.Regexp
|
||||||
_patternCache map[string]*Pattern
|
|
||||||
_splitRegex *regexp.Regexp
|
|
||||||
_cache ChunkCache
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
_splitRegex = regexp.MustCompile(" +")
|
_splitRegex = regexp.MustCompile(" +")
|
||||||
clearPatternCache()
|
|
||||||
clearChunkCache()
|
|
||||||
}
|
|
||||||
|
|
||||||
func clearPatternCache() {
|
|
||||||
// We can uniquely identify the pattern for a given string since
|
|
||||||
// search mode and caseMode do not change while the program is running
|
|
||||||
_patternCache = make(map[string]*Pattern)
|
|
||||||
}
|
|
||||||
|
|
||||||
func clearChunkCache() {
|
|
||||||
_cache = NewChunkCache()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// BuildPattern builds Pattern object from the given arguments
|
// BuildPattern builds Pattern object from the given arguments
|
||||||
func BuildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case, normalize bool, forward bool,
|
func BuildPattern(cache *ChunkCache, patternCache map[string]*Pattern, fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case, normalize bool, forward bool,
|
||||||
withPos bool, cacheable bool, nth []Range, delimiter Delimiter, runes []rune) *Pattern {
|
withPos bool, cacheable bool, nth []Range, delimiter Delimiter, runes []rune) *Pattern {
|
||||||
|
|
||||||
var asString string
|
var asString string
|
||||||
@@ -98,7 +83,9 @@ func BuildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case,
|
|||||||
asString = string(runes)
|
asString = string(runes)
|
||||||
}
|
}
|
||||||
|
|
||||||
cached, found := _patternCache[asString]
|
// We can uniquely identify the pattern for a given string since
|
||||||
|
// search mode and caseMode do not change while the program is running
|
||||||
|
cached, found := patternCache[asString]
|
||||||
if found {
|
if found {
|
||||||
return cached
|
return cached
|
||||||
}
|
}
|
||||||
@@ -153,6 +140,7 @@ func BuildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case,
|
|||||||
cacheable: cacheable,
|
cacheable: cacheable,
|
||||||
nth: nth,
|
nth: nth,
|
||||||
delimiter: delimiter,
|
delimiter: delimiter,
|
||||||
|
cache: cache,
|
||||||
procFun: make(map[termType]algo.Algo)}
|
procFun: make(map[termType]algo.Algo)}
|
||||||
|
|
||||||
ptr.cacheKey = ptr.buildCacheKey()
|
ptr.cacheKey = ptr.buildCacheKey()
|
||||||
@@ -162,7 +150,7 @@ func BuildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case,
|
|||||||
ptr.procFun[termPrefix] = algo.PrefixMatch
|
ptr.procFun[termPrefix] = algo.PrefixMatch
|
||||||
ptr.procFun[termSuffix] = algo.SuffixMatch
|
ptr.procFun[termSuffix] = algo.SuffixMatch
|
||||||
|
|
||||||
_patternCache[asString] = ptr
|
patternCache[asString] = ptr
|
||||||
return ptr
|
return ptr
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,18 +270,18 @@ func (p *Pattern) Match(chunk *Chunk, slab *util.Slab) []Result {
|
|||||||
// ChunkCache: Exact match
|
// ChunkCache: Exact match
|
||||||
cacheKey := p.CacheKey()
|
cacheKey := p.CacheKey()
|
||||||
if p.cacheable {
|
if p.cacheable {
|
||||||
if cached := _cache.Lookup(chunk, cacheKey); cached != nil {
|
if cached := p.cache.Lookup(chunk, cacheKey); cached != nil {
|
||||||
return cached
|
return cached
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prefix/suffix cache
|
// Prefix/suffix cache
|
||||||
space := _cache.Search(chunk, cacheKey)
|
space := p.cache.Search(chunk, cacheKey)
|
||||||
|
|
||||||
matches := p.matchChunk(chunk, space, slab)
|
matches := p.matchChunk(chunk, space, slab)
|
||||||
|
|
||||||
if p.cacheable {
|
if p.cacheable {
|
||||||
_cache.Add(chunk, cacheKey, matches)
|
p.cache.Add(chunk, cacheKey, matches)
|
||||||
}
|
}
|
||||||
return matches
|
return matches
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,10 +64,15 @@ func TestParseTermsEmpty(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func buildPattern(fuzzy bool, fuzzyAlgo algo.Algo, extended bool, caseMode Case, normalize bool, forward bool,
|
||||||
|
withPos bool, cacheable bool, nth []Range, delimiter Delimiter, runes []rune) *Pattern {
|
||||||
|
return BuildPattern(NewChunkCache(), make(map[string]*Pattern),
|
||||||
|
fuzzy, fuzzyAlgo, extended, caseMode, normalize, forward,
|
||||||
|
withPos, cacheable, nth, delimiter, runes)
|
||||||
|
}
|
||||||
|
|
||||||
func TestExact(t *testing.T) {
|
func TestExact(t *testing.T) {
|
||||||
defer clearPatternCache()
|
pattern := buildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, false, true,
|
||||||
clearPatternCache()
|
|
||||||
pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, false, true,
|
|
||||||
[]Range{}, Delimiter{}, []rune("'abc"))
|
[]Range{}, Delimiter{}, []rune("'abc"))
|
||||||
chars := util.ToChars([]byte("aabbcc abc"))
|
chars := util.ToChars([]byte("aabbcc abc"))
|
||||||
res, pos := algo.ExactMatchNaive(
|
res, pos := algo.ExactMatchNaive(
|
||||||
@@ -81,9 +86,7 @@ func TestExact(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEqual(t *testing.T) {
|
func TestEqual(t *testing.T) {
|
||||||
defer clearPatternCache()
|
pattern := buildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune("^AbC$"))
|
||||||
clearPatternCache()
|
|
||||||
pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune("^AbC$"))
|
|
||||||
|
|
||||||
match := func(str string, sidxExpected int, eidxExpected int) {
|
match := func(str string, sidxExpected int, eidxExpected int) {
|
||||||
chars := util.ToChars([]byte(str))
|
chars := util.ToChars([]byte(str))
|
||||||
@@ -104,19 +107,12 @@ func TestEqual(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCaseSensitivity(t *testing.T) {
|
func TestCaseSensitivity(t *testing.T) {
|
||||||
defer clearPatternCache()
|
pat1 := buildPattern(true, algo.FuzzyMatchV2, false, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune("abc"))
|
||||||
clearPatternCache()
|
pat2 := buildPattern(true, algo.FuzzyMatchV2, false, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune("Abc"))
|
||||||
pat1 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune("abc"))
|
pat3 := buildPattern(true, algo.FuzzyMatchV2, false, CaseIgnore, false, true, false, true, []Range{}, Delimiter{}, []rune("abc"))
|
||||||
clearPatternCache()
|
pat4 := buildPattern(true, algo.FuzzyMatchV2, false, CaseIgnore, false, true, false, true, []Range{}, Delimiter{}, []rune("Abc"))
|
||||||
pat2 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune("Abc"))
|
pat5 := buildPattern(true, algo.FuzzyMatchV2, false, CaseRespect, false, true, false, true, []Range{}, Delimiter{}, []rune("abc"))
|
||||||
clearPatternCache()
|
pat6 := buildPattern(true, algo.FuzzyMatchV2, false, CaseRespect, false, true, false, true, []Range{}, Delimiter{}, []rune("Abc"))
|
||||||
pat3 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseIgnore, false, true, false, true, []Range{}, Delimiter{}, []rune("abc"))
|
|
||||||
clearPatternCache()
|
|
||||||
pat4 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseIgnore, false, true, false, true, []Range{}, Delimiter{}, []rune("Abc"))
|
|
||||||
clearPatternCache()
|
|
||||||
pat5 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseRespect, false, true, false, true, []Range{}, Delimiter{}, []rune("abc"))
|
|
||||||
clearPatternCache()
|
|
||||||
pat6 := BuildPattern(true, algo.FuzzyMatchV2, false, CaseRespect, false, true, false, true, []Range{}, Delimiter{}, []rune("Abc"))
|
|
||||||
|
|
||||||
if string(pat1.text) != "abc" || pat1.caseSensitive != false ||
|
if string(pat1.text) != "abc" || pat1.caseSensitive != false ||
|
||||||
string(pat2.text) != "Abc" || pat2.caseSensitive != true ||
|
string(pat2.text) != "Abc" || pat2.caseSensitive != true ||
|
||||||
@@ -129,7 +125,7 @@ func TestCaseSensitivity(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestOrigTextAndTransformed(t *testing.T) {
|
func TestOrigTextAndTransformed(t *testing.T) {
|
||||||
pattern := BuildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune("jg"))
|
pattern := buildPattern(true, algo.FuzzyMatchV2, true, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune("jg"))
|
||||||
tokens := Tokenize("junegunn", Delimiter{})
|
tokens := Tokenize("junegunn", Delimiter{})
|
||||||
trans := Transform(tokens, []Range{{1, 1}})
|
trans := Transform(tokens, []Range{{1, 1}})
|
||||||
|
|
||||||
@@ -163,15 +159,13 @@ func TestOrigTextAndTransformed(t *testing.T) {
|
|||||||
|
|
||||||
func TestCacheKey(t *testing.T) {
|
func TestCacheKey(t *testing.T) {
|
||||||
test := func(extended bool, patStr string, expected string, cacheable bool) {
|
test := func(extended bool, patStr string, expected string, cacheable bool) {
|
||||||
clearPatternCache()
|
pat := buildPattern(true, algo.FuzzyMatchV2, extended, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune(patStr))
|
||||||
pat := BuildPattern(true, algo.FuzzyMatchV2, extended, CaseSmart, false, true, false, true, []Range{}, Delimiter{}, []rune(patStr))
|
|
||||||
if pat.CacheKey() != expected {
|
if pat.CacheKey() != expected {
|
||||||
t.Errorf("Expected: %s, actual: %s", expected, pat.CacheKey())
|
t.Errorf("Expected: %s, actual: %s", expected, pat.CacheKey())
|
||||||
}
|
}
|
||||||
if pat.cacheable != cacheable {
|
if pat.cacheable != cacheable {
|
||||||
t.Errorf("Expected: %t, actual: %t (%s)", cacheable, pat.cacheable, patStr)
|
t.Errorf("Expected: %t, actual: %t (%s)", cacheable, pat.cacheable, patStr)
|
||||||
}
|
}
|
||||||
clearPatternCache()
|
|
||||||
}
|
}
|
||||||
test(false, "foo !bar", "foo !bar", true)
|
test(false, "foo !bar", "foo !bar", true)
|
||||||
test(false, "foo | bar !baz", "foo | bar !baz", true)
|
test(false, "foo | bar !baz", "foo | bar !baz", true)
|
||||||
@@ -187,15 +181,13 @@ func TestCacheKey(t *testing.T) {
|
|||||||
|
|
||||||
func TestCacheable(t *testing.T) {
|
func TestCacheable(t *testing.T) {
|
||||||
test := func(fuzzy bool, str string, expected string, cacheable bool) {
|
test := func(fuzzy bool, str string, expected string, cacheable bool) {
|
||||||
clearPatternCache()
|
pat := buildPattern(fuzzy, algo.FuzzyMatchV2, true, CaseSmart, true, true, false, true, []Range{}, Delimiter{}, []rune(str))
|
||||||
pat := BuildPattern(fuzzy, algo.FuzzyMatchV2, true, CaseSmart, true, true, false, true, []Range{}, Delimiter{}, []rune(str))
|
|
||||||
if pat.CacheKey() != expected {
|
if pat.CacheKey() != expected {
|
||||||
t.Errorf("Expected: %s, actual: %s", expected, pat.CacheKey())
|
t.Errorf("Expected: %s, actual: %s", expected, pat.CacheKey())
|
||||||
}
|
}
|
||||||
if cacheable != pat.cacheable {
|
if cacheable != pat.cacheable {
|
||||||
t.Errorf("Invalid Pattern.cacheable for \"%s\": %v (expected: %v)", str, pat.cacheable, cacheable)
|
t.Errorf("Invalid Pattern.cacheable for \"%s\": %v (expected: %v)", str, pat.cacheable, cacheable)
|
||||||
}
|
}
|
||||||
clearPatternCache()
|
|
||||||
}
|
}
|
||||||
test(true, "foo bar", "foo\tbar", true)
|
test(true, "foo bar", "foo\tbar", true)
|
||||||
test(true, "foo 'bar", "foo\tbar", false)
|
test(true, "foo 'bar", "foo\tbar", false)
|
||||||
|
|||||||
@@ -3,6 +3,4 @@
|
|||||||
package protector
|
package protector
|
||||||
|
|
||||||
// Protect calls OS specific protections like pledge on OpenBSD
|
// Protect calls OS specific protections like pledge on OpenBSD
|
||||||
func Protect() {
|
func Protect() {}
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -86,18 +86,34 @@ func (r *Reader) terminate() {
|
|||||||
r.mutex.Unlock()
|
r.mutex.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Reader) restart(command string, environ []string) {
|
func (r *Reader) restart(command commandSpec, environ []string) {
|
||||||
r.event = int32(EvtReady)
|
r.event = int32(EvtReady)
|
||||||
r.startEventPoller()
|
r.startEventPoller()
|
||||||
success := r.readFromCommand(command, environ)
|
success := r.readFromCommand(command.command, environ)
|
||||||
r.fin(success)
|
r.fin(success)
|
||||||
|
removeFiles(command.tempFiles)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Reader) readChannel(inputChan chan string) bool {
|
||||||
|
for {
|
||||||
|
item, more := <-inputChan
|
||||||
|
if !more {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if r.pusher([]byte(item)) {
|
||||||
|
atomic.StoreInt32(&r.event, int32(EvtReadNew))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadSource reads data from the default command or from standard input
|
// ReadSource reads data from the default command or from standard input
|
||||||
func (r *Reader) ReadSource(root string, opts walkerOpts, ignores []string) {
|
func (r *Reader) ReadSource(inputChan chan string, root string, opts walkerOpts, ignores []string) {
|
||||||
r.startEventPoller()
|
r.startEventPoller()
|
||||||
var success bool
|
var success bool
|
||||||
if util.IsTty() {
|
if inputChan != nil {
|
||||||
|
success = r.readChannel(inputChan)
|
||||||
|
} else if util.IsTty() {
|
||||||
cmd := os.Getenv("FZF_DEFAULT_COMMAND")
|
cmd := os.Getenv("FZF_DEFAULT_COMMAND")
|
||||||
if len(cmd) == 0 {
|
if len(cmd) == 0 {
|
||||||
success = r.readFiles(root, opts, ignores)
|
success = r.readFiles(root, opts, ignores)
|
||||||
|
|||||||
@@ -73,28 +73,28 @@ func parseListenAddress(address string) (listenAddress, error) {
|
|||||||
return listenAddress{parts[0], port}, nil
|
return listenAddress{parts[0], port}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func startHttpServer(address listenAddress, actionChannel chan []*action, responseChannel chan string) (int, error) {
|
func startHttpServer(address listenAddress, actionChannel chan []*action, responseChannel chan string) (net.Listener, int, error) {
|
||||||
host := address.host
|
host := address.host
|
||||||
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, errors.New("FZF_API_KEY is required to allow remote access")
|
return nil, 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)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return port, fmt.Errorf("failed to listen on %s", addrStr)
|
return nil, port, fmt.Errorf("failed to listen on %s", addrStr)
|
||||||
}
|
}
|
||||||
if port == 0 {
|
if port == 0 {
|
||||||
addr := listener.Addr().String()
|
addr := listener.Addr().String()
|
||||||
parts := strings.Split(addr, ":")
|
parts := strings.Split(addr, ":")
|
||||||
if len(parts) < 2 {
|
if len(parts) < 2 {
|
||||||
return port, fmt.Errorf("cannot extract port: %s", addr)
|
return nil, port, fmt.Errorf("cannot extract port: %s", addr)
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
port, err = strconv.Atoi(parts[len(parts)-1])
|
port, err = strconv.Atoi(parts[len(parts)-1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return port, err
|
return nil, port, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,18 +109,16 @@ func startHttpServer(address listenAddress, actionChannel chan []*action, respon
|
|||||||
conn, err := listener.Accept()
|
conn, err := listener.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, net.ErrClosed) {
|
if errors.Is(err, net.ErrClosed) {
|
||||||
break
|
return
|
||||||
} else {
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
conn.Write([]byte(server.handleHttpRequest(conn)))
|
conn.Write([]byte(server.handleHttpRequest(conn)))
|
||||||
conn.Close()
|
conn.Close()
|
||||||
}
|
}
|
||||||
listener.Close()
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return port, nil
|
return listener, port, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Here we are writing a simplistic HTTP server without using net/http
|
// Here we are writing a simplistic HTTP server without using net/http
|
||||||
@@ -217,12 +215,9 @@ func (server *httpServer) handleHttpRequest(conn net.Conn) string {
|
|||||||
}
|
}
|
||||||
body = body[:contentLength]
|
body = body[:contentLength]
|
||||||
|
|
||||||
errorMessage := ""
|
actions, err := parseSingleActionList(strings.Trim(string(body), "\r\n"))
|
||||||
actions := parseSingleActionList(strings.Trim(string(body), "\r\n"), func(message string) {
|
if err != nil {
|
||||||
errorMessage = message
|
return bad(err.Error())
|
||||||
})
|
|
||||||
if len(errorMessage) > 0 {
|
|
||||||
return bad(errorMessage)
|
|
||||||
}
|
}
|
||||||
if len(actions) == 0 {
|
if len(actions) == 0 {
|
||||||
return bad("no action specified")
|
return bad("no action specified")
|
||||||
|
|||||||
361
src/terminal.go
361
src/terminal.go
@@ -2,10 +2,12 @@ package fzf
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"regexp"
|
"regexp"
|
||||||
@@ -48,9 +50,7 @@ var placeholder *regexp.Regexp
|
|||||||
var whiteSuffix *regexp.Regexp
|
var whiteSuffix *regexp.Regexp
|
||||||
var offsetComponentRegex *regexp.Regexp
|
var offsetComponentRegex *regexp.Regexp
|
||||||
var offsetTrimCharsRegex *regexp.Regexp
|
var offsetTrimCharsRegex *regexp.Regexp
|
||||||
var activeTempFiles []string
|
|
||||||
var passThroughRegex *regexp.Regexp
|
var passThroughRegex *regexp.Regexp
|
||||||
var actionTypeRegex *regexp.Regexp
|
|
||||||
|
|
||||||
const clearCode string = "\x1b[2J"
|
const clearCode string = "\x1b[2J"
|
||||||
|
|
||||||
@@ -62,7 +62,6 @@ func init() {
|
|||||||
whiteSuffix = regexp.MustCompile(`\s*$`)
|
whiteSuffix = regexp.MustCompile(`\s*$`)
|
||||||
offsetComponentRegex = regexp.MustCompile(`([+-][0-9]+)|(-?/[1-9][0-9]*)`)
|
offsetComponentRegex = regexp.MustCompile(`([+-][0-9]+)|(-?/[1-9][0-9]*)`)
|
||||||
offsetTrimCharsRegex = regexp.MustCompile(`[^0-9/+-]`)
|
offsetTrimCharsRegex = regexp.MustCompile(`[^0-9/+-]`)
|
||||||
activeTempFiles = []string{}
|
|
||||||
|
|
||||||
// Parts of the preview output that should be passed through to the terminal
|
// Parts of the preview output that should be passed through to the terminal
|
||||||
// * https://github.com/tmux/tmux/wiki/FAQ#what-is-the-passthrough-escape-sequence-and-how-do-i-use-it
|
// * https://github.com/tmux/tmux/wiki/FAQ#what-is-the-passthrough-escape-sequence-and-how-do-i-use-it
|
||||||
@@ -112,6 +111,16 @@ func (s *resumableState) Set(flag bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type commandSpec struct {
|
||||||
|
command string
|
||||||
|
tempFiles []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type quitSignal struct {
|
||||||
|
code int
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
type previewer struct {
|
type previewer struct {
|
||||||
version int64
|
version int64
|
||||||
lines []string
|
lines []string
|
||||||
@@ -226,6 +235,7 @@ type Terminal struct {
|
|||||||
printQuery bool
|
printQuery bool
|
||||||
history *History
|
history *History
|
||||||
cycle bool
|
cycle bool
|
||||||
|
highlightLine bool
|
||||||
headerVisible bool
|
headerVisible bool
|
||||||
headerFirst bool
|
headerFirst bool
|
||||||
headerLines int
|
headerLines int
|
||||||
@@ -241,6 +251,7 @@ type Terminal struct {
|
|||||||
unicode bool
|
unicode bool
|
||||||
listenAddr *listenAddress
|
listenAddr *listenAddress
|
||||||
listenPort *int
|
listenPort *int
|
||||||
|
listener net.Listener
|
||||||
listenUnsafe bool
|
listenUnsafe bool
|
||||||
borderShape tui.BorderShape
|
borderShape tui.BorderShape
|
||||||
cleanExit bool
|
cleanExit bool
|
||||||
@@ -259,7 +270,7 @@ type Terminal struct {
|
|||||||
hasResizeActions bool
|
hasResizeActions bool
|
||||||
triggerLoad bool
|
triggerLoad bool
|
||||||
reading bool
|
reading bool
|
||||||
running bool
|
running *util.AtomicBool
|
||||||
failed *string
|
failed *string
|
||||||
jumping jumpMode
|
jumping jumpMode
|
||||||
jumpLabels string
|
jumpLabels string
|
||||||
@@ -278,12 +289,12 @@ type Terminal struct {
|
|||||||
previewBox *util.EventBox
|
previewBox *util.EventBox
|
||||||
eventBox *util.EventBox
|
eventBox *util.EventBox
|
||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
initFunc func()
|
initFunc func() error
|
||||||
prevLines []itemLine
|
prevLines []itemLine
|
||||||
suppress bool
|
suppress bool
|
||||||
sigstop bool
|
sigstop bool
|
||||||
startChan chan fitpad
|
startChan chan fitpad
|
||||||
killChan chan int
|
killChan chan bool
|
||||||
serverInputChan chan []*action
|
serverInputChan chan []*action
|
||||||
serverOutputChan chan string
|
serverOutputChan chan string
|
||||||
eventChan chan tui.Event
|
eventChan chan tui.Event
|
||||||
@@ -298,6 +309,8 @@ type Terminal struct {
|
|||||||
areaLines int
|
areaLines int
|
||||||
areaColumns int
|
areaColumns int
|
||||||
forcePreview bool
|
forcePreview bool
|
||||||
|
clickHeaderLine int
|
||||||
|
clickHeaderColumn int
|
||||||
}
|
}
|
||||||
|
|
||||||
type selectedItem struct {
|
type selectedItem struct {
|
||||||
@@ -338,6 +351,7 @@ const (
|
|||||||
reqPreviewRefresh
|
reqPreviewRefresh
|
||||||
reqPreviewDelayed
|
reqPreviewDelayed
|
||||||
reqQuit
|
reqQuit
|
||||||
|
reqFatal
|
||||||
)
|
)
|
||||||
|
|
||||||
type action struct {
|
type action struct {
|
||||||
@@ -378,6 +392,7 @@ const (
|
|||||||
actDeleteChar
|
actDeleteChar
|
||||||
actDeleteCharEof
|
actDeleteCharEof
|
||||||
actEndOfLine
|
actEndOfLine
|
||||||
|
actFatal
|
||||||
actForwardChar
|
actForwardChar
|
||||||
actForwardWord
|
actForwardWord
|
||||||
actKillLine
|
actKillLine
|
||||||
@@ -498,7 +513,7 @@ type placeholderFlags struct {
|
|||||||
type searchRequest struct {
|
type searchRequest struct {
|
||||||
sort bool
|
sort bool
|
||||||
sync bool
|
sync bool
|
||||||
command *string
|
command *commandSpec
|
||||||
environ []string
|
environ []string
|
||||||
changed bool
|
changed bool
|
||||||
}
|
}
|
||||||
@@ -509,6 +524,7 @@ type previewRequest struct {
|
|||||||
pwindowSize tui.TermSize
|
pwindowSize tui.TermSize
|
||||||
scrollOffset int
|
scrollOffset int
|
||||||
list []*Item
|
list []*Item
|
||||||
|
env []string
|
||||||
}
|
}
|
||||||
|
|
||||||
type previewResult struct {
|
type previewResult struct {
|
||||||
@@ -535,6 +551,7 @@ func defaultKeymap() map[tui.Event][]*action {
|
|||||||
keymap[e] = toActions(a)
|
keymap[e] = toActions(a)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
add(tui.Fatal, actFatal)
|
||||||
add(tui.Invalid, actInvalid)
|
add(tui.Invalid, actInvalid)
|
||||||
add(tui.CtrlA, actBeginningOfLine)
|
add(tui.CtrlA, actBeginningOfLine)
|
||||||
add(tui.CtrlB, actBackwardChar)
|
add(tui.CtrlB, actBackwardChar)
|
||||||
@@ -640,7 +657,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, executor *util.Executor) *Terminal {
|
func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor) (*Terminal, error) {
|
||||||
input := trimQuery(opts.Query)
|
input := trimQuery(opts.Query)
|
||||||
var delay time.Duration
|
var delay time.Duration
|
||||||
if opts.Tac {
|
if opts.Tac {
|
||||||
@@ -658,11 +675,12 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
|||||||
}
|
}
|
||||||
var renderer tui.Renderer
|
var renderer tui.Renderer
|
||||||
fullscreen := !opts.Height.auto && (opts.Height.size == 0 || opts.Height.percent && opts.Height.size == 100)
|
fullscreen := !opts.Height.auto && (opts.Height.size == 0 || opts.Height.percent && opts.Height.size == 100)
|
||||||
|
var err error
|
||||||
if fullscreen {
|
if fullscreen {
|
||||||
if tui.HasFullscreenRenderer() {
|
if tui.HasFullscreenRenderer() {
|
||||||
renderer = tui.NewFullscreenRenderer(opts.Theme, opts.Black, opts.Mouse)
|
renderer = tui.NewFullscreenRenderer(opts.Theme, opts.Black, opts.Mouse)
|
||||||
} else {
|
} else {
|
||||||
renderer = tui.NewLightRenderer(opts.Theme, opts.Black, opts.Mouse, opts.Tabstop, opts.ClearOnExit,
|
renderer, err = tui.NewLightRenderer(opts.Theme, opts.Black, opts.Mouse, opts.Tabstop, opts.ClearOnExit,
|
||||||
true, func(h int) int { return h })
|
true, func(h int) int { return h })
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -678,7 +696,10 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
|||||||
effectiveMinHeight += borderLines(opts.BorderShape)
|
effectiveMinHeight += borderLines(opts.BorderShape)
|
||||||
return util.Min(termHeight, util.Max(evaluateHeight(opts, termHeight), effectiveMinHeight))
|
return util.Min(termHeight, util.Max(evaluateHeight(opts, termHeight), effectiveMinHeight))
|
||||||
}
|
}
|
||||||
renderer = tui.NewLightRenderer(opts.Theme, opts.Black, opts.Mouse, opts.Tabstop, opts.ClearOnExit, false, maxHeightFunc)
|
renderer, err = tui.NewLightRenderer(opts.Theme, opts.Black, opts.Mouse, opts.Tabstop, opts.ClearOnExit, false, maxHeightFunc)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
wordRubout := "[^\\pL\\pN][\\pL\\pN]"
|
wordRubout := "[^\\pL\\pN][\\pL\\pN]"
|
||||||
wordNext := "[\\pL\\pN][^\\pL\\pN]|(.$)"
|
wordNext := "[\\pL\\pN][^\\pL\\pN]|(.$)"
|
||||||
@@ -691,6 +712,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
|||||||
for key, action := range opts.Keymap {
|
for key, action := range opts.Keymap {
|
||||||
keymapCopy[key] = action
|
keymapCopy[key] = action
|
||||||
}
|
}
|
||||||
|
|
||||||
t := Terminal{
|
t := Terminal{
|
||||||
initDelay: delay,
|
initDelay: delay,
|
||||||
infoStyle: opts.InfoStyle,
|
infoStyle: opts.InfoStyle,
|
||||||
@@ -739,6 +761,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
|||||||
executor: executor,
|
executor: executor,
|
||||||
paused: opts.Phony,
|
paused: opts.Phony,
|
||||||
cycle: opts.Cycle,
|
cycle: opts.Cycle,
|
||||||
|
highlightLine: opts.CursorLine,
|
||||||
headerVisible: true,
|
headerVisible: true,
|
||||||
headerFirst: opts.HeaderFirst,
|
headerFirst: opts.HeaderFirst,
|
||||||
headerLines: opts.HeaderLines,
|
headerLines: opts.HeaderLines,
|
||||||
@@ -752,7 +775,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
|||||||
hasLoadActions: false,
|
hasLoadActions: false,
|
||||||
triggerLoad: false,
|
triggerLoad: false,
|
||||||
reading: true,
|
reading: true,
|
||||||
running: true,
|
running: util.NewAtomicBool(true),
|
||||||
failed: nil,
|
failed: nil,
|
||||||
jumping: jumpDisabled,
|
jumping: jumpDisabled,
|
||||||
jumpLabels: opts.JumpLabels,
|
jumpLabels: opts.JumpLabels,
|
||||||
@@ -773,12 +796,12 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
|||||||
slab: util.MakeSlab(slab16Size, slab32Size),
|
slab: util.MakeSlab(slab16Size, slab32Size),
|
||||||
theme: opts.Theme,
|
theme: opts.Theme,
|
||||||
startChan: make(chan fitpad, 1),
|
startChan: make(chan fitpad, 1),
|
||||||
killChan: make(chan int),
|
killChan: make(chan bool),
|
||||||
serverInputChan: make(chan []*action, 100),
|
serverInputChan: make(chan []*action, 100),
|
||||||
serverOutputChan: make(chan string),
|
serverOutputChan: make(chan string),
|
||||||
eventChan: make(chan tui.Event, 6), // (load + result + zero|one) | (focus) | (resize) | (GetChar)
|
eventChan: make(chan tui.Event, 6), // (load + result + zero|one) | (focus) | (resize) | (GetChar)
|
||||||
tui: renderer,
|
tui: renderer,
|
||||||
initFunc: func() { renderer.Init() },
|
initFunc: func() error { return renderer.Init() },
|
||||||
executing: util.NewAtomicBool(false),
|
executing: util.NewAtomicBool(false),
|
||||||
lastAction: actStart,
|
lastAction: actStart,
|
||||||
lastFocus: minItem.Index()}
|
lastFocus: minItem.Index()}
|
||||||
@@ -830,14 +853,15 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor
|
|||||||
_, t.hasLoadActions = t.keymap[tui.Load.AsEvent()]
|
_, t.hasLoadActions = t.keymap[tui.Load.AsEvent()]
|
||||||
|
|
||||||
if t.listenAddr != nil {
|
if t.listenAddr != nil {
|
||||||
port, err := startHttpServer(*t.listenAddr, t.serverInputChan, t.serverOutputChan)
|
listener, port, err := startHttpServer(*t.listenAddr, t.serverInputChan, t.serverOutputChan)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorExit(err.Error())
|
return nil, err
|
||||||
}
|
}
|
||||||
|
t.listener = listener
|
||||||
t.listenPort = &port
|
t.listenPort = &port
|
||||||
}
|
}
|
||||||
|
|
||||||
return &t
|
return &t, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) environ() []string {
|
func (t *Terminal) environ() []string {
|
||||||
@@ -857,6 +881,8 @@ func (t *Terminal) environ() []string {
|
|||||||
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)))
|
env = append(env, fmt.Sprintf("FZF_POS=%d", util.Min(t.merger.Length(), t.cy+1)))
|
||||||
|
env = append(env, fmt.Sprintf("FZF_CLICK_HEADER_LINE=%d", t.clickHeaderLine))
|
||||||
|
env = append(env, fmt.Sprintf("FZF_CLICK_HEADER_COLUMN=%d", t.clickHeaderColumn))
|
||||||
return env
|
return env
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1288,7 +1314,8 @@ func (t *Terminal) resizeWindows(forcePreview bool) {
|
|||||||
t.pborder.Close()
|
t.pborder.Close()
|
||||||
t.pborder = nil
|
t.pborder = nil
|
||||||
}
|
}
|
||||||
if t.pwindow != nil {
|
hadPreviewWindow := t.hasPreviewWindow()
|
||||||
|
if hadPreviewWindow {
|
||||||
t.pwindow.Close()
|
t.pwindow.Close()
|
||||||
t.pwindow = nil
|
t.pwindow = nil
|
||||||
}
|
}
|
||||||
@@ -1387,6 +1414,9 @@ func (t *Terminal) resizeWindows(forcePreview bool) {
|
|||||||
pwidth = util.Max(0, pwidth)
|
pwidth = util.Max(0, pwidth)
|
||||||
pheight = util.Max(0, pheight)
|
pheight = util.Max(0, pheight)
|
||||||
t.pwindow = t.tui.NewWindow(y, x, pwidth, pheight, true, noBorder)
|
t.pwindow = t.tui.NewWindow(y, x, pwidth, pheight, true, noBorder)
|
||||||
|
if !hadPreviewWindow {
|
||||||
|
t.pwindow.Erase()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
verticalPad := 2
|
verticalPad := 2
|
||||||
minPreviewHeight := 3
|
minPreviewHeight := 3
|
||||||
@@ -1452,6 +1482,14 @@ func (t *Terminal) resizeWindows(forcePreview bool) {
|
|||||||
// Add a 1-column margin between the preview window and the main window
|
// Add a 1-column margin between the preview window and the main window
|
||||||
t.window = t.tui.NewWindow(
|
t.window = t.tui.NewWindow(
|
||||||
marginInt[0], marginInt[3]+pwidth+1, width-pwidth-1, height, false, noBorder)
|
marginInt[0], marginInt[3]+pwidth+1, width-pwidth-1, height, false, noBorder)
|
||||||
|
|
||||||
|
// Clear characters on the margin
|
||||||
|
// fzf --bind 'space:preview(seq 100)' --preview-window left,1
|
||||||
|
for y := 0; y < height; y++ {
|
||||||
|
t.window.Move(y, -1)
|
||||||
|
t.window.Print(" ")
|
||||||
|
}
|
||||||
|
|
||||||
createPreviewWindow(marginInt[0], marginInt[3], pwidth, height)
|
createPreviewWindow(marginInt[0], marginInt[3], pwidth, height)
|
||||||
} else {
|
} else {
|
||||||
t.window = t.tui.NewWindow(
|
t.window = t.tui.NewWindow(
|
||||||
@@ -1486,10 +1524,6 @@ func (t *Terminal) resizeWindows(forcePreview bool) {
|
|||||||
// Print border label
|
// Print border label
|
||||||
t.printLabel(t.border, t.borderLabel, t.borderLabelOpts, t.borderLabelLen, t.borderShape, false)
|
t.printLabel(t.border, t.borderLabel, t.borderLabelOpts, t.borderLabelLen, t.borderShape, false)
|
||||||
t.printLabel(t.pborder, t.previewLabel, t.previewLabelOpts, t.previewLabelLen, t.previewOpts.border, false)
|
t.printLabel(t.pborder, t.previewLabel, t.previewLabelOpts, t.previewLabelLen, t.previewOpts.border, false)
|
||||||
|
|
||||||
for i := 0; i < t.window.Height(); i++ {
|
|
||||||
t.window.MoveAndClear(i, 0)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) printLabel(window tui.Window, render labelPrinter, opts labelOpts, length int, borderShape tui.BorderShape, redrawBorder bool) {
|
func (t *Terminal) printLabel(window tui.Window, render labelPrinter, opts labelOpts, length int, borderShape tui.BorderShape, redrawBorder bool) {
|
||||||
@@ -1645,7 +1679,7 @@ func (t *Terminal) printInfo() {
|
|||||||
case infoDefault:
|
case infoDefault:
|
||||||
t.move(line+1, 0, t.separatorLen == 0)
|
t.move(line+1, 0, t.separatorLen == 0)
|
||||||
printSpinner()
|
printSpinner()
|
||||||
t.move(line+1, 2, false)
|
t.window.Print(" ") // Margin
|
||||||
pos = 2
|
pos = 2
|
||||||
case infoRight:
|
case infoRight:
|
||||||
t.move(line+1, 0, false)
|
t.move(line+1, 0, false)
|
||||||
@@ -1710,14 +1744,17 @@ func (t *Terminal) printInfo() {
|
|||||||
printSeparator(fillLength, true)
|
printSeparator(fillLength, true)
|
||||||
}
|
}
|
||||||
t.window.CPrint(tui.ColInfo, output)
|
t.window.CPrint(tui.ColInfo, output)
|
||||||
|
t.window.Print(" ") // Margin
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if t.infoStyle == infoInlineRight {
|
if t.infoStyle == infoInlineRight {
|
||||||
if len(t.infoPrefix) == 0 {
|
if len(t.infoPrefix) == 0 {
|
||||||
pos = util.Max(pos, t.window.Width()-util.StringWidth(output)-3)
|
t.move(line, pos, false)
|
||||||
|
newPos := util.Max(pos, t.window.Width()-util.StringWidth(output)-3)
|
||||||
|
t.window.Print(strings.Repeat(" ", newPos-pos))
|
||||||
|
pos = newPos
|
||||||
if pos < t.window.Width() {
|
if pos < t.window.Width() {
|
||||||
t.move(line, pos, false)
|
|
||||||
printSpinner()
|
printSpinner()
|
||||||
pos++
|
pos++
|
||||||
}
|
}
|
||||||
@@ -1865,7 +1902,7 @@ func (t *Terminal) printItem(result Result, line int, i int, current bool, bar b
|
|||||||
t.window.CPrint(tui.ColCurrentCursor, label)
|
t.window.CPrint(tui.ColCurrentCursor, label)
|
||||||
}
|
}
|
||||||
if selected {
|
if selected {
|
||||||
t.window.CPrint(tui.ColCurrentSelected, t.marker)
|
t.window.CPrint(tui.ColCurrentMarker, t.marker)
|
||||||
} else {
|
} else {
|
||||||
t.window.CPrint(tui.ColCurrentSelectedEmpty, t.markerEmpty)
|
t.window.CPrint(tui.ColCurrentSelectedEmpty, t.markerEmpty)
|
||||||
}
|
}
|
||||||
@@ -1877,15 +1914,36 @@ func (t *Terminal) printItem(result Result, line int, i int, current bool, bar b
|
|||||||
t.window.CPrint(tui.ColCursor, label)
|
t.window.CPrint(tui.ColCursor, label)
|
||||||
}
|
}
|
||||||
if selected {
|
if selected {
|
||||||
t.window.CPrint(tui.ColSelected, t.marker)
|
t.window.CPrint(tui.ColMarker, t.marker)
|
||||||
} else {
|
} else {
|
||||||
t.window.Print(t.markerEmpty)
|
t.window.Print(t.markerEmpty)
|
||||||
}
|
}
|
||||||
newLine.width = t.printHighlighted(result, tui.ColNormal, tui.ColMatch, false, true)
|
var base, match tui.ColorPair
|
||||||
|
if selected {
|
||||||
|
base = tui.ColSelected
|
||||||
|
match = tui.ColSelectedMatch
|
||||||
|
} else {
|
||||||
|
base = tui.ColNormal
|
||||||
|
match = tui.ColMatch
|
||||||
|
}
|
||||||
|
newLine.width = t.printHighlighted(result, base, match, false, true)
|
||||||
}
|
}
|
||||||
fillSpaces := prevLine.width - newLine.width
|
if (current || selected) && t.highlightLine {
|
||||||
if fillSpaces > 0 {
|
color := tui.ColSelected
|
||||||
t.window.Print(strings.Repeat(" ", fillSpaces))
|
if current {
|
||||||
|
color = tui.ColCurrent
|
||||||
|
}
|
||||||
|
maxWidth := t.window.Width() - (t.pointerLen + t.markerLen + 1)
|
||||||
|
fillSpaces := maxWidth - newLine.width
|
||||||
|
newLine.width = maxWidth
|
||||||
|
if fillSpaces > 0 {
|
||||||
|
t.window.CPrint(color, strings.Repeat(" ", fillSpaces))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fillSpaces := prevLine.width - newLine.width
|
||||||
|
if fillSpaces > 0 {
|
||||||
|
t.window.Print(strings.Repeat(" ", fillSpaces))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
printBar()
|
printBar()
|
||||||
t.prevLines[i] = newLine
|
t.prevLines[i] = newLine
|
||||||
@@ -2088,12 +2146,11 @@ func (t *Terminal) renderPreviewArea(unchanged bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
height := t.pwindow.Height()
|
height := t.pwindow.Height()
|
||||||
header := []string{}
|
|
||||||
body := t.previewer.lines
|
body := t.previewer.lines
|
||||||
headerLines := t.previewOpts.headerLines
|
headerLines := t.previewOpts.headerLines
|
||||||
// Do not enable preview header lines if it's value is too large
|
// Do not enable preview header lines if it's value is too large
|
||||||
if headerLines > 0 && headerLines < util.Min(len(body), height) {
|
if headerLines > 0 && headerLines < util.Min(len(body), height) {
|
||||||
header = t.previewer.lines[0:headerLines]
|
header := t.previewer.lines[0:headerLines]
|
||||||
body = t.previewer.lines[headerLines:]
|
body = t.previewer.lines[headerLines:]
|
||||||
// Always redraw header
|
// Always redraw header
|
||||||
t.renderPreviewText(height, header, 0, false)
|
t.renderPreviewText(height, header, 0, false)
|
||||||
@@ -2217,9 +2274,8 @@ Loop:
|
|||||||
t.pwindow.Move(height-1, maxWidth-1)
|
t.pwindow.Move(height-1, maxWidth-1)
|
||||||
t.previewed.filled = true
|
t.previewed.filled = true
|
||||||
break Loop
|
break Loop
|
||||||
} else {
|
|
||||||
t.pwindow.MoveAndClear(y+requiredLines, 0)
|
|
||||||
}
|
}
|
||||||
|
t.pwindow.MoveAndClear(y+requiredLines, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2470,8 +2526,6 @@ func parsePlaceholder(match string) (bool, string, placeholderFlags) {
|
|||||||
case 'q':
|
case 'q':
|
||||||
flags.forceUpdate = true
|
flags.forceUpdate = true
|
||||||
// query flag is not skipped
|
// query flag is not skipped
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2494,26 +2548,6 @@ func hasPreviewFlags(template string) (slot bool, plus bool, forceUpdate bool) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeTemporaryFile(data []string, printSep string) string {
|
|
||||||
f, err := os.CreateTemp("", "fzf-preview-*")
|
|
||||||
if err != nil {
|
|
||||||
errorExit("Unable to create temporary file")
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
f.WriteString(strings.Join(data, printSep))
|
|
||||||
f.WriteString(printSep)
|
|
||||||
activeTempFiles = append(activeTempFiles, f.Name())
|
|
||||||
return f.Name()
|
|
||||||
}
|
|
||||||
|
|
||||||
func cleanTemporaryFiles() {
|
|
||||||
for _, filename := range activeTempFiles {
|
|
||||||
os.Remove(filename)
|
|
||||||
}
|
|
||||||
activeTempFiles = []string{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type replacePlaceholderParams struct {
|
type replacePlaceholderParams struct {
|
||||||
template string
|
template string
|
||||||
stripAnsi bool
|
stripAnsi bool
|
||||||
@@ -2527,7 +2561,7 @@ type replacePlaceholderParams struct {
|
|||||||
executor *util.Executor
|
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, []string) {
|
||||||
return replacePlaceholder(replacePlaceholderParams{
|
return replacePlaceholder(replacePlaceholderParams{
|
||||||
template: template,
|
template: template,
|
||||||
stripAnsi: t.ansi,
|
stripAnsi: t.ansi,
|
||||||
@@ -2548,8 +2582,9 @@ func (t *Terminal) evaluateScrollOffset() int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// We only need the current item to calculate the scroll offset
|
// We only need the current item to calculate the scroll offset
|
||||||
offsetExpr := offsetTrimCharsRegex.ReplaceAllString(
|
replaced, tempFiles := t.replacePlaceholder(t.previewOpts.scroll, false, "", []*Item{t.currentItem(), nil})
|
||||||
t.replacePlaceholder(t.previewOpts.scroll, false, "", []*Item{t.currentItem(), nil}), "")
|
removeFiles(tempFiles)
|
||||||
|
offsetExpr := offsetTrimCharsRegex.ReplaceAllString(replaced, "")
|
||||||
|
|
||||||
atoi := func(s string) int {
|
atoi := func(s string) int {
|
||||||
n, e := strconv.Atoi(s)
|
n, e := strconv.Atoi(s)
|
||||||
@@ -2577,7 +2612,8 @@ func (t *Terminal) evaluateScrollOffset() int {
|
|||||||
return util.Max(0, base)
|
return util.Max(0, base)
|
||||||
}
|
}
|
||||||
|
|
||||||
func replacePlaceholder(params replacePlaceholderParams) string {
|
func replacePlaceholder(params replacePlaceholderParams) (string, []string) {
|
||||||
|
tempFiles := []string{}
|
||||||
current := params.allItems[:1]
|
current := params.allItems[:1]
|
||||||
selected := params.allItems[1:]
|
selected := params.allItems[1:]
|
||||||
if current[0] == nil {
|
if current[0] == nil {
|
||||||
@@ -2588,7 +2624,7 @@ func replacePlaceholder(params replacePlaceholderParams) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// replace placeholders one by one
|
// replace placeholders one by one
|
||||||
return placeholder.ReplaceAllStringFunc(params.template, func(match string) string {
|
replaced := placeholder.ReplaceAllStringFunc(params.template, func(match string) string {
|
||||||
escaped, match, flags := parsePlaceholder(match)
|
escaped, match, flags := parsePlaceholder(match)
|
||||||
|
|
||||||
// this function implements the effects a placeholder has on items
|
// this function implements the effects a placeholder has on items
|
||||||
@@ -2671,10 +2707,14 @@ func replacePlaceholder(params replacePlaceholderParams) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if flags.file {
|
if flags.file {
|
||||||
return writeTemporaryFile(replacements, params.printsep)
|
file := writeTemporaryFile(replacements, params.printsep)
|
||||||
|
tempFiles = append(tempFiles, file)
|
||||||
|
return file
|
||||||
}
|
}
|
||||||
return strings.Join(replacements, " ")
|
return strings.Join(replacements, " ")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return replaced, tempFiles
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) redraw() {
|
func (t *Terminal) redraw() {
|
||||||
@@ -2691,7 +2731,7 @@ func (t *Terminal) executeCommand(template string, forcePlus bool, background bo
|
|||||||
if !valid && !capture {
|
if !valid && !capture {
|
||||||
return line
|
return line
|
||||||
}
|
}
|
||||||
command := t.replacePlaceholder(template, forcePlus, string(t.input), list)
|
command, tempFiles := t.replacePlaceholder(template, forcePlus, string(t.input), list)
|
||||||
cmd := t.executor.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)
|
||||||
@@ -2722,7 +2762,7 @@ func (t *Terminal) executeCommand(template string, forcePlus bool, background bo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
t.executing.Set(false)
|
t.executing.Set(false)
|
||||||
cleanTemporaryFiles()
|
removeFiles(tempFiles)
|
||||||
return line
|
return line
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2821,18 +2861,18 @@ func (t *Terminal) toggleItem(item *Item) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) killPreview(code int) {
|
func (t *Terminal) killPreview() {
|
||||||
select {
|
select {
|
||||||
case t.killChan <- code:
|
case t.killChan <- true:
|
||||||
default:
|
default:
|
||||||
if code != exitCancel {
|
|
||||||
t.eventBox.Set(EvtQuit, code)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) cancelPreview() {
|
func (t *Terminal) cancelPreview() {
|
||||||
t.killPreview(exitCancel)
|
select {
|
||||||
|
case t.killChan <- false:
|
||||||
|
default:
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) pwindowSize() tui.TermSize {
|
func (t *Terminal) pwindowSize() tui.TermSize {
|
||||||
@@ -2856,7 +2896,7 @@ func (t *Terminal) currentIndex() int32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Loop is called to start Terminal I/O
|
// Loop is called to start Terminal I/O
|
||||||
func (t *Terminal) Loop() {
|
func (t *Terminal) Loop() error {
|
||||||
// prof := profile.Start(profile.ProfilePath("/tmp/"))
|
// prof := profile.Start(profile.ProfilePath("/tmp/"))
|
||||||
fitpad := <-t.startChan
|
fitpad := <-t.startChan
|
||||||
fit := fitpad.fit
|
fit := fitpad.fit
|
||||||
@@ -2880,14 +2920,23 @@ func (t *Terminal) Loop() {
|
|||||||
return util.Min(termHeight, contentHeight+pad)
|
return util.Min(termHeight, contentHeight+pad)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Context
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
{ // Late initialization
|
{ // Late initialization
|
||||||
intChan := make(chan os.Signal, 1)
|
intChan := make(chan os.Signal, 1)
|
||||||
signal.Notify(intChan, os.Interrupt, syscall.SIGTERM)
|
signal.Notify(intChan, os.Interrupt, syscall.SIGTERM)
|
||||||
go func() {
|
go func() {
|
||||||
for s := range intChan {
|
for {
|
||||||
// Don't quit by SIGINT while executing because it should be for the executing command and not for fzf itself
|
select {
|
||||||
if !(s == os.Interrupt && t.executing.Get()) {
|
case <-ctx.Done():
|
||||||
t.reqBox.Set(reqQuit, nil)
|
return
|
||||||
|
case s := <-intChan:
|
||||||
|
// Don't quit by SIGINT while executing because it should be for the executing command and not for fzf itself
|
||||||
|
if !(s == os.Interrupt && t.executing.Get()) {
|
||||||
|
t.reqBox.Set(reqQuit, nil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@@ -2896,8 +2945,12 @@ func (t *Terminal) Loop() {
|
|||||||
notifyOnCont(contChan)
|
notifyOnCont(contChan)
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
<-contChan
|
select {
|
||||||
t.reqBox.Set(reqReinit, nil)
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-contChan:
|
||||||
|
t.reqBox.Set(reqReinit, nil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -2906,16 +2959,26 @@ func (t *Terminal) Loop() {
|
|||||||
notifyOnResize(resizeChan) // Non-portable
|
notifyOnResize(resizeChan) // Non-portable
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
<-resizeChan
|
select {
|
||||||
t.reqBox.Set(reqResize, nil)
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-resizeChan:
|
||||||
|
t.reqBox.Set(reqResize, nil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
t.mutex.Lock()
|
t.mutex.Lock()
|
||||||
t.initFunc()
|
if err := t.initFunc(); err != nil {
|
||||||
|
t.mutex.Unlock()
|
||||||
|
cancel()
|
||||||
|
t.eventBox.Set(EvtQuit, quitSignal{ExitError, err})
|
||||||
|
return err
|
||||||
|
}
|
||||||
t.termSize = t.tui.Size()
|
t.termSize = t.tui.Size()
|
||||||
t.resizeWindows(false)
|
t.resizeWindows(false)
|
||||||
|
t.window.Erase()
|
||||||
t.printPrompt()
|
t.printPrompt()
|
||||||
t.printInfo()
|
t.printInfo()
|
||||||
t.printHeader()
|
t.printHeader()
|
||||||
@@ -2929,7 +2992,7 @@ func (t *Terminal) Loop() {
|
|||||||
|
|
||||||
// Keep the spinner spinning
|
// Keep the spinner spinning
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for t.running.Get() {
|
||||||
t.mutex.Lock()
|
t.mutex.Lock()
|
||||||
reading := t.reading
|
reading := t.reading
|
||||||
t.mutex.Unlock()
|
t.mutex.Unlock()
|
||||||
@@ -2944,15 +3007,20 @@ func (t *Terminal) Loop() {
|
|||||||
if t.hasPreviewer() {
|
if t.hasPreviewer() {
|
||||||
go func() {
|
go func() {
|
||||||
var version int64
|
var version int64
|
||||||
|
stop := false
|
||||||
for {
|
for {
|
||||||
var items []*Item
|
var items []*Item
|
||||||
var commandTemplate string
|
var commandTemplate string
|
||||||
var pwindow tui.Window
|
var pwindow tui.Window
|
||||||
var pwindowSize tui.TermSize
|
var pwindowSize tui.TermSize
|
||||||
|
var env []string
|
||||||
initialOffset := 0
|
initialOffset := 0
|
||||||
t.previewBox.Wait(func(events *util.Events) {
|
t.previewBox.Wait(func(events *util.Events) {
|
||||||
for req, value := range *events {
|
for req, value := range *events {
|
||||||
switch req {
|
switch req {
|
||||||
|
case reqQuit:
|
||||||
|
stop = true
|
||||||
|
return
|
||||||
case reqPreviewEnqueue:
|
case reqPreviewEnqueue:
|
||||||
request := value.(previewRequest)
|
request := value.(previewRequest)
|
||||||
commandTemplate = request.template
|
commandTemplate = request.template
|
||||||
@@ -2960,17 +3028,20 @@ func (t *Terminal) Loop() {
|
|||||||
items = request.list
|
items = request.list
|
||||||
pwindow = request.pwindow
|
pwindow = request.pwindow
|
||||||
pwindowSize = request.pwindowSize
|
pwindowSize = request.pwindowSize
|
||||||
|
env = request.env
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
events.Clear()
|
events.Clear()
|
||||||
})
|
})
|
||||||
|
if stop {
|
||||||
|
break
|
||||||
|
}
|
||||||
version++
|
version++
|
||||||
// We don't display preview window if no match
|
// We don't display preview window if no match
|
||||||
if items[0] != nil {
|
if items[0] != nil {
|
||||||
_, query := t.Input()
|
_, query := t.Input()
|
||||||
command := t.replacePlaceholder(commandTemplate, false, string(query), items)
|
command, tempFiles := t.replacePlaceholder(commandTemplate, false, string(query), items)
|
||||||
cmd := t.executor.ExecCommand(command, true)
|
cmd := t.executor.ExecCommand(command, true)
|
||||||
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)
|
||||||
columns := fmt.Sprintf("COLUMNS=%d", pwindowSize.Columns)
|
columns := fmt.Sprintf("COLUMNS=%d", pwindowSize.Columns)
|
||||||
@@ -3055,12 +3126,13 @@ func (t *Terminal) Loop() {
|
|||||||
Loop:
|
Loop:
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
break Loop
|
||||||
case <-timer.C:
|
case <-timer.C:
|
||||||
t.reqBox.Set(reqPreviewDelayed, version)
|
t.reqBox.Set(reqPreviewDelayed, version)
|
||||||
case code := <-t.killChan:
|
case immediately := <-t.killChan:
|
||||||
if code != exitCancel {
|
if immediately {
|
||||||
util.KillCommand(cmd)
|
util.KillCommand(cmd)
|
||||||
t.eventBox.Set(EvtQuit, code)
|
|
||||||
} else {
|
} else {
|
||||||
// We can immediately kill a long-running preview program
|
// We can immediately kill a long-running preview program
|
||||||
// once we started rendering its partial output
|
// once we started rendering its partial output
|
||||||
@@ -3090,12 +3162,11 @@ func (t *Terminal) Loop() {
|
|||||||
finishChan <- true // Tell Goroutine 3 to stop
|
finishChan <- true // Tell Goroutine 3 to stop
|
||||||
<-reapChan // Goroutine 2 and 3 finished
|
<-reapChan // Goroutine 2 and 3 finished
|
||||||
<-reapChan
|
<-reapChan
|
||||||
|
removeFiles(tempFiles)
|
||||||
} else {
|
} else {
|
||||||
// Failed to start the command. Report the error immediately.
|
// Failed to start the command. Report the error immediately.
|
||||||
t.reqBox.Set(reqPreviewDisplay, previewResult{version, []string{err.Error()}, 0, ""})
|
t.reqBox.Set(reqPreviewDisplay, previewResult{version, []string{err.Error()}, 0, ""})
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanTemporaryFiles()
|
|
||||||
} else {
|
} else {
|
||||||
t.reqBox.Set(reqPreviewDisplay, previewResult{version, nil, 0, ""})
|
t.reqBox.Set(reqPreviewDisplay, previewResult{version, nil, 0, ""})
|
||||||
}
|
}
|
||||||
@@ -3107,19 +3178,25 @@ func (t *Terminal) Loop() {
|
|||||||
if len(command) > 0 && t.canPreview() {
|
if len(command) > 0 && t.canPreview() {
|
||||||
_, list := t.buildPlusList(command, false)
|
_, list := t.buildPlusList(command, false)
|
||||||
t.cancelPreview()
|
t.cancelPreview()
|
||||||
t.previewBox.Set(reqPreviewEnqueue, previewRequest{command, t.pwindow, t.pwindowSize(), t.evaluateScrollOffset(), list})
|
t.previewBox.Set(reqPreviewEnqueue, previewRequest{command, t.pwindow, t.pwindowSize(), t.evaluateScrollOffset(), list, t.environ()})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
var focusedIndex int32 = minItem.Index()
|
var focusedIndex = minItem.Index()
|
||||||
var version int64 = -1
|
var version int64 = -1
|
||||||
running := true
|
running := true
|
||||||
code := exitError
|
code := ExitError
|
||||||
exit := func(getCode func() int) {
|
exit := func(getCode func() int) {
|
||||||
|
if t.hasPreviewer() {
|
||||||
|
t.previewBox.Set(reqQuit, nil)
|
||||||
|
}
|
||||||
|
if t.listener != nil {
|
||||||
|
t.listener.Close()
|
||||||
|
}
|
||||||
t.tui.Close()
|
t.tui.Close()
|
||||||
code = getCode()
|
code = getCode()
|
||||||
if code <= exitNoMatch && t.history != nil {
|
if code <= ExitNoMatch && t.history != nil {
|
||||||
t.history.append(string(t.input))
|
t.history.append(string(t.input))
|
||||||
}
|
}
|
||||||
running = false
|
running = false
|
||||||
@@ -3187,9 +3264,9 @@ func (t *Terminal) Loop() {
|
|||||||
case reqClose:
|
case reqClose:
|
||||||
exit(func() int {
|
exit(func() int {
|
||||||
if t.output() {
|
if t.output() {
|
||||||
return exitOk
|
return ExitOk
|
||||||
}
|
}
|
||||||
return exitNoMatch
|
return ExitNoMatch
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
case reqPreviewDisplay:
|
case reqPreviewDisplay:
|
||||||
@@ -3217,11 +3294,14 @@ func (t *Terminal) Loop() {
|
|||||||
case reqPrintQuery:
|
case reqPrintQuery:
|
||||||
exit(func() int {
|
exit(func() int {
|
||||||
t.printer(string(t.input))
|
t.printer(string(t.input))
|
||||||
return exitOk
|
return ExitOk
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
case reqQuit:
|
case reqQuit:
|
||||||
exit(func() int { return exitInterrupt })
|
exit(func() int { return ExitInterrupt })
|
||||||
|
return
|
||||||
|
case reqFatal:
|
||||||
|
exit(func() int { return ExitError })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3229,8 +3309,11 @@ func (t *Terminal) Loop() {
|
|||||||
t.mutex.Unlock()
|
t.mutex.Unlock()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// prof.Stop()
|
|
||||||
t.killPreview(code)
|
t.eventBox.Set(EvtQuit, quitSignal{code, nil})
|
||||||
|
t.running.Set(false)
|
||||||
|
t.killPreview()
|
||||||
|
cancel()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
looping := true
|
looping := true
|
||||||
@@ -3240,8 +3323,16 @@ func (t *Terminal) Loop() {
|
|||||||
barrier := make(chan bool)
|
barrier := make(chan bool)
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
<-barrier
|
select {
|
||||||
t.eventChan <- t.tui.GetChar()
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-barrier:
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case t.eventChan <- t.tui.GetChar():
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
previewDraggingPos := -1
|
previewDraggingPos := -1
|
||||||
@@ -3249,7 +3340,7 @@ func (t *Terminal) Loop() {
|
|||||||
pbarDragging := false
|
pbarDragging := false
|
||||||
wasDown := false
|
wasDown := false
|
||||||
for looping {
|
for looping {
|
||||||
var newCommand *string
|
var newCommand *commandSpec
|
||||||
var reloadSync bool
|
var reloadSync bool
|
||||||
changed := false
|
changed := false
|
||||||
beof := false
|
beof := false
|
||||||
@@ -3337,7 +3428,7 @@ func (t *Terminal) Loop() {
|
|||||||
t.pressed = ret
|
t.pressed = ret
|
||||||
t.reqBox.Set(reqClose, nil)
|
t.reqBox.Set(reqClose, nil)
|
||||||
t.mutex.Unlock()
|
t.mutex.Unlock()
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3346,8 +3437,7 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var doAction func(*action) bool
|
var doAction func(*action) bool
|
||||||
var doActions func(actions []*action) bool
|
doActions := func(actions []*action) bool {
|
||||||
doActions = func(actions []*action) bool {
|
|
||||||
for iter := 0; iter <= maxFocusEvents; iter++ {
|
for iter := 0; iter <= maxFocusEvents; iter++ {
|
||||||
currentIndex := t.currentIndex()
|
currentIndex := t.currentIndex()
|
||||||
for _, action := range actions {
|
for _, action := range actions {
|
||||||
@@ -3375,7 +3465,8 @@ func (t *Terminal) Loop() {
|
|||||||
case actBecome:
|
case actBecome:
|
||||||
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)
|
// We do not remove temp files in this case
|
||||||
|
command, _ := t.replacePlaceholder(a.a, false, string(t.input), list)
|
||||||
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))
|
||||||
@@ -3417,7 +3508,7 @@ func (t *Terminal) Loop() {
|
|||||||
if valid {
|
if valid {
|
||||||
t.cancelPreview()
|
t.cancelPreview()
|
||||||
t.previewBox.Set(reqPreviewEnqueue,
|
t.previewBox.Set(reqPreviewEnqueue,
|
||||||
previewRequest{t.previewOpts.command, t.pwindow, t.pwindowSize(), t.evaluateScrollOffset(), list})
|
previewRequest{t.previewOpts.command, t.pwindow, t.pwindowSize(), t.evaluateScrollOffset(), list, t.environ()})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Discard the preview content so that it won't accidentally appear
|
// Discard the preview content so that it won't accidentally appear
|
||||||
@@ -3531,8 +3622,9 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
case actTransform:
|
case actTransform:
|
||||||
body := t.executeCommand(a.a, false, true, true, false)
|
body := t.executeCommand(a.a, false, true, true, false)
|
||||||
actions := parseSingleActionList(strings.Trim(body, "\r\n"), func(message string) {})
|
if actions, err := parseSingleActionList(strings.Trim(body, "\r\n")); err == nil {
|
||||||
return doActions(actions)
|
return doActions(actions)
|
||||||
|
}
|
||||||
case actTransformBorderLabel:
|
case actTransformBorderLabel:
|
||||||
label := t.executeCommand(a.a, false, true, true, true)
|
label := t.executeCommand(a.a, false, true, true, true)
|
||||||
t.borderLabelOpts.label = label
|
t.borderLabelOpts.label = label
|
||||||
@@ -3564,6 +3656,8 @@ func (t *Terminal) Loop() {
|
|||||||
t.input = current.text.ToRunes()
|
t.input = current.text.ToRunes()
|
||||||
t.cx = len(t.input)
|
t.cx = len(t.input)
|
||||||
}
|
}
|
||||||
|
case actFatal:
|
||||||
|
req(reqFatal)
|
||||||
case actAbort:
|
case actAbort:
|
||||||
req(reqQuit)
|
req(reqQuit)
|
||||||
case actDeleteChar:
|
case actDeleteChar:
|
||||||
@@ -3989,10 +4083,10 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if me.Down {
|
if me.Down {
|
||||||
mx = util.Constrain(mx-t.promptLen, 0, len(t.input))
|
mxCons := util.Constrain(mx-t.promptLen, 0, len(t.input))
|
||||||
if my == t.promptLine() && mx >= 0 {
|
if my == t.promptLine() && mxCons >= 0 {
|
||||||
// Prompt
|
// Prompt
|
||||||
t.cx = mx + t.xoffset
|
t.cx = mxCons + t.xoffset
|
||||||
} else if my >= min {
|
} else if my >= min {
|
||||||
t.vset(t.offset + my - min)
|
t.vset(t.offset + my - min)
|
||||||
req(reqList)
|
req(reqList)
|
||||||
@@ -4007,6 +4101,29 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return doActions(actionsFor(evt))
|
return doActions(actionsFor(evt))
|
||||||
|
} else if t.headerVisible {
|
||||||
|
// Header
|
||||||
|
numLines := t.visibleHeaderLines()
|
||||||
|
lineOffset := 0
|
||||||
|
if !t.headerFirst {
|
||||||
|
// offset for info line
|
||||||
|
if t.noSeparatorLine() {
|
||||||
|
lineOffset = 1
|
||||||
|
} else {
|
||||||
|
lineOffset = 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
my -= lineOffset
|
||||||
|
mx -= 2 // offset gutter
|
||||||
|
if my >= 0 && my < numLines && mx >= 0 {
|
||||||
|
if t.layout == layoutReverse {
|
||||||
|
t.clickHeaderLine = my + 1
|
||||||
|
} else {
|
||||||
|
t.clickHeaderLine = numLines - my
|
||||||
|
}
|
||||||
|
t.clickHeaderColumn = mx + 1
|
||||||
|
return doActions(actionsFor(tui.ClickHeader))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case actReload, actReloadSync:
|
case actReload, actReloadSync:
|
||||||
@@ -4021,21 +4138,23 @@ func (t *Terminal) Loop() {
|
|||||||
valid = !slot || forceUpdate
|
valid = !slot || forceUpdate
|
||||||
}
|
}
|
||||||
if valid {
|
if valid {
|
||||||
command := t.replacePlaceholder(a.a, false, string(t.input), list)
|
command, tempFiles := t.replacePlaceholder(a.a, false, string(t.input), list)
|
||||||
newCommand = &command
|
newCommand = &commandSpec{command, tempFiles}
|
||||||
reloadSync = a.t == actReloadSync
|
reloadSync = a.t == actReloadSync
|
||||||
t.reading = true
|
t.reading = true
|
||||||
}
|
}
|
||||||
case actUnbind:
|
case actUnbind:
|
||||||
keys := parseKeyChords(a.a, "PANIC")
|
if keys, err := parseKeyChords(a.a, "PANIC"); err == nil {
|
||||||
for key := range keys {
|
for key := range keys {
|
||||||
delete(t.keymap, key)
|
delete(t.keymap, key)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case actRebind:
|
case actRebind:
|
||||||
keys := parseKeyChords(a.a, "PANIC")
|
if keys, err := parseKeyChords(a.a, "PANIC"); err == nil {
|
||||||
for key := range keys {
|
for key := range keys {
|
||||||
if originalAction, found := t.keymapOrg[key]; found {
|
if originalAction, found := t.keymapOrg[key]; found {
|
||||||
t.keymap[key] = originalAction
|
t.keymap[key] = originalAction
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case actChangePreview:
|
case actChangePreview:
|
||||||
@@ -4049,6 +4168,7 @@ func (t *Terminal) Loop() {
|
|||||||
|
|
||||||
// Reset preview options and apply the additional options
|
// Reset preview options and apply the additional options
|
||||||
t.previewOpts = t.initialPreviewOpts
|
t.previewOpts = t.initialPreviewOpts
|
||||||
|
t.previewOpts.command = currentPreviewOpts.command
|
||||||
|
|
||||||
// Split window options
|
// Split window options
|
||||||
tokens := strings.Split(a.a, "|")
|
tokens := strings.Split(a.a, "|")
|
||||||
@@ -4181,6 +4301,7 @@ func (t *Terminal) Loop() {
|
|||||||
t.reqBox.Set(event, nil)
|
t.reqBox.Set(event, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) constrain() {
|
func (t *Terminal) constrain() {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func replacePlaceholderTest(template string, stripAnsi bool, delimiter Delimiter, printsep string, forcePlus bool, query string, allItems []*Item) string {
|
func replacePlaceholderTest(template string, stripAnsi bool, delimiter Delimiter, printsep string, forcePlus bool, query string, allItems []*Item) string {
|
||||||
return replacePlaceholder(replacePlaceholderParams{
|
replaced, _ := replacePlaceholder(replacePlaceholderParams{
|
||||||
template: template,
|
template: template,
|
||||||
stripAnsi: stripAnsi,
|
stripAnsi: stripAnsi,
|
||||||
delimiter: delimiter,
|
delimiter: delimiter,
|
||||||
@@ -25,6 +25,7 @@ func replacePlaceholderTest(template string, stripAnsi bool, delimiter Delimiter
|
|||||||
prompt: "prompt",
|
prompt: "prompt",
|
||||||
executor: util.NewExecutor(""),
|
executor: util.NewExecutor(""),
|
||||||
})
|
})
|
||||||
|
return replaced
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReplacePlaceholder(t *testing.T) {
|
func TestReplacePlaceholder(t *testing.T) {
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ func withPrefixLengths(tokens []string, begin int) []Token {
|
|||||||
|
|
||||||
prefixLength := begin
|
prefixLength := begin
|
||||||
for idx := range tokens {
|
for idx := range tokens {
|
||||||
chars := util.ToChars(sbytes(tokens[idx]))
|
chars := util.ToChars(stringBytes(tokens[idx]))
|
||||||
ret[idx] = Token{&chars, int32(prefixLength)}
|
ret[idx] = Token{&chars, int32(prefixLength)}
|
||||||
prefixLength += chars.Length()
|
prefixLength += chars.Length()
|
||||||
}
|
}
|
||||||
@@ -187,7 +187,7 @@ func Transform(tokens []Token, withNth []Range) []Token {
|
|||||||
if r.begin == r.end {
|
if r.begin == r.end {
|
||||||
idx := r.begin
|
idx := r.begin
|
||||||
if idx == rangeEllipsis {
|
if idx == rangeEllipsis {
|
||||||
chars := util.ToChars(sbytes(joinTokens(tokens)))
|
chars := util.ToChars(stringBytes(joinTokens(tokens)))
|
||||||
parts = append(parts, &chars)
|
parts = append(parts, &chars)
|
||||||
} else {
|
} else {
|
||||||
if idx < 0 {
|
if idx < 0 {
|
||||||
|
|||||||
@@ -71,14 +71,14 @@ func TestTransform(t *testing.T) {
|
|||||||
{
|
{
|
||||||
tokens := Tokenize(input, Delimiter{})
|
tokens := Tokenize(input, Delimiter{})
|
||||||
{
|
{
|
||||||
ranges := splitNth("1,2,3")
|
ranges, _ := splitNth("1,2,3")
|
||||||
tx := Transform(tokens, ranges)
|
tx := Transform(tokens, ranges)
|
||||||
if joinTokens(tx) != "abc: def: ghi: " {
|
if joinTokens(tx) != "abc: def: ghi: " {
|
||||||
t.Errorf("%s", tx)
|
t.Errorf("%s", tx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
ranges := splitNth("1..2,3,2..,1")
|
ranges, _ := splitNth("1..2,3,2..,1")
|
||||||
tx := Transform(tokens, ranges)
|
tx := Transform(tokens, ranges)
|
||||||
if string(joinTokens(tx)) != "abc: def: ghi: def: ghi: jklabc: " ||
|
if string(joinTokens(tx)) != "abc: def: ghi: def: ghi: jklabc: " ||
|
||||||
len(tx) != 4 ||
|
len(tx) != 4 ||
|
||||||
@@ -93,7 +93,7 @@ func TestTransform(t *testing.T) {
|
|||||||
{
|
{
|
||||||
tokens := Tokenize(input, delimiterRegexp(":"))
|
tokens := Tokenize(input, delimiterRegexp(":"))
|
||||||
{
|
{
|
||||||
ranges := splitNth("1..2,3,2..,1")
|
ranges, _ := splitNth("1..2,3,2..,1")
|
||||||
tx := Transform(tokens, ranges)
|
tx := Transform(tokens, ranges)
|
||||||
if joinTokens(tx) != " abc: def: ghi: def: ghi: jkl abc:" ||
|
if joinTokens(tx) != " abc: def: ghi: def: ghi: jkl abc:" ||
|
||||||
len(tx) != 4 ||
|
len(tx) != 4 ||
|
||||||
@@ -108,5 +108,6 @@ func TestTransform(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestTransformIndexOutOfBounds(t *testing.T) {
|
func TestTransformIndexOutOfBounds(t *testing.T) {
|
||||||
Transform([]Token{}, splitNth("1"))
|
s, _ := splitNth("1")
|
||||||
|
Transform([]Token{}, s)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ func HasFullscreenRenderer() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
var DefaultBorderShape BorderShape = BorderRounded
|
var DefaultBorderShape = BorderRounded
|
||||||
|
|
||||||
func (a Attr) Merge(b Attr) Attr {
|
func (a Attr) Merge(b Attr) Attr {
|
||||||
return a | b
|
return a | b
|
||||||
@@ -29,7 +29,7 @@ const (
|
|||||||
StrikeThrough = Attr(1 << 7)
|
StrikeThrough = Attr(1 << 7)
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *FullscreenRenderer) Init() {}
|
func (r *FullscreenRenderer) Init() error { return nil }
|
||||||
func (r *FullscreenRenderer) Resize(maxHeightFunc func(int) int) {}
|
func (r *FullscreenRenderer) Resize(maxHeightFunc func(int) int) {}
|
||||||
func (r *FullscreenRenderer) Pause(bool) {}
|
func (r *FullscreenRenderer) Pause(bool) {}
|
||||||
func (r *FullscreenRenderer) Resume(bool, bool) {}
|
func (r *FullscreenRenderer) Resume(bool, bool) {}
|
||||||
|
|||||||
@@ -83,34 +83,35 @@ func _() {
|
|||||||
_ = x[Alt-72]
|
_ = x[Alt-72]
|
||||||
_ = x[CtrlAlt-73]
|
_ = x[CtrlAlt-73]
|
||||||
_ = x[Invalid-74]
|
_ = x[Invalid-74]
|
||||||
_ = x[Mouse-75]
|
_ = x[Fatal-75]
|
||||||
_ = x[DoubleClick-76]
|
_ = x[Mouse-76]
|
||||||
_ = x[LeftClick-77]
|
_ = x[DoubleClick-77]
|
||||||
_ = x[RightClick-78]
|
_ = x[LeftClick-78]
|
||||||
_ = x[SLeftClick-79]
|
_ = x[RightClick-79]
|
||||||
_ = x[SRightClick-80]
|
_ = x[SLeftClick-80]
|
||||||
_ = x[ScrollUp-81]
|
_ = x[SRightClick-81]
|
||||||
_ = x[ScrollDown-82]
|
_ = x[ScrollUp-82]
|
||||||
_ = x[SScrollUp-83]
|
_ = x[ScrollDown-83]
|
||||||
_ = x[SScrollDown-84]
|
_ = x[SScrollUp-84]
|
||||||
_ = x[PreviewScrollUp-85]
|
_ = x[SScrollDown-85]
|
||||||
_ = x[PreviewScrollDown-86]
|
_ = x[PreviewScrollUp-86]
|
||||||
_ = x[Resize-87]
|
_ = x[PreviewScrollDown-87]
|
||||||
_ = x[Change-88]
|
_ = x[Resize-88]
|
||||||
_ = x[BackwardEOF-89]
|
_ = x[Change-89]
|
||||||
_ = x[Start-90]
|
_ = x[BackwardEOF-90]
|
||||||
_ = x[Load-91]
|
_ = x[Start-91]
|
||||||
_ = x[Focus-92]
|
_ = x[Load-92]
|
||||||
_ = x[One-93]
|
_ = x[Focus-93]
|
||||||
_ = x[Zero-94]
|
_ = x[One-94]
|
||||||
_ = x[Result-95]
|
_ = x[Zero-95]
|
||||||
_ = x[Jump-96]
|
_ = x[Result-96]
|
||||||
_ = x[JumpCancel-97]
|
_ = x[Jump-97]
|
||||||
|
_ = x[JumpCancel-98]
|
||||||
}
|
}
|
||||||
|
|
||||||
const _EventType_name = "RuneCtrlACtrlBCtrlCCtrlDCtrlECtrlFCtrlGCtrlHTabCtrlJCtrlKCtrlLCtrlMCtrlNCtrlOCtrlPCtrlQCtrlRCtrlSCtrlTCtrlUCtrlVCtrlWCtrlXCtrlYCtrlZEscCtrlSpaceCtrlDeleteCtrlBackSlashCtrlRightBracketCtrlCaretCtrlSlashShiftTabBackspaceDeletePageUpPageDownUpDownLeftRightHomeEndInsertShiftUpShiftDownShiftLeftShiftRightShiftDeleteF1F2F3F4F5F6F7F8F9F10F11F12AltBackspaceAltUpAltDownAltLeftAltRightAltShiftUpAltShiftDownAltShiftLeftAltShiftRightAltCtrlAltInvalidMouseDoubleClickLeftClickRightClickSLeftClickSRightClickScrollUpScrollDownSScrollUpSScrollDownPreviewScrollUpPreviewScrollDownResizeChangeBackwardEOFStartLoadFocusOneZeroResultJumpJumpCancel"
|
const _EventType_name = "RuneCtrlACtrlBCtrlCCtrlDCtrlECtrlFCtrlGCtrlHTabCtrlJCtrlKCtrlLCtrlMCtrlNCtrlOCtrlPCtrlQCtrlRCtrlSCtrlTCtrlUCtrlVCtrlWCtrlXCtrlYCtrlZEscCtrlSpaceCtrlDeleteCtrlBackSlashCtrlRightBracketCtrlCaretCtrlSlashShiftTabBackspaceDeletePageUpPageDownUpDownLeftRightHomeEndInsertShiftUpShiftDownShiftLeftShiftRightShiftDeleteF1F2F3F4F5F6F7F8F9F10F11F12AltBackspaceAltUpAltDownAltLeftAltRightAltShiftUpAltShiftDownAltShiftLeftAltShiftRightAltCtrlAltInvalidFatalMouseDoubleClickLeftClickRightClickSLeftClickSRightClickScrollUpScrollDownSScrollUpSScrollDownPreviewScrollUpPreviewScrollDownResizeChangeBackwardEOFStartLoadFocusOneZeroResultJumpJumpCancel"
|
||||||
|
|
||||||
var _EventType_index = [...]uint16{0, 4, 9, 14, 19, 24, 29, 34, 39, 44, 47, 52, 57, 62, 67, 72, 77, 82, 87, 92, 97, 102, 107, 112, 117, 122, 127, 132, 135, 144, 154, 167, 183, 192, 201, 209, 218, 224, 230, 238, 240, 244, 248, 253, 257, 260, 266, 273, 282, 291, 301, 312, 314, 316, 318, 320, 322, 324, 326, 328, 330, 333, 336, 339, 351, 356, 363, 370, 378, 388, 400, 412, 425, 428, 435, 442, 447, 458, 467, 477, 487, 498, 506, 516, 525, 536, 551, 568, 574, 580, 591, 596, 600, 605, 608, 612, 618, 622, 632}
|
var _EventType_index = [...]uint16{0, 4, 9, 14, 19, 24, 29, 34, 39, 44, 47, 52, 57, 62, 67, 72, 77, 82, 87, 92, 97, 102, 107, 112, 117, 122, 127, 132, 135, 144, 154, 167, 183, 192, 201, 209, 218, 224, 230, 238, 240, 244, 248, 253, 257, 260, 266, 273, 282, 291, 301, 312, 314, 316, 318, 320, 322, 324, 326, 328, 330, 333, 336, 339, 351, 356, 363, 370, 378, 388, 400, 412, 425, 428, 435, 442, 447, 452, 463, 472, 482, 492, 503, 511, 521, 530, 541, 556, 573, 579, 585, 596, 601, 605, 610, 613, 617, 623, 627, 637}
|
||||||
|
|
||||||
func (i EventType) String() string {
|
func (i EventType) String() string {
|
||||||
if i < 0 || i >= EventType(len(_EventType_index)-1) {
|
if i < 0 || i >= EventType(len(_EventType_index)-1) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package tui
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
@@ -10,6 +11,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"github.com/junegunn/fzf/src/util"
|
||||||
"github.com/rivo/uniseg"
|
"github.com/rivo/uniseg"
|
||||||
|
|
||||||
"golang.org/x/term"
|
"golang.org/x/term"
|
||||||
@@ -27,8 +29,8 @@ const (
|
|||||||
|
|
||||||
const consoleDevice string = "/dev/tty"
|
const consoleDevice string = "/dev/tty"
|
||||||
|
|
||||||
var offsetRegexp *regexp.Regexp = regexp.MustCompile("(.*)\x1b\\[([0-9]+);([0-9]+)R")
|
var offsetRegexp = regexp.MustCompile("(.*)\x1b\\[([0-9]+);([0-9]+)R")
|
||||||
var offsetRegexpBegin *regexp.Regexp = regexp.MustCompile("^\x1b\\[[0-9]+;[0-9]+R")
|
var offsetRegexpBegin = regexp.MustCompile("^\x1b\\[[0-9]+;[0-9]+R")
|
||||||
|
|
||||||
func (r *LightRenderer) PassThrough(str string) {
|
func (r *LightRenderer) PassThrough(str string) {
|
||||||
r.queued.WriteString("\x1b7" + str + "\x1b8")
|
r.queued.WriteString("\x1b7" + str + "\x1b8")
|
||||||
@@ -78,6 +80,7 @@ func (r *LightRenderer) flush() {
|
|||||||
|
|
||||||
// Light renderer
|
// Light renderer
|
||||||
type LightRenderer struct {
|
type LightRenderer struct {
|
||||||
|
closed *util.AtomicBool
|
||||||
theme *ColorTheme
|
theme *ColorTheme
|
||||||
mouse bool
|
mouse bool
|
||||||
forceBlack bool
|
forceBlack bool
|
||||||
@@ -123,19 +126,24 @@ type LightWindow struct {
|
|||||||
bg Color
|
bg Color
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLightRenderer(theme *ColorTheme, forceBlack bool, mouse bool, tabstop int, clearOnExit bool, fullscreen bool, maxHeightFunc func(int) int) Renderer {
|
func NewLightRenderer(theme *ColorTheme, forceBlack bool, mouse bool, tabstop int, clearOnExit bool, fullscreen bool, maxHeightFunc func(int) int) (Renderer, error) {
|
||||||
|
in, err := openTtyIn()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
r := LightRenderer{
|
r := LightRenderer{
|
||||||
|
closed: util.NewAtomicBool(false),
|
||||||
theme: theme,
|
theme: theme,
|
||||||
forceBlack: forceBlack,
|
forceBlack: forceBlack,
|
||||||
mouse: mouse,
|
mouse: mouse,
|
||||||
clearOnExit: clearOnExit,
|
clearOnExit: clearOnExit,
|
||||||
ttyin: openTtyIn(),
|
ttyin: in,
|
||||||
yoffset: 0,
|
yoffset: 0,
|
||||||
tabstop: tabstop,
|
tabstop: tabstop,
|
||||||
fullscreen: fullscreen,
|
fullscreen: fullscreen,
|
||||||
upOneLine: false,
|
upOneLine: false,
|
||||||
maxHeightFunc: maxHeightFunc}
|
maxHeightFunc: maxHeightFunc}
|
||||||
return &r
|
return &r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func repeat(r rune, times int) string {
|
func repeat(r rune, times int) string {
|
||||||
@@ -153,11 +161,11 @@ func atoi(s string, defaultValue int) int {
|
|||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) Init() {
|
func (r *LightRenderer) Init() error {
|
||||||
r.escDelay = atoi(os.Getenv("ESCDELAY"), defaultEscDelay)
|
r.escDelay = atoi(os.Getenv("ESCDELAY"), defaultEscDelay)
|
||||||
|
|
||||||
if err := r.initPlatform(); err != nil {
|
if err := r.initPlatform(); err != nil {
|
||||||
errorExit(err.Error())
|
return err
|
||||||
}
|
}
|
||||||
r.updateTerminalSize()
|
r.updateTerminalSize()
|
||||||
initTheme(r.theme, r.defaultTheme(), r.forceBlack)
|
initTheme(r.theme, r.defaultTheme(), r.forceBlack)
|
||||||
@@ -195,6 +203,7 @@ func (r *LightRenderer) Init() {
|
|||||||
if !r.fullscreen && r.mouse {
|
if !r.fullscreen && r.mouse {
|
||||||
r.yoffset, _ = r.findOffset()
|
r.yoffset, _ = r.findOffset()
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) Resize(maxHeightFunc func(int) int) {
|
func (r *LightRenderer) Resize(maxHeightFunc func(int) int) {
|
||||||
@@ -233,15 +242,16 @@ func getEnv(name string, defaultValue int) int {
|
|||||||
return atoi(env, defaultValue)
|
return atoi(env, defaultValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) getBytes() []byte {
|
func (r *LightRenderer) getBytes() ([]byte, error) {
|
||||||
return r.getBytesInternal(r.buffer, false)
|
bytes, err := r.getBytesInternal(r.buffer, false)
|
||||||
|
return bytes, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) []byte {
|
func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) ([]byte, error) {
|
||||||
c, ok := r.getch(nonblock)
|
c, ok := r.getch(nonblock)
|
||||||
if !nonblock && !ok {
|
if !nonblock && !ok {
|
||||||
r.Close()
|
r.Close()
|
||||||
errorExit("Failed to read " + consoleDevice)
|
return nil, errors.New("failed to read " + consoleDevice)
|
||||||
}
|
}
|
||||||
|
|
||||||
retries := 0
|
retries := 0
|
||||||
@@ -272,19 +282,23 @@ func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) []byte {
|
|||||||
// so terminate fzf immediately.
|
// so terminate fzf immediately.
|
||||||
if len(buffer) > maxInputBuffer {
|
if len(buffer) > maxInputBuffer {
|
||||||
r.Close()
|
r.Close()
|
||||||
panic(fmt.Sprintf("Input buffer overflow (%d): %v", len(buffer), buffer))
|
return nil, fmt.Errorf("input buffer overflow (%d): %v", len(buffer), buffer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return buffer
|
return buffer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) GetChar() Event {
|
func (r *LightRenderer) GetChar() Event {
|
||||||
|
var err error
|
||||||
if len(r.buffer) == 0 {
|
if len(r.buffer) == 0 {
|
||||||
r.buffer = r.getBytes()
|
r.buffer, err = r.getBytes()
|
||||||
|
if err != nil {
|
||||||
|
return Event{Fatal, 0, nil}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if len(r.buffer) == 0 {
|
if len(r.buffer) == 0 {
|
||||||
panic("Empty buffer")
|
return Event{Fatal, 0, nil}
|
||||||
}
|
}
|
||||||
|
|
||||||
sz := 1
|
sz := 1
|
||||||
@@ -315,7 +329,9 @@ func (r *LightRenderer) GetChar() Event {
|
|||||||
ev := r.escSequence(&sz)
|
ev := r.escSequence(&sz)
|
||||||
// Second chance
|
// Second chance
|
||||||
if ev.Type == Invalid {
|
if ev.Type == Invalid {
|
||||||
r.buffer = r.getBytes()
|
if r.buffer, err = r.getBytes(); err != nil {
|
||||||
|
return Event{Fatal, 0, nil}
|
||||||
|
}
|
||||||
ev = r.escSequence(&sz)
|
ev = r.escSequence(&sz)
|
||||||
}
|
}
|
||||||
return ev
|
return ev
|
||||||
@@ -738,6 +754,7 @@ func (r *LightRenderer) Close() {
|
|||||||
r.flush()
|
r.flush()
|
||||||
r.closePlatform()
|
r.closePlatform()
|
||||||
r.restoreTerminal()
|
r.restoreTerminal()
|
||||||
|
r.closed.Set(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) Top() int {
|
func (r *LightRenderer) Top() int {
|
||||||
@@ -821,44 +838,32 @@ func (w *LightWindow) drawBorderHorizontal(top, bottom bool) {
|
|||||||
color = ColPreviewBorder
|
color = ColPreviewBorder
|
||||||
}
|
}
|
||||||
hw := runeWidth(w.border.top)
|
hw := runeWidth(w.border.top)
|
||||||
pad := repeat(' ', w.width/hw)
|
|
||||||
|
|
||||||
w.Move(0, 0)
|
|
||||||
if top {
|
if top {
|
||||||
|
w.Move(0, 0)
|
||||||
w.CPrint(color, repeat(w.border.top, w.width/hw))
|
w.CPrint(color, repeat(w.border.top, w.width/hw))
|
||||||
} else {
|
|
||||||
w.CPrint(color, pad)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for y := 1; y < w.height-1; y++ {
|
|
||||||
w.Move(y, 0)
|
|
||||||
w.CPrint(color, pad)
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Move(w.height-1, 0)
|
|
||||||
if bottom {
|
if bottom {
|
||||||
|
w.Move(w.height-1, 0)
|
||||||
w.CPrint(color, repeat(w.border.bottom, w.width/hw))
|
w.CPrint(color, repeat(w.border.bottom, w.width/hw))
|
||||||
} else {
|
|
||||||
w.CPrint(color, pad)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *LightWindow) drawBorderVertical(left, right bool) {
|
func (w *LightWindow) drawBorderVertical(left, right bool) {
|
||||||
width := w.width - 2
|
vw := runeWidth(w.border.left)
|
||||||
if !left || !right {
|
|
||||||
width++
|
|
||||||
}
|
|
||||||
color := ColBorder
|
color := ColBorder
|
||||||
if w.preview {
|
if w.preview {
|
||||||
color = ColPreviewBorder
|
color = ColPreviewBorder
|
||||||
}
|
}
|
||||||
for y := 0; y < w.height; y++ {
|
for y := 0; y < w.height; y++ {
|
||||||
w.Move(y, 0)
|
|
||||||
if left {
|
if left {
|
||||||
|
w.Move(y, 0)
|
||||||
w.CPrint(color, string(w.border.left))
|
w.CPrint(color, string(w.border.left))
|
||||||
|
w.CPrint(color, " ") // Margin
|
||||||
}
|
}
|
||||||
w.CPrint(color, repeat(' ', width))
|
|
||||||
if right {
|
if right {
|
||||||
|
w.Move(y, w.width-vw-1)
|
||||||
|
w.CPrint(color, " ") // Margin
|
||||||
w.CPrint(color, string(w.border.right))
|
w.CPrint(color, string(w.border.right))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -880,7 +885,10 @@ func (w *LightWindow) drawBorderAround(onlyHorizontal bool) {
|
|||||||
for y := 1; y < w.height-1; y++ {
|
for y := 1; y < w.height-1; y++ {
|
||||||
w.Move(y, 0)
|
w.Move(y, 0)
|
||||||
w.CPrint(color, string(w.border.left))
|
w.CPrint(color, string(w.border.left))
|
||||||
w.CPrint(color, repeat(' ', w.width-vw*2))
|
w.CPrint(color, " ") // Margin
|
||||||
|
|
||||||
|
w.Move(y, w.width-vw-1)
|
||||||
|
w.CPrint(color, " ") // Margin
|
||||||
w.CPrint(color, string(w.border.right))
|
w.CPrint(color, string(w.border.right))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
package tui
|
package tui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"errors"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -48,19 +48,18 @@ func (r *LightRenderer) closePlatform() {
|
|||||||
// NOOP
|
// NOOP
|
||||||
}
|
}
|
||||||
|
|
||||||
func openTtyIn() *os.File {
|
func openTtyIn() (*os.File, error) {
|
||||||
in, err := os.OpenFile(consoleDevice, syscall.O_RDONLY, 0)
|
in, err := os.OpenFile(consoleDevice, syscall.O_RDONLY, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tty := ttyname()
|
tty := ttyname()
|
||||||
if len(tty) > 0 {
|
if len(tty) > 0 {
|
||||||
if in, err := os.OpenFile(tty, syscall.O_RDONLY, 0); err == nil {
|
if in, err := os.OpenFile(tty, syscall.O_RDONLY, 0); err == nil {
|
||||||
return in
|
return in, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fmt.Fprintln(os.Stderr, "Failed to open "+consoleDevice)
|
return nil, errors.New("failed to open " + consoleDevice)
|
||||||
util.Exit(2)
|
|
||||||
}
|
}
|
||||||
return in
|
return in, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) setupTerminal() {
|
func (r *LightRenderer) setupTerminal() {
|
||||||
@@ -86,9 +85,14 @@ func (r *LightRenderer) updateTerminalSize() {
|
|||||||
func (r *LightRenderer) findOffset() (row int, col int) {
|
func (r *LightRenderer) findOffset() (row int, col int) {
|
||||||
r.csi("6n")
|
r.csi("6n")
|
||||||
r.flush()
|
r.flush()
|
||||||
|
var err error
|
||||||
bytes := []byte{}
|
bytes := []byte{}
|
||||||
for tries := 0; tries < offsetPollTries; tries++ {
|
for tries := 0; tries < offsetPollTries; tries++ {
|
||||||
bytes = r.getBytesInternal(bytes, tries > 0)
|
bytes, err = r.getBytesInternal(bytes, tries > 0)
|
||||||
|
if err != nil {
|
||||||
|
return -1, -1
|
||||||
|
}
|
||||||
|
|
||||||
offsets := offsetRegexp.FindSubmatch(bytes)
|
offsets := offsetRegexp.FindSubmatch(bytes)
|
||||||
if len(offsets) > 3 {
|
if len(offsets) > 3 {
|
||||||
// Add anything we skipped over to the input buffer
|
// Add anything we skipped over to the input buffer
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ func (r *LightRenderer) initPlatform() error {
|
|||||||
go func() {
|
go func() {
|
||||||
fd := int(r.inHandle)
|
fd := int(r.inHandle)
|
||||||
b := make([]byte, 1)
|
b := make([]byte, 1)
|
||||||
for {
|
for !r.closed.Get() {
|
||||||
// HACK: if run from PSReadline, something resets ConsoleMode to remove ENABLE_VIRTUAL_TERMINAL_INPUT.
|
// HACK: if run from PSReadline, something resets ConsoleMode to remove ENABLE_VIRTUAL_TERMINAL_INPUT.
|
||||||
_ = windows.SetConsoleMode(windows.Handle(r.inHandle), consoleFlagsInput)
|
_ = windows.SetConsoleMode(windows.Handle(r.inHandle), consoleFlagsInput)
|
||||||
|
|
||||||
@@ -91,9 +91,9 @@ func (r *LightRenderer) closePlatform() {
|
|||||||
windows.SetConsoleMode(windows.Handle(r.inHandle), r.origStateInput)
|
windows.SetConsoleMode(windows.Handle(r.inHandle), r.origStateInput)
|
||||||
}
|
}
|
||||||
|
|
||||||
func openTtyIn() *os.File {
|
func openTtyIn() (*os.File, error) {
|
||||||
// not used
|
// not used
|
||||||
return nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *LightRenderer) setupTerminal() error {
|
func (r *LightRenderer) setupTerminal() error {
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
"github.com/gdamore/tcell/v2/encoding"
|
|
||||||
"github.com/junegunn/fzf/src/util"
|
"github.com/junegunn/fzf/src/util"
|
||||||
|
|
||||||
"github.com/rivo/uniseg"
|
"github.com/rivo/uniseg"
|
||||||
@@ -146,13 +145,13 @@ var (
|
|||||||
_initialResize bool = true
|
_initialResize bool = true
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *FullscreenRenderer) initScreen() {
|
func (r *FullscreenRenderer) initScreen() error {
|
||||||
s, e := tcell.NewScreen()
|
s, e := tcell.NewScreen()
|
||||||
if e != nil {
|
if e != nil {
|
||||||
errorExit(e.Error())
|
return e
|
||||||
}
|
}
|
||||||
if e = s.Init(); e != nil {
|
if e = s.Init(); e != nil {
|
||||||
errorExit(e.Error())
|
return e
|
||||||
}
|
}
|
||||||
if r.mouse {
|
if r.mouse {
|
||||||
s.EnableMouse()
|
s.EnableMouse()
|
||||||
@@ -160,16 +159,21 @@ func (r *FullscreenRenderer) initScreen() {
|
|||||||
s.DisableMouse()
|
s.DisableMouse()
|
||||||
}
|
}
|
||||||
_screen = s
|
_screen = s
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *FullscreenRenderer) Init() {
|
func (r *FullscreenRenderer) Init() error {
|
||||||
if os.Getenv("TERM") == "cygwin" {
|
if os.Getenv("TERM") == "cygwin" {
|
||||||
os.Setenv("TERM", "")
|
os.Setenv("TERM", "")
|
||||||
}
|
}
|
||||||
encoding.Register()
|
|
||||||
|
|
||||||
r.initScreen()
|
if err := r.initScreen(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
initTheme(r.theme, r.defaultTheme(), r.forceBlack)
|
initTheme(r.theme, r.defaultTheme(), r.forceBlack)
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *FullscreenRenderer) Top() int {
|
func (r *FullscreenRenderer) Top() int {
|
||||||
@@ -561,7 +565,11 @@ func fill(x, y, w, h int, n ColorPair, r rune) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *TcellWindow) Erase() {
|
func (w *TcellWindow) Erase() {
|
||||||
fill(w.left-1, w.top, w.width+1, w.height-1, w.normal, ' ')
|
if w.borderStyle.shape.HasLeft() {
|
||||||
|
fill(w.left-1, w.top, w.width, w.height-1, w.normal, ' ')
|
||||||
|
} else {
|
||||||
|
fill(w.left, w.top, w.width-1, w.height-1, w.normal, ' ')
|
||||||
|
}
|
||||||
w.drawBorder(false)
|
w.drawBorder(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package tui
|
package tui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -104,6 +102,7 @@ const (
|
|||||||
CtrlAlt
|
CtrlAlt
|
||||||
|
|
||||||
Invalid
|
Invalid
|
||||||
|
Fatal
|
||||||
|
|
||||||
Mouse
|
Mouse
|
||||||
DoubleClick
|
DoubleClick
|
||||||
@@ -130,6 +129,7 @@ const (
|
|||||||
Result
|
Result
|
||||||
Jump
|
Jump
|
||||||
JumpCancel
|
JumpCancel
|
||||||
|
ClickHeader
|
||||||
)
|
)
|
||||||
|
|
||||||
func (t EventType) AsEvent() Event {
|
func (t EventType) AsEvent() Event {
|
||||||
@@ -303,6 +303,9 @@ type ColorTheme struct {
|
|||||||
Disabled ColorAttr
|
Disabled ColorAttr
|
||||||
Fg ColorAttr
|
Fg ColorAttr
|
||||||
Bg ColorAttr
|
Bg ColorAttr
|
||||||
|
SelectedFg ColorAttr
|
||||||
|
SelectedBg ColorAttr
|
||||||
|
SelectedMatch ColorAttr
|
||||||
PreviewFg ColorAttr
|
PreviewFg ColorAttr
|
||||||
PreviewBg ColorAttr
|
PreviewBg ColorAttr
|
||||||
DarkBg ColorAttr
|
DarkBg ColorAttr
|
||||||
@@ -314,7 +317,7 @@ type ColorTheme struct {
|
|||||||
Spinner ColorAttr
|
Spinner ColorAttr
|
||||||
Info ColorAttr
|
Info ColorAttr
|
||||||
Cursor ColorAttr
|
Cursor ColorAttr
|
||||||
Selected ColorAttr
|
Marker ColorAttr
|
||||||
Header ColorAttr
|
Header ColorAttr
|
||||||
Separator ColorAttr
|
Separator ColorAttr
|
||||||
Scrollbar ColorAttr
|
Scrollbar ColorAttr
|
||||||
@@ -368,6 +371,14 @@ const (
|
|||||||
BorderRight
|
BorderRight
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (s BorderShape) HasLeft() bool {
|
||||||
|
switch s {
|
||||||
|
case BorderNone, BorderRight, BorderTop, BorderBottom, BorderHorizontal: // No Left
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (s BorderShape) HasRight() bool {
|
func (s BorderShape) HasRight() bool {
|
||||||
switch s {
|
switch s {
|
||||||
case BorderNone, BorderLeft, BorderTop, BorderBottom, BorderHorizontal: // No right
|
case BorderNone, BorderLeft, BorderTop, BorderBottom, BorderHorizontal: // No right
|
||||||
@@ -516,7 +527,7 @@ type TermSize struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Renderer interface {
|
type Renderer interface {
|
||||||
Init()
|
Init() error
|
||||||
Resize(maxHeightFunc func(int) int)
|
Resize(maxHeightFunc func(int) int)
|
||||||
Pause(clear bool)
|
Pause(clear bool)
|
||||||
Resume(clear bool, sigcont bool)
|
Resume(clear bool, sigcont bool)
|
||||||
@@ -595,12 +606,14 @@ var (
|
|||||||
ColMatch ColorPair
|
ColMatch ColorPair
|
||||||
ColCursor ColorPair
|
ColCursor ColorPair
|
||||||
ColCursorEmpty ColorPair
|
ColCursorEmpty ColorPair
|
||||||
|
ColMarker ColorPair
|
||||||
ColSelected ColorPair
|
ColSelected ColorPair
|
||||||
|
ColSelectedMatch ColorPair
|
||||||
ColCurrent ColorPair
|
ColCurrent ColorPair
|
||||||
ColCurrentMatch ColorPair
|
ColCurrentMatch ColorPair
|
||||||
ColCurrentCursor ColorPair
|
ColCurrentCursor ColorPair
|
||||||
ColCurrentCursorEmpty ColorPair
|
ColCurrentCursorEmpty ColorPair
|
||||||
ColCurrentSelected ColorPair
|
ColCurrentMarker ColorPair
|
||||||
ColCurrentSelectedEmpty ColorPair
|
ColCurrentSelectedEmpty ColorPair
|
||||||
ColSpinner ColorPair
|
ColSpinner ColorPair
|
||||||
ColInfo ColorPair
|
ColInfo ColorPair
|
||||||
@@ -622,6 +635,9 @@ func EmptyTheme() *ColorTheme {
|
|||||||
Input: ColorAttr{colUndefined, AttrUndefined},
|
Input: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Fg: ColorAttr{colUndefined, AttrUndefined},
|
Fg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Bg: ColorAttr{colUndefined, AttrUndefined},
|
Bg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
SelectedFg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
SelectedBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
|
SelectedMatch: ColorAttr{colUndefined, AttrUndefined},
|
||||||
DarkBg: ColorAttr{colUndefined, AttrUndefined},
|
DarkBg: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Prompt: ColorAttr{colUndefined, AttrUndefined},
|
Prompt: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Match: ColorAttr{colUndefined, AttrUndefined},
|
Match: ColorAttr{colUndefined, AttrUndefined},
|
||||||
@@ -630,7 +646,7 @@ func EmptyTheme() *ColorTheme {
|
|||||||
Spinner: ColorAttr{colUndefined, AttrUndefined},
|
Spinner: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Info: ColorAttr{colUndefined, AttrUndefined},
|
Info: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Cursor: ColorAttr{colUndefined, AttrUndefined},
|
Cursor: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Selected: ColorAttr{colUndefined, AttrUndefined},
|
Marker: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Header: ColorAttr{colUndefined, AttrUndefined},
|
Header: ColorAttr{colUndefined, AttrUndefined},
|
||||||
Border: ColorAttr{colUndefined, AttrUndefined},
|
Border: ColorAttr{colUndefined, AttrUndefined},
|
||||||
BorderLabel: ColorAttr{colUndefined, AttrUndefined},
|
BorderLabel: ColorAttr{colUndefined, AttrUndefined},
|
||||||
@@ -652,6 +668,9 @@ func NoColorTheme() *ColorTheme {
|
|||||||
Input: ColorAttr{colDefault, AttrUndefined},
|
Input: ColorAttr{colDefault, AttrUndefined},
|
||||||
Fg: ColorAttr{colDefault, AttrUndefined},
|
Fg: ColorAttr{colDefault, AttrUndefined},
|
||||||
Bg: ColorAttr{colDefault, AttrUndefined},
|
Bg: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
SelectedFg: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
SelectedBg: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
SelectedMatch: ColorAttr{colDefault, AttrUndefined},
|
||||||
DarkBg: ColorAttr{colDefault, AttrUndefined},
|
DarkBg: ColorAttr{colDefault, AttrUndefined},
|
||||||
Prompt: ColorAttr{colDefault, AttrUndefined},
|
Prompt: ColorAttr{colDefault, AttrUndefined},
|
||||||
Match: ColorAttr{colDefault, Underline},
|
Match: ColorAttr{colDefault, Underline},
|
||||||
@@ -660,7 +679,7 @@ func NoColorTheme() *ColorTheme {
|
|||||||
Spinner: ColorAttr{colDefault, AttrUndefined},
|
Spinner: ColorAttr{colDefault, AttrUndefined},
|
||||||
Info: ColorAttr{colDefault, AttrUndefined},
|
Info: ColorAttr{colDefault, AttrUndefined},
|
||||||
Cursor: ColorAttr{colDefault, AttrUndefined},
|
Cursor: ColorAttr{colDefault, AttrUndefined},
|
||||||
Selected: ColorAttr{colDefault, AttrUndefined},
|
Marker: ColorAttr{colDefault, AttrUndefined},
|
||||||
Header: ColorAttr{colDefault, AttrUndefined},
|
Header: ColorAttr{colDefault, AttrUndefined},
|
||||||
Border: ColorAttr{colDefault, AttrUndefined},
|
Border: ColorAttr{colDefault, AttrUndefined},
|
||||||
BorderLabel: ColorAttr{colDefault, AttrUndefined},
|
BorderLabel: ColorAttr{colDefault, AttrUndefined},
|
||||||
@@ -676,17 +695,15 @@ func NoColorTheme() *ColorTheme {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func errorExit(message string) {
|
|
||||||
fmt.Fprintln(os.Stderr, message)
|
|
||||||
util.Exit(2)
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
Default16 = &ColorTheme{
|
Default16 = &ColorTheme{
|
||||||
Colored: true,
|
Colored: true,
|
||||||
Input: ColorAttr{colDefault, AttrUndefined},
|
Input: ColorAttr{colDefault, AttrUndefined},
|
||||||
Fg: ColorAttr{colDefault, AttrUndefined},
|
Fg: ColorAttr{colDefault, AttrUndefined},
|
||||||
Bg: ColorAttr{colDefault, AttrUndefined},
|
Bg: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
SelectedFg: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
SelectedBg: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
SelectedMatch: ColorAttr{colDefault, AttrUndefined},
|
||||||
DarkBg: ColorAttr{colBlack, AttrUndefined},
|
DarkBg: ColorAttr{colBlack, AttrUndefined},
|
||||||
Prompt: ColorAttr{colBlue, AttrUndefined},
|
Prompt: ColorAttr{colBlue, AttrUndefined},
|
||||||
Match: ColorAttr{colGreen, AttrUndefined},
|
Match: ColorAttr{colGreen, AttrUndefined},
|
||||||
@@ -695,7 +712,7 @@ func init() {
|
|||||||
Spinner: ColorAttr{colGreen, AttrUndefined},
|
Spinner: ColorAttr{colGreen, AttrUndefined},
|
||||||
Info: ColorAttr{colWhite, AttrUndefined},
|
Info: ColorAttr{colWhite, AttrUndefined},
|
||||||
Cursor: ColorAttr{colRed, AttrUndefined},
|
Cursor: ColorAttr{colRed, AttrUndefined},
|
||||||
Selected: ColorAttr{colMagenta, AttrUndefined},
|
Marker: ColorAttr{colMagenta, AttrUndefined},
|
||||||
Header: ColorAttr{colCyan, AttrUndefined},
|
Header: ColorAttr{colCyan, AttrUndefined},
|
||||||
Border: ColorAttr{colBlack, AttrUndefined},
|
Border: ColorAttr{colBlack, AttrUndefined},
|
||||||
BorderLabel: ColorAttr{colWhite, AttrUndefined},
|
BorderLabel: ColorAttr{colWhite, AttrUndefined},
|
||||||
@@ -714,6 +731,9 @@ func init() {
|
|||||||
Input: ColorAttr{colDefault, AttrUndefined},
|
Input: ColorAttr{colDefault, AttrUndefined},
|
||||||
Fg: ColorAttr{colDefault, AttrUndefined},
|
Fg: ColorAttr{colDefault, AttrUndefined},
|
||||||
Bg: ColorAttr{colDefault, AttrUndefined},
|
Bg: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
SelectedFg: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
SelectedBg: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
SelectedMatch: ColorAttr{colDefault, AttrUndefined},
|
||||||
DarkBg: ColorAttr{236, AttrUndefined},
|
DarkBg: ColorAttr{236, AttrUndefined},
|
||||||
Prompt: ColorAttr{110, AttrUndefined},
|
Prompt: ColorAttr{110, AttrUndefined},
|
||||||
Match: ColorAttr{108, AttrUndefined},
|
Match: ColorAttr{108, AttrUndefined},
|
||||||
@@ -722,7 +742,7 @@ func init() {
|
|||||||
Spinner: ColorAttr{148, AttrUndefined},
|
Spinner: ColorAttr{148, AttrUndefined},
|
||||||
Info: ColorAttr{144, AttrUndefined},
|
Info: ColorAttr{144, AttrUndefined},
|
||||||
Cursor: ColorAttr{161, AttrUndefined},
|
Cursor: ColorAttr{161, AttrUndefined},
|
||||||
Selected: ColorAttr{168, AttrUndefined},
|
Marker: ColorAttr{168, AttrUndefined},
|
||||||
Header: ColorAttr{109, AttrUndefined},
|
Header: ColorAttr{109, AttrUndefined},
|
||||||
Border: ColorAttr{59, AttrUndefined},
|
Border: ColorAttr{59, AttrUndefined},
|
||||||
BorderLabel: ColorAttr{145, AttrUndefined},
|
BorderLabel: ColorAttr{145, AttrUndefined},
|
||||||
@@ -741,6 +761,9 @@ func init() {
|
|||||||
Input: ColorAttr{colDefault, AttrUndefined},
|
Input: ColorAttr{colDefault, AttrUndefined},
|
||||||
Fg: ColorAttr{colDefault, AttrUndefined},
|
Fg: ColorAttr{colDefault, AttrUndefined},
|
||||||
Bg: ColorAttr{colDefault, AttrUndefined},
|
Bg: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
SelectedFg: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
SelectedBg: ColorAttr{colDefault, AttrUndefined},
|
||||||
|
SelectedMatch: ColorAttr{colDefault, AttrUndefined},
|
||||||
DarkBg: ColorAttr{251, AttrUndefined},
|
DarkBg: ColorAttr{251, AttrUndefined},
|
||||||
Prompt: ColorAttr{25, AttrUndefined},
|
Prompt: ColorAttr{25, AttrUndefined},
|
||||||
Match: ColorAttr{66, AttrUndefined},
|
Match: ColorAttr{66, AttrUndefined},
|
||||||
@@ -749,7 +772,7 @@ func init() {
|
|||||||
Spinner: ColorAttr{65, AttrUndefined},
|
Spinner: ColorAttr{65, AttrUndefined},
|
||||||
Info: ColorAttr{101, AttrUndefined},
|
Info: ColorAttr{101, AttrUndefined},
|
||||||
Cursor: ColorAttr{161, AttrUndefined},
|
Cursor: ColorAttr{161, AttrUndefined},
|
||||||
Selected: ColorAttr{168, AttrUndefined},
|
Marker: ColorAttr{168, AttrUndefined},
|
||||||
Header: ColorAttr{31, AttrUndefined},
|
Header: ColorAttr{31, AttrUndefined},
|
||||||
Border: ColorAttr{145, AttrUndefined},
|
Border: ColorAttr{145, AttrUndefined},
|
||||||
BorderLabel: ColorAttr{59, AttrUndefined},
|
BorderLabel: ColorAttr{59, AttrUndefined},
|
||||||
@@ -791,12 +814,15 @@ func initTheme(theme *ColorTheme, baseTheme *ColorTheme, forceBlack bool) {
|
|||||||
theme.Spinner = o(baseTheme.Spinner, theme.Spinner)
|
theme.Spinner = o(baseTheme.Spinner, theme.Spinner)
|
||||||
theme.Info = o(baseTheme.Info, theme.Info)
|
theme.Info = o(baseTheme.Info, theme.Info)
|
||||||
theme.Cursor = o(baseTheme.Cursor, theme.Cursor)
|
theme.Cursor = o(baseTheme.Cursor, theme.Cursor)
|
||||||
theme.Selected = o(baseTheme.Selected, theme.Selected)
|
theme.Marker = o(baseTheme.Marker, theme.Marker)
|
||||||
theme.Header = o(baseTheme.Header, theme.Header)
|
theme.Header = o(baseTheme.Header, theme.Header)
|
||||||
theme.Border = o(baseTheme.Border, theme.Border)
|
theme.Border = o(baseTheme.Border, theme.Border)
|
||||||
theme.BorderLabel = o(baseTheme.BorderLabel, theme.BorderLabel)
|
theme.BorderLabel = o(baseTheme.BorderLabel, theme.BorderLabel)
|
||||||
|
|
||||||
// These colors are not defined in the base themes
|
// These colors are not defined in the base themes
|
||||||
|
theme.SelectedFg = o(theme.Fg, theme.SelectedFg)
|
||||||
|
theme.SelectedBg = o(theme.Bg, theme.SelectedBg)
|
||||||
|
theme.SelectedMatch = o(theme.CurrentMatch, theme.SelectedMatch)
|
||||||
theme.Disabled = o(theme.Input, theme.Disabled)
|
theme.Disabled = o(theme.Input, theme.Disabled)
|
||||||
theme.Gutter = o(theme.DarkBg, theme.Gutter)
|
theme.Gutter = o(theme.DarkBg, theme.Gutter)
|
||||||
theme.PreviewFg = o(theme.Fg, theme.PreviewFg)
|
theme.PreviewFg = o(theme.Fg, theme.PreviewFg)
|
||||||
@@ -822,17 +848,23 @@ func initPalette(theme *ColorTheme) {
|
|||||||
|
|
||||||
ColPrompt = pair(theme.Prompt, theme.Bg)
|
ColPrompt = pair(theme.Prompt, theme.Bg)
|
||||||
ColNormal = pair(theme.Fg, theme.Bg)
|
ColNormal = pair(theme.Fg, theme.Bg)
|
||||||
|
ColSelected = pair(theme.SelectedFg, theme.SelectedBg)
|
||||||
ColInput = pair(theme.Input, theme.Bg)
|
ColInput = pair(theme.Input, theme.Bg)
|
||||||
ColDisabled = pair(theme.Disabled, theme.Bg)
|
ColDisabled = pair(theme.Disabled, theme.Bg)
|
||||||
ColMatch = pair(theme.Match, theme.Bg)
|
ColMatch = pair(theme.Match, theme.Bg)
|
||||||
|
ColSelectedMatch = pair(theme.SelectedMatch, theme.SelectedBg)
|
||||||
ColCursor = pair(theme.Cursor, theme.Gutter)
|
ColCursor = pair(theme.Cursor, theme.Gutter)
|
||||||
ColCursorEmpty = pair(blank, theme.Gutter)
|
ColCursorEmpty = pair(blank, theme.Gutter)
|
||||||
ColSelected = pair(theme.Selected, theme.Gutter)
|
if theme.SelectedBg.Color != theme.Bg.Color {
|
||||||
|
ColMarker = pair(theme.Marker, theme.SelectedBg)
|
||||||
|
} else {
|
||||||
|
ColMarker = pair(theme.Marker, theme.Gutter)
|
||||||
|
}
|
||||||
ColCurrent = pair(theme.Current, theme.DarkBg)
|
ColCurrent = pair(theme.Current, theme.DarkBg)
|
||||||
ColCurrentMatch = pair(theme.CurrentMatch, theme.DarkBg)
|
ColCurrentMatch = pair(theme.CurrentMatch, theme.DarkBg)
|
||||||
ColCurrentCursor = pair(theme.Cursor, theme.DarkBg)
|
ColCurrentCursor = pair(theme.Cursor, theme.DarkBg)
|
||||||
ColCurrentCursorEmpty = pair(blank, theme.DarkBg)
|
ColCurrentCursorEmpty = pair(blank, theme.DarkBg)
|
||||||
ColCurrentSelected = pair(theme.Selected, theme.DarkBg)
|
ColCurrentMarker = pair(theme.Marker, theme.DarkBg)
|
||||||
ColCurrentSelectedEmpty = pair(blank, theme.DarkBg)
|
ColCurrentSelectedEmpty = pair(blank, theme.DarkBg)
|
||||||
ColSpinner = pair(theme.Spinner, theme.Bg)
|
ColSpinner = pair(theme.Spinner, theme.Bg)
|
||||||
ColInfo = pair(theme.Info, theme.Bg)
|
ColInfo = pair(theme.Info, theme.Bg)
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package util
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -25,14 +24,5 @@ func RunAtExitFuncs() {
|
|||||||
for i := len(fns) - 1; i >= 0; i-- {
|
for i := len(fns) - 1; i >= 0; i-- {
|
||||||
fns[i]()
|
fns[i]()
|
||||||
}
|
}
|
||||||
}
|
atExitFuncs = nil
|
||||||
|
|
||||||
// Exit executes any functions registered with AtExit() then exits the program
|
|
||||||
// with os.Exit(code).
|
|
||||||
//
|
|
||||||
// NOTE: It must be used instead of os.Exit() since calling os.Exit() terminates
|
|
||||||
// the program before any of the AtExit functions can run.
|
|
||||||
func Exit(code int) {
|
|
||||||
defer os.Exit(code)
|
|
||||||
RunAtExitFuncs()
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ func (x *Executor) Become(stdin *os.File, environ []string, command string) {
|
|||||||
shellPath, err := exec.LookPath(x.shell)
|
shellPath, err := exec.LookPath(x.shell)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "fzf (become): %s\n", err.Error())
|
fmt.Fprintf(os.Stderr, "fzf (become): %s\n", err.Error())
|
||||||
Exit(127)
|
os.Exit(127)
|
||||||
}
|
}
|
||||||
args := append([]string{shellPath}, append(x.args, command)...)
|
args := append([]string{shellPath}, append(x.args, command)...)
|
||||||
SetStdin(stdin)
|
SetStdin(stdin)
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ func NewExecutor(withShell string) *Executor {
|
|||||||
args = args[1:]
|
args = args[1:]
|
||||||
} else if strings.HasPrefix(basename, "cmd") {
|
} else if strings.HasPrefix(basename, "cmd") {
|
||||||
shellType = shellTypeCmd
|
shellType = shellTypeCmd
|
||||||
args = []string{"/v:on/s/c"}
|
args = []string{"/s/c"}
|
||||||
} else if strings.HasPrefix(basename, "pwsh") || strings.HasPrefix(basename, "powershell") {
|
} else if strings.HasPrefix(basename, "pwsh") || strings.HasPrefix(basename, "powershell") {
|
||||||
shellType = shellTypePowerShell
|
shellType = shellTypePowerShell
|
||||||
args = []string{"-NoProfile", "-Command"}
|
args = []string{"-NoProfile", "-Command"}
|
||||||
@@ -97,15 +97,15 @@ func (x *Executor) Become(stdin *os.File, environ []string, command string) {
|
|||||||
err := cmd.Start()
|
err := cmd.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "fzf (become): %s\n", err.Error())
|
fmt.Fprintf(os.Stderr, "fzf (become): %s\n", err.Error())
|
||||||
Exit(127)
|
os.Exit(127)
|
||||||
}
|
}
|
||||||
err = cmd.Wait()
|
err = cmd.Wait()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if exitError, ok := err.(*exec.ExitError); ok {
|
if exitError, ok := err.(*exec.ExitError); ok {
|
||||||
Exit(exitError.ExitCode())
|
os.Exit(exitError.ExitCode())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func escapeArg(s string) string {
|
func escapeArg(s string) string {
|
||||||
@@ -119,8 +119,6 @@ func escapeArg(s string) string {
|
|||||||
slashes = 0
|
slashes = 0
|
||||||
case '\\':
|
case '\\':
|
||||||
slashes++
|
slashes++
|
||||||
case '&', '|', '<', '>', '(', ')', '@', '^', '%', '!':
|
|
||||||
b = append(b, '^')
|
|
||||||
case '"':
|
case '"':
|
||||||
for ; slashes > 0; slashes-- {
|
for ; slashes > 0; slashes-- {
|
||||||
b = append(b, '\\')
|
b = append(b, '\\')
|
||||||
|
|||||||
@@ -557,7 +557,7 @@ class TestGoFZF < TestBase
|
|||||||
|
|
||||||
def test_expect
|
def test_expect
|
||||||
test = lambda do |key, feed, expected = key|
|
test = lambda do |key, feed, expected = key|
|
||||||
tmux.send_keys "seq 1 100 | #{fzf(:expect, key)}", :Enter
|
tmux.send_keys "seq 1 100 | #{fzf(:expect, key, :prompt, "[#{key}]")}", :Enter
|
||||||
tmux.until { |lines| assert_equal ' 100/100', lines[-2] }
|
tmux.until { |lines| assert_equal ' 100/100', lines[-2] }
|
||||||
tmux.send_keys '55'
|
tmux.send_keys '55'
|
||||||
tmux.until { |lines| assert_equal ' 1/100', lines[-2] }
|
tmux.until { |lines| assert_equal ' 1/100', lines[-2] }
|
||||||
@@ -2693,6 +2693,13 @@ class TestGoFZF < TestBase
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_change_preview_window_should_not_reset_change_preview
|
||||||
|
tmux.send_keys "#{FZF} --preview-window up,border-none --bind 'start:change-preview(echo hello)' --bind 'enter:change-preview-window(border-left)'", :Enter
|
||||||
|
tmux.until { |lines| assert_includes lines, 'hello' }
|
||||||
|
tmux.send_keys :Enter
|
||||||
|
tmux.until { |lines| assert_includes lines, '│ hello' }
|
||||||
|
end
|
||||||
|
|
||||||
def test_change_preview_window_rotate
|
def test_change_preview_window_rotate
|
||||||
tmux.send_keys "seq 100 | #{FZF} --preview-window left,border-none --preview 'echo hello' --bind '" \
|
tmux.send_keys "seq 100 | #{FZF} --preview-window left,border-none --preview 'echo hello' --bind '" \
|
||||||
"a:change-preview-window(right|down|up|hidden|)'", :Enter
|
"a:change-preview-window(right|down|up|hidden|)'", :Enter
|
||||||
@@ -2970,6 +2977,13 @@ class TestGoFZF < TestBase
|
|||||||
tmux.until { assert_match(%r{[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏] 100/100}, _1[-1]) }
|
tmux.until { assert_match(%r{[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏] 100/100}, _1[-1]) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_info_inline_right_clearance
|
||||||
|
tmux.send_keys "seq 100000 | #{FZF} --info inline-right", :Enter
|
||||||
|
tmux.until { assert_match(%r{100000/100000}, _1[-1]) }
|
||||||
|
tmux.send_keys 'x'
|
||||||
|
tmux.until { assert_match(%r{ 0/100000}, _1[-1]) }
|
||||||
|
end
|
||||||
|
|
||||||
def test_prev_next_selected
|
def test_prev_next_selected
|
||||||
tmux.send_keys 'seq 10 | fzf --multi --bind ctrl-n:next-selected,ctrl-p:prev-selected', :Enter
|
tmux.send_keys 'seq 10 | fzf --multi --bind ctrl-n:next-selected,ctrl-p:prev-selected', :Enter
|
||||||
tmux.until { |lines| assert_equal 10, lines.item_count }
|
tmux.until { |lines| assert_equal 10, lines.item_count }
|
||||||
|
|||||||
Reference in New Issue
Block a user