mirror of
https://github.com/junegunn/fzf.git
synced 2025-11-16 07:13:48 -05:00
Compare commits
53 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f97d275413 | ||
|
|
3acb4ca90e | ||
|
|
e86b81bbf5 | ||
|
|
a5447b8b75 | ||
|
|
7ce6452d83 | ||
|
|
5643a306bd | ||
|
|
3c877c504b | ||
|
|
892d1acccb | ||
|
|
1a9c282f76 | ||
|
|
fd1ba46f77 | ||
|
|
a4745626dd | ||
|
|
17bb7ad278 | ||
|
|
152988c17b | ||
|
|
4cd37fc02b | ||
|
|
69b9d674a3 | ||
|
|
bad8061547 | ||
|
|
62963dcefd | ||
|
|
68a35e4735 | ||
|
|
9b9ad77e1c | ||
|
|
118b4d4a01 | ||
|
|
da14ab6f16 | ||
|
|
09a4ca6ab5 | ||
|
|
8a2df79711 | ||
|
|
c30e486b64 | ||
|
|
a575c0c54b | ||
|
|
77fe96ac0d | ||
|
|
5234c3759a | ||
|
|
41b3511ad9 | ||
|
|
128e4a2e8d | ||
|
|
07ac90d798 | ||
|
|
7de87a9b2c | ||
|
|
dff865239a | ||
|
|
07f8f70c5b | ||
|
|
f625c5aabe | ||
|
|
8a74976c1f | ||
|
|
b6bfd4a5cb | ||
|
|
008fb9d258 | ||
|
|
db6db49ed6 | ||
|
|
05453881c3 | ||
|
|
5e47ab9431 | ||
|
|
ec70acd0b9 | ||
|
|
25e61056b6 | ||
|
|
d579e335b5 | ||
|
|
7e344ceb85 | ||
|
|
0145b82ea0 | ||
|
|
b4efe7aab7 | ||
|
|
9ffe951f6d | ||
|
|
a5ea4f57bd | ||
|
|
88f4c16755 | ||
|
|
c7ee071efa | ||
|
|
0740ef7ceb | ||
|
|
b29bd809ac | ||
|
|
8977c9257a |
22
.github/ISSUE_TEMPLATE.md
vendored
22
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,22 +0,0 @@
|
||||
<!-- ISSUES NOT FOLLOWING THIS TEMPLATE WILL BE CLOSED AND DELETED -->
|
||||
|
||||
<!-- Check all that apply [x] -->
|
||||
|
||||
- [ ] I have read through the manual page (`man fzf`)
|
||||
- [ ] I have the latest version of fzf
|
||||
- [ ] I have searched through the existing issues
|
||||
|
||||
## Info
|
||||
|
||||
- OS
|
||||
- [ ] Linux
|
||||
- [ ] Mac OS X
|
||||
- [ ] Windows
|
||||
- [ ] Etc.
|
||||
- Shell
|
||||
- [ ] bash
|
||||
- [ ] zsh
|
||||
- [ ] fish
|
||||
|
||||
## Problem / Steps to reproduce
|
||||
|
||||
49
.github/ISSUE_TEMPLATE/issue_template.yml
vendored
Normal file
49
.github/ISSUE_TEMPLATE/issue_template.yml
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
---
|
||||
name: Issue Template
|
||||
description: Report a problem or bug related to fzf to help us improve
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: ISSUES NOT FOLLOWING THIS TEMPLATE WILL BE CLOSED AND DELETED
|
||||
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Checklist
|
||||
options:
|
||||
- label: I have read through the manual page (`man fzf`)
|
||||
required: true
|
||||
- label: I have searched through the existing issues
|
||||
required: true
|
||||
- label: For bug reports, I have checked if the bug is reproducible in the latest version of fzf
|
||||
required: false
|
||||
|
||||
- type: input
|
||||
attributes:
|
||||
label: Output of `fzf --version`
|
||||
placeholder: e.g. 0.48.1 (d579e33)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: OS
|
||||
options:
|
||||
- label: Linux
|
||||
- label: macOS
|
||||
- label: Windows
|
||||
- label: Etc.
|
||||
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Shell
|
||||
options:
|
||||
- label: bash
|
||||
- label: zsh
|
||||
- label: fish
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Problem / Steps to reproduce
|
||||
validations:
|
||||
required: true
|
||||
2
.github/workflows/linux.yml
vendored
2
.github/workflows/linux.yml
vendored
@@ -25,7 +25,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.19
|
||||
go-version: "1.20"
|
||||
|
||||
- name: Setup Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
|
||||
2
.github/workflows/macos.yml
vendored
2
.github/workflows/macos.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.18
|
||||
go-version: "1.20"
|
||||
|
||||
- name: Setup Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
|
||||
@@ -20,10 +20,6 @@ builds:
|
||||
cat > /tmp/fzf-gon-amd64.hcl << EOF
|
||||
source = ["./dist/fzf-macos_darwin_amd64_v1/fzf"]
|
||||
bundle_id = "kr.junegunn.fzf"
|
||||
apple_id {
|
||||
username = "junegunn.c@gmail.com"
|
||||
password = "@env:AC_PASSWORD"
|
||||
}
|
||||
sign {
|
||||
application_identity = "Developer ID Application: Junegunn Choi (Y254DRW44Z)"
|
||||
}
|
||||
@@ -48,10 +44,6 @@ builds:
|
||||
cat > /tmp/fzf-gon-arm64.hcl << EOF
|
||||
source = ["./dist/fzf-macos-arm_darwin_arm64/fzf"]
|
||||
bundle_id = "kr.junegunn.fzf"
|
||||
apple_id {
|
||||
username = "junegunn.c@gmail.com"
|
||||
password = "@env:AC_PASSWORD"
|
||||
}
|
||||
sign {
|
||||
application_identity = "Developer ID Application: Junegunn Choi (Y254DRW44Z)"
|
||||
}
|
||||
|
||||
16
BUILD.md
16
BUILD.md
@@ -6,7 +6,7 @@ Build instructions
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Go 1.18 or above
|
||||
- Go 1.20 or above
|
||||
|
||||
### Using Makefile
|
||||
|
||||
@@ -24,13 +24,23 @@ make build
|
||||
make release
|
||||
```
|
||||
|
||||
> :warning: Makefile uses git commands to determine the version and the
|
||||
> revision information for `fzf --version`. So if you're building fzf from an
|
||||
> [!WARNING]
|
||||
> Makefile uses git commands to determine the version and the revision
|
||||
> information for `fzf --version`. So if you're building fzf from an
|
||||
> environment where its git information is not available, you have to manually
|
||||
> set `$FZF_VERSION` and `$FZF_REVISION`.
|
||||
>
|
||||
> e.g. `FZF_VERSION=0.24.0 FZF_REVISION=tarball make`
|
||||
|
||||
> [!TIP]
|
||||
> To build fzf with profiling options enabled, set `TAGS=pprof`
|
||||
>
|
||||
> ```sh
|
||||
> TAGS=pprof make clean install
|
||||
> fzf --profile-cpu /tmp/cpu.pprof --profile-mem /tmp/mem.pprof \
|
||||
> --profile-block /tmp/block.pprof --profile-mutex /tmp/mutex.pprof
|
||||
> ```
|
||||
|
||||
Third-party libraries used
|
||||
--------------------------
|
||||
|
||||
|
||||
82
CHANGELOG.md
82
CHANGELOG.md
@@ -1,6 +1,88 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
0.50.0
|
||||
------
|
||||
- Search performance optimization. You can observe 50%+ improvement in some scenarios.
|
||||
```
|
||||
$ rg --line-number --no-heading --smart-case . > $DATA
|
||||
|
||||
$ wc < $DATA
|
||||
5520118 26862362 897487793
|
||||
|
||||
$ hyperfine -w 1 -L bin fzf-0.49.0,fzf-7ce6452,fzf-a5447b8,fzf '{bin} --filter "///" < $DATA | head -30'
|
||||
Summary
|
||||
fzf --filter "///" < $DATA | head -30 ran
|
||||
1.16 ± 0.03 times faster than fzf-a5447b8 --filter "///" < $DATA | head -30
|
||||
1.23 ± 0.03 times faster than fzf-7ce6452 --filter "///" < $DATA | head -30
|
||||
1.52 ± 0.03 times faster than fzf-0.49.0 --filter "///" < $DATA | head -30
|
||||
```
|
||||
- Added `jump` and `jump-cancel` events that are triggered when leaving `jump` mode
|
||||
```sh
|
||||
# Default behavior
|
||||
fzf --bind space:jump
|
||||
|
||||
# Same as jump-accept action
|
||||
fzf --bind space:jump,jump:accept
|
||||
|
||||
# Accept on jump, abort on cancel
|
||||
fzf --bind space:jump,jump:accept,jump-cancel:abort
|
||||
|
||||
# Change header on jump-cancel
|
||||
fzf --bind 'space:change-header(Type jump label)+jump,jump-cancel:change-header:Jump cancelled'
|
||||
```
|
||||
- Added a new environment variable `$FZF_KEY` exported to the child processes. It's the name of the last key pressed.
|
||||
```sh
|
||||
fzf --bind 'space:jump,jump:accept,jump-cancel:transform:[[ $FZF_KEY =~ ctrl-c ]] && echo abort'
|
||||
```
|
||||
- fzf can be built with profiling options. See [BUILD.md](BUILD.md) for more information.
|
||||
- Bug fixes
|
||||
|
||||
0.49.0
|
||||
------
|
||||
- Ingestion performance improved by around 40% (more or less depending on options)
|
||||
- `--info=hidden` and `--info=inline-right` will no longer hide the horizontal separator by default. This gives you more flexibility in customizing the layout.
|
||||
```sh
|
||||
fzf --border --info=inline-right
|
||||
fzf --border --info=inline-right --separator ═
|
||||
fzf --border --info=inline-right --no-separator
|
||||
fzf --border --info=hidden
|
||||
fzf --border --info=hidden --separator ━
|
||||
fzf --border --info=hidden --no-separator
|
||||
```
|
||||
- Added two environment variables exported to the child processes
|
||||
- `FZF_PREVIEW_LABEL`
|
||||
- `FZF_BORDER_LABEL`
|
||||
```sh
|
||||
# Use the current value of $FZF_PREVIEW_LABEL to determine which actions to perform
|
||||
git ls-files |
|
||||
fzf --header 'Press CTRL-P to change preview mode' \
|
||||
--bind='ctrl-p:transform:[[ $FZF_PREVIEW_LABEL =~ cat ]] \
|
||||
&& echo "change-preview(git log --color=always \{})+change-preview-label([[ log ]])" \
|
||||
|| echo "change-preview(bat --color=always \{})+change-preview-label([[ cat ]])"'
|
||||
```
|
||||
- Renamed `track` action to `track-current` to highlight the difference between the global tracking state set by `--track` and a one-off tracking action
|
||||
- `track` is still available as an alias
|
||||
- Added `untrack-current` and `toggle-track-current` actions
|
||||
- `*-current` actions are no-op when the global tracking state is set
|
||||
- Bug fixes and minor improvements
|
||||
|
||||
0.48.1
|
||||
------
|
||||
- CTRL-T and ALT-C bindings can be disabled by setting `FZF_CTRL_T_COMMAND` and `FZF_ALT_C_COMMAND` to empty strings respectively when sourcing the script
|
||||
```sh
|
||||
# bash
|
||||
FZF_CTRL_T_COMMAND= FZF_ALT_C_COMMAND= eval "$(fzf --bash)"
|
||||
|
||||
# zsh
|
||||
FZF_CTRL_T_COMMAND= FZF_ALT_C_COMMAND= eval "$(fzf --zsh)"
|
||||
|
||||
# fish
|
||||
fzf --fish | FZF_CTRL_T_COMMAND= FZF_ALT_C_COMMAND= source
|
||||
```
|
||||
- Setting the variables after sourcing the script will have no effect
|
||||
- Bug fixes
|
||||
|
||||
0.48.0
|
||||
------
|
||||
- Shell integration scripts are now embedded in the fzf binary. This simplifies the distribution, and the users are less likely to have problems caused by using incompatible scripts and binaries.
|
||||
|
||||
3
Makefile
3
Makefile
@@ -4,7 +4,7 @@ GOOS ?= $(word 1, $(subst /, " ", $(word 4, $(shell go version))))
|
||||
|
||||
MAKEFILE := $(realpath $(lastword $(MAKEFILE_LIST)))
|
||||
ROOT_DIR := $(shell dirname $(MAKEFILE))
|
||||
SOURCES := $(wildcard *.go src/*.go src/*/*.go) $(MAKEFILE)
|
||||
SOURCES := $(wildcard *.go src/*.go src/*/*.go shell/*sh) $(MAKEFILE)
|
||||
|
||||
ifdef FZF_VERSION
|
||||
VERSION := $(FZF_VERSION)
|
||||
@@ -79,6 +79,7 @@ all: target/$(BINARY)
|
||||
test: $(SOURCES)
|
||||
[ -z "$$(gofmt -s -d src)" ] || (gofmt -s -d src; exit 1)
|
||||
SHELL=/bin/sh GOOS= $(GO) test -v -tags "$(TAGS)" \
|
||||
github.com/junegunn/fzf \
|
||||
github.com/junegunn/fzf/src \
|
||||
github.com/junegunn/fzf/src/algo \
|
||||
github.com/junegunn/fzf/src/tui \
|
||||
|
||||
8
go.mod
8
go.mod
@@ -1,13 +1,13 @@
|
||||
module github.com/junegunn/fzf
|
||||
|
||||
require (
|
||||
github.com/charlievieth/fastwalk v1.0.2
|
||||
github.com/charlievieth/fastwalk v1.0.3
|
||||
github.com/gdamore/tcell/v2 v2.7.4
|
||||
github.com/mattn/go-isatty v0.0.20
|
||||
github.com/mattn/go-shellwords v1.0.12
|
||||
github.com/rivo/uniseg v0.4.7
|
||||
golang.org/x/sys v0.18.0
|
||||
golang.org/x/term v0.18.0
|
||||
golang.org/x/sys v0.19.0
|
||||
golang.org/x/term v0.19.0
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -17,4 +17,4 @@ require (
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
)
|
||||
|
||||
go 1.17
|
||||
go 1.20
|
||||
|
||||
12
go.sum
12
go.sum
@@ -1,5 +1,5 @@
|
||||
github.com/charlievieth/fastwalk v1.0.2 h1:KYWo7xszmoldOGrwdNIeznSzhj9mhgk+6DwHunG99bc=
|
||||
github.com/charlievieth/fastwalk v1.0.2/go.mod h1:JSfglY/gmL/rqsUS1NCsJTocB5n6sSl9ApAqif4CUbs=
|
||||
github.com/charlievieth/fastwalk v1.0.3 h1:eNWFaNPe5srPqQ5yyDbhAf11paeZaHWcihRhpuYFfSg=
|
||||
github.com/charlievieth/fastwalk v1.0.3/go.mod h1:JSfglY/gmL/rqsUS1NCsJTocB5n6sSl9ApAqif4CUbs=
|
||||
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
||||
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||
github.com/gdamore/tcell/v2 v2.7.4 h1:sg6/UnTM9jGpZU+oFYAsDahfchWAFW8Xx2yFinNSAYU=
|
||||
@@ -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.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.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
golang.org/x/sys v0.19.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-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.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
|
||||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
||||
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
|
||||
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
|
||||
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.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
|
||||
2
install
2
install
@@ -2,7 +2,7 @@
|
||||
|
||||
set -u
|
||||
|
||||
version=0.48.0
|
||||
version=0.50.0
|
||||
auto_completion=
|
||||
key_bindings=
|
||||
update_config=2
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
$version="0.48.0"
|
||||
$version="0.50.0"
|
||||
|
||||
$fzf_base=Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
|
||||
|
||||
2
main.go
2
main.go
@@ -9,7 +9,7 @@ import (
|
||||
"github.com/junegunn/fzf/src/protector"
|
||||
)
|
||||
|
||||
var version string = "0.48"
|
||||
var version string = "0.50"
|
||||
var revision string = "devel"
|
||||
|
||||
//go:embed shell/key-bindings.bash
|
||||
|
||||
158
main_test.go
Normal file
158
main_test.go
Normal file
@@ -0,0 +1,158 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/importer"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func loadPackages(t *testing.T) []*build.Package {
|
||||
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 := build.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
|
||||
THE SOFTWARE.
|
||||
..
|
||||
.TH fzf-tmux 1 "Mar 2024" "fzf 0.48.0" "fzf-tmux - open fzf in tmux split pane"
|
||||
.TH fzf-tmux 1 "Apr 2024" "fzf 0.50.0" "fzf-tmux - open fzf in tmux split pane"
|
||||
|
||||
.SH NAME
|
||||
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
|
||||
THE SOFTWARE.
|
||||
..
|
||||
.TH fzf 1 "Mar 2024" "fzf 0.48.0" "fzf - a command-line fuzzy finder"
|
||||
.TH fzf 1 "Apr 2024" "fzf 0.50.0" "fzf - a command-line fuzzy finder"
|
||||
|
||||
.SH NAME
|
||||
fzf - a command-line fuzzy finder
|
||||
@@ -191,7 +191,7 @@ actions are affected:
|
||||
\fBkill-word\fR
|
||||
.TP
|
||||
.BI "--jump-labels=" "CHARS"
|
||||
Label characters for \fBjump\fR and \fBjump-accept\fR
|
||||
Label characters for \fBjump\fR mode.
|
||||
.SS Layout
|
||||
.TP
|
||||
.BI "--height=" "[~]HEIGHT[%]"
|
||||
@@ -372,20 +372,21 @@ e.g.
|
||||
|
||||
.TP
|
||||
.BI "--info=" "STYLE"
|
||||
Determines the display style of finder info (match counters).
|
||||
Determines the display style of the finder info. (e.g. match counter, loading indicator, etc.)
|
||||
|
||||
.BR default " On the left end of the horizontal separator"
|
||||
.br
|
||||
.BR default " Display on the next line to the prompt"
|
||||
.BR right " On the right end of the horizontal separator"
|
||||
.br
|
||||
.BR right " Display on the right end of the next line to the prompt"
|
||||
.BR hidden " Do not display finder info"
|
||||
.br
|
||||
.BR inline " Display on the same line with the default separator ' < '"
|
||||
.BR inline " After the prompt with the default prefix ' < '"
|
||||
.br
|
||||
.BR inline:SEPARATOR " Display on the same line with a non-default separator"
|
||||
.BR inline:PREFIX " After the prompt with a non-default prefix"
|
||||
.br
|
||||
.BR inline-right " Display on the right end of the same line
|
||||
.BR inline-right " On the right end of the prompt line"
|
||||
.br
|
||||
.BR hidden " Do not display finder info"
|
||||
.BR inline-right:PREFIX " On the right end of the prompt line with a custom prefix"
|
||||
.br
|
||||
|
||||
.TP
|
||||
@@ -975,8 +976,14 @@ fzf exports the following environment variables to its child processes.
|
||||
.br
|
||||
.BR FZF_PROMPT " Prompt string"
|
||||
.br
|
||||
.BR FZF_PREVIEW_LABEL " Preview label string"
|
||||
.br
|
||||
.BR FZF_BORDER_LABEL " Border label string"
|
||||
.br
|
||||
.BR FZF_ACTION " The name of the last action performed"
|
||||
.br
|
||||
.BR FZF_KEY " The name of the last key pressed"
|
||||
.br
|
||||
.BR FZF_PORT " Port number when --listen option is used"
|
||||
.br
|
||||
|
||||
@@ -1047,21 +1054,21 @@ e.g.
|
||||
.br
|
||||
\fIctrl-]\fR
|
||||
.br
|
||||
\fIctrl-^\fR (\fIctrl-6\fR)
|
||||
\fIctrl-^\fR (\fIctrl-6\fR)
|
||||
.br
|
||||
\fIctrl-/\fR (\fIctrl-_\fR)
|
||||
\fIctrl-/\fR (\fIctrl-_\fR)
|
||||
.br
|
||||
\fIctrl-alt-[a-z]\fR
|
||||
.br
|
||||
\fIalt-[*]\fR (Any case-sensitive single character is allowed)
|
||||
\fIalt-[*]\fR (Any case-sensitive single character is allowed)
|
||||
.br
|
||||
\fIf[1-12]\fR
|
||||
.br
|
||||
\fIenter\fR (\fIreturn\fR \fIctrl-m\fR)
|
||||
\fIenter\fR (\fIreturn\fR \fIctrl-m\fR)
|
||||
.br
|
||||
\fIspace\fR
|
||||
.br
|
||||
\fIbspace\fR (\fIbs\fR)
|
||||
\fIbackspace\fR (\fIbspace\fR \fIbs\fR)
|
||||
.br
|
||||
\fIalt-up\fR
|
||||
.br
|
||||
@@ -1075,15 +1082,15 @@ e.g.
|
||||
.br
|
||||
\fIalt-space\fR
|
||||
.br
|
||||
\fIalt-bspace\fR (\fIalt-bs\fR)
|
||||
\fIalt-backspace\fR (\fIalt-bspace\fR \fIalt-bs\fR)
|
||||
.br
|
||||
\fItab\fR
|
||||
.br
|
||||
\fIbtab\fR (\fIshift-tab\fR)
|
||||
\fIshift-tab\fR (\fIbtab\fR)
|
||||
.br
|
||||
\fIesc\fR
|
||||
.br
|
||||
\fIdel\fR
|
||||
\fIdelete\fR (\fIdel\fR)
|
||||
.br
|
||||
\fIup\fR
|
||||
.br
|
||||
@@ -1099,9 +1106,9 @@ e.g.
|
||||
.br
|
||||
\fIinsert\fR
|
||||
.br
|
||||
\fIpgup\fR (\fIpage-up\fR)
|
||||
\fIpage-up\fR (\fIpgup\fR)
|
||||
.br
|
||||
\fIpgdn\fR (\fIpage-down\fR)
|
||||
\fIpage-down\fR (\fIpgdn\fR)
|
||||
.br
|
||||
\fIshift-up\fR
|
||||
.br
|
||||
@@ -1155,6 +1162,7 @@ e.g.
|
||||
\fB# Move cursor to the last item and select all items
|
||||
seq 1000 | fzf --multi --sync --bind start:last+select-all\fR
|
||||
.RE
|
||||
|
||||
\fIload\fR
|
||||
.RS
|
||||
Triggered when the input stream is complete and the initial processing of the
|
||||
@@ -1164,6 +1172,7 @@ e.g.
|
||||
\fB# Change the prompt to "loaded" when the input stream is complete
|
||||
(seq 10; sleep 1; seq 11 20) | fzf --prompt 'Loading> ' --bind 'load:change-prompt:Loaded> '\fR
|
||||
.RE
|
||||
|
||||
\fIresize\fR
|
||||
.RS
|
||||
Triggered when the terminal size is changed.
|
||||
@@ -1171,6 +1180,7 @@ Triggered when the terminal size is changed.
|
||||
e.g.
|
||||
\fBfzf --bind 'resize:transform-header:echo Resized: ${FZF_COLUMNS}x${FZF_LINES}'\fR
|
||||
.RE
|
||||
|
||||
\fIresult\fR
|
||||
.RS
|
||||
Triggered when the filtering for the current query is complete and the result list is ready.
|
||||
@@ -1204,6 +1214,7 @@ e.g.
|
||||
# Beware not to introduce an infinite loop
|
||||
seq 10 | fzf --bind 'focus:up' --cycle\fR
|
||||
.RE
|
||||
|
||||
\fIone\fR
|
||||
.RS
|
||||
Triggered when there's only one match. \fBone:accept\fR binding is comparable
|
||||
@@ -1215,6 +1226,7 @@ e.g.
|
||||
\fB# Automatically select the only match
|
||||
seq 10 | fzf --bind one:accept\fR
|
||||
.RE
|
||||
|
||||
\fIzero\fR
|
||||
.RS
|
||||
Triggered when there's no match. \fBzero:abort\fR binding is comparable to
|
||||
@@ -1236,6 +1248,22 @@ e.g.
|
||||
\fBfzf --bind backward-eof:abort\fR
|
||||
.RE
|
||||
|
||||
\fIjump\fR
|
||||
.RS
|
||||
Triggered when successfully jumped to the target item in \fBjump\fR mode.
|
||||
|
||||
e.g.
|
||||
\fBfzf --bind space:jump,jump:accept\fR
|
||||
.RE
|
||||
|
||||
\fIjump-cancel\fR
|
||||
.RS
|
||||
Triggered when \fBjump\fR mode is cancelled.
|
||||
|
||||
e.g.
|
||||
\fBfzf --bind space:jump,jump:accept,jump-cancel:abort\fR
|
||||
.RE
|
||||
|
||||
.SS AVAILABLE ACTIONS:
|
||||
A key or an event can be bound to one or more of the following actions.
|
||||
|
||||
@@ -1278,7 +1306,6 @@ A key or an event can be bound to one or more of the following actions.
|
||||
\fBforward-word\fR \fIalt-f shift-right\fR
|
||||
\fBignore\fR
|
||||
\fBjump\fR (EasyMotion-like 2-keystroke movement)
|
||||
\fBjump-accept\fR (jump and accept)
|
||||
\fBkill-line\fR
|
||||
\fBkill-word\fR \fIalt-d\fR
|
||||
\fBlast\fR (move to the last match; same as \fBpos(-1)\fR)
|
||||
@@ -1326,9 +1353,10 @@ A key or an event can be bound to one or more of the following actions.
|
||||
\fBtoggle-preview-wrap\fR
|
||||
\fBtoggle-search\fR (toggle search functionality)
|
||||
\fBtoggle-sort\fR
|
||||
\fBtoggle-track\fR
|
||||
\fBtoggle-track\fR (toggle global tracking option (\fB--track\fR))
|
||||
\fBtoggle-track-current\fR (toggle tracking of the current item)
|
||||
\fBtoggle+up\fR \fIbtab (shift-tab)\fR
|
||||
\fBtrack\fR (track the current item; automatically disabled if focus changes)
|
||||
\fBtrack-current\fR (track the current item; automatically disabled if focus changes)
|
||||
\fBtransform(...)\fR (transform states using the output of an external command)
|
||||
\fBtransform-border-label(...)\fR (transform border label using an external command)
|
||||
\fBtransform-header(...)\fR (transform header using an external command)
|
||||
@@ -1338,6 +1366,7 @@ A key or an event can be bound to one or more of the following actions.
|
||||
\fBunbind(...)\fR (unbind bindings)
|
||||
\fBunix-line-discard\fR \fIctrl-u\fR
|
||||
\fBunix-word-rubout\fR \fIctrl-w\fR
|
||||
\fBuntrack-current\fR (stop tracking the current item; no-op if global tracking is enabled)
|
||||
\fBup\fR \fIctrl-k ctrl-p up\fR
|
||||
\fByank\fR \fIctrl-y\fR
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
# - $FZF_COMPLETION_TRIGGER (default: '**')
|
||||
# - $FZF_COMPLETION_OPTS (default: empty)
|
||||
|
||||
[[ $- =~ i ]] || return 0
|
||||
if [[ $- =~ i ]]; then
|
||||
|
||||
|
||||
# To use custom commands instead of find, override _fzf_compgen_{path,dir}
|
||||
@@ -291,7 +291,7 @@ _fzf_handle_dynamic_completion() {
|
||||
"$REPLY" "$@"
|
||||
elif [[ -n "${_fzf_completion_loader-}" ]]; then
|
||||
orig_complete=$(complete -p "$orig_cmd" 2> /dev/null)
|
||||
_completion_loader "$@"
|
||||
$_fzf_completion_loader "$@"
|
||||
ret=$?
|
||||
# _completion_loader may not have updated completion for the command
|
||||
if [[ "$(complete -p "$orig_cmd" 2> /dev/null)" != "$orig_complete" ]]; then
|
||||
@@ -308,6 +308,7 @@ _fzf_handle_dynamic_completion() {
|
||||
eval "$orig_complete"
|
||||
fi
|
||||
fi
|
||||
[[ $ret -eq 0 ]] && return 124
|
||||
return $ret
|
||||
fi
|
||||
}
|
||||
@@ -516,8 +517,16 @@ a_cmds="
|
||||
# Preserve existing completion
|
||||
__fzf_orig_completion < <(complete -p $d_cmds $a_cmds ssh 2> /dev/null)
|
||||
|
||||
if type _completion_loader > /dev/null 2>&1; then
|
||||
_fzf_completion_loader=1
|
||||
if type _comp_load > /dev/null 2>&1; then
|
||||
# _comp_load was added in bash-completion 2.12 to replace _completion_loader.
|
||||
# We use it without -D option so that it does not use _comp_complete_minimal as the fallback.
|
||||
_fzf_completion_loader=_comp_load
|
||||
elif type __load_completion > /dev/null 2>&1; then
|
||||
# In bash-completion 2.11, _completion_loader internally calls __load_completion
|
||||
# and if it returns a non-zero status, it sets the default 'minimal' completion.
|
||||
_fzf_completion_loader=__load_completion
|
||||
elif type _completion_loader > /dev/null 2>&1; then
|
||||
_fzf_completion_loader=_completion_loader
|
||||
fi
|
||||
|
||||
__fzf_defc() {
|
||||
@@ -539,7 +548,7 @@ done
|
||||
|
||||
# Directory
|
||||
for cmd in $d_cmds; do
|
||||
__fzf_defc "$cmd" _fzf_dir_completion "-o nospace -o dirnames"
|
||||
__fzf_defc "$cmd" _fzf_dir_completion "-o bashdefault -o nospace -o dirnames"
|
||||
done
|
||||
|
||||
# ssh
|
||||
@@ -572,3 +581,5 @@ _fzf_setup_completion 'var' export unset printenv
|
||||
_fzf_setup_completion 'alias' unalias
|
||||
_fzf_setup_completion 'host' telnet
|
||||
_fzf_setup_completion 'proc' kill
|
||||
|
||||
fi
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
# - $FZF_COMPLETION_TRIGGER (default: '**')
|
||||
# - $FZF_COMPLETION_OPTS (default: empty)
|
||||
|
||||
[[ -o interactive ]] || return 0
|
||||
if [[ -o interactive ]]; then
|
||||
|
||||
|
||||
# Both branches of the following `if` do the same thing -- define
|
||||
@@ -351,3 +351,5 @@ bindkey '^I' fzf-completion
|
||||
eval $__fzf_completion_options
|
||||
'unset' '__fzf_completion_options'
|
||||
}
|
||||
|
||||
fi
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
# - $FZF_ALT_C_COMMAND
|
||||
# - $FZF_ALT_C_OPTS
|
||||
|
||||
[[ $- =~ i ]] || return 0
|
||||
if [[ $- =~ i ]]; then
|
||||
|
||||
|
||||
# Key bindings
|
||||
@@ -41,7 +41,7 @@ __fzf_cd__() {
|
||||
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore --reverse --walker=dir,follow,hidden --scheme=path ${FZF_DEFAULT_OPTS-} ${FZF_ALT_C_OPTS-} +m"
|
||||
dir=$(
|
||||
FZF_DEFAULT_COMMAND=${FZF_ALT_C_COMMAND:-} FZF_DEFAULT_OPTS="$opts" $(__fzfcmd)
|
||||
) && printf 'builtin cd -- %q' "$dir"
|
||||
) && printf 'builtin cd -- %q' "$(builtin unset CDPATH && builtin cd -- "$dir" && builtin pwd)"
|
||||
}
|
||||
|
||||
if command -v perl > /dev/null; then
|
||||
@@ -102,9 +102,11 @@ bind -m emacs-standard '"\C-z": vi-editing-mode'
|
||||
|
||||
if (( BASH_VERSINFO[0] < 4 )); then
|
||||
# CTRL-T - Paste the selected file path into the command line
|
||||
bind -m emacs-standard '"\C-t": " \C-b\C-k \C-u`__fzf_select__`\e\C-e\er\C-a\C-y\C-h\C-e\e \C-y\ey\C-x\C-x\C-f"'
|
||||
bind -m vi-command '"\C-t": "\C-z\C-t\C-z"'
|
||||
bind -m vi-insert '"\C-t": "\C-z\C-t\C-z"'
|
||||
if [[ "${FZF_CTRL_T_COMMAND-x}" != "" ]]; then
|
||||
bind -m emacs-standard '"\C-t": " \C-b\C-k \C-u`__fzf_select__`\e\C-e\er\C-a\C-y\C-h\C-e\e \C-y\ey\C-x\C-x\C-f"'
|
||||
bind -m vi-command '"\C-t": "\C-z\C-t\C-z"'
|
||||
bind -m vi-insert '"\C-t": "\C-z\C-t\C-z"'
|
||||
fi
|
||||
|
||||
# CTRL-R - Paste the selected command from history into the command line
|
||||
bind -m emacs-standard '"\C-r": "\C-e \C-u\C-y\ey\C-u`__fzf_history__`\e\C-e\er"'
|
||||
@@ -112,9 +114,11 @@ if (( BASH_VERSINFO[0] < 4 )); then
|
||||
bind -m vi-insert '"\C-r": "\C-z\C-r\C-z"'
|
||||
else
|
||||
# CTRL-T - Paste the selected file path into the command line
|
||||
bind -m emacs-standard -x '"\C-t": fzf-file-widget'
|
||||
bind -m vi-command -x '"\C-t": fzf-file-widget'
|
||||
bind -m vi-insert -x '"\C-t": fzf-file-widget'
|
||||
if [[ "${FZF_CTRL_T_COMMAND-x}" != "" ]]; then
|
||||
bind -m emacs-standard -x '"\C-t": fzf-file-widget'
|
||||
bind -m vi-command -x '"\C-t": fzf-file-widget'
|
||||
bind -m vi-insert -x '"\C-t": fzf-file-widget'
|
||||
fi
|
||||
|
||||
# CTRL-R - Paste the selected command from history into the command line
|
||||
bind -m emacs-standard -x '"\C-r": __fzf_history__'
|
||||
@@ -123,6 +127,10 @@ else
|
||||
fi
|
||||
|
||||
# ALT-C - cd into the selected directory
|
||||
bind -m emacs-standard '"\ec": " \C-b\C-k \C-u`__fzf_cd__`\e\C-e\er\C-m\C-y\C-h\e \C-y\ey\C-x\C-x\C-d"'
|
||||
bind -m vi-command '"\ec": "\C-z\ec\C-z"'
|
||||
bind -m vi-insert '"\ec": "\C-z\ec\C-z"'
|
||||
if [[ "${FZF_ALT_C_COMMAND-x}" != "" ]]; then
|
||||
bind -m emacs-standard '"\ec": " \C-b\C-k \C-u`__fzf_cd__`\e\C-e\er\C-m\C-y\C-h\e \C-y\ey\C-x\C-x\C-d"'
|
||||
bind -m vi-command '"\ec": "\C-z\ec\C-z"'
|
||||
bind -m vi-insert '"\ec": "\C-z\ec\C-z"'
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
@@ -21,13 +21,13 @@ function fzf_key_bindings
|
||||
# Store current token in $dir as root for the 'find' command
|
||||
function fzf-file-widget -d "List files and folders"
|
||||
set -l commandline (__fzf_parse_commandline)
|
||||
set -l dir $commandline[1]
|
||||
set -lx dir $commandline[1]
|
||||
set -l fzf_query $commandline[2]
|
||||
set -l prefix $commandline[3]
|
||||
|
||||
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
||||
begin
|
||||
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse --walker=file,dir,follow,hidden --scheme=path --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS"
|
||||
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse --walker=file,dir,follow,hidden --walker-root='$dir' --scheme=path --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS"
|
||||
set -lx FZF_DEFAULT_COMMAND "$FZF_CTRL_T_COMMAND"
|
||||
eval (__fzfcmd)' -m --query "'$fzf_query'"' | while read -l r; set result $result $r; end
|
||||
end
|
||||
@@ -70,13 +70,13 @@ function fzf_key_bindings
|
||||
|
||||
function fzf-cd-widget -d "Change directory"
|
||||
set -l commandline (__fzf_parse_commandline)
|
||||
set -l dir $commandline[1]
|
||||
set -lx dir $commandline[1]
|
||||
set -l fzf_query $commandline[2]
|
||||
set -l prefix $commandline[3]
|
||||
|
||||
test -n "$FZF_TMUX_HEIGHT"; or set FZF_TMUX_HEIGHT 40%
|
||||
begin
|
||||
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse --walker=dir,follow,hidden --scheme=path --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS"
|
||||
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT --reverse --walker=dir,follow,hidden --walker-root='$dir' --scheme=path --bind=ctrl-z:ignore $FZF_DEFAULT_OPTS $FZF_ALT_C_OPTS"
|
||||
set -lx FZF_DEFAULT_COMMAND "$FZF_ALT_C_COMMAND"
|
||||
eval (__fzfcmd)' +m --query "'$fzf_query'"' | read -l result
|
||||
|
||||
@@ -104,14 +104,22 @@ function fzf_key_bindings
|
||||
end
|
||||
end
|
||||
|
||||
bind \ct fzf-file-widget
|
||||
bind \cr fzf-history-widget
|
||||
bind \ec fzf-cd-widget
|
||||
if not set -q FZF_CTRL_T_COMMAND; or test -n "$FZF_CTRL_T_COMMAND"
|
||||
bind \ct fzf-file-widget
|
||||
end
|
||||
if not set -q FZF_ALT_C_COMMAND; or test -n "$FZF_ALT_C_COMMAND"
|
||||
bind \ec fzf-cd-widget
|
||||
end
|
||||
|
||||
if bind -M insert > /dev/null 2>&1
|
||||
bind -M insert \ct fzf-file-widget
|
||||
bind -M insert \cr fzf-history-widget
|
||||
bind -M insert \ec fzf-cd-widget
|
||||
if not set -q FZF_CTRL_T_COMMAND; or test -n "$FZF_CTRL_T_COMMAND"
|
||||
bind -M insert \ct fzf-file-widget
|
||||
end
|
||||
if not set -q FZF_ALT_C_COMMAND; or test -n "$FZF_ALT_C_COMMAND"
|
||||
bind -M insert \ec fzf-cd-widget
|
||||
end
|
||||
end
|
||||
|
||||
function __fzf_parse_commandline -d 'Parse the current command line token and return split of existing filepath, fzf query, and optional -option= prefix'
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
# - $FZF_ALT_C_COMMAND
|
||||
# - $FZF_ALT_C_OPTS
|
||||
|
||||
[[ -o interactive ]] || return 0
|
||||
if [[ -o interactive ]]; then
|
||||
|
||||
|
||||
# Key bindings
|
||||
@@ -62,10 +62,12 @@ fzf-file-widget() {
|
||||
zle reset-prompt
|
||||
return $ret
|
||||
}
|
||||
zle -N fzf-file-widget
|
||||
bindkey -M emacs '^T' fzf-file-widget
|
||||
bindkey -M vicmd '^T' fzf-file-widget
|
||||
bindkey -M viins '^T' fzf-file-widget
|
||||
if [[ "${FZF_CTRL_T_COMMAND-x}" != "" ]]; then
|
||||
zle -N fzf-file-widget
|
||||
bindkey -M emacs '^T' fzf-file-widget
|
||||
bindkey -M vicmd '^T' fzf-file-widget
|
||||
bindkey -M viins '^T' fzf-file-widget
|
||||
fi
|
||||
|
||||
# ALT-C - cd into the selected directory
|
||||
fzf-cd-widget() {
|
||||
@@ -76,17 +78,19 @@ fzf-cd-widget() {
|
||||
return 0
|
||||
fi
|
||||
zle push-line # Clear buffer. Auto-restored on next prompt.
|
||||
BUFFER="builtin cd -- ${(q)dir}"
|
||||
BUFFER="builtin cd -- ${(q)dir:a}"
|
||||
zle accept-line
|
||||
local ret=$?
|
||||
unset dir # ensure this doesn't end up appearing in prompt expansion
|
||||
zle reset-prompt
|
||||
return $ret
|
||||
}
|
||||
zle -N fzf-cd-widget
|
||||
bindkey -M emacs '\ec' fzf-cd-widget
|
||||
bindkey -M vicmd '\ec' fzf-cd-widget
|
||||
bindkey -M viins '\ec' fzf-cd-widget
|
||||
if [[ "${FZF_ALT_C_COMMAND-x}" != "" ]]; then
|
||||
zle -N fzf-cd-widget
|
||||
bindkey -M emacs '\ec' fzf-cd-widget
|
||||
bindkey -M vicmd '\ec' fzf-cd-widget
|
||||
bindkey -M viins '\ec' fzf-cd-widget
|
||||
fi
|
||||
|
||||
# CTRL-R - Paste the selected command from history into the command line
|
||||
fzf-history-widget() {
|
||||
@@ -115,3 +119,5 @@ bindkey -M viins '^R' fzf-history-widget
|
||||
eval $__fzf_key_bindings_options
|
||||
'unset' '__fzf_key_bindings_options'
|
||||
}
|
||||
|
||||
fi
|
||||
|
||||
@@ -54,72 +54,74 @@ func _() {
|
||||
_ = x[actToggleIn-43]
|
||||
_ = x[actToggleOut-44]
|
||||
_ = x[actToggleTrack-45]
|
||||
_ = x[actToggleHeader-46]
|
||||
_ = x[actTrack-47]
|
||||
_ = x[actDown-48]
|
||||
_ = x[actUp-49]
|
||||
_ = x[actPageUp-50]
|
||||
_ = x[actPageDown-51]
|
||||
_ = x[actPosition-52]
|
||||
_ = x[actHalfPageUp-53]
|
||||
_ = x[actHalfPageDown-54]
|
||||
_ = x[actOffsetUp-55]
|
||||
_ = x[actOffsetDown-56]
|
||||
_ = x[actJump-57]
|
||||
_ = x[actJumpAccept-58]
|
||||
_ = x[actPrintQuery-59]
|
||||
_ = x[actRefreshPreview-60]
|
||||
_ = x[actReplaceQuery-61]
|
||||
_ = x[actToggleSort-62]
|
||||
_ = x[actShowPreview-63]
|
||||
_ = x[actHidePreview-64]
|
||||
_ = x[actTogglePreview-65]
|
||||
_ = x[actTogglePreviewWrap-66]
|
||||
_ = x[actTransform-67]
|
||||
_ = x[actTransformBorderLabel-68]
|
||||
_ = x[actTransformHeader-69]
|
||||
_ = x[actTransformPreviewLabel-70]
|
||||
_ = x[actTransformPrompt-71]
|
||||
_ = x[actTransformQuery-72]
|
||||
_ = x[actPreview-73]
|
||||
_ = x[actChangePreview-74]
|
||||
_ = x[actChangePreviewWindow-75]
|
||||
_ = x[actPreviewTop-76]
|
||||
_ = x[actPreviewBottom-77]
|
||||
_ = x[actPreviewUp-78]
|
||||
_ = x[actPreviewDown-79]
|
||||
_ = x[actPreviewPageUp-80]
|
||||
_ = x[actPreviewPageDown-81]
|
||||
_ = x[actPreviewHalfPageUp-82]
|
||||
_ = x[actPreviewHalfPageDown-83]
|
||||
_ = x[actPrevHistory-84]
|
||||
_ = x[actPrevSelected-85]
|
||||
_ = x[actPut-86]
|
||||
_ = x[actNextHistory-87]
|
||||
_ = x[actNextSelected-88]
|
||||
_ = x[actExecute-89]
|
||||
_ = x[actExecuteSilent-90]
|
||||
_ = x[actExecuteMulti-91]
|
||||
_ = x[actSigStop-92]
|
||||
_ = x[actFirst-93]
|
||||
_ = x[actLast-94]
|
||||
_ = x[actReload-95]
|
||||
_ = x[actReloadSync-96]
|
||||
_ = x[actDisableSearch-97]
|
||||
_ = x[actEnableSearch-98]
|
||||
_ = x[actSelect-99]
|
||||
_ = x[actDeselect-100]
|
||||
_ = x[actUnbind-101]
|
||||
_ = x[actRebind-102]
|
||||
_ = x[actBecome-103]
|
||||
_ = x[actResponse-104]
|
||||
_ = x[actShowHeader-105]
|
||||
_ = x[actHideHeader-106]
|
||||
_ = x[actToggleTrackCurrent-46]
|
||||
_ = x[actToggleHeader-47]
|
||||
_ = x[actTrackCurrent-48]
|
||||
_ = x[actUntrackCurrent-49]
|
||||
_ = x[actDown-50]
|
||||
_ = x[actUp-51]
|
||||
_ = x[actPageUp-52]
|
||||
_ = x[actPageDown-53]
|
||||
_ = x[actPosition-54]
|
||||
_ = x[actHalfPageUp-55]
|
||||
_ = x[actHalfPageDown-56]
|
||||
_ = x[actOffsetUp-57]
|
||||
_ = x[actOffsetDown-58]
|
||||
_ = x[actJump-59]
|
||||
_ = x[actJumpAccept-60]
|
||||
_ = x[actPrintQuery-61]
|
||||
_ = x[actRefreshPreview-62]
|
||||
_ = x[actReplaceQuery-63]
|
||||
_ = x[actToggleSort-64]
|
||||
_ = x[actShowPreview-65]
|
||||
_ = x[actHidePreview-66]
|
||||
_ = x[actTogglePreview-67]
|
||||
_ = x[actTogglePreviewWrap-68]
|
||||
_ = x[actTransform-69]
|
||||
_ = x[actTransformBorderLabel-70]
|
||||
_ = x[actTransformHeader-71]
|
||||
_ = x[actTransformPreviewLabel-72]
|
||||
_ = x[actTransformPrompt-73]
|
||||
_ = x[actTransformQuery-74]
|
||||
_ = x[actPreview-75]
|
||||
_ = x[actChangePreview-76]
|
||||
_ = x[actChangePreviewWindow-77]
|
||||
_ = x[actPreviewTop-78]
|
||||
_ = x[actPreviewBottom-79]
|
||||
_ = x[actPreviewUp-80]
|
||||
_ = x[actPreviewDown-81]
|
||||
_ = x[actPreviewPageUp-82]
|
||||
_ = x[actPreviewPageDown-83]
|
||||
_ = x[actPreviewHalfPageUp-84]
|
||||
_ = x[actPreviewHalfPageDown-85]
|
||||
_ = x[actPrevHistory-86]
|
||||
_ = x[actPrevSelected-87]
|
||||
_ = x[actPut-88]
|
||||
_ = x[actNextHistory-89]
|
||||
_ = x[actNextSelected-90]
|
||||
_ = x[actExecute-91]
|
||||
_ = x[actExecuteSilent-92]
|
||||
_ = x[actExecuteMulti-93]
|
||||
_ = x[actSigStop-94]
|
||||
_ = x[actFirst-95]
|
||||
_ = x[actLast-96]
|
||||
_ = x[actReload-97]
|
||||
_ = x[actReloadSync-98]
|
||||
_ = x[actDisableSearch-99]
|
||||
_ = x[actEnableSearch-100]
|
||||
_ = x[actSelect-101]
|
||||
_ = x[actDeselect-102]
|
||||
_ = x[actUnbind-103]
|
||||
_ = x[actRebind-104]
|
||||
_ = x[actBecome-105]
|
||||
_ = x[actResponse-106]
|
||||
_ = x[actShowHeader-107]
|
||||
_ = x[actHideHeader-108]
|
||||
}
|
||||
|
||||
const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeHeaderactChangePreviewLabelactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleHeaderactTrackactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformHeaderactTransformPreviewLabelactTransformPromptactTransformQueryactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactBecomeactResponseactShowHeaderactHideHeader"
|
||||
const _actionType_name = "actIgnoreactStartactClickactInvalidactCharactMouseactBeginningOfLineactAbortactAcceptactAcceptNonEmptyactAcceptOrPrintQueryactBackwardCharactBackwardDeleteCharactBackwardDeleteCharEofactBackwardWordactCancelactChangeBorderLabelactChangeHeaderactChangePreviewLabelactChangePromptactChangeQueryactClearScreenactClearQueryactClearSelectionactCloseactDeleteCharactDeleteCharEofactEndOfLineactForwardCharactForwardWordactKillLineactKillWordactUnixLineDiscardactUnixWordRuboutactYankactBackwardKillWordactSelectAllactDeselectAllactToggleactToggleSearchactToggleAllactToggleDownactToggleUpactToggleInactToggleOutactToggleTrackactToggleTrackCurrentactToggleHeaderactTrackCurrentactUntrackCurrentactDownactUpactPageUpactPageDownactPositionactHalfPageUpactHalfPageDownactOffsetUpactOffsetDownactJumpactJumpAcceptactPrintQueryactRefreshPreviewactReplaceQueryactToggleSortactShowPreviewactHidePreviewactTogglePreviewactTogglePreviewWrapactTransformactTransformBorderLabelactTransformHeaderactTransformPreviewLabelactTransformPromptactTransformQueryactPreviewactChangePreviewactChangePreviewWindowactPreviewTopactPreviewBottomactPreviewUpactPreviewDownactPreviewPageUpactPreviewPageDownactPreviewHalfPageUpactPreviewHalfPageDownactPrevHistoryactPrevSelectedactPutactNextHistoryactNextSelectedactExecuteactExecuteSilentactExecuteMultiactSigStopactFirstactLastactReloadactReloadSyncactDisableSearchactEnableSearchactSelectactDeselectactUnbindactRebindactBecomeactResponseactShowHeaderactHideHeader"
|
||||
|
||||
var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 42, 50, 68, 76, 85, 102, 123, 138, 159, 183, 198, 207, 227, 242, 263, 278, 292, 306, 319, 336, 344, 357, 373, 385, 399, 413, 424, 435, 453, 470, 477, 496, 508, 522, 531, 546, 558, 571, 582, 593, 605, 619, 634, 642, 649, 654, 663, 674, 685, 698, 713, 724, 737, 744, 757, 770, 787, 802, 815, 829, 843, 859, 879, 891, 914, 932, 956, 974, 991, 1001, 1017, 1039, 1052, 1068, 1080, 1094, 1110, 1128, 1148, 1170, 1184, 1199, 1205, 1219, 1234, 1244, 1260, 1275, 1285, 1293, 1300, 1309, 1322, 1338, 1353, 1362, 1373, 1382, 1391, 1400, 1411, 1424, 1437}
|
||||
var _actionType_index = [...]uint16{0, 9, 17, 25, 35, 42, 50, 68, 76, 85, 102, 123, 138, 159, 183, 198, 207, 227, 242, 263, 278, 292, 306, 319, 336, 344, 357, 373, 385, 399, 413, 424, 435, 453, 470, 477, 496, 508, 522, 531, 546, 558, 571, 582, 593, 605, 619, 640, 655, 670, 687, 694, 699, 708, 719, 730, 743, 758, 769, 782, 789, 802, 815, 832, 847, 860, 874, 888, 904, 924, 936, 959, 977, 1001, 1019, 1036, 1046, 1062, 1084, 1097, 1113, 1125, 1139, 1155, 1173, 1193, 1215, 1229, 1244, 1250, 1264, 1279, 1289, 1305, 1320, 1330, 1338, 1345, 1354, 1367, 1383, 1398, 1407, 1418, 1427, 1436, 1445, 1456, 1469, 1482}
|
||||
|
||||
func (i actionType) String() string {
|
||||
if i < 0 || i >= actionType(len(_actionType_index)-1) {
|
||||
|
||||
133
src/algo/algo.go
133
src/algo/algo.go
@@ -153,6 +153,12 @@ var (
|
||||
bonusBoundaryDelimiter int16 = bonusBoundary + 1
|
||||
|
||||
initialCharClass charClass = charWhite
|
||||
|
||||
// A minor optimization that can give 15%+ performance boost
|
||||
asciiCharClasses [unicode.MaxASCII + 1]charClass
|
||||
|
||||
// A minor optimization that can give yet another 5% performance boost
|
||||
bonusMatrix [charNumber + 1][charNumber + 1]int16
|
||||
)
|
||||
|
||||
type charClass int
|
||||
@@ -187,6 +193,27 @@ func Init(scheme string) bool {
|
||||
default:
|
||||
return false
|
||||
}
|
||||
for i := 0; i <= unicode.MaxASCII; i++ {
|
||||
char := rune(i)
|
||||
c := charNonWord
|
||||
if char >= 'a' && char <= 'z' {
|
||||
c = charLower
|
||||
} else if char >= 'A' && char <= 'Z' {
|
||||
c = charUpper
|
||||
} else if char >= '0' && char <= '9' {
|
||||
c = charNumber
|
||||
} else if strings.ContainsRune(whiteChars, char) {
|
||||
c = charWhite
|
||||
} else if strings.ContainsRune(delimiterChars, char) {
|
||||
c = charDelimiter
|
||||
}
|
||||
asciiCharClasses[i] = c
|
||||
}
|
||||
for i := 0; i <= int(charNumber); i++ {
|
||||
for j := 0; j <= int(charNumber); j++ {
|
||||
bonusMatrix[i][j] = bonusFor(charClass(i), charClass(j))
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -214,21 +241,6 @@ func alloc32(offset int, slab *util.Slab, size int) (int, []int32) {
|
||||
return offset, make([]int32, size)
|
||||
}
|
||||
|
||||
func charClassOfAscii(char rune) charClass {
|
||||
if char >= 'a' && char <= 'z' {
|
||||
return charLower
|
||||
} else if char >= 'A' && char <= 'Z' {
|
||||
return charUpper
|
||||
} else if char >= '0' && char <= '9' {
|
||||
return charNumber
|
||||
} else if strings.ContainsRune(whiteChars, char) {
|
||||
return charWhite
|
||||
} else if strings.ContainsRune(delimiterChars, char) {
|
||||
return charDelimiter
|
||||
}
|
||||
return charNonWord
|
||||
}
|
||||
|
||||
func charClassOfNonAscii(char rune) charClass {
|
||||
if unicode.IsLower(char) {
|
||||
return charLower
|
||||
@@ -248,7 +260,7 @@ func charClassOfNonAscii(char rune) charClass {
|
||||
|
||||
func charClassOf(char rune) charClass {
|
||||
if char <= unicode.MaxASCII {
|
||||
return charClassOfAscii(char)
|
||||
return asciiCharClasses[char]
|
||||
}
|
||||
return charClassOfNonAscii(char)
|
||||
}
|
||||
@@ -287,7 +299,7 @@ func bonusAt(input *util.Chars, idx int) int16 {
|
||||
if idx == 0 {
|
||||
return bonusBoundaryWhite
|
||||
}
|
||||
return bonusFor(charClassOf(input.Get(idx-1)), charClassOf(input.Get(idx)))
|
||||
return bonusMatrix[charClassOf(input.Get(idx-1))][charClassOf(input.Get(idx))]
|
||||
}
|
||||
|
||||
func normalizeRune(r rune) rune {
|
||||
@@ -340,30 +352,45 @@ func isAscii(runes []rune) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func asciiFuzzyIndex(input *util.Chars, pattern []rune, caseSensitive bool) int {
|
||||
func asciiFuzzyIndex(input *util.Chars, pattern []rune, caseSensitive bool) (int, int) {
|
||||
// Can't determine
|
||||
if !input.IsBytes() {
|
||||
return 0
|
||||
return 0, input.Length()
|
||||
}
|
||||
|
||||
// Not possible
|
||||
if !isAscii(pattern) {
|
||||
return -1
|
||||
return -1, -1
|
||||
}
|
||||
|
||||
firstIdx, idx := 0, 0
|
||||
firstIdx, idx, lastIdx := 0, 0, 0
|
||||
var b byte
|
||||
for pidx := 0; pidx < len(pattern); pidx++ {
|
||||
idx = trySkip(input, caseSensitive, byte(pattern[pidx]), idx)
|
||||
b = byte(pattern[pidx])
|
||||
idx = trySkip(input, caseSensitive, b, idx)
|
||||
if idx < 0 {
|
||||
return -1
|
||||
return -1, -1
|
||||
}
|
||||
if pidx == 0 && idx > 0 {
|
||||
// Step back to find the right bonus point
|
||||
firstIdx = idx - 1
|
||||
}
|
||||
lastIdx = idx
|
||||
idx++
|
||||
}
|
||||
return firstIdx
|
||||
|
||||
// Find the last appearance of the last character of the pattern to limit the search scope
|
||||
bu := b
|
||||
if !caseSensitive && b >= 'a' && b <= 'z' {
|
||||
bu = b - 32
|
||||
}
|
||||
scope := input.Bytes()[lastIdx:]
|
||||
for offset := len(scope) - 1; offset > 0; offset-- {
|
||||
if scope[offset] == b || scope[offset] == bu {
|
||||
return firstIdx, lastIdx + offset + 1
|
||||
}
|
||||
}
|
||||
return firstIdx, lastIdx + 1
|
||||
}
|
||||
|
||||
func debugV2(T []rune, pattern []rune, F []int32, lastIdx int, H []int16, C []int16) {
|
||||
@@ -412,6 +439,9 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
|
||||
return Result{0, 0, 0}, posArray(withPos, M)
|
||||
}
|
||||
N := input.Length()
|
||||
if M > N {
|
||||
return Result{-1, -1, 0}, nil
|
||||
}
|
||||
|
||||
// Since O(nm) algorithm can be prohibitively expensive for large input,
|
||||
// we fall back to the greedy algorithm.
|
||||
@@ -420,10 +450,12 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
|
||||
}
|
||||
|
||||
// Phase 1. Optimized search for ASCII string
|
||||
idx := asciiFuzzyIndex(input, pattern, caseSensitive)
|
||||
if idx < 0 {
|
||||
minIdx, maxIdx := asciiFuzzyIndex(input, pattern, caseSensitive)
|
||||
if minIdx < 0 {
|
||||
return Result{-1, -1, 0}, nil
|
||||
}
|
||||
// fmt.Println(N, maxIdx, idx, maxIdx-idx, input.ToString())
|
||||
N = maxIdx - minIdx
|
||||
|
||||
// Reuse pre-allocated integer slice to avoid unnecessary sweeping of garbages
|
||||
offset16 := 0
|
||||
@@ -436,20 +468,19 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
|
||||
offset32, F := alloc32(offset32, slab, M)
|
||||
// Rune array
|
||||
_, T := alloc32(offset32, slab, N)
|
||||
input.CopyRunes(T)
|
||||
input.CopyRunes(T, minIdx)
|
||||
|
||||
// Phase 2. Calculate bonus for each point
|
||||
maxScore, maxScorePos := int16(0), 0
|
||||
pidx, lastIdx := 0, 0
|
||||
pchar0, pchar, prevH0, prevClass, inGap := pattern[0], pattern[0], int16(0), initialCharClass, false
|
||||
Tsub := T[idx:]
|
||||
H0sub, C0sub, Bsub := H0[idx:][:len(Tsub)], C0[idx:][:len(Tsub)], B[idx:][:len(Tsub)]
|
||||
for off, char := range Tsub {
|
||||
for off, char := range T {
|
||||
var class charClass
|
||||
if char <= unicode.MaxASCII {
|
||||
class = charClassOfAscii(char)
|
||||
class = asciiCharClasses[char]
|
||||
if !caseSensitive && class == charUpper {
|
||||
char += 32
|
||||
T[off] = char
|
||||
}
|
||||
} else {
|
||||
class = charClassOfNonAscii(char)
|
||||
@@ -459,28 +490,28 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
|
||||
if normalize {
|
||||
char = normalizeRune(char)
|
||||
}
|
||||
T[off] = char
|
||||
}
|
||||
|
||||
Tsub[off] = char
|
||||
bonus := bonusFor(prevClass, class)
|
||||
Bsub[off] = bonus
|
||||
bonus := bonusMatrix[prevClass][class]
|
||||
B[off] = bonus
|
||||
prevClass = class
|
||||
|
||||
if char == pchar {
|
||||
if pidx < M {
|
||||
F[pidx] = int32(idx + off)
|
||||
F[pidx] = int32(off)
|
||||
pidx++
|
||||
pchar = pattern[util.Min(pidx, M-1)]
|
||||
}
|
||||
lastIdx = idx + off
|
||||
lastIdx = off
|
||||
}
|
||||
|
||||
if char == pchar0 {
|
||||
score := scoreMatch + bonus*bonusFirstCharMultiplier
|
||||
H0sub[off] = score
|
||||
C0sub[off] = 1
|
||||
H0[off] = score
|
||||
C0[off] = 1
|
||||
if M == 1 && (forward && score > maxScore || !forward && score >= maxScore) {
|
||||
maxScore, maxScorePos = score, idx+off
|
||||
maxScore, maxScorePos = score, off
|
||||
if forward && bonus >= bonusBoundary {
|
||||
break
|
||||
}
|
||||
@@ -488,24 +519,24 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
|
||||
inGap = false
|
||||
} else {
|
||||
if inGap {
|
||||
H0sub[off] = util.Max16(prevH0+scoreGapExtension, 0)
|
||||
H0[off] = util.Max16(prevH0+scoreGapExtension, 0)
|
||||
} else {
|
||||
H0sub[off] = util.Max16(prevH0+scoreGapStart, 0)
|
||||
H0[off] = util.Max16(prevH0+scoreGapStart, 0)
|
||||
}
|
||||
C0sub[off] = 0
|
||||
C0[off] = 0
|
||||
inGap = true
|
||||
}
|
||||
prevH0 = H0sub[off]
|
||||
prevH0 = H0[off]
|
||||
}
|
||||
if pidx != M {
|
||||
return Result{-1, -1, 0}, nil
|
||||
}
|
||||
if M == 1 {
|
||||
result := Result{maxScorePos, maxScorePos + 1, int(maxScore)}
|
||||
result := Result{minIdx + maxScorePos, minIdx + maxScorePos + 1, int(maxScore)}
|
||||
if !withPos {
|
||||
return result, nil
|
||||
}
|
||||
pos := []int{maxScorePos}
|
||||
pos := []int{minIdx + maxScorePos}
|
||||
return result, &pos
|
||||
}
|
||||
|
||||
@@ -602,7 +633,7 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
|
||||
}
|
||||
|
||||
if s > s1 && (s > s2 || s == s2 && preferMatch) {
|
||||
*pos = append(*pos, j)
|
||||
*pos = append(*pos, j+minIdx)
|
||||
if i == 0 {
|
||||
break
|
||||
}
|
||||
@@ -615,7 +646,7 @@ func FuzzyMatchV2(caseSensitive bool, normalize bool, forward bool, input *util.
|
||||
// Start offset we return here is only relevant when begin tiebreak is used.
|
||||
// However finding the accurate offset requires backtracking, and we don't
|
||||
// want to pay extra cost for the option that has lost its importance.
|
||||
return Result{j, maxScorePos + 1, int(maxScore)}, pos
|
||||
return Result{minIdx + j, minIdx + maxScorePos + 1, int(maxScore)}, pos
|
||||
}
|
||||
|
||||
// Implement the same sorting criteria as V2
|
||||
@@ -645,7 +676,7 @@ func calculateScore(caseSensitive bool, normalize bool, text *util.Chars, patter
|
||||
*pos = append(*pos, idx)
|
||||
}
|
||||
score += scoreMatch
|
||||
bonus := bonusFor(prevClass, class)
|
||||
bonus := bonusMatrix[prevClass][class]
|
||||
if consecutive == 0 {
|
||||
firstBonus = bonus
|
||||
} else {
|
||||
@@ -683,7 +714,8 @@ func FuzzyMatchV1(caseSensitive bool, normalize bool, forward bool, text *util.C
|
||||
if len(pattern) == 0 {
|
||||
return Result{0, 0, 0}, nil
|
||||
}
|
||||
if asciiFuzzyIndex(text, pattern, caseSensitive) < 0 {
|
||||
idx, _ := asciiFuzzyIndex(text, pattern, caseSensitive)
|
||||
if idx < 0 {
|
||||
return Result{-1, -1, 0}, nil
|
||||
}
|
||||
|
||||
@@ -777,7 +809,8 @@ func ExactMatchNaive(caseSensitive bool, normalize bool, forward bool, text *uti
|
||||
return Result{-1, -1, 0}, nil
|
||||
}
|
||||
|
||||
if asciiFuzzyIndex(text, pattern, caseSensitive) < 0 {
|
||||
idx, _ := asciiFuzzyIndex(text, pattern, caseSensitive)
|
||||
if idx < 0 {
|
||||
return Result{-1, -1, 0}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,10 @@ import (
|
||||
"github.com/junegunn/fzf/src/util"
|
||||
)
|
||||
|
||||
func init() {
|
||||
Init("default")
|
||||
}
|
||||
|
||||
func assertMatch(t *testing.T, fun Algo, caseSensitive, forward bool, input, pattern string, sidx int, eidx int, score int) {
|
||||
assertMatch2(t, fun, caseSensitive, false, forward, input, pattern, sidx, eidx, score)
|
||||
}
|
||||
|
||||
@@ -312,7 +312,7 @@ func parseAnsiCode(s string, delimiter byte) (int, byte, string) {
|
||||
// Inlined version of strconv.Atoi() that only handles positive
|
||||
// integers and does not allocate on error.
|
||||
code := 0
|
||||
for _, ch := range []byte(s) {
|
||||
for _, ch := range sbytes(s) {
|
||||
ch -= '0'
|
||||
if ch > 9 {
|
||||
return -1, delimiter, remaining
|
||||
|
||||
@@ -14,6 +14,7 @@ const (
|
||||
|
||||
// Reader
|
||||
readerBufferSize = 64 * 1024
|
||||
readerSlabSize = 128 * 1024
|
||||
readerPollIntervalMin = 10 * time.Millisecond
|
||||
readerPollIntervalStep = 5 * time.Millisecond
|
||||
readerPollIntervalMax = 50 * time.Millisecond
|
||||
|
||||
57
src/core.go
57
src/core.go
@@ -3,8 +3,9 @@ package fzf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/junegunn/fzf/src/util"
|
||||
)
|
||||
@@ -18,8 +19,18 @@ Matcher -> EvtSearchFin -> Terminal (update list)
|
||||
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
|
||||
func Run(opts *Options, version string, revision string) {
|
||||
defer util.RunAtExitFuncs()
|
||||
|
||||
sort := opts.Sort > 0
|
||||
sortCriteria = opts.Criteria
|
||||
|
||||
@@ -29,7 +40,7 @@ func Run(opts *Options, version string, revision string) {
|
||||
} else {
|
||||
fmt.Println(version)
|
||||
}
|
||||
os.Exit(exitOk)
|
||||
util.Exit(exitOk)
|
||||
}
|
||||
|
||||
// Event channel
|
||||
@@ -45,16 +56,16 @@ func Run(opts *Options, version string, revision string) {
|
||||
if opts.Theme.Colored {
|
||||
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
|
||||
prevLineAnsiState = lineAnsiState
|
||||
trimmed, offsets, newState := extractColor(string(data), lineAnsiState, nil)
|
||||
trimmed, offsets, newState := extractColor(ustring(data), lineAnsiState, nil)
|
||||
lineAnsiState = newState
|
||||
return util.ToChars([]byte(trimmed)), offsets
|
||||
return util.ToChars(sbytes(trimmed)), offsets
|
||||
}
|
||||
} else {
|
||||
// When color is disabled but ansi option is given,
|
||||
// we simply strip out ANSI codes from the input
|
||||
ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
|
||||
trimmed, _, _ := extractColor(string(data), nil, nil)
|
||||
return util.ToChars([]byte(trimmed)), nil
|
||||
trimmed, _, _ := extractColor(ustring(data), nil, nil)
|
||||
return util.ToChars(sbytes(trimmed)), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -66,7 +77,7 @@ func Run(opts *Options, version string, revision string) {
|
||||
if len(opts.WithNth) == 0 {
|
||||
chunkList = NewChunkList(func(item *Item, data []byte) bool {
|
||||
if len(header) < opts.HeaderLines {
|
||||
header = append(header, string(data))
|
||||
header = append(header, ustring(data))
|
||||
eventBox.Set(EvtHeader, header)
|
||||
return false
|
||||
}
|
||||
@@ -77,7 +88,7 @@ func Run(opts *Options, version string, revision string) {
|
||||
})
|
||||
} else {
|
||||
chunkList = NewChunkList(func(item *Item, data []byte) bool {
|
||||
tokens := Tokenize(string(data), opts.Delimiter)
|
||||
tokens := Tokenize(ustring(data), opts.Delimiter)
|
||||
if opts.Ansi && opts.Theme.Colored && len(tokens) > 1 {
|
||||
var ansiState *ansiState
|
||||
if prevLineAnsiState != nil {
|
||||
@@ -101,7 +112,7 @@ func Run(opts *Options, version string, revision string) {
|
||||
eventBox.Set(EvtHeader, header)
|
||||
return false
|
||||
}
|
||||
item.text, item.colors = ansiProcessor([]byte(transformed))
|
||||
item.text, item.colors = ansiProcessor(sbytes(transformed))
|
||||
item.text.TrimTrailingWhitespaces()
|
||||
item.text.Index = itemIndex
|
||||
item.origText = &data
|
||||
@@ -154,14 +165,17 @@ func Run(opts *Options, version string, revision string) {
|
||||
found := false
|
||||
if streamingFilter {
|
||||
slab := util.MakeSlab(slab16Size, slab32Size)
|
||||
mutex := sync.Mutex{}
|
||||
reader := NewReader(
|
||||
func(runes []byte) bool {
|
||||
item := Item{}
|
||||
if chunkList.trans(&item, runes) {
|
||||
mutex.Lock()
|
||||
if result, _, _ := pattern.MatchItem(&item, false, slab); result != nil {
|
||||
opts.Printer(item.text.ToString())
|
||||
found = true
|
||||
}
|
||||
mutex.Unlock()
|
||||
}
|
||||
return false
|
||||
}, eventBox, opts.ReadZero, false)
|
||||
@@ -180,9 +194,9 @@ func Run(opts *Options, version string, revision string) {
|
||||
}
|
||||
}
|
||||
if found {
|
||||
os.Exit(exitOk)
|
||||
util.Exit(exitOk)
|
||||
}
|
||||
os.Exit(exitNoMatch)
|
||||
util.Exit(exitNoMatch)
|
||||
}
|
||||
|
||||
// Synchronous search
|
||||
@@ -245,11 +259,8 @@ func Run(opts *Options, version string, revision string) {
|
||||
delay := true
|
||||
ticks++
|
||||
input := func() []rune {
|
||||
reloaded := snapshotRevision != inputRevision
|
||||
paused, input := terminal.Input()
|
||||
if reloaded && paused {
|
||||
query = []rune{}
|
||||
} else if !paused {
|
||||
if !paused {
|
||||
query = input
|
||||
}
|
||||
return query
|
||||
@@ -264,7 +275,7 @@ func Run(opts *Options, version string, revision string) {
|
||||
if reading {
|
||||
reader.terminate()
|
||||
}
|
||||
os.Exit(value.(int))
|
||||
util.Exit(value.(int))
|
||||
case EvtReadNew, EvtReadFin:
|
||||
if evt == EvtReadFin && nextCommand != nil {
|
||||
restart(*nextCommand, nextEnviron)
|
||||
@@ -278,6 +289,9 @@ func Run(opts *Options, version string, revision string) {
|
||||
useSnapshot = false
|
||||
}
|
||||
if !useSnapshot {
|
||||
if snapshotRevision != inputRevision {
|
||||
query = []rune{}
|
||||
}
|
||||
snapshot, count = chunkList.Snapshot()
|
||||
snapshotRevision = inputRevision
|
||||
}
|
||||
@@ -319,10 +333,13 @@ func Run(opts *Options, version string, revision string) {
|
||||
break
|
||||
}
|
||||
if !useSnapshot {
|
||||
newSnapshot, _ := chunkList.Snapshot()
|
||||
newSnapshot, newCount := chunkList.Snapshot()
|
||||
// We want to avoid showing empty list when reload is triggered
|
||||
// and the query string is changed at the same time i.e. command != nil && changed
|
||||
if command == nil || len(newSnapshot) > 0 {
|
||||
if command == nil || newCount > 0 {
|
||||
if snapshotRevision != inputRevision {
|
||||
query = []rune{}
|
||||
}
|
||||
snapshot = newSnapshot
|
||||
snapshotRevision = inputRevision
|
||||
}
|
||||
@@ -360,9 +377,9 @@ func Run(opts *Options, version string, revision string) {
|
||||
opts.Printer(val.Get(i).item.AsString(opts.Ansi))
|
||||
}
|
||||
if count > 0 {
|
||||
os.Exit(exitOk)
|
||||
util.Exit(exitOk)
|
||||
}
|
||||
os.Exit(exitNoMatch)
|
||||
util.Exit(exitNoMatch)
|
||||
}
|
||||
determine(val.final)
|
||||
}
|
||||
|
||||
110
src/options.go
110
src/options.go
@@ -52,7 +52,7 @@ const usage = `usage: fzf [options]
|
||||
--hscroll-off=COLS Number of screen columns to keep to the right of the
|
||||
highlighted substring (default: 10)
|
||||
--filepath-word Make word-wise movements respect path separators
|
||||
--jump-labels=CHARS Label characters for jump and jump-accept
|
||||
--jump-labels=CHARS Label characters for jump mode
|
||||
|
||||
Layout
|
||||
--height=[~]HEIGHT[%] Display fzf window below the cursor with the given
|
||||
@@ -75,7 +75,7 @@ const usage = `usage: fzf [options]
|
||||
--margin=MARGIN Screen margin (TRBL | TB,RL | T,RL,B | T,R,B,L)
|
||||
--padding=PADDING Padding inside border (TRBL | TB,RL | T,RL,B | T,R,B,L)
|
||||
--info=STYLE Finder info style
|
||||
[default|right|hidden|inline[:SEPARATOR]|inline-right]
|
||||
[default|right|hidden|inline[-right][:PREFIX]]
|
||||
--separator=STR String to form horizontal separator on info line
|
||||
--no-separator Hide info line separator
|
||||
--scrollbar[=C1[C2]] Scrollbar character(s) (each for main and preview window)
|
||||
@@ -143,7 +143,7 @@ const usage = `usage: fzf [options]
|
||||
|
||||
`
|
||||
|
||||
const defaultInfoSep = " < "
|
||||
const defaultInfoPrefix = " < "
|
||||
|
||||
// Case denotes case-sensitivity of search
|
||||
type Case int
|
||||
@@ -217,10 +217,6 @@ const (
|
||||
infoHidden
|
||||
)
|
||||
|
||||
func (s infoStyle) noExtraLine() bool {
|
||||
return s == infoInline || s == infoInlineRight || s == infoHidden
|
||||
}
|
||||
|
||||
type labelOpts struct {
|
||||
label string
|
||||
column int
|
||||
@@ -327,7 +323,7 @@ type Options struct {
|
||||
ScrollOff int
|
||||
FileWord bool
|
||||
InfoStyle infoStyle
|
||||
InfoSep string
|
||||
InfoPrefix string
|
||||
Separator *string
|
||||
JumpLabels string
|
||||
Prompt string
|
||||
@@ -367,6 +363,10 @@ type Options struct {
|
||||
WalkerRoot string
|
||||
WalkerSkip []string
|
||||
Version bool
|
||||
CPUProfile string
|
||||
MEMProfile string
|
||||
BlockProfile string
|
||||
MutexProfile string
|
||||
}
|
||||
|
||||
func filterNonEmpty(input []string) []string {
|
||||
@@ -458,14 +458,14 @@ func defaultOptions() *Options {
|
||||
|
||||
func help(code int) {
|
||||
os.Stdout.WriteString(usage)
|
||||
os.Exit(code)
|
||||
util.Exit(code)
|
||||
}
|
||||
|
||||
var errorContext = ""
|
||||
|
||||
func errorExit(msg string) {
|
||||
os.Stderr.WriteString(errorContext + msg + "\n")
|
||||
os.Exit(exitError)
|
||||
util.Exit(exitError)
|
||||
}
|
||||
|
||||
func optString(arg string, prefixes ...string) (bool, string) {
|
||||
@@ -670,8 +670,8 @@ func parseKeyChordsImpl(str string, message string, exit func(string)) map[tui.E
|
||||
add(tui.CtrlM)
|
||||
case "space":
|
||||
chords[tui.Key(' ')] = key
|
||||
case "bspace", "bs":
|
||||
add(tui.BSpace)
|
||||
case "backspace", "bspace", "bs":
|
||||
add(tui.Backspace)
|
||||
case "ctrl-space":
|
||||
add(tui.CtrlSpace)
|
||||
case "ctrl-delete":
|
||||
@@ -702,12 +702,16 @@ func parseKeyChordsImpl(str string, message string, exit func(string)) map[tui.E
|
||||
add(tui.One)
|
||||
case "zero":
|
||||
add(tui.Zero)
|
||||
case "jump":
|
||||
add(tui.Jump)
|
||||
case "jump-cancel":
|
||||
add(tui.JumpCancel)
|
||||
case "alt-enter", "alt-return":
|
||||
chords[tui.CtrlAltKey('m')] = key
|
||||
case "alt-space":
|
||||
chords[tui.AltKey(' ')] = key
|
||||
case "alt-bs", "alt-bspace":
|
||||
add(tui.AltBS)
|
||||
case "alt-bs", "alt-bspace", "alt-backspace":
|
||||
add(tui.AltBackspace)
|
||||
case "alt-up":
|
||||
add(tui.AltUp)
|
||||
case "alt-down":
|
||||
@@ -719,11 +723,11 @@ func parseKeyChordsImpl(str string, message string, exit func(string)) map[tui.E
|
||||
case "tab":
|
||||
add(tui.Tab)
|
||||
case "btab", "shift-tab":
|
||||
add(tui.BTab)
|
||||
add(tui.ShiftTab)
|
||||
case "esc":
|
||||
add(tui.ESC)
|
||||
case "del":
|
||||
add(tui.Del)
|
||||
add(tui.Esc)
|
||||
case "delete", "del":
|
||||
add(tui.Delete)
|
||||
case "home":
|
||||
add(tui.Home)
|
||||
case "end":
|
||||
@@ -731,27 +735,27 @@ func parseKeyChordsImpl(str string, message string, exit func(string)) map[tui.E
|
||||
case "insert":
|
||||
add(tui.Insert)
|
||||
case "pgup", "page-up":
|
||||
add(tui.PgUp)
|
||||
add(tui.PageUp)
|
||||
case "pgdn", "page-down":
|
||||
add(tui.PgDn)
|
||||
add(tui.PageDown)
|
||||
case "alt-shift-up", "shift-alt-up":
|
||||
add(tui.AltSUp)
|
||||
add(tui.AltShiftUp)
|
||||
case "alt-shift-down", "shift-alt-down":
|
||||
add(tui.AltSDown)
|
||||
add(tui.AltShiftDown)
|
||||
case "alt-shift-left", "shift-alt-left":
|
||||
add(tui.AltSLeft)
|
||||
add(tui.AltShiftLeft)
|
||||
case "alt-shift-right", "shift-alt-right":
|
||||
add(tui.AltSRight)
|
||||
add(tui.AltShiftRight)
|
||||
case "shift-up":
|
||||
add(tui.SUp)
|
||||
add(tui.ShiftUp)
|
||||
case "shift-down":
|
||||
add(tui.SDown)
|
||||
add(tui.ShiftDown)
|
||||
case "shift-left":
|
||||
add(tui.SLeft)
|
||||
add(tui.ShiftLeft)
|
||||
case "shift-right":
|
||||
add(tui.SRight)
|
||||
add(tui.ShiftRight)
|
||||
case "shift-delete":
|
||||
add(tui.SDelete)
|
||||
add(tui.ShiftDelete)
|
||||
case "left-click":
|
||||
add(tui.LeftClick)
|
||||
case "right-click":
|
||||
@@ -1211,14 +1215,18 @@ func parseActionList(masked string, original string, prevActions []*action, putA
|
||||
appendAction(actToggleSearch)
|
||||
case "toggle-track":
|
||||
appendAction(actToggleTrack)
|
||||
case "toggle-track-current":
|
||||
appendAction(actToggleTrackCurrent)
|
||||
case "toggle-header":
|
||||
appendAction(actToggleHeader)
|
||||
case "show-header":
|
||||
appendAction(actShowHeader)
|
||||
case "hide-header":
|
||||
appendAction(actHideHeader)
|
||||
case "track":
|
||||
appendAction(actTrack)
|
||||
case "track", "track-current":
|
||||
appendAction(actTrackCurrent)
|
||||
case "untrack-current":
|
||||
appendAction(actUntrackCurrent)
|
||||
case "select":
|
||||
appendAction(actSelect)
|
||||
case "select-all":
|
||||
@@ -1502,17 +1510,24 @@ func parseInfoStyle(str string) (infoStyle, string) {
|
||||
case "right":
|
||||
return infoRight, ""
|
||||
case "inline":
|
||||
return infoInline, defaultInfoSep
|
||||
return infoInline, defaultInfoPrefix
|
||||
case "inline-right":
|
||||
return infoInlineRight, ""
|
||||
case "hidden":
|
||||
return infoHidden, ""
|
||||
default:
|
||||
prefix := "inline:"
|
||||
if strings.HasPrefix(str, prefix) {
|
||||
return infoInline, strings.ReplaceAll(str[len(prefix):], "\n", " ")
|
||||
type infoSpec struct {
|
||||
name string
|
||||
style infoStyle
|
||||
}
|
||||
errorExit("invalid info style (expected: default|right|hidden|inline[:SEPARATOR]|inline-right)")
|
||||
for _, spec := range []infoSpec{
|
||||
{"inline", infoInline},
|
||||
{"inline-right", infoInlineRight}} {
|
||||
if strings.HasPrefix(str, spec.name+":") {
|
||||
return spec.style, strings.ReplaceAll(str[len(spec.name)+1:], "\n", " ")
|
||||
}
|
||||
}
|
||||
errorExit("invalid info style (expected: default|right|hidden|inline[-right][:PREFIX])")
|
||||
}
|
||||
return infoDefault, ""
|
||||
}
|
||||
@@ -1803,13 +1818,13 @@ func parseOptions(opts *Options, allArgs []string) {
|
||||
case "--no-filepath-word":
|
||||
opts.FileWord = false
|
||||
case "--info":
|
||||
opts.InfoStyle, opts.InfoSep = parseInfoStyle(
|
||||
opts.InfoStyle, opts.InfoPrefix = parseInfoStyle(
|
||||
nextString(allArgs, &i, "info style required"))
|
||||
case "--no-info":
|
||||
opts.InfoStyle = infoHidden
|
||||
case "--inline-info":
|
||||
opts.InfoStyle = infoInline
|
||||
opts.InfoSep = defaultInfoSep
|
||||
opts.InfoPrefix = defaultInfoPrefix
|
||||
case "--no-inline-info":
|
||||
opts.InfoStyle = infoDefault
|
||||
case "--separator":
|
||||
@@ -1967,6 +1982,14 @@ func parseOptions(opts *Options, allArgs []string) {
|
||||
opts.WalkerSkip = filterNonEmpty(strings.Split(nextString(allArgs, &i, "directory names to ignore required"), ","))
|
||||
case "--version":
|
||||
opts.Version = true
|
||||
case "--profile-cpu":
|
||||
opts.CPUProfile = nextString(allArgs, &i, "file path required: cpu")
|
||||
case "--profile-mem":
|
||||
opts.MEMProfile = nextString(allArgs, &i, "file path required: mem")
|
||||
case "--profile-block":
|
||||
opts.BlockProfile = nextString(allArgs, &i, "file path required: block")
|
||||
case "--profile-mutex":
|
||||
opts.MutexProfile = nextString(allArgs, &i, "file path required: mutex")
|
||||
case "--":
|
||||
// Ignored
|
||||
default:
|
||||
@@ -2011,7 +2034,7 @@ func parseOptions(opts *Options, allArgs []string) {
|
||||
} else if match, value := optString(arg, "--layout="); match {
|
||||
opts.Layout = parseLayout(value)
|
||||
} else if match, value := optString(arg, "--info="); match {
|
||||
opts.InfoStyle, opts.InfoSep = parseInfoStyle(value)
|
||||
opts.InfoStyle, opts.InfoPrefix = parseInfoStyle(value)
|
||||
} else if match, value := optString(arg, "--separator="); match {
|
||||
opts.Separator = &value
|
||||
} else if match, value := optString(arg, "--scrollbar="); match {
|
||||
@@ -2236,9 +2259,7 @@ func postProcessOptions(opts *Options) {
|
||||
theme.Spinner = boldify(theme.Spinner)
|
||||
}
|
||||
|
||||
if opts.Scheme != "default" {
|
||||
processScheme(opts)
|
||||
}
|
||||
processScheme(opts)
|
||||
}
|
||||
|
||||
func expectsArbitraryString(opt string) bool {
|
||||
@@ -2292,6 +2313,11 @@ func ParseOptions() *Options {
|
||||
errorContext = ""
|
||||
parseOptions(opts, os.Args[1:])
|
||||
|
||||
if err := opts.initProfiling(); err != nil {
|
||||
errorExit("failed to start pprof profiles: " + err.Error())
|
||||
}
|
||||
|
||||
postProcessOptions(opts)
|
||||
|
||||
return opts
|
||||
}
|
||||
|
||||
11
src/options_no_pprof.go
Normal file
11
src/options_no_pprof.go
Normal file
@@ -0,0 +1,11 @@
|
||||
//go:build !pprof
|
||||
// +build !pprof
|
||||
|
||||
package fzf
|
||||
|
||||
func (o *Options) initProfiling() error {
|
||||
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 nil
|
||||
}
|
||||
73
src/options_pprof.go
Normal file
73
src/options_pprof.go
Normal file
@@ -0,0 +1,73 @@
|
||||
//go:build pprof
|
||||
// +build pprof
|
||||
|
||||
package fzf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
|
||||
"github.com/junegunn/fzf/src/util"
|
||||
)
|
||||
|
||||
func (o *Options) initProfiling() error {
|
||||
if o.CPUProfile != "" {
|
||||
f, err := os.Create(o.CPUProfile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create CPU profile: %w", err)
|
||||
}
|
||||
|
||||
if err := pprof.StartCPUProfile(f); err != nil {
|
||||
return fmt.Errorf("could not start CPU profile: %w", err)
|
||||
}
|
||||
|
||||
util.AtExit(func() {
|
||||
pprof.StopCPUProfile()
|
||||
if err := f.Close(); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Error: closing cpu profile:", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
stopProfile := func(name string, f *os.File) {
|
||||
if err := pprof.Lookup(name).WriteTo(f, 0); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: could not write %s profile: %v\n", name, err)
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: closing %s profile: %v\n", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
if o.MEMProfile != "" {
|
||||
f, err := os.Create(o.MEMProfile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create MEM profile: %w", err)
|
||||
}
|
||||
util.AtExit(func() {
|
||||
runtime.GC()
|
||||
stopProfile("allocs", f)
|
||||
})
|
||||
}
|
||||
|
||||
if o.BlockProfile != "" {
|
||||
runtime.SetBlockProfileRate(1)
|
||||
f, err := os.Create(o.BlockProfile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create BLOCK profile: %w", err)
|
||||
}
|
||||
util.AtExit(func() { stopProfile("block", f) })
|
||||
}
|
||||
|
||||
if o.MutexProfile != "" {
|
||||
runtime.SetMutexProfileFraction(1)
|
||||
f, err := os.Create(o.MutexProfile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create MUTEX profile: %w", err)
|
||||
}
|
||||
util.AtExit(func() { stopProfile("mutex", f) })
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
89
src/options_pprof_test.go
Normal file
89
src/options_pprof_test.go
Normal file
@@ -0,0 +1,89 @@
|
||||
//go:build pprof
|
||||
// +build pprof
|
||||
|
||||
package fzf
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/junegunn/fzf/src/util"
|
||||
)
|
||||
|
||||
// runInitProfileTests is an internal flag used TestInitProfiling
|
||||
var runInitProfileTests = flag.Bool("test-init-profile", false, "run init profile tests")
|
||||
|
||||
func TestInitProfiling(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("short test")
|
||||
}
|
||||
|
||||
// Run this test in a separate process since it interferes with
|
||||
// profiling and modifies the global atexit state. Without this
|
||||
// running `go test -bench . -cpuprofile cpu.out` will fail.
|
||||
if !*runInitProfileTests {
|
||||
t.Parallel()
|
||||
|
||||
// Make sure we are not the child process.
|
||||
if os.Getenv("_FZF_CHILD_PROC") != "" {
|
||||
t.Fatal("already running as child process!")
|
||||
}
|
||||
|
||||
cmd := exec.Command(os.Args[0],
|
||||
"-test.timeout", "30s",
|
||||
"-test.run", "^"+t.Name()+"$",
|
||||
"-test-init-profile",
|
||||
)
|
||||
cmd.Env = append(os.Environ(), "_FZF_CHILD_PROC=1")
|
||||
|
||||
out, err := cmd.CombinedOutput()
|
||||
out = bytes.TrimSpace(out)
|
||||
if err != nil {
|
||||
t.Fatalf("Child test process failed: %v:\n%s", err, out)
|
||||
}
|
||||
// Make sure the test actually ran
|
||||
if bytes.Contains(out, []byte("no tests to run")) {
|
||||
t.Fatalf("Failed to run test %q:\n%s", t.Name(), out)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Child process
|
||||
|
||||
tempdir := t.TempDir()
|
||||
t.Cleanup(util.RunAtExitFuncs)
|
||||
|
||||
o := Options{
|
||||
CPUProfile: filepath.Join(tempdir, "cpu.prof"),
|
||||
MEMProfile: filepath.Join(tempdir, "mem.prof"),
|
||||
BlockProfile: filepath.Join(tempdir, "block.prof"),
|
||||
MutexProfile: filepath.Join(tempdir, "mutex.prof"),
|
||||
}
|
||||
if err := o.initProfiling(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
profiles := []string{
|
||||
o.CPUProfile,
|
||||
o.MEMProfile,
|
||||
o.BlockProfile,
|
||||
o.MutexProfile,
|
||||
}
|
||||
for _, name := range profiles {
|
||||
if _, err := os.Stat(name); err != nil {
|
||||
t.Errorf("Failed to create profile %s: %v", filepath.Base(name), err)
|
||||
}
|
||||
}
|
||||
|
||||
util.RunAtExitFuncs()
|
||||
|
||||
for _, name := range profiles {
|
||||
if _, err := os.Stat(name); err != nil {
|
||||
t.Errorf("Failed to write profile %s: %v", filepath.Base(name), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -170,8 +170,8 @@ func TestParseKeys(t *testing.T) {
|
||||
check(tui.CtrlM, "Return")
|
||||
checkEvent(tui.Key(' '), "space")
|
||||
check(tui.Tab, "tab")
|
||||
check(tui.BTab, "btab")
|
||||
check(tui.ESC, "esc")
|
||||
check(tui.ShiftTab, "btab")
|
||||
check(tui.Esc, "esc")
|
||||
check(tui.Up, "up")
|
||||
check(tui.Down, "down")
|
||||
check(tui.Left, "left")
|
||||
@@ -182,16 +182,16 @@ func TestParseKeys(t *testing.T) {
|
||||
t.Error(11)
|
||||
}
|
||||
check(tui.Tab, "Ctrl-I")
|
||||
check(tui.PgUp, "page-up")
|
||||
check(tui.PgDn, "Page-Down")
|
||||
check(tui.PageUp, "page-up")
|
||||
check(tui.PageDown, "Page-Down")
|
||||
check(tui.Home, "Home")
|
||||
check(tui.End, "End")
|
||||
check(tui.AltBS, "Alt-BSpace")
|
||||
check(tui.SLeft, "shift-left")
|
||||
check(tui.SRight, "shift-right")
|
||||
check(tui.BTab, "shift-tab")
|
||||
check(tui.AltBackspace, "Alt-BSpace")
|
||||
check(tui.ShiftLeft, "shift-left")
|
||||
check(tui.ShiftRight, "shift-right")
|
||||
check(tui.ShiftTab, "shift-tab")
|
||||
check(tui.CtrlM, "Enter")
|
||||
check(tui.BSpace, "bspace")
|
||||
check(tui.Backspace, "bspace")
|
||||
}
|
||||
|
||||
func TestParseKeysWithComma(t *testing.T) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package fzf
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
@@ -111,32 +111,90 @@ func (r *Reader) ReadSource(root string, opts walkerOpts, ignores []string) {
|
||||
}
|
||||
|
||||
func (r *Reader) feed(src io.Reader) {
|
||||
/*
|
||||
readerSlabSize, ae := strconv.Atoi(os.Getenv("SLAB_KB"))
|
||||
if ae != nil {
|
||||
readerSlabSize = 128 * 1024
|
||||
} else {
|
||||
readerSlabSize *= 1024
|
||||
}
|
||||
readerBufferSize, be := strconv.Atoi(os.Getenv("BUF_KB"))
|
||||
if be != nil {
|
||||
readerBufferSize = 64 * 1024
|
||||
} else {
|
||||
readerBufferSize *= 1024
|
||||
}
|
||||
*/
|
||||
|
||||
delim := byte('\n')
|
||||
trimCR := util.IsWindows()
|
||||
if r.delimNil {
|
||||
delim = '\000'
|
||||
trimCR = false
|
||||
}
|
||||
reader := bufio.NewReaderSize(src, readerBufferSize)
|
||||
|
||||
slab := make([]byte, readerSlabSize)
|
||||
leftover := []byte{}
|
||||
var err error
|
||||
for {
|
||||
// ReadBytes returns err != nil if and only if the returned data does not
|
||||
// end in delim.
|
||||
bytea, err := reader.ReadBytes(delim)
|
||||
byteaLen := len(bytea)
|
||||
if byteaLen > 0 {
|
||||
if err == nil {
|
||||
// get rid of carriage return if under Windows:
|
||||
if util.IsWindows() && byteaLen >= 2 && bytea[byteaLen-2] == byte('\r') {
|
||||
bytea = bytea[:byteaLen-2]
|
||||
} else {
|
||||
bytea = bytea[:byteaLen-1]
|
||||
}
|
||||
}
|
||||
if r.pusher(bytea) {
|
||||
atomic.StoreInt32(&r.event, int32(EvtReadNew))
|
||||
n := 0
|
||||
scope := slab[:util.Min(len(slab), readerBufferSize)]
|
||||
for i := 0; i < 100; i++ {
|
||||
n, err = src.Read(scope)
|
||||
if n > 0 || err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
|
||||
// We're not making any progress after 100 tries. Stop.
|
||||
if n == 0 && err == nil {
|
||||
break
|
||||
}
|
||||
|
||||
buf := slab[:n]
|
||||
slab = slab[n:]
|
||||
|
||||
for len(buf) > 0 {
|
||||
if i := bytes.IndexByte(buf, delim); i >= 0 {
|
||||
// Found the delimiter
|
||||
slice := buf[:i+1]
|
||||
buf = buf[i+1:]
|
||||
if trimCR && len(slice) >= 2 && slice[len(slice)-2] == byte('\r') {
|
||||
slice = slice[:len(slice)-2]
|
||||
} else {
|
||||
slice = slice[:len(slice)-1]
|
||||
}
|
||||
if len(leftover) > 0 {
|
||||
slice = append(leftover, slice...)
|
||||
leftover = []byte{}
|
||||
}
|
||||
if (err == nil || len(slice) > 0) && r.pusher(slice) {
|
||||
atomic.StoreInt32(&r.event, int32(EvtReadNew))
|
||||
}
|
||||
} else {
|
||||
// Could not find the delimiter in the buffer
|
||||
// NOTE: We can further optimize this by keeping track of the cursor
|
||||
// position in the slab so that a straddling item that doesn't go
|
||||
// beyond the boundary of a slab doesn't need to be copied to
|
||||
// another buffer. However, the performance gain is negligible in
|
||||
// practice (< 0.1%) and is not
|
||||
// worth the added complexity.
|
||||
leftover = append(leftover, buf...)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if err == io.EOF {
|
||||
leftover = append(leftover, buf...)
|
||||
break
|
||||
}
|
||||
|
||||
if len(slab) == 0 {
|
||||
slab = make([]byte, readerSlabSize)
|
||||
}
|
||||
}
|
||||
if len(leftover) > 0 && r.pusher(leftover) {
|
||||
atomic.StoreInt32(&r.event, int32(EvtReadNew))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ const (
|
||||
httpUnauthorized = "HTTP/1.1 401 Unauthorized" + crlf
|
||||
httpUnavailable = "HTTP/1.1 503 Service Unavailable" + crlf
|
||||
httpReadTimeout = 10 * time.Second
|
||||
channelTimeout = 2 * time.Second
|
||||
jsonContentType = "Content-Type: application/json" + crlf
|
||||
maxContentLength = 1024 * 1024
|
||||
)
|
||||
@@ -170,7 +171,7 @@ func (server *httpServer) handleHttpRequest(conn net.Conn) string {
|
||||
select {
|
||||
case response := <-server.responseChannel:
|
||||
return good(response)
|
||||
case <-time.After(2 * time.Second):
|
||||
case <-time.After(channelTimeout):
|
||||
go func() {
|
||||
// Drain the channel
|
||||
<-server.responseChannel
|
||||
@@ -227,7 +228,11 @@ func (server *httpServer) handleHttpRequest(conn net.Conn) string {
|
||||
return bad("no action specified")
|
||||
}
|
||||
|
||||
server.actionChannel <- actions
|
||||
select {
|
||||
case server.actionChannel <- actions:
|
||||
case <-time.After(channelTimeout):
|
||||
return httpUnavailable + crlf
|
||||
}
|
||||
return httpOk + crlf
|
||||
}
|
||||
|
||||
|
||||
245
src/terminal.go
245
src/terminal.go
@@ -55,6 +55,9 @@ var actionTypeRegex *regexp.Regexp
|
||||
|
||||
const clearCode string = "\x1b[2J"
|
||||
|
||||
// Number of maximum focus events to process synchronously
|
||||
const maxFocusEvents = 10000
|
||||
|
||||
func init() {
|
||||
placeholder = regexp.MustCompile(`\\?(?:{[+sf]*[0-9,-.]*}|{q}|{fzf:(?:query|action|prompt)}|{\+?f?nf?})`)
|
||||
whiteSuffix = regexp.MustCompile(`\s*$`)
|
||||
@@ -178,7 +181,7 @@ type Status struct {
|
||||
type Terminal struct {
|
||||
initDelay time.Duration
|
||||
infoStyle infoStyle
|
||||
infoSep string
|
||||
infoPrefix string
|
||||
separator labelPrinter
|
||||
separatorLen int
|
||||
spinner []string
|
||||
@@ -290,6 +293,7 @@ type Terminal struct {
|
||||
executing *util.AtomicBool
|
||||
termSize tui.TermSize
|
||||
lastAction actionType
|
||||
lastKey string
|
||||
lastFocus int32
|
||||
areaLines int
|
||||
areaColumns int
|
||||
@@ -391,8 +395,10 @@ const (
|
||||
actToggleIn
|
||||
actToggleOut
|
||||
actToggleTrack
|
||||
actToggleTrackCurrent
|
||||
actToggleHeader
|
||||
actTrack
|
||||
actTrackCurrent
|
||||
actUntrackCurrent
|
||||
actDown
|
||||
actUp
|
||||
actPageUp
|
||||
@@ -403,7 +409,7 @@ const (
|
||||
actOffsetUp
|
||||
actOffsetDown
|
||||
actJump
|
||||
actJumpAccept
|
||||
actJumpAccept // XXX Deprecated in favor of jump:accept binding
|
||||
actPrintQuery
|
||||
actRefreshPreview
|
||||
actReplaceQuery
|
||||
@@ -455,14 +461,7 @@ const (
|
||||
)
|
||||
|
||||
func (a actionType) Name() string {
|
||||
name := ""
|
||||
for i, r := range a.String()[3:] {
|
||||
if i > 0 && r >= 'A' && r <= 'Z' {
|
||||
name += "-"
|
||||
}
|
||||
name += string(r)
|
||||
}
|
||||
return strings.ToLower(name)
|
||||
return util.ToKebabCase(a.String()[3:])
|
||||
}
|
||||
|
||||
func processExecution(action actionType) bool {
|
||||
@@ -541,14 +540,14 @@ func defaultKeymap() map[tui.Event][]*action {
|
||||
add(tui.CtrlC, actAbort)
|
||||
add(tui.CtrlG, actAbort)
|
||||
add(tui.CtrlQ, actAbort)
|
||||
add(tui.ESC, actAbort)
|
||||
add(tui.Esc, actAbort)
|
||||
add(tui.CtrlD, actDeleteCharEof)
|
||||
add(tui.CtrlE, actEndOfLine)
|
||||
add(tui.CtrlF, actForwardChar)
|
||||
add(tui.CtrlH, actBackwardDeleteChar)
|
||||
add(tui.BSpace, actBackwardDeleteChar)
|
||||
add(tui.Backspace, actBackwardDeleteChar)
|
||||
add(tui.Tab, actToggleDown)
|
||||
add(tui.BTab, actToggleUp)
|
||||
add(tui.ShiftTab, actToggleUp)
|
||||
add(tui.CtrlJ, actDown)
|
||||
add(tui.CtrlK, actUp)
|
||||
add(tui.CtrlL, actClearScreen)
|
||||
@@ -563,11 +562,11 @@ func defaultKeymap() map[tui.Event][]*action {
|
||||
}
|
||||
|
||||
addEvent(tui.AltKey('b'), actBackwardWord)
|
||||
add(tui.SLeft, actBackwardWord)
|
||||
add(tui.ShiftLeft, actBackwardWord)
|
||||
addEvent(tui.AltKey('f'), actForwardWord)
|
||||
add(tui.SRight, actForwardWord)
|
||||
add(tui.ShiftRight, actForwardWord)
|
||||
addEvent(tui.AltKey('d'), actKillWord)
|
||||
add(tui.AltBS, actBackwardKillWord)
|
||||
add(tui.AltBackspace, actBackwardKillWord)
|
||||
|
||||
add(tui.Up, actUp)
|
||||
add(tui.Down, actDown)
|
||||
@@ -576,12 +575,12 @@ func defaultKeymap() map[tui.Event][]*action {
|
||||
|
||||
add(tui.Home, actBeginningOfLine)
|
||||
add(tui.End, actEndOfLine)
|
||||
add(tui.Del, actDeleteChar)
|
||||
add(tui.PgUp, actPageUp)
|
||||
add(tui.PgDn, actPageDown)
|
||||
add(tui.Delete, actDeleteChar)
|
||||
add(tui.PageUp, actPageUp)
|
||||
add(tui.PageDown, actPageDown)
|
||||
|
||||
add(tui.SUp, actPreviewUp)
|
||||
add(tui.SDown, actPreviewDown)
|
||||
add(tui.ShiftUp, actPreviewUp)
|
||||
add(tui.ShiftDown, actPreviewDown)
|
||||
|
||||
add(tui.Mouse, actMouse)
|
||||
add(tui.LeftClick, actClick)
|
||||
@@ -672,7 +671,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
||||
if previewBox != nil && opts.Preview.aboveOrBelow() {
|
||||
effectiveMinHeight += 1 + borderLines(opts.Preview.border)
|
||||
}
|
||||
if opts.InfoStyle.noExtraLine() {
|
||||
if noSeparatorLine(opts.InfoStyle, opts.Separator == nil || uniseg.StringWidth(*opts.Separator) > 0) {
|
||||
effectiveMinHeight--
|
||||
}
|
||||
effectiveMinHeight += borderLines(opts.BorderShape)
|
||||
@@ -694,7 +693,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
||||
t := Terminal{
|
||||
initDelay: delay,
|
||||
infoStyle: opts.InfoStyle,
|
||||
infoSep: opts.InfoSep,
|
||||
infoPrefix: opts.InfoPrefix,
|
||||
separator: nil,
|
||||
spinner: makeSpinner(opts.Unicode),
|
||||
promptString: opts.Prompt,
|
||||
@@ -773,7 +772,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
||||
theme: opts.Theme,
|
||||
startChan: make(chan fitpad, 1),
|
||||
killChan: make(chan int),
|
||||
serverInputChan: make(chan []*action, 10),
|
||||
serverInputChan: make(chan []*action, 100),
|
||||
serverOutputChan: make(chan string),
|
||||
eventChan: make(chan tui.Event, 6), // (load + result + zero|one) | (focus) | (resize) | (GetChar)
|
||||
tui: renderer,
|
||||
@@ -846,7 +845,10 @@ func (t *Terminal) environ() []string {
|
||||
}
|
||||
env = append(env, "FZF_QUERY="+string(t.input))
|
||||
env = append(env, "FZF_ACTION="+t.lastAction.Name())
|
||||
env = append(env, "FZF_KEY="+t.lastKey)
|
||||
env = append(env, "FZF_PROMPT="+string(t.promptString))
|
||||
env = append(env, "FZF_PREVIEW_LABEL="+t.previewLabelOpts.label)
|
||||
env = append(env, "FZF_BORDER_LABEL="+t.borderLabelOpts.label)
|
||||
env = append(env, fmt.Sprintf("FZF_TOTAL_COUNT=%d", t.count))
|
||||
env = append(env, fmt.Sprintf("FZF_MATCH_COUNT=%d", t.merger.Length()))
|
||||
env = append(env, fmt.Sprintf("FZF_SELECT_COUNT=%d", len(t.selected)))
|
||||
@@ -875,7 +877,7 @@ func (t *Terminal) visibleHeaderLines() int {
|
||||
// Extra number of lines needed to display fzf
|
||||
func (t *Terminal) extraLines() int {
|
||||
extra := t.visibleHeaderLines() + 1
|
||||
if !t.noInfoLine() {
|
||||
if !t.noSeparatorLine() {
|
||||
extra++
|
||||
}
|
||||
return extra
|
||||
@@ -981,8 +983,18 @@ func (t *Terminal) parsePrompt(prompt string) (func(), int) {
|
||||
return output, promptLen
|
||||
}
|
||||
|
||||
func (t *Terminal) noInfoLine() bool {
|
||||
return t.infoStyle.noExtraLine()
|
||||
func noSeparatorLine(style infoStyle, separator bool) bool {
|
||||
switch style {
|
||||
case infoInline:
|
||||
return true
|
||||
case infoHidden, infoInlineRight:
|
||||
return !separator
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *Terminal) noSeparatorLine() bool {
|
||||
return noSeparatorLine(t.infoStyle, t.separatorLen > 0)
|
||||
}
|
||||
|
||||
func getScrollbar(total int, height int, offset int) (int, int) {
|
||||
@@ -1057,11 +1069,11 @@ func (t *Terminal) UpdateProgress(progress float32) {
|
||||
// UpdateList updates Merger to display the list
|
||||
func (t *Terminal) UpdateList(merger *Merger) {
|
||||
t.mutex.Lock()
|
||||
var prevIndex int32 = -1
|
||||
prevIndex := minItem.Index()
|
||||
reset := t.revision != merger.Revision()
|
||||
if !reset && t.track != trackDisabled {
|
||||
if t.merger.Length() > 0 {
|
||||
prevIndex = t.merger.Get(t.cy).item.Index()
|
||||
prevIndex = t.currentIndex()
|
||||
} else if merger.Length() > 0 {
|
||||
prevIndex = merger.First().item.Index()
|
||||
}
|
||||
@@ -1234,7 +1246,7 @@ func (t *Terminal) adjustMarginAndPadding() (int, int, [4]int, [4]int) {
|
||||
|
||||
minAreaWidth := minWidth
|
||||
minAreaHeight := minHeight
|
||||
if t.noInfoLine() {
|
||||
if t.noSeparatorLine() {
|
||||
minAreaHeight -= 1
|
||||
}
|
||||
if t.needPreviewWindow() {
|
||||
@@ -1515,7 +1527,7 @@ func (t *Terminal) move(y int, x int, clear bool) {
|
||||
y = h - y - 1
|
||||
case layoutReverseList:
|
||||
n := 2 + t.visibleHeaderLines()
|
||||
if t.noInfoLine() {
|
||||
if t.noSeparatorLine() {
|
||||
n--
|
||||
}
|
||||
if y < n {
|
||||
@@ -1555,7 +1567,7 @@ func (t *Terminal) updatePromptOffset() ([]rune, []rune) {
|
||||
func (t *Terminal) promptLine() int {
|
||||
if t.headerFirst {
|
||||
max := t.window.Height() - 1
|
||||
if !t.noInfoLine() {
|
||||
if !t.noSeparatorLine() {
|
||||
max--
|
||||
}
|
||||
return util.Min(t.visibleHeaderLines(), max)
|
||||
@@ -1600,20 +1612,8 @@ func (t *Terminal) printInfo() {
|
||||
t.window.Print(" ") // Clear spinner
|
||||
}
|
||||
}
|
||||
switch t.infoStyle {
|
||||
case infoDefault:
|
||||
t.move(line+1, 0, t.separatorLen == 0)
|
||||
printSpinner()
|
||||
t.move(line+1, 2, false)
|
||||
pos = 2
|
||||
case infoRight:
|
||||
t.move(line+1, 0, false)
|
||||
case infoInlineRight:
|
||||
pos = t.promptLen + t.queryLen[0] + t.queryLen[1] + 1
|
||||
t.move(line, pos, true)
|
||||
case infoInline:
|
||||
pos = t.promptLen + t.queryLen[0] + t.queryLen[1] + 1
|
||||
str := t.infoSep
|
||||
printInfoPrefix := func() {
|
||||
str := t.infoPrefix
|
||||
maxWidth := t.window.Width() - pos
|
||||
width := util.StringWidth(str)
|
||||
if width > maxWidth {
|
||||
@@ -1628,7 +1628,34 @@ func (t *Terminal) printInfo() {
|
||||
t.window.CPrint(tui.ColPrompt, str)
|
||||
}
|
||||
pos += width
|
||||
}
|
||||
printSeparator := func(fillLength int, pad bool) {
|
||||
// --------_
|
||||
if t.separatorLen > 0 {
|
||||
t.separator(t.window, fillLength)
|
||||
t.window.Print(" ")
|
||||
} else if pad {
|
||||
t.window.Print(strings.Repeat(" ", fillLength+1))
|
||||
}
|
||||
}
|
||||
switch t.infoStyle {
|
||||
case infoDefault:
|
||||
t.move(line+1, 0, t.separatorLen == 0)
|
||||
printSpinner()
|
||||
t.move(line+1, 2, false)
|
||||
pos = 2
|
||||
case infoRight:
|
||||
t.move(line+1, 0, false)
|
||||
case infoInlineRight:
|
||||
pos = t.promptLen + t.queryLen[0] + t.queryLen[1] + 1
|
||||
case infoInline:
|
||||
pos = t.promptLen + t.queryLen[0] + t.queryLen[1] + 1
|
||||
printInfoPrefix()
|
||||
case infoHidden:
|
||||
if t.separatorLen > 0 {
|
||||
t.move(line+1, 0, false)
|
||||
printSeparator(t.window.Width()-1, false)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1642,8 +1669,11 @@ func (t *Terminal) printInfo() {
|
||||
output += " -S"
|
||||
}
|
||||
}
|
||||
if t.track != trackDisabled {
|
||||
switch t.track {
|
||||
case trackEnabled:
|
||||
output += " +T"
|
||||
case trackCurrent:
|
||||
output += " +t"
|
||||
}
|
||||
if t.multi > 0 {
|
||||
if t.multi == maxMulti {
|
||||
@@ -1659,15 +1689,6 @@ func (t *Terminal) printInfo() {
|
||||
output = fmt.Sprintf("[Command failed: %s]", *t.failed)
|
||||
}
|
||||
|
||||
printSeparator := func(fillLength int, pad bool) {
|
||||
// --------_
|
||||
if t.separatorLen > 0 {
|
||||
t.separator(t.window, fillLength)
|
||||
t.window.Print(" ")
|
||||
} else if pad {
|
||||
t.window.Print(strings.Repeat(" ", fillLength+1))
|
||||
}
|
||||
}
|
||||
if t.infoStyle == infoRight {
|
||||
maxWidth := t.window.Width()
|
||||
if t.reading {
|
||||
@@ -1690,19 +1711,35 @@ func (t *Terminal) printInfo() {
|
||||
}
|
||||
|
||||
if t.infoStyle == infoInlineRight {
|
||||
pos = util.Max(pos, t.window.Width()-util.StringWidth(output)-3)
|
||||
if pos >= t.window.Width() {
|
||||
return
|
||||
if len(t.infoPrefix) == 0 {
|
||||
pos = util.Max(pos, t.window.Width()-util.StringWidth(output)-3)
|
||||
if pos < t.window.Width() {
|
||||
t.move(line, pos, false)
|
||||
printSpinner()
|
||||
pos++
|
||||
}
|
||||
if pos < t.window.Width()-1 {
|
||||
t.window.Print(" ")
|
||||
pos++
|
||||
}
|
||||
} else {
|
||||
pos = util.Max(pos, t.window.Width()-util.StringWidth(output)-util.StringWidth(t.infoPrefix)-1)
|
||||
printInfoPrefix()
|
||||
}
|
||||
t.move(line, pos, false)
|
||||
printSpinner()
|
||||
t.window.Print(" ")
|
||||
pos += 2
|
||||
}
|
||||
|
||||
maxWidth := t.window.Width() - pos
|
||||
output = t.trimMessage(output, maxWidth)
|
||||
t.window.CPrint(tui.ColInfo, output)
|
||||
|
||||
if t.infoStyle == infoInlineRight {
|
||||
if t.separatorLen > 0 {
|
||||
t.move(line+1, 0, false)
|
||||
printSeparator(t.window.Width()-1, false)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
fillLength := maxWidth - len(output) - 2
|
||||
if fillLength > 0 {
|
||||
t.window.CPrint(tui.ColSeparator, " ")
|
||||
@@ -1717,7 +1754,7 @@ func (t *Terminal) printHeader() {
|
||||
max := t.window.Height()
|
||||
if t.headerFirst {
|
||||
max--
|
||||
if !t.noInfoLine() {
|
||||
if !t.noSeparatorLine() {
|
||||
max--
|
||||
}
|
||||
}
|
||||
@@ -1734,7 +1771,7 @@ func (t *Terminal) printHeader() {
|
||||
}
|
||||
if !t.headerFirst {
|
||||
line++
|
||||
if !t.noInfoLine() {
|
||||
if !t.noSeparatorLine() {
|
||||
line++
|
||||
}
|
||||
}
|
||||
@@ -1766,7 +1803,7 @@ func (t *Terminal) printList() {
|
||||
i = maxy - 1 - j
|
||||
}
|
||||
line := i + 2 + t.visibleHeaderLines()
|
||||
if t.noInfoLine() {
|
||||
if t.noSeparatorLine() {
|
||||
line--
|
||||
}
|
||||
if i < count {
|
||||
@@ -3092,7 +3129,7 @@ func (t *Terminal) Loop() {
|
||||
switch req {
|
||||
case reqPrompt:
|
||||
t.printPrompt()
|
||||
if t.noInfoLine() {
|
||||
if t.infoStyle == infoInline || t.infoStyle == infoInlineRight {
|
||||
t.printInfo()
|
||||
}
|
||||
case reqInfo:
|
||||
@@ -3248,6 +3285,7 @@ func (t *Terminal) Loop() {
|
||||
t.mutex.Lock()
|
||||
previousInput := t.input
|
||||
previousCx := t.cx
|
||||
t.lastKey = event.KeyName()
|
||||
events := []util.EventType{}
|
||||
req := func(evts ...util.EventType) {
|
||||
for _, event := range evts {
|
||||
@@ -3305,18 +3343,22 @@ func (t *Terminal) Loop() {
|
||||
var doAction func(*action) bool
|
||||
var doActions func(actions []*action) bool
|
||||
doActions = func(actions []*action) bool {
|
||||
currentIndex := t.currentIndex()
|
||||
for _, action := range actions {
|
||||
if !doAction(action) {
|
||||
return false
|
||||
for iter := 0; iter <= maxFocusEvents; iter++ {
|
||||
currentIndex := t.currentIndex()
|
||||
for _, action := range actions {
|
||||
if !doAction(action) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if onFocus, prs := t.keymap[tui.Focus.AsEvent()]; prs {
|
||||
if newIndex := t.currentIndex(); newIndex != currentIndex {
|
||||
t.lastFocus = newIndex
|
||||
return doActions(onFocus)
|
||||
if onFocus, prs := t.keymap[tui.Focus.AsEvent()]; prs && iter < maxFocusEvents {
|
||||
if newIndex := t.currentIndex(); newIndex != currentIndex {
|
||||
t.lastFocus = newIndex
|
||||
actions = onFocus
|
||||
continue
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -3464,11 +3506,13 @@ func (t *Terminal) Loop() {
|
||||
req(reqHeader)
|
||||
}
|
||||
case actChangeBorderLabel:
|
||||
t.borderLabelOpts.label = a.a
|
||||
if t.border != nil {
|
||||
t.borderLabel, t.borderLabelLen = t.ansiLabelPrinter(a.a, &tui.ColBorderLabel, false)
|
||||
req(reqRedrawBorderLabel)
|
||||
}
|
||||
case actChangePreviewLabel:
|
||||
t.previewLabelOpts.label = a.a
|
||||
if t.pborder != nil {
|
||||
t.previewLabel, t.previewLabelLen = t.ansiLabelPrinter(a.a, &tui.ColPreviewLabel, false)
|
||||
req(reqRedrawPreviewLabel)
|
||||
@@ -3478,14 +3522,16 @@ func (t *Terminal) Loop() {
|
||||
actions := parseSingleActionList(strings.Trim(body, "\r\n"), func(message string) {})
|
||||
return doActions(actions)
|
||||
case actTransformBorderLabel:
|
||||
label := t.executeCommand(a.a, false, true, true, true)
|
||||
t.borderLabelOpts.label = label
|
||||
if t.border != nil {
|
||||
label := t.executeCommand(a.a, false, true, true, true)
|
||||
t.borderLabel, t.borderLabelLen = t.ansiLabelPrinter(label, &tui.ColBorderLabel, false)
|
||||
req(reqRedrawBorderLabel)
|
||||
}
|
||||
case actTransformPreviewLabel:
|
||||
label := t.executeCommand(a.a, false, true, true, true)
|
||||
t.previewLabelOpts.label = label
|
||||
if t.pborder != nil {
|
||||
label := t.executeCommand(a.a, false, true, true, true)
|
||||
t.previewLabel, t.previewLabelLen = t.ansiLabelPrinter(label, &tui.ColPreviewLabel, false)
|
||||
req(reqRedrawPreviewLabel)
|
||||
}
|
||||
@@ -3771,6 +3817,14 @@ func (t *Terminal) Loop() {
|
||||
t.track = trackEnabled
|
||||
}
|
||||
req(reqInfo)
|
||||
case actToggleTrackCurrent:
|
||||
switch t.track {
|
||||
case trackCurrent:
|
||||
t.track = trackDisabled
|
||||
case trackDisabled:
|
||||
t.track = trackCurrent
|
||||
}
|
||||
req(reqInfo)
|
||||
case actShowHeader:
|
||||
t.headerVisible = true
|
||||
req(reqList, reqInfo, reqPrompt, reqHeader)
|
||||
@@ -3780,11 +3834,16 @@ func (t *Terminal) Loop() {
|
||||
case actToggleHeader:
|
||||
t.headerVisible = !t.headerVisible
|
||||
req(reqList, reqInfo, reqPrompt, reqHeader)
|
||||
case actTrack:
|
||||
case actTrackCurrent:
|
||||
if t.track == trackDisabled {
|
||||
t.track = trackCurrent
|
||||
}
|
||||
req(reqInfo)
|
||||
case actUntrackCurrent:
|
||||
if t.track == trackCurrent {
|
||||
t.track = trackDisabled
|
||||
}
|
||||
req(reqInfo)
|
||||
case actEnableSearch:
|
||||
t.paused = false
|
||||
changed = true
|
||||
@@ -3873,7 +3932,7 @@ func (t *Terminal) Loop() {
|
||||
mx -= t.window.Left()
|
||||
my -= t.window.Top()
|
||||
min := 2 + t.visibleHeaderLines()
|
||||
if t.noInfoLine() {
|
||||
if t.noSeparatorLine() {
|
||||
min--
|
||||
}
|
||||
h := t.window.Height()
|
||||
@@ -4045,6 +4104,9 @@ func (t *Terminal) Loop() {
|
||||
// Break out of jump mode if any action is submitted to the server
|
||||
if t.jumping != jumpDisabled {
|
||||
t.jumping = jumpDisabled
|
||||
if acts, prs := t.keymap[tui.JumpCancel.AsEvent()]; prs && !doActions(acts) {
|
||||
continue
|
||||
}
|
||||
req(reqList)
|
||||
}
|
||||
if len(actions) == 0 {
|
||||
@@ -4058,19 +4120,17 @@ func (t *Terminal) Loop() {
|
||||
t.truncateQuery()
|
||||
queryChanged = string(previousInput) != string(t.input)
|
||||
changed = changed || queryChanged
|
||||
if onChanges, prs := t.keymap[tui.Change.AsEvent()]; queryChanged && prs {
|
||||
if !doActions(onChanges) {
|
||||
continue
|
||||
}
|
||||
if onChanges, prs := t.keymap[tui.Change.AsEvent()]; queryChanged && prs && !doActions(onChanges) {
|
||||
continue
|
||||
}
|
||||
if onEOFs, prs := t.keymap[tui.BackwardEOF.AsEvent()]; beof && prs {
|
||||
if !doActions(onEOFs) {
|
||||
continue
|
||||
}
|
||||
if onEOFs, prs := t.keymap[tui.BackwardEOF.AsEvent()]; beof && prs && !doActions(onEOFs) {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
jumpEvent := tui.JumpCancel
|
||||
if event.Type == tui.Rune {
|
||||
if idx := strings.IndexRune(t.jumpLabels, event.Char); idx >= 0 && idx < t.maxItems() && idx < t.merger.Length() {
|
||||
jumpEvent = tui.Jump
|
||||
t.cy = idx + t.offset
|
||||
if t.jumping == jumpAcceptEnabled {
|
||||
req(reqClose)
|
||||
@@ -4078,6 +4138,9 @@ func (t *Terminal) Loop() {
|
||||
}
|
||||
}
|
||||
t.jumping = jumpDisabled
|
||||
if acts, prs := t.keymap[jumpEvent.AsEvent()]; prs && !doActions(acts) {
|
||||
continue
|
||||
}
|
||||
req(reqList)
|
||||
}
|
||||
|
||||
@@ -4114,7 +4177,7 @@ func (t *Terminal) constrain() {
|
||||
// count of lines can be displayed
|
||||
height := t.maxItems()
|
||||
|
||||
t.cy = util.Constrain(t.cy, 0, count-1)
|
||||
t.cy = util.Constrain(t.cy, 0, util.Max(0, count-1))
|
||||
|
||||
minOffset := util.Max(t.cy-height+1, 0)
|
||||
maxOffset := util.Max(util.Min(count-height, t.cy), 0)
|
||||
@@ -4165,7 +4228,7 @@ func (t *Terminal) vset(o int) bool {
|
||||
|
||||
func (t *Terminal) maxItems() int {
|
||||
max := t.window.Height() - 2 - t.visibleHeaderLines()
|
||||
if t.noInfoLine() {
|
||||
if t.noSeparatorLine() {
|
||||
max++
|
||||
}
|
||||
return util.Max(max, 0)
|
||||
|
||||
@@ -91,7 +91,7 @@ func withPrefixLengths(tokens []string, begin int) []Token {
|
||||
|
||||
prefixLength := begin
|
||||
for idx := range tokens {
|
||||
chars := util.ToChars([]byte(tokens[idx]))
|
||||
chars := util.ToChars(sbytes(tokens[idx]))
|
||||
ret[idx] = Token{&chars, int32(prefixLength)}
|
||||
prefixLength += chars.Length()
|
||||
}
|
||||
@@ -187,7 +187,7 @@ func Transform(tokens []Token, withNth []Range) []Token {
|
||||
if r.begin == r.end {
|
||||
idx := r.begin
|
||||
if idx == rangeEllipsis {
|
||||
chars := util.ToChars([]byte(joinTokens(tokens)))
|
||||
chars := util.ToChars(sbytes(joinTokens(tokens)))
|
||||
parts = append(parts, &chars)
|
||||
} else {
|
||||
if idx < 0 {
|
||||
|
||||
120
src/tui/eventtype_string.go
Normal file
120
src/tui/eventtype_string.go
Normal file
@@ -0,0 +1,120 @@
|
||||
// Code generated by "stringer -type=EventType"; DO NOT EDIT.
|
||||
|
||||
package tui
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[Rune-0]
|
||||
_ = x[CtrlA-1]
|
||||
_ = x[CtrlB-2]
|
||||
_ = x[CtrlC-3]
|
||||
_ = x[CtrlD-4]
|
||||
_ = x[CtrlE-5]
|
||||
_ = x[CtrlF-6]
|
||||
_ = x[CtrlG-7]
|
||||
_ = x[CtrlH-8]
|
||||
_ = x[Tab-9]
|
||||
_ = x[CtrlJ-10]
|
||||
_ = x[CtrlK-11]
|
||||
_ = x[CtrlL-12]
|
||||
_ = x[CtrlM-13]
|
||||
_ = x[CtrlN-14]
|
||||
_ = x[CtrlO-15]
|
||||
_ = x[CtrlP-16]
|
||||
_ = x[CtrlQ-17]
|
||||
_ = x[CtrlR-18]
|
||||
_ = x[CtrlS-19]
|
||||
_ = x[CtrlT-20]
|
||||
_ = x[CtrlU-21]
|
||||
_ = x[CtrlV-22]
|
||||
_ = x[CtrlW-23]
|
||||
_ = x[CtrlX-24]
|
||||
_ = x[CtrlY-25]
|
||||
_ = x[CtrlZ-26]
|
||||
_ = x[Esc-27]
|
||||
_ = x[CtrlSpace-28]
|
||||
_ = x[CtrlDelete-29]
|
||||
_ = x[CtrlBackSlash-30]
|
||||
_ = x[CtrlRightBracket-31]
|
||||
_ = x[CtrlCaret-32]
|
||||
_ = x[CtrlSlash-33]
|
||||
_ = x[ShiftTab-34]
|
||||
_ = x[Backspace-35]
|
||||
_ = x[Delete-36]
|
||||
_ = x[PageUp-37]
|
||||
_ = x[PageDown-38]
|
||||
_ = x[Up-39]
|
||||
_ = x[Down-40]
|
||||
_ = x[Left-41]
|
||||
_ = x[Right-42]
|
||||
_ = x[Home-43]
|
||||
_ = x[End-44]
|
||||
_ = x[Insert-45]
|
||||
_ = x[ShiftUp-46]
|
||||
_ = x[ShiftDown-47]
|
||||
_ = x[ShiftLeft-48]
|
||||
_ = x[ShiftRight-49]
|
||||
_ = x[ShiftDelete-50]
|
||||
_ = x[F1-51]
|
||||
_ = x[F2-52]
|
||||
_ = x[F3-53]
|
||||
_ = x[F4-54]
|
||||
_ = x[F5-55]
|
||||
_ = x[F6-56]
|
||||
_ = x[F7-57]
|
||||
_ = x[F8-58]
|
||||
_ = x[F9-59]
|
||||
_ = x[F10-60]
|
||||
_ = x[F11-61]
|
||||
_ = x[F12-62]
|
||||
_ = x[AltBackspace-63]
|
||||
_ = x[AltUp-64]
|
||||
_ = x[AltDown-65]
|
||||
_ = x[AltLeft-66]
|
||||
_ = x[AltRight-67]
|
||||
_ = x[AltShiftUp-68]
|
||||
_ = x[AltShiftDown-69]
|
||||
_ = x[AltShiftLeft-70]
|
||||
_ = x[AltShiftRight-71]
|
||||
_ = x[Alt-72]
|
||||
_ = x[CtrlAlt-73]
|
||||
_ = x[Invalid-74]
|
||||
_ = x[Mouse-75]
|
||||
_ = x[DoubleClick-76]
|
||||
_ = x[LeftClick-77]
|
||||
_ = x[RightClick-78]
|
||||
_ = x[SLeftClick-79]
|
||||
_ = x[SRightClick-80]
|
||||
_ = x[ScrollUp-81]
|
||||
_ = x[ScrollDown-82]
|
||||
_ = x[SScrollUp-83]
|
||||
_ = x[SScrollDown-84]
|
||||
_ = x[PreviewScrollUp-85]
|
||||
_ = x[PreviewScrollDown-86]
|
||||
_ = x[Resize-87]
|
||||
_ = x[Change-88]
|
||||
_ = x[BackwardEOF-89]
|
||||
_ = x[Start-90]
|
||||
_ = x[Load-91]
|
||||
_ = x[Focus-92]
|
||||
_ = x[One-93]
|
||||
_ = x[Zero-94]
|
||||
_ = x[Result-95]
|
||||
_ = x[Jump-96]
|
||||
_ = x[JumpCancel-97]
|
||||
}
|
||||
|
||||
const _EventType_name = "RuneCtrlACtrlBCtrlCCtrlDCtrlECtrlFCtrlGCtrlHTabCtrlJCtrlKCtrlLCtrlMCtrlNCtrlOCtrlPCtrlQCtrlRCtrlSCtrlTCtrlUCtrlVCtrlWCtrlXCtrlYCtrlZEscCtrlSpaceCtrlDeleteCtrlBackSlashCtrlRightBracketCtrlCaretCtrlSlashShiftTabBackspaceDeletePageUpPageDownUpDownLeftRightHomeEndInsertShiftUpShiftDownShiftLeftShiftRightShiftDeleteF1F2F3F4F5F6F7F8F9F10F11F12AltBackspaceAltUpAltDownAltLeftAltRightAltShiftUpAltShiftDownAltShiftLeftAltShiftRightAltCtrlAltInvalidMouseDoubleClickLeftClickRightClickSLeftClickSRightClickScrollUpScrollDownSScrollUpSScrollDownPreviewScrollUpPreviewScrollDownResizeChangeBackwardEOFStartLoadFocusOneZeroResultJumpJumpCancel"
|
||||
|
||||
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}
|
||||
|
||||
func (i EventType) String() string {
|
||||
if i < 0 || i >= EventType(len(_EventType_index)-1) {
|
||||
return "EventType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _EventType_name[_EventType_index[i]:_EventType_index[i+1]]
|
||||
}
|
||||
@@ -71,7 +71,7 @@ func (r *LightRenderer) csi(code string) string {
|
||||
|
||||
func (r *LightRenderer) flush() {
|
||||
if r.queued.Len() > 0 {
|
||||
fmt.Fprint(os.Stderr, "\x1b[?25l"+r.queued.String()+"\x1b[?25h")
|
||||
fmt.Fprint(os.Stderr, "\x1b[?7l\x1b[?25l"+r.queued.String()+"\x1b[?25h\x1b[?7h")
|
||||
r.queued.Reset()
|
||||
}
|
||||
}
|
||||
@@ -245,7 +245,7 @@ func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) []byte {
|
||||
}
|
||||
|
||||
retries := 0
|
||||
if c == ESC.Int() || nonblock {
|
||||
if c == Esc.Int() || nonblock {
|
||||
retries = r.escDelay / escPollInterval
|
||||
}
|
||||
buffer = append(buffer, byte(c))
|
||||
@@ -260,7 +260,7 @@ func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) []byte {
|
||||
continue
|
||||
}
|
||||
break
|
||||
} else if c == ESC.Int() && pc != c {
|
||||
} else if c == Esc.Int() && pc != c {
|
||||
retries = r.escDelay / escPollInterval
|
||||
} else {
|
||||
retries = 0
|
||||
@@ -300,7 +300,7 @@ func (r *LightRenderer) GetChar() Event {
|
||||
case CtrlQ.Byte():
|
||||
return Event{CtrlQ, 0, nil}
|
||||
case 127:
|
||||
return Event{BSpace, 0, nil}
|
||||
return Event{Backspace, 0, nil}
|
||||
case 0:
|
||||
return Event{CtrlSpace, 0, nil}
|
||||
case 28:
|
||||
@@ -311,7 +311,7 @@ func (r *LightRenderer) GetChar() Event {
|
||||
return Event{CtrlCaret, 0, nil}
|
||||
case 31:
|
||||
return Event{CtrlSlash, 0, nil}
|
||||
case ESC.Byte():
|
||||
case Esc.Byte():
|
||||
ev := r.escSequence(&sz)
|
||||
// Second chance
|
||||
if ev.Type == Invalid {
|
||||
@@ -327,7 +327,7 @@ func (r *LightRenderer) GetChar() Event {
|
||||
}
|
||||
char, rsz := utf8.DecodeRune(r.buffer)
|
||||
if char == utf8.RuneError {
|
||||
return Event{ESC, 0, nil}
|
||||
return Event{Esc, 0, nil}
|
||||
}
|
||||
sz = rsz
|
||||
return Event{Rune, char, nil}
|
||||
@@ -335,7 +335,7 @@ func (r *LightRenderer) GetChar() Event {
|
||||
|
||||
func (r *LightRenderer) escSequence(sz *int) Event {
|
||||
if len(r.buffer) < 2 {
|
||||
return Event{ESC, 0, nil}
|
||||
return Event{Esc, 0, nil}
|
||||
}
|
||||
|
||||
loc := offsetRegexpBegin.FindIndex(r.buffer)
|
||||
@@ -349,15 +349,15 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
||||
return CtrlAltKey(rune(r.buffer[1] + 'a' - 1))
|
||||
}
|
||||
alt := false
|
||||
if len(r.buffer) > 2 && r.buffer[1] == ESC.Byte() {
|
||||
if len(r.buffer) > 2 && r.buffer[1] == Esc.Byte() {
|
||||
r.buffer = r.buffer[1:]
|
||||
alt = true
|
||||
}
|
||||
switch r.buffer[1] {
|
||||
case ESC.Byte():
|
||||
return Event{ESC, 0, nil}
|
||||
case Esc.Byte():
|
||||
return Event{Esc, 0, nil}
|
||||
case 127:
|
||||
return Event{AltBS, 0, nil}
|
||||
return Event{AltBackspace, 0, nil}
|
||||
case '[', 'O':
|
||||
if len(r.buffer) < 3 {
|
||||
return Event{Invalid, 0, nil}
|
||||
@@ -386,7 +386,7 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
||||
}
|
||||
return Event{Up, 0, nil}
|
||||
case 'Z':
|
||||
return Event{BTab, 0, nil}
|
||||
return Event{ShiftTab, 0, nil}
|
||||
case 'H':
|
||||
return Event{Home, 0, nil}
|
||||
case 'F':
|
||||
@@ -434,7 +434,7 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
||||
return Event{Invalid, 0, nil} // INS
|
||||
case '3':
|
||||
if r.buffer[3] == '~' {
|
||||
return Event{Del, 0, nil}
|
||||
return Event{Delete, 0, nil}
|
||||
}
|
||||
if len(r.buffer) == 6 && r.buffer[5] == '~' {
|
||||
*sz = 6
|
||||
@@ -442,16 +442,16 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
||||
case '5':
|
||||
return Event{CtrlDelete, 0, nil}
|
||||
case '2':
|
||||
return Event{SDelete, 0, nil}
|
||||
return Event{ShiftDelete, 0, nil}
|
||||
}
|
||||
}
|
||||
return Event{Invalid, 0, nil}
|
||||
case '4':
|
||||
return Event{End, 0, nil}
|
||||
case '5':
|
||||
return Event{PgUp, 0, nil}
|
||||
return Event{PageUp, 0, nil}
|
||||
case '6':
|
||||
return Event{PgDn, 0, nil}
|
||||
return Event{PageDown, 0, nil}
|
||||
case '7':
|
||||
return Event{Home, 0, nil}
|
||||
case '8':
|
||||
@@ -489,16 +489,29 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
||||
}
|
||||
*sz = 6
|
||||
switch r.buffer[4] {
|
||||
case '1', '2', '3', '5':
|
||||
case '1', '2', '3', '4', '5':
|
||||
// Kitty iTerm2 WezTerm
|
||||
// SHIFT-ARROW "\e[1;2D"
|
||||
// ALT-SHIFT-ARROW "\e[1;4D" "\e[1;10D" "\e[1;4D"
|
||||
// CTRL-SHIFT-ARROW "\e[1;6D" N/A
|
||||
// CMD-SHIFT-ARROW "\e[1;10D" N/A N/A ("\e[1;2D")
|
||||
alt := r.buffer[4] == '3'
|
||||
altShift := r.buffer[4] == '1' && r.buffer[5] == '0'
|
||||
char := r.buffer[5]
|
||||
if altShift {
|
||||
altShift := false
|
||||
if r.buffer[4] == '1' && r.buffer[5] == '0' {
|
||||
altShift = true
|
||||
if len(r.buffer) < 7 {
|
||||
return Event{Invalid, 0, nil}
|
||||
}
|
||||
*sz = 7
|
||||
char = r.buffer[6]
|
||||
} else if r.buffer[4] == '4' {
|
||||
altShift = true
|
||||
if len(r.buffer) < 6 {
|
||||
return Event{Invalid, 0, nil}
|
||||
}
|
||||
*sz = 6
|
||||
char = r.buffer[5]
|
||||
}
|
||||
switch char {
|
||||
case 'A':
|
||||
@@ -506,33 +519,33 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
||||
return Event{AltUp, 0, nil}
|
||||
}
|
||||
if altShift {
|
||||
return Event{AltSUp, 0, nil}
|
||||
return Event{AltShiftUp, 0, nil}
|
||||
}
|
||||
return Event{SUp, 0, nil}
|
||||
return Event{ShiftUp, 0, nil}
|
||||
case 'B':
|
||||
if alt {
|
||||
return Event{AltDown, 0, nil}
|
||||
}
|
||||
if altShift {
|
||||
return Event{AltSDown, 0, nil}
|
||||
return Event{AltShiftDown, 0, nil}
|
||||
}
|
||||
return Event{SDown, 0, nil}
|
||||
return Event{ShiftDown, 0, nil}
|
||||
case 'C':
|
||||
if alt {
|
||||
return Event{AltRight, 0, nil}
|
||||
}
|
||||
if altShift {
|
||||
return Event{AltSRight, 0, nil}
|
||||
return Event{AltShiftRight, 0, nil}
|
||||
}
|
||||
return Event{SRight, 0, nil}
|
||||
return Event{ShiftRight, 0, nil}
|
||||
case 'D':
|
||||
if alt {
|
||||
return Event{AltLeft, 0, nil}
|
||||
}
|
||||
if altShift {
|
||||
return Event{AltSLeft, 0, nil}
|
||||
return Event{AltShiftLeft, 0, nil}
|
||||
}
|
||||
return Event{SLeft, 0, nil}
|
||||
return Event{ShiftLeft, 0, nil}
|
||||
}
|
||||
} // r.buffer[4]
|
||||
} // r.buffer[3]
|
||||
|
||||
@@ -58,7 +58,7 @@ func openTtyIn() *os.File {
|
||||
}
|
||||
}
|
||||
fmt.Fprintln(os.Stderr, "Failed to open "+consoleDevice)
|
||||
os.Exit(2)
|
||||
util.Exit(2)
|
||||
}
|
||||
return in
|
||||
}
|
||||
|
||||
@@ -320,16 +320,16 @@ func (r *FullscreenRenderer) GetChar() Event {
|
||||
switch ev.Rune() {
|
||||
case 0:
|
||||
if ctrl {
|
||||
return Event{BSpace, 0, nil}
|
||||
return Event{Backspace, 0, nil}
|
||||
}
|
||||
case rune(tcell.KeyCtrlH):
|
||||
switch {
|
||||
case ctrl:
|
||||
return keyfn('h')
|
||||
case alt:
|
||||
return Event{AltBS, 0, nil}
|
||||
return Event{AltBackspace, 0, nil}
|
||||
case none, shift:
|
||||
return Event{BSpace, 0, nil}
|
||||
return Event{Backspace, 0, nil}
|
||||
}
|
||||
}
|
||||
case tcell.KeyCtrlI:
|
||||
@@ -382,17 +382,17 @@ func (r *FullscreenRenderer) GetChar() Event {
|
||||
// section 3: (Alt)+Backspace2
|
||||
case tcell.KeyBackspace2:
|
||||
if alt {
|
||||
return Event{AltBS, 0, nil}
|
||||
return Event{AltBackspace, 0, nil}
|
||||
}
|
||||
return Event{BSpace, 0, nil}
|
||||
return Event{Backspace, 0, nil}
|
||||
|
||||
// section 4: (Alt+Shift)+Key(Up|Down|Left|Right)
|
||||
case tcell.KeyUp:
|
||||
if altShift {
|
||||
return Event{AltSUp, 0, nil}
|
||||
return Event{AltShiftUp, 0, nil}
|
||||
}
|
||||
if shift {
|
||||
return Event{SUp, 0, nil}
|
||||
return Event{ShiftUp, 0, nil}
|
||||
}
|
||||
if alt {
|
||||
return Event{AltUp, 0, nil}
|
||||
@@ -400,10 +400,10 @@ func (r *FullscreenRenderer) GetChar() Event {
|
||||
return Event{Up, 0, nil}
|
||||
case tcell.KeyDown:
|
||||
if altShift {
|
||||
return Event{AltSDown, 0, nil}
|
||||
return Event{AltShiftDown, 0, nil}
|
||||
}
|
||||
if shift {
|
||||
return Event{SDown, 0, nil}
|
||||
return Event{ShiftDown, 0, nil}
|
||||
}
|
||||
if alt {
|
||||
return Event{AltDown, 0, nil}
|
||||
@@ -411,10 +411,10 @@ func (r *FullscreenRenderer) GetChar() Event {
|
||||
return Event{Down, 0, nil}
|
||||
case tcell.KeyLeft:
|
||||
if altShift {
|
||||
return Event{AltSLeft, 0, nil}
|
||||
return Event{AltShiftLeft, 0, nil}
|
||||
}
|
||||
if shift {
|
||||
return Event{SLeft, 0, nil}
|
||||
return Event{ShiftLeft, 0, nil}
|
||||
}
|
||||
if alt {
|
||||
return Event{AltLeft, 0, nil}
|
||||
@@ -422,10 +422,10 @@ func (r *FullscreenRenderer) GetChar() Event {
|
||||
return Event{Left, 0, nil}
|
||||
case tcell.KeyRight:
|
||||
if altShift {
|
||||
return Event{AltSRight, 0, nil}
|
||||
return Event{AltShiftRight, 0, nil}
|
||||
}
|
||||
if shift {
|
||||
return Event{SRight, 0, nil}
|
||||
return Event{ShiftRight, 0, nil}
|
||||
}
|
||||
if alt {
|
||||
return Event{AltRight, 0, nil}
|
||||
@@ -442,17 +442,17 @@ func (r *FullscreenRenderer) GetChar() Event {
|
||||
return Event{CtrlDelete, 0, nil}
|
||||
}
|
||||
if shift {
|
||||
return Event{SDelete, 0, nil}
|
||||
return Event{ShiftDelete, 0, nil}
|
||||
}
|
||||
return Event{Del, 0, nil}
|
||||
return Event{Delete, 0, nil}
|
||||
case tcell.KeyEnd:
|
||||
return Event{End, 0, nil}
|
||||
case tcell.KeyPgUp:
|
||||
return Event{PgUp, 0, nil}
|
||||
return Event{PageUp, 0, nil}
|
||||
case tcell.KeyPgDn:
|
||||
return Event{PgDn, 0, nil}
|
||||
return Event{PageDown, 0, nil}
|
||||
case tcell.KeyBacktab:
|
||||
return Event{BTab, 0, nil}
|
||||
return Event{ShiftTab, 0, nil}
|
||||
case tcell.KeyF1:
|
||||
return Event{F1, 0, nil}
|
||||
case tcell.KeyF2:
|
||||
@@ -498,7 +498,7 @@ func (r *FullscreenRenderer) GetChar() Event {
|
||||
|
||||
// section 7: Esc
|
||||
case tcell.KeyEsc:
|
||||
return Event{ESC, 0, nil}
|
||||
return Event{Esc, 0, nil}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -102,22 +102,22 @@ func TestGetCharEventKey(t *testing.T) {
|
||||
// KeyBackspace2 is alias for KeyDEL = 0x7F (ASCII) (allegedly unused by Windows)
|
||||
// KeyDelete = 0x2E (VK_DELETE constant in Windows)
|
||||
// KeyBackspace is alias for KeyBS = 0x08 (ASCII) (implicit alias with KeyCtrlH)
|
||||
{giveKey{tcell.KeyBackspace2, 0, tcell.ModNone}, wantKey{BSpace, 0, nil}}, // fabricated
|
||||
{giveKey{tcell.KeyBackspace2, 0, tcell.ModAlt}, wantKey{AltBS, 0, nil}}, // fabricated
|
||||
{giveKey{tcell.KeyDEL, 0, tcell.ModNone}, wantKey{BSpace, 0, nil}}, // fabricated, unhandled
|
||||
{giveKey{tcell.KeyDelete, 0, tcell.ModNone}, wantKey{Del, 0, nil}},
|
||||
{giveKey{tcell.KeyDelete, 0, tcell.ModAlt}, wantKey{Del, 0, nil}},
|
||||
{giveKey{tcell.KeyBackspace2, 0, tcell.ModNone}, wantKey{Backspace, 0, nil}}, // fabricated
|
||||
{giveKey{tcell.KeyBackspace2, 0, tcell.ModAlt}, wantKey{AltBackspace, 0, nil}}, // fabricated
|
||||
{giveKey{tcell.KeyDEL, 0, tcell.ModNone}, wantKey{Backspace, 0, nil}}, // fabricated, unhandled
|
||||
{giveKey{tcell.KeyDelete, 0, tcell.ModNone}, wantKey{Delete, 0, nil}},
|
||||
{giveKey{tcell.KeyDelete, 0, tcell.ModAlt}, wantKey{Delete, 0, nil}},
|
||||
{giveKey{tcell.KeyBackspace, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
|
||||
{giveKey{tcell.KeyBS, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
|
||||
{giveKey{tcell.KeyCtrlH, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
|
||||
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModNone}, wantKey{BSpace, 0, nil}}, // actual "Backspace" keystroke
|
||||
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModAlt}, wantKey{AltBS, 0, nil}}, // actual "Alt+Backspace" keystroke
|
||||
{giveKey{tcell.KeyDEL, rune(tcell.KeyDEL), tcell.ModCtrl}, wantKey{BSpace, 0, nil}}, // actual "Ctrl+Backspace" keystroke
|
||||
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModShift}, wantKey{BSpace, 0, nil}}, // actual "Shift+Backspace" keystroke
|
||||
{giveKey{tcell.KeyCtrlH, 0, tcell.ModCtrl | tcell.ModAlt}, wantKey{BSpace, 0, nil}}, // actual "Ctrl+Alt+Backspace" keystroke
|
||||
{giveKey{tcell.KeyCtrlH, 0, tcell.ModCtrl | tcell.ModShift}, wantKey{BSpace, 0, nil}}, // actual "Ctrl+Shift+Backspace" keystroke
|
||||
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModShift | tcell.ModAlt}, wantKey{AltBS, 0, nil}}, // actual "Shift+Alt+Backspace" keystroke
|
||||
{giveKey{tcell.KeyCtrlH, 0, tcell.ModCtrl | tcell.ModAlt | tcell.ModShift}, wantKey{BSpace, 0, nil}}, // actual "Ctrl+Shift+Alt+Backspace" keystroke
|
||||
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModNone}, wantKey{Backspace, 0, nil}}, // actual "Backspace" keystroke
|
||||
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModAlt}, wantKey{AltBackspace, 0, nil}}, // actual "Alt+Backspace" keystroke
|
||||
{giveKey{tcell.KeyDEL, rune(tcell.KeyDEL), tcell.ModCtrl}, wantKey{Backspace, 0, nil}}, // actual "Ctrl+Backspace" keystroke
|
||||
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModShift}, wantKey{Backspace, 0, nil}}, // actual "Shift+Backspace" keystroke
|
||||
{giveKey{tcell.KeyCtrlH, 0, tcell.ModCtrl | tcell.ModAlt}, wantKey{Backspace, 0, nil}}, // actual "Ctrl+Alt+Backspace" keystroke
|
||||
{giveKey{tcell.KeyCtrlH, 0, tcell.ModCtrl | tcell.ModShift}, wantKey{Backspace, 0, nil}}, // actual "Ctrl+Shift+Backspace" keystroke
|
||||
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModShift | tcell.ModAlt}, wantKey{AltBackspace, 0, nil}}, // actual "Shift+Alt+Backspace" keystroke
|
||||
{giveKey{tcell.KeyCtrlH, 0, tcell.ModCtrl | tcell.ModAlt | tcell.ModShift}, wantKey{Backspace, 0, nil}}, // actual "Ctrl+Shift+Alt+Backspace" keystroke
|
||||
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModCtrl}, wantKey{CtrlH, 0, nil}}, // actual "Ctrl+H" keystroke
|
||||
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModCtrl | tcell.ModAlt}, wantKey{CtrlAlt, 'h', nil}}, // fabricated "Ctrl+Alt+H" keystroke
|
||||
{giveKey{tcell.KeyCtrlH, rune(tcell.KeyCtrlH), tcell.ModCtrl | tcell.ModShift}, wantKey{CtrlH, 0, nil}}, // actual "Ctrl+Shift+H" keystroke
|
||||
@@ -126,8 +126,8 @@ func TestGetCharEventKey(t *testing.T) {
|
||||
// section 4: (Alt+Shift)+Key(Up|Down|Left|Right)
|
||||
{giveKey{tcell.KeyUp, 0, tcell.ModNone}, wantKey{Up, 0, nil}},
|
||||
{giveKey{tcell.KeyDown, 0, tcell.ModAlt}, wantKey{AltDown, 0, nil}},
|
||||
{giveKey{tcell.KeyLeft, 0, tcell.ModShift}, wantKey{SLeft, 0, nil}},
|
||||
{giveKey{tcell.KeyRight, 0, tcell.ModShift | tcell.ModAlt}, wantKey{AltSRight, 0, nil}},
|
||||
{giveKey{tcell.KeyLeft, 0, tcell.ModShift}, wantKey{ShiftLeft, 0, nil}},
|
||||
{giveKey{tcell.KeyRight, 0, tcell.ModShift | tcell.ModAlt}, wantKey{AltShiftRight, 0, nil}},
|
||||
{giveKey{tcell.KeyUpLeft, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
|
||||
{giveKey{tcell.KeyUpRight, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
|
||||
{giveKey{tcell.KeyDownLeft, 0, tcell.ModNone}, wantKey{Invalid, 0, nil}}, // fabricated, unhandled
|
||||
@@ -161,11 +161,11 @@ func TestGetCharEventKey(t *testing.T) {
|
||||
|
||||
// section 7: Esc
|
||||
// KeyEsc and KeyEscape are aliases for KeyESC
|
||||
{giveKey{tcell.KeyEsc, rune(tcell.KeyEsc), tcell.ModNone}, wantKey{ESC, 0, nil}}, // fabricated
|
||||
{giveKey{tcell.KeyESC, rune(tcell.KeyESC), tcell.ModNone}, wantKey{ESC, 0, nil}}, // unhandled
|
||||
{giveKey{tcell.KeyEscape, rune(tcell.KeyEscape), tcell.ModNone}, wantKey{ESC, 0, nil}}, // fabricated, unhandled
|
||||
{giveKey{tcell.KeyESC, rune(tcell.KeyESC), tcell.ModCtrl}, wantKey{ESC, 0, nil}}, // actual Ctrl+[ keystroke
|
||||
{giveKey{tcell.KeyCtrlLeftSq, rune(tcell.KeyCtrlLeftSq), tcell.ModCtrl}, wantKey{ESC, 0, nil}}, // fabricated, unhandled
|
||||
{giveKey{tcell.KeyEsc, rune(tcell.KeyEsc), tcell.ModNone}, wantKey{Esc, 0, nil}}, // fabricated
|
||||
{giveKey{tcell.KeyESC, rune(tcell.KeyESC), tcell.ModNone}, wantKey{Esc, 0, nil}}, // unhandled
|
||||
{giveKey{tcell.KeyEscape, rune(tcell.KeyEscape), tcell.ModNone}, wantKey{Esc, 0, nil}}, // fabricated, unhandled
|
||||
{giveKey{tcell.KeyESC, rune(tcell.KeyESC), tcell.ModCtrl}, wantKey{Esc, 0, nil}}, // actual Ctrl+[ keystroke
|
||||
{giveKey{tcell.KeyCtrlLeftSq, rune(tcell.KeyCtrlLeftSq), tcell.ModCtrl}, wantKey{Esc, 0, nil}}, // fabricated, unhandled
|
||||
|
||||
// section 8: Invalid
|
||||
{giveKey{tcell.KeyRune, 'a', tcell.ModMeta}, wantKey{Rune, 'a', nil}}, // fabricated
|
||||
@@ -259,7 +259,7 @@ Quick reference
|
||||
37 LeftClick
|
||||
38 RightClick
|
||||
39 BTab
|
||||
40 BSpace
|
||||
40 Backspace
|
||||
41 Del
|
||||
42 PgUp
|
||||
43 PgDn
|
||||
@@ -272,7 +272,7 @@ Quick reference
|
||||
50 Insert
|
||||
51 SUp
|
||||
52 SDown
|
||||
53 SLeft
|
||||
53 ShiftLeft
|
||||
54 SRight
|
||||
55 F1
|
||||
56 F2
|
||||
@@ -288,15 +288,15 @@ Quick reference
|
||||
66 F12
|
||||
67 Change
|
||||
68 BackwardEOF
|
||||
69 AltBS
|
||||
69 AltBackspace
|
||||
70 AltUp
|
||||
71 AltDown
|
||||
72 AltLeft
|
||||
73 AltRight
|
||||
74 AltSUp
|
||||
75 AltSDown
|
||||
76 AltSLeft
|
||||
77 AltSRight
|
||||
76 AltShiftLeft
|
||||
77 AltShiftRight
|
||||
78 Alt
|
||||
79 CtrlAlt
|
||||
..
|
||||
|
||||
116
src/tui/tui.go
116
src/tui/tui.go
@@ -6,10 +6,13 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/junegunn/fzf/src/util"
|
||||
"github.com/rivo/uniseg"
|
||||
)
|
||||
|
||||
// Types of user action
|
||||
//
|
||||
//go:generate stringer -type=EventType
|
||||
type EventType int
|
||||
|
||||
const (
|
||||
@@ -41,7 +44,7 @@ const (
|
||||
CtrlX
|
||||
CtrlY
|
||||
CtrlZ
|
||||
ESC
|
||||
Esc
|
||||
CtrlSpace
|
||||
CtrlDelete
|
||||
|
||||
@@ -51,27 +54,12 @@ const (
|
||||
CtrlCaret
|
||||
CtrlSlash
|
||||
|
||||
Invalid
|
||||
Resize
|
||||
Mouse
|
||||
DoubleClick
|
||||
LeftClick
|
||||
RightClick
|
||||
SLeftClick
|
||||
SRightClick
|
||||
ScrollUp
|
||||
ScrollDown
|
||||
SScrollUp
|
||||
SScrollDown
|
||||
PreviewScrollUp
|
||||
PreviewScrollDown
|
||||
ShiftTab
|
||||
Backspace
|
||||
|
||||
BTab
|
||||
BSpace
|
||||
|
||||
Del
|
||||
PgUp
|
||||
PgDn
|
||||
Delete
|
||||
PageUp
|
||||
PageDown
|
||||
|
||||
Up
|
||||
Down
|
||||
@@ -81,11 +69,11 @@ const (
|
||||
End
|
||||
Insert
|
||||
|
||||
SUp
|
||||
SDown
|
||||
SLeft
|
||||
SRight
|
||||
SDelete
|
||||
ShiftUp
|
||||
ShiftDown
|
||||
ShiftLeft
|
||||
ShiftRight
|
||||
ShiftDelete
|
||||
|
||||
F1
|
||||
F2
|
||||
@@ -100,6 +88,38 @@ const (
|
||||
F11
|
||||
F12
|
||||
|
||||
AltBackspace
|
||||
|
||||
AltUp
|
||||
AltDown
|
||||
AltLeft
|
||||
AltRight
|
||||
|
||||
AltShiftUp
|
||||
AltShiftDown
|
||||
AltShiftLeft
|
||||
AltShiftRight
|
||||
|
||||
Alt
|
||||
CtrlAlt
|
||||
|
||||
Invalid
|
||||
|
||||
Mouse
|
||||
DoubleClick
|
||||
LeftClick
|
||||
RightClick
|
||||
SLeftClick
|
||||
SRightClick
|
||||
ScrollUp
|
||||
ScrollDown
|
||||
SScrollUp
|
||||
SScrollDown
|
||||
PreviewScrollUp
|
||||
PreviewScrollDown
|
||||
|
||||
// Events
|
||||
Resize
|
||||
Change
|
||||
BackwardEOF
|
||||
Start
|
||||
@@ -108,21 +128,8 @@ const (
|
||||
One
|
||||
Zero
|
||||
Result
|
||||
|
||||
AltBS
|
||||
|
||||
AltUp
|
||||
AltDown
|
||||
AltLeft
|
||||
AltRight
|
||||
|
||||
AltSUp
|
||||
AltSDown
|
||||
AltSLeft
|
||||
AltSRight
|
||||
|
||||
Alt
|
||||
CtrlAlt
|
||||
Jump
|
||||
JumpCancel
|
||||
)
|
||||
|
||||
func (t EventType) AsEvent() Event {
|
||||
@@ -142,6 +149,31 @@ func (e Event) Comparable() Event {
|
||||
return Event{e.Type, e.Char, nil}
|
||||
}
|
||||
|
||||
func (e Event) KeyName() string {
|
||||
if e.Type >= Invalid {
|
||||
return ""
|
||||
}
|
||||
|
||||
switch e.Type {
|
||||
case Rune:
|
||||
return string(e.Char)
|
||||
case Alt:
|
||||
return "alt-" + string(e.Char)
|
||||
case CtrlAlt:
|
||||
return "ctrl-alt-" + string(e.Char)
|
||||
case CtrlBackSlash:
|
||||
return "ctrl-\\"
|
||||
case CtrlRightBracket:
|
||||
return "ctrl-]"
|
||||
case CtrlCaret:
|
||||
return "ctrl-^"
|
||||
case CtrlSlash:
|
||||
return "ctrl-/"
|
||||
}
|
||||
|
||||
return util.ToKebabCase(e.Type.String())
|
||||
}
|
||||
|
||||
func Key(r rune) Event {
|
||||
return Event{Rune, r, nil}
|
||||
}
|
||||
@@ -646,7 +678,7 @@ func NoColorTheme() *ColorTheme {
|
||||
|
||||
func errorExit(message string) {
|
||||
fmt.Fprintln(os.Stderr, message)
|
||||
os.Exit(2)
|
||||
util.Exit(2)
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
38
src/util/atexit.go
Normal file
38
src/util/atexit.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var atExitFuncs []func()
|
||||
|
||||
// AtExit registers the function fn to be called on program termination.
|
||||
// The functions will be called in reverse order they were registered.
|
||||
func AtExit(fn func()) {
|
||||
if fn == nil {
|
||||
panic("AtExit called with nil func")
|
||||
}
|
||||
once := &sync.Once{}
|
||||
atExitFuncs = append(atExitFuncs, func() {
|
||||
once.Do(fn)
|
||||
})
|
||||
}
|
||||
|
||||
// RunAtExitFuncs runs any functions registered with AtExit().
|
||||
func RunAtExitFuncs() {
|
||||
fns := atExitFuncs
|
||||
for i := len(fns) - 1; i >= 0; i-- {
|
||||
fns[i]()
|
||||
}
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
24
src/util/atexit_test.go
Normal file
24
src/util/atexit_test.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAtExit(t *testing.T) {
|
||||
want := []int{3, 2, 1, 0}
|
||||
var called []int
|
||||
for i := 0; i < 4; i++ {
|
||||
n := i
|
||||
AtExit(func() { called = append(called, n) })
|
||||
}
|
||||
RunAtExitFuncs()
|
||||
if !reflect.DeepEqual(called, want) {
|
||||
t.Errorf("AtExit: want call order: %v got: %v", want, called)
|
||||
}
|
||||
|
||||
RunAtExitFuncs()
|
||||
if !reflect.DeepEqual(called, want) {
|
||||
t.Error("AtExit: should only call exit funcs once")
|
||||
}
|
||||
}
|
||||
@@ -163,7 +163,7 @@ func (chars *Chars) ToString() string {
|
||||
if runes := chars.optionalRunes(); runes != nil {
|
||||
return string(runes)
|
||||
}
|
||||
return string(chars.slice)
|
||||
return unsafe.String(unsafe.SliceData(chars.slice), len(chars.slice))
|
||||
}
|
||||
|
||||
func (chars *Chars) ToRunes() []rune {
|
||||
@@ -178,12 +178,12 @@ func (chars *Chars) ToRunes() []rune {
|
||||
return runes
|
||||
}
|
||||
|
||||
func (chars *Chars) CopyRunes(dest []rune) {
|
||||
func (chars *Chars) CopyRunes(dest []rune, from int) {
|
||||
if runes := chars.optionalRunes(); runes != nil {
|
||||
copy(dest, runes)
|
||||
copy(dest, runes[from:])
|
||||
return
|
||||
}
|
||||
for idx, b := range chars.slice[:len(dest)] {
|
||||
for idx, b := range chars.slice[from:][:len(dest)] {
|
||||
dest[idx] = rune(b)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,3 +176,15 @@ func RepeatToFill(str string, length int, limit int) string {
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
// ToKebabCase converts the given CamelCase string to kebab-case
|
||||
func ToKebabCase(s string) string {
|
||||
name := ""
|
||||
for i, r := range s {
|
||||
if i > 0 && r >= 'A' && r <= 'Z' {
|
||||
name += "-"
|
||||
}
|
||||
name += string(r)
|
||||
}
|
||||
return strings.ToLower(name)
|
||||
}
|
||||
|
||||
130
test/test_go.rb
130
test/test_go.rb
@@ -18,7 +18,6 @@ UNSETS = %w[
|
||||
FZF_ALT_C_COMMAND
|
||||
FZF_ALT_C_OPTS FZF_CTRL_R_OPTS
|
||||
FZF_API_KEY
|
||||
fish_history
|
||||
].freeze
|
||||
DEFAULT_TIMEOUT = 10
|
||||
|
||||
@@ -67,7 +66,7 @@ class Shell
|
||||
end
|
||||
|
||||
def fish
|
||||
UNSETS.map { |v| v + '= ' }.join + ' FZF_DEFAULT_OPTS=--no-scrollbar fish'
|
||||
"unset #{UNSETS.join(' ')}; FZF_DEFAULT_OPTS=--no-scrollbar fish_history= fish"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1469,6 +1468,19 @@ class TestGoFZF < TestBase
|
||||
assert_equal '3', readonce.chomp
|
||||
end
|
||||
|
||||
def test_jump_events
|
||||
tmux.send_keys "seq 1000 | #{fzf("--multi --jump-labels 12345 --bind 'ctrl-j:jump,jump:preview(echo jumped to {}),jump-cancel:preview(echo jump cancelled at {})'")}", :Enter
|
||||
tmux.until { |lines| assert_equal ' 1000/1000 (0)', lines[-2] }
|
||||
tmux.send_keys 'C-j'
|
||||
tmux.until { |lines| assert_includes lines[-7], '5 5' }
|
||||
tmux.send_keys '3'
|
||||
tmux.until { |lines| assert(lines.any? { _1.include?('jumped to 3') }) }
|
||||
tmux.send_keys 'C-j'
|
||||
tmux.until { |lines| assert_includes lines[-7], '5 5' }
|
||||
tmux.send_keys 'C-c'
|
||||
tmux.until { |lines| assert(lines.any? { _1.include?('jump cancelled at 3') }) }
|
||||
end
|
||||
|
||||
def test_pointer
|
||||
tmux.send_keys "seq 10 | #{fzf("--pointer '>>'")}", :Enter
|
||||
# Assert that specified pointer is displayed
|
||||
@@ -1720,7 +1732,7 @@ class TestGoFZF < TestBase
|
||||
end
|
||||
|
||||
def test_info_hidden
|
||||
tmux.send_keys 'seq 10 | fzf --info=hidden', :Enter
|
||||
tmux.send_keys 'seq 10 | fzf --info=hidden --no-separator', :Enter
|
||||
tmux.until { |lines| assert_equal '> 1', lines[-2] }
|
||||
end
|
||||
|
||||
@@ -2464,6 +2476,84 @@ class TestGoFZF < TestBase
|
||||
tmux.until { |lines| assert_equal 10, lines.match_count }
|
||||
end
|
||||
|
||||
def test_reload_disabled_case1
|
||||
tmux.send_keys "seq 100 | #{FZF} --query 99 --bind 'space:disable-search+reload(sleep 2; seq 1000)'", :Enter
|
||||
tmux.until do |lines|
|
||||
assert_equal 100, lines.item_count
|
||||
assert_equal 1, lines.match_count
|
||||
end
|
||||
tmux.send_keys :Space
|
||||
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||
tmux.send_keys :BSpace
|
||||
tmux.until { |lines| assert_equal 0, lines.match_count }
|
||||
tmux.until { |lines| assert_equal 1000, lines.match_count }
|
||||
end
|
||||
|
||||
def test_reload_disabled_case2
|
||||
tmux.send_keys "seq 100 | #{FZF} --query 99 --bind 'space:disable-search+reload-sync(sleep 2; seq 1000)'", :Enter
|
||||
tmux.until do |lines|
|
||||
assert_equal 100, lines.item_count
|
||||
assert_equal 1, lines.match_count
|
||||
end
|
||||
tmux.send_keys :Space
|
||||
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||
tmux.send_keys :BSpace
|
||||
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||
tmux.until { |lines| assert_equal 1000, lines.match_count }
|
||||
end
|
||||
|
||||
def test_reload_disabled_case3
|
||||
tmux.send_keys "seq 100 | #{FZF} --query 99 --bind 'space:disable-search+reload(sleep 2; seq 1000)+backward-delete-char'", :Enter
|
||||
tmux.until do |lines|
|
||||
assert_equal 100, lines.item_count
|
||||
assert_equal 1, lines.match_count
|
||||
end
|
||||
tmux.send_keys :Space
|
||||
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||
tmux.send_keys :BSpace
|
||||
tmux.until { |lines| assert_equal 0, lines.match_count }
|
||||
tmux.until { |lines| assert_equal 1000, lines.match_count }
|
||||
end
|
||||
|
||||
def test_reload_disabled_case4
|
||||
tmux.send_keys "seq 100 | #{FZF} --query 99 --bind 'space:disable-search+reload-sync(sleep 2; seq 1000)+backward-delete-char'", :Enter
|
||||
tmux.until do |lines|
|
||||
assert_equal 100, lines.item_count
|
||||
assert_equal 1, lines.match_count
|
||||
end
|
||||
tmux.send_keys :Space
|
||||
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||
tmux.send_keys :BSpace
|
||||
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||
tmux.until { |lines| assert_equal 1000, lines.match_count }
|
||||
end
|
||||
|
||||
def test_reload_disabled_case5
|
||||
tmux.send_keys "seq 100 | #{FZF} --query 99 --bind 'space:disable-search+reload(echo xx; sleep 2; seq 1000)'", :Enter
|
||||
tmux.until do |lines|
|
||||
assert_equal 100, lines.item_count
|
||||
assert_equal 1, lines.match_count
|
||||
end
|
||||
tmux.send_keys :Space
|
||||
tmux.until do |lines|
|
||||
assert_equal 1, lines.item_count
|
||||
assert_equal 1, lines.match_count
|
||||
end
|
||||
tmux.send_keys :BSpace
|
||||
tmux.until { |lines| assert_equal 1001, lines.match_count }
|
||||
end
|
||||
|
||||
def test_reload_disabled_case6
|
||||
tmux.send_keys "seq 1000 | #{FZF} --disabled --bind 'change:reload:sleep 0.5; seq {q}'", :Enter
|
||||
tmux.until { |lines| assert_equal 1000, lines.match_count }
|
||||
tmux.send_keys '9'
|
||||
tmux.until { |lines| assert_equal 9, lines.match_count }
|
||||
tmux.send_keys '9'
|
||||
tmux.until { |lines| assert_equal 99, lines.match_count }
|
||||
|
||||
# TODO: How do we verify if an intermediate empty list is not shown?
|
||||
end
|
||||
|
||||
def test_scroll_off
|
||||
tmux.send_keys "seq 1000 | #{FZF} --scroll-off=3 --bind l:last", :Enter
|
||||
tmux.until { |lines| assert_equal 1000, lines.item_count }
|
||||
@@ -2800,6 +2890,27 @@ class TestGoFZF < TestBase
|
||||
end
|
||||
end
|
||||
|
||||
def test_labels_variables
|
||||
tmux.send_keys ': | fzf --border --border-label foobar --preview "echo \$FZF_BORDER_LABEL // \$FZF_PREVIEW_LABEL" --preview-label barfoo --bind "space:change-border-label(barbaz)+change-preview-label(bazbar)+refresh-preview,enter:transform-border-label(echo 123)+transform-preview-label(echo 456)+refresh-preview"', :Enter
|
||||
tmux.until do
|
||||
assert_includes(_1[0], '─foobar─')
|
||||
assert_includes(_1[1], '─barfoo─')
|
||||
assert_includes(_1[2], ' foobar // barfoo ')
|
||||
end
|
||||
tmux.send_keys :Space
|
||||
tmux.until do
|
||||
assert_includes(_1[0], '─barbaz─')
|
||||
assert_includes(_1[1], '─bazbar─')
|
||||
assert_includes(_1[2], ' barbaz // bazbar ')
|
||||
end
|
||||
tmux.send_keys :Enter
|
||||
tmux.until do
|
||||
assert_includes(_1[0], '─123─')
|
||||
assert_includes(_1[1], '─456─')
|
||||
assert_includes(_1[2], ' 123 // 456 ')
|
||||
end
|
||||
end
|
||||
|
||||
def test_info_separator_unicode
|
||||
tmux.send_keys 'seq 100 | fzf -q55', :Enter
|
||||
tmux.until { assert_includes(_1[-2], ' 1/100 ─') }
|
||||
@@ -3003,7 +3114,7 @@ class TestGoFZF < TestBase
|
||||
end
|
||||
tmux.send_keys :t
|
||||
tmux.until do |lines|
|
||||
assert_includes lines[-2], '+T'
|
||||
assert_includes lines[-2], '+t'
|
||||
end
|
||||
tmux.send_keys :BSpace
|
||||
tmux.until do |lines|
|
||||
@@ -3015,7 +3126,7 @@ class TestGoFZF < TestBase
|
||||
tmux.send_keys '4'
|
||||
tmux.until do |lines|
|
||||
assert_equal 28, lines.match_count
|
||||
refute_includes lines[-2], '+T'
|
||||
refute_includes lines[-2], '+t'
|
||||
end
|
||||
tmux.send_keys :BSpace
|
||||
tmux.until do |lines|
|
||||
@@ -3024,11 +3135,11 @@ class TestGoFZF < TestBase
|
||||
end
|
||||
tmux.send_keys :t
|
||||
tmux.until do |lines|
|
||||
assert_includes lines[-2], '+T'
|
||||
assert_includes lines[-2], '+t'
|
||||
end
|
||||
tmux.send_keys :Up
|
||||
tmux.until do |lines|
|
||||
refute_includes lines[-2], '+T'
|
||||
refute_includes lines[-2], '+t'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3308,7 +3419,10 @@ module CompletionTest
|
||||
tmux.send_keys 'cat /tmp/fzf\ test/**', :Tab
|
||||
tmux.until { |lines| assert_operator lines.match_count, :>, 0 }
|
||||
tmux.send_keys 'foobar$'
|
||||
tmux.until { |lines| assert_equal 1, lines.match_count }
|
||||
tmux.until do |lines|
|
||||
assert_equal 1, lines.match_count
|
||||
assert lines.any_include?('> /tmp/fzf test/foobar')
|
||||
end
|
||||
tmux.send_keys :Enter
|
||||
tmux.until(true) { |lines| assert_equal 'cat /tmp/fzf\ test/foobar', lines[-1] }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user