mirror of
https://github.com/junegunn/fzf.git
synced 2025-07-26 17:51:58 -07:00
Compare commits
24 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
f2ce233a6d | ||
|
6a75e30941 | ||
|
a3244c4892 | ||
|
a5ad8fd3bd | ||
|
deccdb1ec5 | ||
|
12a43b5e62 | ||
|
e1291aa6d2 | ||
|
bb26f32ac7 | ||
|
4d928001b8 | ||
|
c4baa6a10c | ||
|
71dec3dc5e | ||
|
e5017c0431 | ||
|
cbb5134874 | ||
|
ff248d566d | ||
|
6ccc12c332 | ||
|
2a669e9a17 | ||
|
5130abe76f | ||
|
fa7c8977a8 | ||
|
24fa183297 | ||
|
131aa5dd15 | ||
|
a06ccc928f | ||
|
d09ad13208 | ||
|
8ac37d5927 | ||
|
7ef0e50507 |
12
CHANGELOG.md
12
CHANGELOG.md
@@ -1,6 +1,18 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
0.16.2
|
||||
------
|
||||
- Dropped ncurses dependency
|
||||
- Binaries for freebsd, openbsd, arm5, arm6, arm7, and arm8
|
||||
- Official 24-bit color support
|
||||
- Added support for composite actions in `--bind`. Multiple actions can be
|
||||
chained using `+` separator.
|
||||
- e.g. `fzf --bind 'ctrl-y:execute(echo -n {} | pbcopy)+abort'`
|
||||
- `--preview-window` with size 0 is allowed. This is used to make fzf execute
|
||||
preview command in the background without displaying the result.
|
||||
- Minor bug fixes and improvements
|
||||
|
||||
0.16.1
|
||||
------
|
||||
- Fixed `--height` option to properly fill the window with the background
|
||||
|
18
install
18
install
@@ -2,7 +2,7 @@
|
||||
|
||||
set -u
|
||||
|
||||
version=0.16.1
|
||||
version=0.16.2
|
||||
auto_completion=
|
||||
key_bindings=
|
||||
update_config=2
|
||||
@@ -160,10 +160,18 @@ archi=$(uname -sm)
|
||||
binary_available=1
|
||||
binary_error=""
|
||||
case "$archi" in
|
||||
Darwin\ x86_64) download fzf-$version-darwin_${binary_arch:-amd64} ;;
|
||||
Darwin\ i*86) download fzf-$version-darwin_${binary_arch:-386} ;;
|
||||
Linux\ x86_64) download fzf-$version-linux_${binary_arch:-amd64} ;;
|
||||
Linux\ i*86) download fzf-$version-linux_${binary_arch:-386} ;;
|
||||
Darwin\ *64) download fzf-$version-darwin_${binary_arch:-amd64} ;;
|
||||
Darwin\ *86) download fzf-$version-darwin_${binary_arch:-386} ;;
|
||||
Linux\ *64) download fzf-$version-linux_${binary_arch:-amd64} ;;
|
||||
Linux\ *86) download fzf-$version-linux_${binary_arch:-386} ;;
|
||||
Linux\ armv5*) download fzf-$version-linux_${binary_arch:-arm5} ;;
|
||||
Linux\ armv6*) download fzf-$version-linux_${binary_arch:-arm6} ;;
|
||||
Linux\ armv7*) download fzf-$version-linux_${binary_arch:-arm7} ;;
|
||||
Linux\ armv8*) download fzf-$version-linux_${binary_arch:-arm8} ;;
|
||||
FreeBSD\ *64) download fzf-$version-freebsd_${binary_arch:-amd64} ;;
|
||||
FreeBSD\ *86) download fzf-$version-freebsd_${binary_arch:-386} ;;
|
||||
OpenBSD\ *64) download fzf-$version-openbsd_${binary_arch:-amd64} ;;
|
||||
OpenBSD\ *86) download fzf-$version-openbsd_${binary_arch:-386} ;;
|
||||
*) binary_available=0 binary_error=1 ;;
|
||||
esac
|
||||
|
||||
|
@@ -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 "Jan 2017" "fzf 0.16.1" "fzf-tmux - open fzf in tmux split pane"
|
||||
.TH fzf-tmux 1 "Jan 2017" "fzf 0.16.2" "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 "Jan 2017" "fzf 0.16.1" "fzf - a command-line fuzzy finder"
|
||||
.TH fzf 1 "Jan 2017" "fzf 0.16.2" "fzf - a command-line fuzzy finder"
|
||||
|
||||
.SH NAME
|
||||
fzf - a command-line fuzzy finder
|
||||
@@ -209,8 +209,7 @@ Number of spaces for a tab character (default: 8)
|
||||
Color configuration. The name of the base color scheme is followed by custom
|
||||
color mappings. Ansi color code of -1 denotes terminal default
|
||||
foreground/background color. You can also specify 24-bit color in \fB#rrggbb\fR
|
||||
format, but the support for 24-bit colors is experimental and only works when
|
||||
\fB--height\fR option is used.
|
||||
format.
|
||||
|
||||
.RS
|
||||
e.g. \fBfzf --color=bg+:24\fR
|
||||
@@ -279,6 +278,9 @@ Determine the layout of the preview window. If the argument ends with
|
||||
\fBtoggle-preview\fR action is triggered. Long lines are truncated by default.
|
||||
Line wrap can be enabled with \fB:wrap\fR flag.
|
||||
|
||||
If size is given as 0, preview window will not be visible, but fzf will still
|
||||
execute the command in the background.
|
||||
|
||||
.RS
|
||||
.B POSITION: (default: right)
|
||||
\fBup
|
||||
@@ -481,17 +483,21 @@ e.g. \fBfzf --bind=ctrl-j:accept,ctrl-k:kill-line\fR
|
||||
\fBselect-all\fR
|
||||
\fBtoggle\fR
|
||||
\fBtoggle-all\fR
|
||||
\fBtoggle-down\fR \fIctrl-i (tab)\fR
|
||||
\fBtoggle-in\fR (\fB--reverse\fR ? \fBtoggle-up\fR : \fBtoggle-down\fR)
|
||||
\fBtoggle-out\fR (\fB--reverse\fR ? \fBtoggle-down\fR : \fBtoggle-up\fR)
|
||||
\fBtoggle+down\fR \fIctrl-i (tab)\fR
|
||||
\fBtoggle-in\fR (\fB--reverse\fR ? \fBtoggle+up\fR : \fBtoggle+down\fR)
|
||||
\fBtoggle-out\fR (\fB--reverse\fR ? \fBtoggle+down\fR : \fBtoggle+up\fR)
|
||||
\fBtoggle-preview\fR
|
||||
\fBtoggle-sort\fR (equivalent to \fB--toggle-sort\fR)
|
||||
\fBtoggle-up\fR \fIbtab (shift-tab)\fR
|
||||
\fBtoggle+up\fR \fIbtab (shift-tab)\fR
|
||||
\fBunix-line-discard\fR \fIctrl-u\fR
|
||||
\fBunix-word-rubout\fR \fIctrl-w\fR
|
||||
\fBup\fR \fIctrl-k ctrl-p up\fR
|
||||
\fByank\fR \fIctrl-y\fR
|
||||
|
||||
Multiple actions can be chained using \fB+\fR separator.
|
||||
|
||||
\fBfzf --bind 'ctrl-a:select-all+accept'\fR
|
||||
|
||||
With \fBexecute(...)\fR action, you can execute arbitrary commands without
|
||||
leaving fzf. For example, you can turn fzf into a simple file browser by
|
||||
binding \fBenter\fR key to \fBless\fR command like follows.
|
||||
|
@@ -167,9 +167,12 @@ function! s:common_sink(action, lines) abort
|
||||
endfunction
|
||||
|
||||
function! s:get_color(attr, ...)
|
||||
let gui = has('termguicolors') && &termguicolors
|
||||
let fam = gui ? 'gui' : 'cterm'
|
||||
let pat = gui ? '^#[a-f0-9]\+' : '^[0-9]\+$'
|
||||
for group in a:000
|
||||
let code = synIDattr(synIDtrans(hlID(group)), a:attr, 'cterm')
|
||||
if code =~ '^[0-9]\+$'
|
||||
let code = synIDattr(synIDtrans(hlID(group)), a:attr, fam)
|
||||
if code =~? pat
|
||||
return code
|
||||
endif
|
||||
endfor
|
||||
|
@@ -56,7 +56,7 @@ __fzf_history__() (
|
||||
shopt -u nocaseglob nocasematch
|
||||
line=$(
|
||||
HISTTIMEFORMAT= history |
|
||||
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS +s --tac --no-reverse -n2..,.. --tiebreak=index --toggle-sort=ctrl-r $FZF_CTRL_R_OPTS +m" $(__fzfcmd) |
|
||||
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS +s --tac -n2..,.. --tiebreak=index --toggle-sort=ctrl-r $FZF_CTRL_R_OPTS +m" $(__fzfcmd) |
|
||||
command grep '^ *[0-9]') &&
|
||||
if [[ $- =~ H ]]; then
|
||||
sed 's/^ *\([0-9]*\)\** .*/!\1/' <<< "$line"
|
||||
|
@@ -45,7 +45,7 @@ function fzf_key_bindings
|
||||
function fzf-history-widget -d "Show command history"
|
||||
set -q FZF_TMUX_HEIGHT; or set FZF_TMUX_HEIGHT 40%
|
||||
begin
|
||||
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT $FZF_DEFAULT_OPTS +s --no-reverse --tiebreak=index $FZF_CTRL_R_OPTS +m"
|
||||
set -lx FZF_DEFAULT_OPTS "--height $FZF_TMUX_HEIGHT $FZF_DEFAULT_OPTS +s --tiebreak=index $FZF_CTRL_R_OPTS +m"
|
||||
history | eval (__fzfcmd) -q '(commandline)' | read -l result
|
||||
and commandline -- $result
|
||||
end
|
||||
|
@@ -55,7 +55,7 @@ fzf-history-widget() {
|
||||
local selected num
|
||||
setopt localoptions noglobsubst pipefail 2> /dev/null
|
||||
selected=( $(fc -l 1 |
|
||||
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} --reverse $FZF_DEFAULT_OPTS +s --tac --no-reverse -n2..,.. --tiebreak=index --toggle-sort=ctrl-r $FZF_CTRL_R_OPTS +m --query=${(q)LBUFFER}" $(__fzfcmd)) )
|
||||
FZF_DEFAULT_OPTS="--height ${FZF_TMUX_HEIGHT:-40%} $FZF_DEFAULT_OPTS +s --tac -n2..,.. --tiebreak=index --toggle-sort=ctrl-r $FZF_CTRL_R_OPTS --query=${(q)LBUFFER} +m" $(__fzfcmd)) )
|
||||
local ret=$?
|
||||
if [ -n "$selected" ]; then
|
||||
num=$selected[1]
|
||||
|
67
src/Makefile
67
src/Makefile
@@ -15,18 +15,35 @@ SRCDIR := $(GOPATH)/src/github.com/junegunn/fzf/src
|
||||
DOCKEROPTS := -i -t -v $(ROOTDIR):/fzf/src
|
||||
BINARY32 := fzf-$(GOOS)_386
|
||||
BINARY64 := fzf-$(GOOS)_amd64
|
||||
BINARYARM5 := fzf-$(GOOS)_arm5
|
||||
BINARYARM6 := fzf-$(GOOS)_arm6
|
||||
BINARYARM7 := fzf-$(GOOS)_arm7
|
||||
BINARYARM8 := fzf-$(GOOS)_arm8
|
||||
VERSION := $(shell awk -F= '/version =/ {print $$2}' constants.go | tr -d "\" ")
|
||||
RELEASE32 := fzf-$(VERSION)-$(GOOS)_386
|
||||
RELEASE64 := fzf-$(VERSION)-$(GOOS)_amd64
|
||||
RELEASEARM5 := fzf-$(VERSION)-$(GOOS)_arm5
|
||||
RELEASEARM6 := fzf-$(VERSION)-$(GOOS)_arm6
|
||||
RELEASEARM7 := fzf-$(VERSION)-$(GOOS)_arm7
|
||||
RELEASEARM8 := fzf-$(VERSION)-$(GOOS)_arm8
|
||||
export GOPATH
|
||||
|
||||
# https://en.wikipedia.org/wiki/Uname
|
||||
UNAME_M := $(shell uname -m)
|
||||
ifeq ($(UNAME_M),x86_64)
|
||||
BINARY := $(BINARY64)
|
||||
else ifeq ($(UNAME_M),amd64)
|
||||
BINARY := $(BINARY64)
|
||||
else ifeq ($(UNAME_M),i686)
|
||||
BINARY := $(BINARY32)
|
||||
else ifeq ($(UNAME_M),i386)
|
||||
BINARY := $(BINARY32)
|
||||
else ifeq ($(UNAME_M),armv5l)
|
||||
BINARY := $(BINARYARM5)
|
||||
else ifeq ($(UNAME_M),armv6l)
|
||||
BINARY := $(BINARYARM6)
|
||||
else ifeq ($(UNAME_M),armv7l)
|
||||
BINARY := $(BINARYARM7)
|
||||
else
|
||||
$(error "Build on $(UNAME_M) is not supported, yet.")
|
||||
endif
|
||||
@@ -35,22 +52,39 @@ all: fzf/$(BINARY)
|
||||
|
||||
ifeq ($(GOOS),windows)
|
||||
release: fzf/$(BINARY32) fzf/$(BINARY64)
|
||||
-cd fzf && cp $(BINARY32) $(RELEASE32).exe && zip $(RELEASE32).zip $(RELEASE32).exe
|
||||
cd fzf && cp $(BINARY64) $(RELEASE64).exe && zip $(RELEASE64).zip $(RELEASE64).exe && \
|
||||
rm -f $(RELEASE32).exe $(RELEASE64).exe
|
||||
cd fzf && cp $(BINARY32) $(RELEASE32).exe && zip $(RELEASE32).zip $(RELEASE32).exe
|
||||
cd fzf && cp $(BINARY64) $(RELEASE64).exe && zip $(RELEASE64).zip $(RELEASE64).exe
|
||||
cd fzf && rm -f $(RELEASE32).exe $(RELEASE64).exe
|
||||
else ifeq ($(GOOS),linux)
|
||||
release: fzf/$(BINARY32) fzf/$(BINARY64) fzf/$(BINARYARM5) fzf/$(BINARYARM6) fzf/$(BINARYARM7) fzf/$(BINARYARM8)
|
||||
cd fzf && cp $(BINARY32) $(RELEASE32) && tar -czf $(RELEASE32).tgz $(RELEASE32)
|
||||
cd fzf && cp $(BINARY64) $(RELEASE64) && tar -czf $(RELEASE64).tgz $(RELEASE64)
|
||||
cd fzf && cp $(BINARYARM5) $(RELEASEARM5) && tar -czf $(RELEASEARM5).tgz $(RELEASEARM5)
|
||||
cd fzf && cp $(BINARYARM6) $(RELEASEARM6) && tar -czf $(RELEASEARM6).tgz $(RELEASEARM6)
|
||||
cd fzf && cp $(BINARYARM7) $(RELEASEARM7) && tar -czf $(RELEASEARM7).tgz $(RELEASEARM7)
|
||||
cd fzf && cp $(BINARYARM8) $(RELEASEARM8) && tar -czf $(RELEASEARM8).tgz $(RELEASEARM8)
|
||||
cd fzf && rm -f $(RELEASE32) $(RELEASE64) $(RELEASEARM5) $(RELEASEARM6) $(RELEASEARM7) $(RELEASEARM8)
|
||||
else
|
||||
release: test fzf/$(BINARY32) fzf/$(BINARY64)
|
||||
-cd fzf && cp $(BINARY32) $(RELEASE32) && tar -czf $(RELEASE32).tgz $(RELEASE32)
|
||||
cd fzf && cp $(BINARY64) $(RELEASE64) && tar -czf $(RELEASE64).tgz $(RELEASE64) && \
|
||||
rm -f $(RELEASE32) $(RELEASE64)
|
||||
release: fzf/$(BINARY32) fzf/$(BINARY64)
|
||||
cd fzf && cp $(BINARY32) $(RELEASE32) && tar -czf $(RELEASE32).tgz $(RELEASE32)
|
||||
cd fzf && cp $(BINARY64) $(RELEASE64) && tar -czf $(RELEASE64).tgz $(RELEASE64)
|
||||
cd fzf && rm -f $(RELEASE32) $(RELEASE64)
|
||||
endif
|
||||
|
||||
release-all: clean test
|
||||
GOOS=darwin make release
|
||||
GOOS=linux make release
|
||||
GOOS=freebsd make release
|
||||
GOOS=openbsd make release
|
||||
GOOS=windows make release
|
||||
|
||||
$(SRCDIR):
|
||||
mkdir -p $(shell dirname $(SRCDIR))
|
||||
ln -s $(ROOTDIR) $(SRCDIR)
|
||||
|
||||
deps: $(SRCDIR) $(SOURCES)
|
||||
cd $(SRCDIR) && go get -tags "$(TAGS)"
|
||||
./deps
|
||||
|
||||
android-build: $(SRCDIR)
|
||||
cd $(SRCDIR) && GOARCH=arm GOARM=7 CGO_ENABLED=1 go get
|
||||
@@ -59,7 +93,7 @@ android-build: $(SRCDIR)
|
||||
rm -f $(RELEASEARM7)
|
||||
|
||||
test: deps
|
||||
SHELL=/bin/sh GOOS=$(GOOS) go test -v -tags "$(TAGS)" ./...
|
||||
SHELL=/bin/sh GOOS= go test -v -tags "$(TAGS)" ./...
|
||||
|
||||
install: $(BINDIR)/fzf
|
||||
|
||||
@@ -70,10 +104,23 @@ clean:
|
||||
cd fzf && rm -f fzf-*
|
||||
|
||||
fzf/$(BINARY32): deps
|
||||
cd fzf && GOARCH=386 CGO_ENABLED=1 go build -a -ldflags "-w -extldflags=$(LDFLAGS)" -tags "$(TAGS)" -o $(BINARY32)
|
||||
cd fzf && GOARCH=386 go build -a -ldflags "-w -extldflags=$(LDFLAGS)" -tags "$(TAGS)" -o $(BINARY32)
|
||||
|
||||
fzf/$(BINARY64): deps
|
||||
cd fzf && go build -a -ldflags "-w -extldflags=$(LDFLAGS)" -tags "$(TAGS)" -o $(BINARY64)
|
||||
cd fzf && GOARCH=amd64 go build -a -ldflags "-w -extldflags=$(LDFLAGS)" -tags "$(TAGS)" -o $(BINARY64)
|
||||
|
||||
# https://github.com/golang/go/wiki/GoArm
|
||||
fzf/$(BINARYARM5): deps
|
||||
cd fzf && GOARCH=arm GOARM=5 go build -a -ldflags "-w -extldflags=$(LDFLAGS)" -tags "$(TAGS)" -o $(BINARYARM5)
|
||||
|
||||
fzf/$(BINARYARM6): deps
|
||||
cd fzf && GOARCH=arm GOARM=6 go build -a -ldflags "-w -extldflags=$(LDFLAGS)" -tags "$(TAGS)" -o $(BINARYARM6)
|
||||
|
||||
fzf/$(BINARYARM7): deps
|
||||
cd fzf && GOARCH=arm GOARM=7 go build -a -ldflags "-w -extldflags=$(LDFLAGS)" -tags "$(TAGS)" -o $(BINARYARM7)
|
||||
|
||||
fzf/$(BINARYARM8): deps
|
||||
cd fzf && GOARCH=arm64 go build -a -ldflags "-w -extldflags=$(LDFLAGS)" -tags "$(TAGS)" -o $(BINARYARM8)
|
||||
|
||||
$(BINDIR)/fzf: fzf/$(BINARY) | $(BINDIR)
|
||||
cp -f fzf/$(BINARY) $(BINDIR)
|
||||
|
@@ -59,20 +59,31 @@ Unit tests can be run with `make test`. Integration tests are written in Ruby
|
||||
script that should be run on tmux.
|
||||
|
||||
```sh
|
||||
cd src
|
||||
|
||||
# Unit tests
|
||||
make test
|
||||
|
||||
# Integration tests
|
||||
ruby ../test/test_go.rb
|
||||
|
||||
# Build binary for the platform
|
||||
make
|
||||
|
||||
# Install the executable to ../bin directory
|
||||
make install
|
||||
|
||||
# Integration tests
|
||||
ruby ../test/test_go.rb
|
||||
# Make release archives
|
||||
make release
|
||||
|
||||
# Make release archives for all supported platforms
|
||||
make release-all
|
||||
```
|
||||
|
||||
Third-party libraries used
|
||||
--------------------------
|
||||
|
||||
- [ncurses][ncurses]
|
||||
- ~[ncurses][ncurses]~
|
||||
- [mattn/go-runewidth](https://github.com/mattn/go-runewidth)
|
||||
- Licensed under [MIT](http://mattn.mit-license.org)
|
||||
- [mattn/go-shellwords](https://github.com/mattn/go-shellwords)
|
||||
|
@@ -8,7 +8,7 @@ import (
|
||||
|
||||
const (
|
||||
// Current version
|
||||
version = "0.16.1"
|
||||
version = "0.16.2"
|
||||
|
||||
// Core
|
||||
coordinatorDelayMax time.Duration = 100 * time.Millisecond
|
||||
|
18
src/deps
Executable file
18
src/deps
Executable file
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
if [ -z "$GOPATH" ]; then
|
||||
echo '$GOPATH not defined'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
reset() (
|
||||
cd "$GOPATH/src/$1"
|
||||
export GIT_DIR="$(pwd)/.git"
|
||||
[ "$(git rev-parse HEAD)" = "$2" ] ||
|
||||
(git fetch && git reset --hard "$2")
|
||||
)
|
||||
|
||||
reset github.com/junegunn/go-isatty 66b8e73f3f5cda9f96b69efd03dd3d7fc4a5cdb8
|
||||
reset github.com/junegunn/go-runewidth 63c378b851290989b19ca955468386485f118c65
|
||||
reset github.com/junegunn/go-shellwords 33bd8f1ebe16d6e5eb688cc885749a63059e9167
|
||||
reset golang.org/x/crypto abc5fa7ad02123a41f02bf1391c9760f7586e608
|
296
src/options.go
296
src/options.go
@@ -171,8 +171,7 @@ type Options struct {
|
||||
Filter *string
|
||||
ToggleSort bool
|
||||
Expect map[int]string
|
||||
Keymap map[int]actionType
|
||||
Execmap map[int]string
|
||||
Keymap map[int][]action
|
||||
Preview previewOpts
|
||||
PrintQuery bool
|
||||
ReadZero bool
|
||||
@@ -220,8 +219,7 @@ func defaultOptions() *Options {
|
||||
Filter: nil,
|
||||
ToggleSort: false,
|
||||
Expect: make(map[int]string),
|
||||
Keymap: make(map[int]actionType),
|
||||
Execmap: make(map[int]string),
|
||||
Keymap: make(map[int][]action),
|
||||
Preview: previewOpts{"", posRight, sizeSpec{50, true}, false, false},
|
||||
PrintQuery: false,
|
||||
ReadZero: false,
|
||||
@@ -578,23 +576,25 @@ func firstKey(keymap map[int]string) int {
|
||||
const (
|
||||
escapedColon = 0
|
||||
escapedComma = 1
|
||||
escapedPlus = 2
|
||||
)
|
||||
|
||||
func parseKeymap(keymap map[int]actionType, execmap map[int]string, str string) {
|
||||
func parseKeymap(keymap map[int][]action, str string) {
|
||||
if executeRegexp == nil {
|
||||
// Backreferences are not supported.
|
||||
// "~!@#$%^&*;/|".each_char.map { |c| Regexp.escape(c) }.map { |c| "#{c}[^#{c}]*#{c}" }.join('|')
|
||||
executeRegexp = regexp.MustCompile(
|
||||
"(?s):execute(-multi)?:.*|:execute(-multi)?(\\([^)]*\\)|\\[[^\\]]*\\]|~[^~]*~|![^!]*!|@[^@]*@|\\#[^\\#]*\\#|\\$[^\\$]*\\$|%[^%]*%|\\^[^\\^]*\\^|&[^&]*&|\\*[^\\*]*\\*|;[^;]*;|/[^/]*/|\\|[^\\|]*\\|)")
|
||||
"(?si):execute(-multi)?:.+|:execute(-multi)?(\\([^)]*\\)|\\[[^\\]]*\\]|~[^~]*~|![^!]*!|@[^@]*@|\\#[^\\#]*\\#|\\$[^\\$]*\\$|%[^%]*%|\\^[^\\^]*\\^|&[^&]*&|\\*[^\\*]*\\*|;[^;]*;|/[^/]*/|\\|[^\\|]*\\|)")
|
||||
}
|
||||
masked := executeRegexp.ReplaceAllStringFunc(str, func(src string) string {
|
||||
if strings.HasPrefix(src, ":execute-multi") {
|
||||
if src[len(":execute")] == '-' {
|
||||
return ":execute-multi(" + strings.Repeat(" ", len(src)-len(":execute-multi()")) + ")"
|
||||
}
|
||||
return ":execute(" + strings.Repeat(" ", len(src)-len(":execute()")) + ")"
|
||||
})
|
||||
masked = strings.Replace(masked, "::", string([]rune{escapedColon, ':'}), -1)
|
||||
masked = strings.Replace(masked, ",:", string([]rune{escapedComma, ':'}), -1)
|
||||
masked = strings.Replace(masked, "+:", string([]rune{escapedPlus, ':'}), -1)
|
||||
|
||||
idx := 0
|
||||
for _, pairStr := range strings.Split(masked, ",") {
|
||||
@@ -610,151 +610,173 @@ func parseKeymap(keymap map[int]actionType, execmap map[int]string, str string)
|
||||
key = ':' + tui.AltZ
|
||||
} else if len(pair[0]) == 1 && pair[0][0] == escapedComma {
|
||||
key = ',' + tui.AltZ
|
||||
} else if len(pair[0]) == 1 && pair[0][0] == escapedPlus {
|
||||
key = '+' + tui.AltZ
|
||||
} else {
|
||||
keys := parseKeyChords(pair[0], "key name required")
|
||||
key = firstKey(keys)
|
||||
}
|
||||
|
||||
act := origPairStr[len(pair[0])+1 : len(origPairStr)]
|
||||
actLower := strings.ToLower(act)
|
||||
switch actLower {
|
||||
case "ignore":
|
||||
keymap[key] = actIgnore
|
||||
case "beginning-of-line":
|
||||
keymap[key] = actBeginningOfLine
|
||||
case "abort":
|
||||
keymap[key] = actAbort
|
||||
case "accept":
|
||||
keymap[key] = actAccept
|
||||
case "print-query":
|
||||
keymap[key] = actPrintQuery
|
||||
case "backward-char":
|
||||
keymap[key] = actBackwardChar
|
||||
case "backward-delete-char":
|
||||
keymap[key] = actBackwardDeleteChar
|
||||
case "backward-word":
|
||||
keymap[key] = actBackwardWord
|
||||
case "clear-screen":
|
||||
keymap[key] = actClearScreen
|
||||
case "delete-char":
|
||||
keymap[key] = actDeleteChar
|
||||
case "delete-char/eof":
|
||||
keymap[key] = actDeleteCharEOF
|
||||
case "end-of-line":
|
||||
keymap[key] = actEndOfLine
|
||||
case "cancel":
|
||||
keymap[key] = actCancel
|
||||
case "forward-char":
|
||||
keymap[key] = actForwardChar
|
||||
case "forward-word":
|
||||
keymap[key] = actForwardWord
|
||||
case "jump":
|
||||
keymap[key] = actJump
|
||||
case "jump-accept":
|
||||
keymap[key] = actJumpAccept
|
||||
case "kill-line":
|
||||
keymap[key] = actKillLine
|
||||
case "kill-word":
|
||||
keymap[key] = actKillWord
|
||||
case "unix-line-discard", "line-discard":
|
||||
keymap[key] = actUnixLineDiscard
|
||||
case "unix-word-rubout", "word-rubout":
|
||||
keymap[key] = actUnixWordRubout
|
||||
case "yank":
|
||||
keymap[key] = actYank
|
||||
case "backward-kill-word":
|
||||
keymap[key] = actBackwardKillWord
|
||||
case "toggle-down":
|
||||
keymap[key] = actToggleDown
|
||||
case "toggle-up":
|
||||
keymap[key] = actToggleUp
|
||||
case "toggle-in":
|
||||
keymap[key] = actToggleIn
|
||||
case "toggle-out":
|
||||
keymap[key] = actToggleOut
|
||||
case "toggle-all":
|
||||
keymap[key] = actToggleAll
|
||||
case "select-all":
|
||||
keymap[key] = actSelectAll
|
||||
case "deselect-all":
|
||||
keymap[key] = actDeselectAll
|
||||
case "toggle":
|
||||
keymap[key] = actToggle
|
||||
case "down":
|
||||
keymap[key] = actDown
|
||||
case "up":
|
||||
keymap[key] = actUp
|
||||
case "page-up":
|
||||
keymap[key] = actPageUp
|
||||
case "page-down":
|
||||
keymap[key] = actPageDown
|
||||
case "half-page-up":
|
||||
keymap[key] = actHalfPageUp
|
||||
case "half-page-down":
|
||||
keymap[key] = actHalfPageDown
|
||||
case "previous-history":
|
||||
keymap[key] = actPreviousHistory
|
||||
case "next-history":
|
||||
keymap[key] = actNextHistory
|
||||
case "toggle-preview":
|
||||
keymap[key] = actTogglePreview
|
||||
case "toggle-sort":
|
||||
keymap[key] = actToggleSort
|
||||
case "preview-up":
|
||||
keymap[key] = actPreviewUp
|
||||
case "preview-down":
|
||||
keymap[key] = actPreviewDown
|
||||
case "preview-page-up":
|
||||
keymap[key] = actPreviewPageUp
|
||||
case "preview-page-down":
|
||||
keymap[key] = actPreviewPageDown
|
||||
default:
|
||||
if isExecuteAction(actLower) {
|
||||
var offset int
|
||||
if strings.HasPrefix(actLower, "execute-multi") {
|
||||
keymap[key] = actExecuteMulti
|
||||
offset = len("execute-multi")
|
||||
} else {
|
||||
keymap[key] = actExecute
|
||||
offset = len("execute")
|
||||
}
|
||||
if act[offset] == ':' {
|
||||
execmap[key] = act[offset+1:]
|
||||
} else {
|
||||
execmap[key] = act[offset+1 : len(act)-1]
|
||||
}
|
||||
} else {
|
||||
errorExit("unknown action: " + act)
|
||||
}
|
||||
idx2 := len(pair[0]) + 1
|
||||
specs := strings.Split(pair[1], "+")
|
||||
actions := make([]action, 0, len(specs))
|
||||
appendAction := func(types ...actionType) {
|
||||
actions = append(actions, toActions(types...)...)
|
||||
}
|
||||
prevSpec := ""
|
||||
for specIndex, maskedSpec := range specs {
|
||||
spec := origPairStr[idx2 : idx2+len(maskedSpec)]
|
||||
idx2 += len(maskedSpec) + 1
|
||||
spec = prevSpec + spec
|
||||
specLower := strings.ToLower(spec)
|
||||
switch specLower {
|
||||
case "ignore":
|
||||
appendAction(actIgnore)
|
||||
case "beginning-of-line":
|
||||
appendAction(actBeginningOfLine)
|
||||
case "abort":
|
||||
appendAction(actAbort)
|
||||
case "accept":
|
||||
appendAction(actAccept)
|
||||
case "print-query":
|
||||
appendAction(actPrintQuery)
|
||||
case "backward-char":
|
||||
appendAction(actBackwardChar)
|
||||
case "backward-delete-char":
|
||||
appendAction(actBackwardDeleteChar)
|
||||
case "backward-word":
|
||||
appendAction(actBackwardWord)
|
||||
case "clear-screen":
|
||||
appendAction(actClearScreen)
|
||||
case "delete-char":
|
||||
appendAction(actDeleteChar)
|
||||
case "delete-char/eof":
|
||||
appendAction(actDeleteCharEOF)
|
||||
case "end-of-line":
|
||||
appendAction(actEndOfLine)
|
||||
case "cancel":
|
||||
appendAction(actCancel)
|
||||
case "forward-char":
|
||||
appendAction(actForwardChar)
|
||||
case "forward-word":
|
||||
appendAction(actForwardWord)
|
||||
case "jump":
|
||||
appendAction(actJump)
|
||||
case "jump-accept":
|
||||
appendAction(actJumpAccept)
|
||||
case "kill-line":
|
||||
appendAction(actKillLine)
|
||||
case "kill-word":
|
||||
appendAction(actKillWord)
|
||||
case "unix-line-discard", "line-discard":
|
||||
appendAction(actUnixLineDiscard)
|
||||
case "unix-word-rubout", "word-rubout":
|
||||
appendAction(actUnixWordRubout)
|
||||
case "yank":
|
||||
appendAction(actYank)
|
||||
case "backward-kill-word":
|
||||
appendAction(actBackwardKillWord)
|
||||
case "toggle-down":
|
||||
appendAction(actToggle, actDown)
|
||||
case "toggle-up":
|
||||
appendAction(actToggle, actUp)
|
||||
case "toggle-in":
|
||||
appendAction(actToggleIn)
|
||||
case "toggle-out":
|
||||
appendAction(actToggleOut)
|
||||
case "toggle-all":
|
||||
appendAction(actToggleAll)
|
||||
case "select-all":
|
||||
appendAction(actSelectAll)
|
||||
case "deselect-all":
|
||||
appendAction(actDeselectAll)
|
||||
case "toggle":
|
||||
appendAction(actToggle)
|
||||
case "down":
|
||||
appendAction(actDown)
|
||||
case "up":
|
||||
appendAction(actUp)
|
||||
case "page-up":
|
||||
appendAction(actPageUp)
|
||||
case "page-down":
|
||||
appendAction(actPageDown)
|
||||
case "half-page-up":
|
||||
appendAction(actHalfPageUp)
|
||||
case "half-page-down":
|
||||
appendAction(actHalfPageDown)
|
||||
case "previous-history":
|
||||
appendAction(actPreviousHistory)
|
||||
case "next-history":
|
||||
appendAction(actNextHistory)
|
||||
case "toggle-preview":
|
||||
appendAction(actTogglePreview)
|
||||
case "toggle-sort":
|
||||
appendAction(actToggleSort)
|
||||
case "preview-up":
|
||||
appendAction(actPreviewUp)
|
||||
case "preview-down":
|
||||
appendAction(actPreviewDown)
|
||||
case "preview-page-up":
|
||||
appendAction(actPreviewPageUp)
|
||||
case "preview-page-down":
|
||||
appendAction(actPreviewPageDown)
|
||||
default:
|
||||
t := isExecuteAction(specLower)
|
||||
if t == actIgnore {
|
||||
errorExit("unknown action: " + spec)
|
||||
} else {
|
||||
var offset int
|
||||
if t == actExecuteMulti {
|
||||
offset = len("execute-multi")
|
||||
} else {
|
||||
offset = len("execute")
|
||||
}
|
||||
if spec[offset] == ':' {
|
||||
if specIndex == len(specs)-1 {
|
||||
actions = append(actions, action{t: t, a: spec[offset+1:]})
|
||||
} else {
|
||||
prevSpec = spec + "+"
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
actions = append(actions, action{t: t, a: spec[offset+1 : len(spec)-1]})
|
||||
}
|
||||
}
|
||||
}
|
||||
prevSpec = ""
|
||||
}
|
||||
keymap[key] = actions
|
||||
}
|
||||
}
|
||||
|
||||
func isExecuteAction(str string) bool {
|
||||
if !strings.HasPrefix(str, "execute") || len(str) < len("execute()") {
|
||||
return false
|
||||
func isExecuteAction(str string) actionType {
|
||||
t := actExecute
|
||||
if !strings.HasPrefix(str, "execute") || len(str) < len("execute(") {
|
||||
return actIgnore
|
||||
}
|
||||
|
||||
b := str[len("execute")]
|
||||
if strings.HasPrefix(str, "execute-multi") {
|
||||
if len(str) < len("execute-multi()") {
|
||||
return false
|
||||
if len(str) < len("execute-multi(") {
|
||||
return actIgnore
|
||||
}
|
||||
t = actExecuteMulti
|
||||
b = str[len("execute-multi")]
|
||||
}
|
||||
e := str[len(str)-1]
|
||||
if b == ':' || b == '(' && e == ')' || b == '[' && e == ']' ||
|
||||
b == e && strings.ContainsAny(string(b), "~!@#$%^&*;/|") {
|
||||
return true
|
||||
return t
|
||||
}
|
||||
return false
|
||||
return actIgnore
|
||||
}
|
||||
|
||||
func parseToggleSort(keymap map[int]actionType, str string) {
|
||||
func parseToggleSort(keymap map[int][]action, str string) {
|
||||
keys := parseKeyChords(str, "key name required")
|
||||
if len(keys) != 1 {
|
||||
errorExit("multiple keys specified")
|
||||
}
|
||||
keymap[firstKey(keys)] = actToggleSort
|
||||
keymap[firstKey(keys)] = toActions(actToggleSort)
|
||||
}
|
||||
|
||||
func strLines(str string) []string {
|
||||
@@ -801,7 +823,7 @@ func parsePreviewWindow(opts *previewOpts, input string) {
|
||||
opts.wrap = false
|
||||
|
||||
tokens := strings.Split(input, ":")
|
||||
sizeRegex := regexp.MustCompile("^[1-9][0-9]*%?$")
|
||||
sizeRegex := regexp.MustCompile("^[0-9]+%?$")
|
||||
for _, token := range tokens {
|
||||
switch token {
|
||||
case "hidden":
|
||||
@@ -919,7 +941,7 @@ func parseOptions(opts *Options, allArgs []string) {
|
||||
case "--tiebreak":
|
||||
opts.Criteria = parseTiebreak(nextString(allArgs, &i, "sort criterion required"))
|
||||
case "--bind":
|
||||
parseKeymap(opts.Keymap, opts.Execmap, nextString(allArgs, &i, "bind expression required"))
|
||||
parseKeymap(opts.Keymap, nextString(allArgs, &i, "bind expression required"))
|
||||
case "--color":
|
||||
spec := optionalNextString(allArgs, &i)
|
||||
if len(spec) == 0 {
|
||||
@@ -1089,7 +1111,7 @@ func parseOptions(opts *Options, allArgs []string) {
|
||||
} else if match, value := optString(arg, "--color="); match {
|
||||
opts.Theme = parseTheme(opts.Theme, value)
|
||||
} else if match, value := optString(arg, "--bind="); match {
|
||||
parseKeymap(opts.Keymap, opts.Execmap, value)
|
||||
parseKeymap(opts.Keymap, value)
|
||||
} else if match, value := optString(arg, "--history="); match {
|
||||
setHistory(value)
|
||||
} else if match, value := optString(arg, "--history-size="); match {
|
||||
@@ -1145,20 +1167,22 @@ func postProcessOptions(opts *Options) {
|
||||
// Default actions for CTRL-N / CTRL-P when --history is set
|
||||
if opts.History != nil {
|
||||
if _, prs := opts.Keymap[tui.CtrlP]; !prs {
|
||||
opts.Keymap[tui.CtrlP] = actPreviousHistory
|
||||
opts.Keymap[tui.CtrlP] = toActions(actPreviousHistory)
|
||||
}
|
||||
if _, prs := opts.Keymap[tui.CtrlN]; !prs {
|
||||
opts.Keymap[tui.CtrlN] = actNextHistory
|
||||
opts.Keymap[tui.CtrlN] = toActions(actNextHistory)
|
||||
}
|
||||
}
|
||||
|
||||
// Extend the default key map
|
||||
keymap := defaultKeymap()
|
||||
for key, act := range opts.Keymap {
|
||||
if act == actToggleSort {
|
||||
opts.ToggleSort = true
|
||||
for key, actions := range opts.Keymap {
|
||||
for _, act := range actions {
|
||||
if act.t == actToggleSort {
|
||||
opts.ToggleSort = true
|
||||
}
|
||||
}
|
||||
keymap[key] = act
|
||||
keymap[key] = actions
|
||||
}
|
||||
opts.Keymap = keymap
|
||||
|
||||
|
@@ -225,49 +225,51 @@ func TestParseKeysWithComma(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBind(t *testing.T) {
|
||||
check := func(action actionType, expected actionType) {
|
||||
if action != expected {
|
||||
t.Errorf("%d != %d", action, expected)
|
||||
}
|
||||
}
|
||||
checkString := func(action string, expected string) {
|
||||
if action != expected {
|
||||
t.Errorf("%d != %d", action, expected)
|
||||
}
|
||||
}
|
||||
keymap := defaultKeymap()
|
||||
execmap := make(map[int]string)
|
||||
check(actBeginningOfLine, keymap[tui.CtrlA])
|
||||
parseKeymap(keymap, execmap,
|
||||
"ctrl-a:kill-line,ctrl-b:toggle-sort,c:page-up,alt-z:page-down,"+
|
||||
"f1:execute(ls {}),f2:execute/echo {}, {}, {}/,f3:execute[echo '({})'],f4:execute;less {};,"+
|
||||
"alt-a:execute@echo (,),[,],/,:,;,%,{}@,alt-b:execute;echo (,),[,],/,:,@,%,{};"+
|
||||
",,:abort,::accept,X:execute:\nfoobar,Y:execute(baz)")
|
||||
check(actKillLine, keymap[tui.CtrlA])
|
||||
check(actToggleSort, keymap[tui.CtrlB])
|
||||
check(actPageUp, keymap[tui.AltZ+'c'])
|
||||
check(actAbort, keymap[tui.AltZ+','])
|
||||
check(actAccept, keymap[tui.AltZ+':'])
|
||||
check(actPageDown, keymap[tui.AltZ])
|
||||
check(actExecute, keymap[tui.F1])
|
||||
check(actExecute, keymap[tui.F2])
|
||||
check(actExecute, keymap[tui.F3])
|
||||
check(actExecute, keymap[tui.F4])
|
||||
checkString("ls {}", execmap[tui.F1])
|
||||
checkString("echo {}, {}, {}", execmap[tui.F2])
|
||||
checkString("echo '({})'", execmap[tui.F3])
|
||||
checkString("less {}", execmap[tui.F4])
|
||||
checkString("echo (,),[,],/,:,;,%,{}", execmap[tui.AltA])
|
||||
checkString("echo (,),[,],/,:,@,%,{}", execmap[tui.AltB])
|
||||
checkString("\nfoobar,Y:execute(baz)", execmap[tui.AltZ+'X'])
|
||||
check := func(keyName int, arg1 string, types ...actionType) {
|
||||
if len(keymap[keyName]) != len(types) {
|
||||
t.Errorf("invalid number of actions (%d != %d)", len(types), len(keymap[keyName]))
|
||||
return
|
||||
}
|
||||
for idx, action := range keymap[keyName] {
|
||||
if types[idx] != action.t {
|
||||
t.Errorf("invalid action type (%d != %d)", types[idx], action.t)
|
||||
}
|
||||
}
|
||||
if len(arg1) > 0 && keymap[keyName][0].a != arg1 {
|
||||
t.Errorf("invalid action argument: (%s != %s)", arg1, keymap[keyName][0].a)
|
||||
}
|
||||
}
|
||||
check(tui.CtrlA, "", actBeginningOfLine)
|
||||
parseKeymap(keymap,
|
||||
"ctrl-a:kill-line,ctrl-b:toggle-sort+up+down,c:page-up,alt-z:page-down,"+
|
||||
"f1:execute(ls {})+abort,f2:execute/echo {}, {}, {}/,f3:execute[echo '({})'],f4:execute;less {};,"+
|
||||
"alt-a:execute-Multi@echo (,),[,],/,:,;,%,{}@,alt-b:execute;echo (,),[,],/,:,@,%,{};,"+
|
||||
"x:Execute(foo+bar),X:execute/bar+baz/"+
|
||||
",,:abort,::accept,+:execute:++\nfoobar,Y:execute(baz)+up")
|
||||
check(tui.CtrlA, "", actKillLine)
|
||||
check(tui.CtrlB, "", actToggleSort, actUp, actDown)
|
||||
check(tui.AltZ+'c', "", actPageUp)
|
||||
check(tui.AltZ+',', "", actAbort)
|
||||
check(tui.AltZ+':', "", actAccept)
|
||||
check(tui.AltZ, "", actPageDown)
|
||||
check(tui.F1, "ls {}", actExecute, actAbort)
|
||||
check(tui.F2, "echo {}, {}, {}", actExecute)
|
||||
check(tui.F3, "echo '({})'", actExecute)
|
||||
check(tui.F4, "less {}", actExecute)
|
||||
check(tui.AltZ+'x', "foo+bar", actExecute)
|
||||
check(tui.AltZ+'X', "bar+baz", actExecute)
|
||||
check(tui.AltA, "echo (,),[,],/,:,;,%,{}", actExecuteMulti)
|
||||
check(tui.AltB, "echo (,),[,],/,:,@,%,{}", actExecute)
|
||||
check(tui.AltZ+'+', "++\nfoobar,Y:execute(baz)+up", actExecute)
|
||||
|
||||
for idx, char := range []rune{'~', '!', '@', '#', '$', '%', '^', '&', '*', '|', ';', '/'} {
|
||||
parseKeymap(keymap, execmap, fmt.Sprintf("%d:execute%cfoobar%c", idx%10, char, char))
|
||||
checkString("foobar", execmap[tui.AltZ+int([]rune(fmt.Sprintf("%d", idx%10))[0])])
|
||||
parseKeymap(keymap, fmt.Sprintf("%d:execute%cfoobar%c", idx%10, char, char))
|
||||
check(tui.AltZ+int([]rune(fmt.Sprintf("%d", idx%10))[0]), "foobar", actExecute)
|
||||
}
|
||||
|
||||
parseKeymap(keymap, execmap, "f1:abort")
|
||||
check(actAbort, keymap[tui.F1])
|
||||
parseKeymap(keymap, "f1:abort")
|
||||
check(tui.F1, "", actAbort)
|
||||
}
|
||||
|
||||
func TestColorSpec(t *testing.T) {
|
||||
@@ -327,7 +329,7 @@ func TestDefaultCtrlNP(t *testing.T) {
|
||||
opts := defaultOptions()
|
||||
parseOptions(opts, words)
|
||||
postProcessOptions(opts)
|
||||
if opts.Keymap[key] != expected {
|
||||
if opts.Keymap[key][0].t != expected {
|
||||
t.Error()
|
||||
}
|
||||
}
|
||||
|
179
src/terminal.go
179
src/terminal.go
@@ -72,8 +72,7 @@ type Terminal struct {
|
||||
toggleSort bool
|
||||
delimiter Delimiter
|
||||
expect map[int]string
|
||||
keymap map[int]actionType
|
||||
execmap map[int]string
|
||||
keymap map[int][]action
|
||||
pressed string
|
||||
printQuery bool
|
||||
history *History
|
||||
@@ -148,6 +147,11 @@ const (
|
||||
reqQuit
|
||||
)
|
||||
|
||||
type action struct {
|
||||
t actionType
|
||||
a string
|
||||
}
|
||||
|
||||
type actionType int
|
||||
|
||||
const (
|
||||
@@ -203,54 +207,62 @@ const (
|
||||
actExecuteMulti
|
||||
)
|
||||
|
||||
func defaultKeymap() map[int]actionType {
|
||||
keymap := make(map[int]actionType)
|
||||
keymap[tui.Invalid] = actInvalid
|
||||
keymap[tui.Resize] = actClearScreen
|
||||
keymap[tui.CtrlA] = actBeginningOfLine
|
||||
keymap[tui.CtrlB] = actBackwardChar
|
||||
keymap[tui.CtrlC] = actAbort
|
||||
keymap[tui.CtrlG] = actAbort
|
||||
keymap[tui.CtrlQ] = actAbort
|
||||
keymap[tui.ESC] = actAbort
|
||||
keymap[tui.CtrlD] = actDeleteCharEOF
|
||||
keymap[tui.CtrlE] = actEndOfLine
|
||||
keymap[tui.CtrlF] = actForwardChar
|
||||
keymap[tui.CtrlH] = actBackwardDeleteChar
|
||||
keymap[tui.BSpace] = actBackwardDeleteChar
|
||||
keymap[tui.Tab] = actToggleDown
|
||||
keymap[tui.BTab] = actToggleUp
|
||||
keymap[tui.CtrlJ] = actDown
|
||||
keymap[tui.CtrlK] = actUp
|
||||
keymap[tui.CtrlL] = actClearScreen
|
||||
keymap[tui.CtrlM] = actAccept
|
||||
keymap[tui.CtrlN] = actDown
|
||||
keymap[tui.CtrlP] = actUp
|
||||
keymap[tui.CtrlU] = actUnixLineDiscard
|
||||
keymap[tui.CtrlW] = actUnixWordRubout
|
||||
keymap[tui.CtrlY] = actYank
|
||||
func toActions(types ...actionType) []action {
|
||||
actions := make([]action, len(types))
|
||||
for idx, t := range types {
|
||||
actions[idx] = action{t: t, a: ""}
|
||||
}
|
||||
return actions
|
||||
}
|
||||
|
||||
keymap[tui.AltB] = actBackwardWord
|
||||
keymap[tui.SLeft] = actBackwardWord
|
||||
keymap[tui.AltF] = actForwardWord
|
||||
keymap[tui.SRight] = actForwardWord
|
||||
keymap[tui.AltD] = actKillWord
|
||||
keymap[tui.AltBS] = actBackwardKillWord
|
||||
func defaultKeymap() map[int][]action {
|
||||
keymap := make(map[int][]action)
|
||||
keymap[tui.Invalid] = toActions(actInvalid)
|
||||
keymap[tui.Resize] = toActions(actClearScreen)
|
||||
keymap[tui.CtrlA] = toActions(actBeginningOfLine)
|
||||
keymap[tui.CtrlB] = toActions(actBackwardChar)
|
||||
keymap[tui.CtrlC] = toActions(actAbort)
|
||||
keymap[tui.CtrlG] = toActions(actAbort)
|
||||
keymap[tui.CtrlQ] = toActions(actAbort)
|
||||
keymap[tui.ESC] = toActions(actAbort)
|
||||
keymap[tui.CtrlD] = toActions(actDeleteCharEOF)
|
||||
keymap[tui.CtrlE] = toActions(actEndOfLine)
|
||||
keymap[tui.CtrlF] = toActions(actForwardChar)
|
||||
keymap[tui.CtrlH] = toActions(actBackwardDeleteChar)
|
||||
keymap[tui.BSpace] = toActions(actBackwardDeleteChar)
|
||||
keymap[tui.Tab] = toActions(actToggleDown)
|
||||
keymap[tui.BTab] = toActions(actToggleUp)
|
||||
keymap[tui.CtrlJ] = toActions(actDown)
|
||||
keymap[tui.CtrlK] = toActions(actUp)
|
||||
keymap[tui.CtrlL] = toActions(actClearScreen)
|
||||
keymap[tui.CtrlM] = toActions(actAccept)
|
||||
keymap[tui.CtrlN] = toActions(actDown)
|
||||
keymap[tui.CtrlP] = toActions(actUp)
|
||||
keymap[tui.CtrlU] = toActions(actUnixLineDiscard)
|
||||
keymap[tui.CtrlW] = toActions(actUnixWordRubout)
|
||||
keymap[tui.CtrlY] = toActions(actYank)
|
||||
|
||||
keymap[tui.Up] = actUp
|
||||
keymap[tui.Down] = actDown
|
||||
keymap[tui.Left] = actBackwardChar
|
||||
keymap[tui.Right] = actForwardChar
|
||||
keymap[tui.AltB] = toActions(actBackwardWord)
|
||||
keymap[tui.SLeft] = toActions(actBackwardWord)
|
||||
keymap[tui.AltF] = toActions(actForwardWord)
|
||||
keymap[tui.SRight] = toActions(actForwardWord)
|
||||
keymap[tui.AltD] = toActions(actKillWord)
|
||||
keymap[tui.AltBS] = toActions(actBackwardKillWord)
|
||||
|
||||
keymap[tui.Home] = actBeginningOfLine
|
||||
keymap[tui.End] = actEndOfLine
|
||||
keymap[tui.Del] = actDeleteChar
|
||||
keymap[tui.PgUp] = actPageUp
|
||||
keymap[tui.PgDn] = actPageDown
|
||||
keymap[tui.Up] = toActions(actUp)
|
||||
keymap[tui.Down] = toActions(actDown)
|
||||
keymap[tui.Left] = toActions(actBackwardChar)
|
||||
keymap[tui.Right] = toActions(actForwardChar)
|
||||
|
||||
keymap[tui.Rune] = actRune
|
||||
keymap[tui.Mouse] = actMouse
|
||||
keymap[tui.DoubleClick] = actAccept
|
||||
keymap[tui.Home] = toActions(actBeginningOfLine)
|
||||
keymap[tui.End] = toActions(actEndOfLine)
|
||||
keymap[tui.Del] = toActions(actDeleteChar)
|
||||
keymap[tui.PgUp] = toActions(actPageUp)
|
||||
keymap[tui.PgDn] = toActions(actPageDown)
|
||||
|
||||
keymap[tui.Rune] = toActions(actRune)
|
||||
keymap[tui.Mouse] = toActions(actMouse)
|
||||
keymap[tui.DoubleClick] = toActions(actAccept)
|
||||
return keymap
|
||||
}
|
||||
|
||||
@@ -293,8 +305,11 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
||||
return util.Max(maxHeight, minHeight)
|
||||
}
|
||||
renderer = tui.NewLightRenderer(opts.Theme, opts.Black, opts.Mouse, opts.Tabstop, maxHeightFunc)
|
||||
} else {
|
||||
} else if tui.HasFullscreenRenderer() {
|
||||
renderer = tui.NewFullscreenRenderer(opts.Theme, opts.Black, opts.Mouse)
|
||||
} else {
|
||||
renderer = tui.NewLightRenderer(opts.Theme, opts.Black, opts.Mouse, opts.Tabstop,
|
||||
func(h int) int { return h })
|
||||
}
|
||||
wordRubout := "[^[:alnum:]][[:alnum:]]"
|
||||
wordNext := "[[:alnum:]][^[:alnum:]]|(.$)"
|
||||
@@ -323,7 +338,6 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
||||
delimiter: opts.Delimiter,
|
||||
expect: opts.Expect,
|
||||
keymap: opts.Keymap,
|
||||
execmap: opts.Execmap,
|
||||
pressed: "",
|
||||
printQuery: opts.PrintQuery,
|
||||
history: opts.History,
|
||||
@@ -495,9 +509,11 @@ func (t *Terminal) resizeWindows() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
previewVisible := t.isPreviewEnabled() && t.preview.size.size > 0
|
||||
minAreaWidth := minWidth
|
||||
minAreaHeight := minHeight
|
||||
if t.isPreviewEnabled() {
|
||||
if previewVisible {
|
||||
switch t.preview.position {
|
||||
case posUp, posDown:
|
||||
minAreaHeight *= 2
|
||||
@@ -517,7 +533,7 @@ func (t *Terminal) resizeWindows() {
|
||||
|
||||
width := screenWidth - marginInt[1] - marginInt[3]
|
||||
height := screenHeight - marginInt[0] - marginInt[2]
|
||||
if t.isPreviewEnabled() {
|
||||
if previewVisible {
|
||||
createPreviewWindow := func(y int, x int, w int, h int) {
|
||||
t.bwindow = t.tui.NewWindow(y, x, w, h, true)
|
||||
pwidth := w - 4
|
||||
@@ -875,7 +891,7 @@ func numLinesMax(str string, max int) int {
|
||||
}
|
||||
|
||||
func (t *Terminal) printPreview() {
|
||||
if !t.isPreviewEnabled() {
|
||||
if !t.hasPreviewWindow() {
|
||||
return
|
||||
}
|
||||
t.pwindow.Erase()
|
||||
@@ -960,7 +976,7 @@ func (t *Terminal) printAll() {
|
||||
|
||||
func (t *Terminal) refresh() {
|
||||
if !t.suppress {
|
||||
if t.isPreviewEnabled() {
|
||||
if t.hasPreviewWindow() {
|
||||
t.tui.RefreshWindows([]tui.Window{t.bwindow, t.pwindow, t.window})
|
||||
} else {
|
||||
t.tui.RefreshWindows([]tui.Window{t.window})
|
||||
@@ -1093,12 +1109,16 @@ func (t *Terminal) executeCommand(template string, items []*Item) {
|
||||
t.refresh()
|
||||
}
|
||||
|
||||
func (t *Terminal) hasPreviewWindow() bool {
|
||||
func (t *Terminal) hasPreviewer() bool {
|
||||
return t.previewBox != nil
|
||||
}
|
||||
|
||||
func (t *Terminal) isPreviewEnabled() bool {
|
||||
return t.previewBox != nil && t.previewer.enabled
|
||||
return t.hasPreviewer() && t.previewer.enabled
|
||||
}
|
||||
|
||||
func (t *Terminal) hasPreviewWindow() bool {
|
||||
return t.pwindow != nil && t.isPreviewEnabled()
|
||||
}
|
||||
|
||||
func (t *Terminal) currentItem() *Item {
|
||||
@@ -1160,7 +1180,7 @@ func (t *Terminal) Loop() {
|
||||
}()
|
||||
}
|
||||
|
||||
if t.hasPreviewWindow() {
|
||||
if t.hasPreviewer() {
|
||||
go func() {
|
||||
for {
|
||||
var request *Item
|
||||
@@ -1314,13 +1334,21 @@ func (t *Terminal) Loop() {
|
||||
}
|
||||
}
|
||||
|
||||
var doAction func(actionType, int) bool
|
||||
doAction = func(action actionType, mapkey int) bool {
|
||||
switch action {
|
||||
var doAction func(action, int) bool
|
||||
doActions := func(actions []action, mapkey int) bool {
|
||||
for _, action := range actions {
|
||||
if !doAction(action, mapkey) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
doAction = func(a action, mapkey int) bool {
|
||||
switch a.t {
|
||||
case actIgnore:
|
||||
case actExecute:
|
||||
if t.cy >= 0 && t.cy < t.merger.Length() {
|
||||
t.executeCommand(t.execmap[mapkey], []*Item{t.currentItem()})
|
||||
t.executeCommand(a.a, []*Item{t.currentItem()})
|
||||
}
|
||||
case actExecuteMulti:
|
||||
if len(t.selected) > 0 {
|
||||
@@ -1328,16 +1356,17 @@ func (t *Terminal) Loop() {
|
||||
for i, sel := range t.sortSelected() {
|
||||
sels[i] = sel.item
|
||||
}
|
||||
t.executeCommand(t.execmap[mapkey], sels)
|
||||
t.executeCommand(a.a, sels)
|
||||
} else {
|
||||
return doAction(actExecute, mapkey)
|
||||
return doAction(action{t: actExecute, a: a.a}, mapkey)
|
||||
}
|
||||
case actInvalid:
|
||||
t.mutex.Unlock()
|
||||
return false
|
||||
case actTogglePreview:
|
||||
if t.hasPreviewWindow() {
|
||||
if t.hasPreviewer() {
|
||||
t.previewer.enabled = !t.previewer.enabled
|
||||
t.tui.Clear()
|
||||
t.resizeWindows()
|
||||
cnt := t.merger.Length()
|
||||
if t.previewer.enabled && cnt > 0 && cnt > t.cy {
|
||||
@@ -1351,19 +1380,19 @@ func (t *Terminal) Loop() {
|
||||
t.mutex.Unlock()
|
||||
return false
|
||||
case actPreviewUp:
|
||||
if t.isPreviewEnabled() {
|
||||
if t.hasPreviewWindow() {
|
||||
scrollPreview(-1)
|
||||
}
|
||||
case actPreviewDown:
|
||||
if t.isPreviewEnabled() {
|
||||
if t.hasPreviewWindow() {
|
||||
scrollPreview(1)
|
||||
}
|
||||
case actPreviewPageUp:
|
||||
if t.isPreviewEnabled() {
|
||||
if t.hasPreviewWindow() {
|
||||
scrollPreview(-t.pwindow.Height())
|
||||
}
|
||||
case actPreviewPageDown:
|
||||
if t.isPreviewEnabled() {
|
||||
if t.hasPreviewWindow() {
|
||||
scrollPreview(t.pwindow.Height())
|
||||
}
|
||||
case actBeginningOfLine:
|
||||
@@ -1431,14 +1460,14 @@ func (t *Terminal) Loop() {
|
||||
}
|
||||
case actToggleIn:
|
||||
if t.reverse {
|
||||
return doAction(actToggleUp, mapkey)
|
||||
return doAction(action{t: actToggleUp}, mapkey)
|
||||
}
|
||||
return doAction(actToggleDown, mapkey)
|
||||
return doAction(action{t: actToggleDown}, mapkey)
|
||||
case actToggleOut:
|
||||
if t.reverse {
|
||||
return doAction(actToggleDown, mapkey)
|
||||
return doAction(action{t: actToggleDown}, mapkey)
|
||||
}
|
||||
return doAction(actToggleUp, mapkey)
|
||||
return doAction(action{t: actToggleUp}, mapkey)
|
||||
case actToggleDown:
|
||||
if t.multi && t.merger.Length() > 0 {
|
||||
toggle()
|
||||
@@ -1540,7 +1569,7 @@ func (t *Terminal) Loop() {
|
||||
}
|
||||
t.vmove(me.S)
|
||||
req(reqList)
|
||||
} else if t.isPreviewEnabled() && t.pwindow.Enclose(my, mx) {
|
||||
} else if t.hasPreviewWindow() && t.pwindow.Enclose(my, mx) {
|
||||
scrollPreview(-me.S)
|
||||
}
|
||||
} else if t.window.Enclose(my, mx) {
|
||||
@@ -1558,7 +1587,7 @@ func (t *Terminal) Loop() {
|
||||
// Double-click
|
||||
if my >= min {
|
||||
if t.vset(t.offset+my-min) && t.cy < t.merger.Length() {
|
||||
return doAction(t.keymap[tui.DoubleClick], tui.DoubleClick)
|
||||
return doActions(t.keymap[tui.DoubleClick], tui.DoubleClick)
|
||||
}
|
||||
}
|
||||
} else if me.Down {
|
||||
@@ -1580,14 +1609,14 @@ func (t *Terminal) Loop() {
|
||||
changed := false
|
||||
mapkey := event.Type
|
||||
if t.jumping == jumpDisabled {
|
||||
action := t.keymap[mapkey]
|
||||
actions := t.keymap[mapkey]
|
||||
if mapkey == tui.Rune {
|
||||
mapkey = int(event.Char) + int(tui.AltZ)
|
||||
if act, prs := t.keymap[mapkey]; prs {
|
||||
action = act
|
||||
actions = act
|
||||
}
|
||||
}
|
||||
if !doAction(action, mapkey) {
|
||||
if !doActions(actions, mapkey) {
|
||||
continue
|
||||
}
|
||||
// Truncate the query if it's too long
|
||||
|
45
src/tui/dummy.go
Normal file
45
src/tui/dummy.go
Normal file
@@ -0,0 +1,45 @@
|
||||
// +build !ncurses
|
||||
// +build !tcell
|
||||
// +build !windows
|
||||
|
||||
package tui
|
||||
|
||||
type Attr int
|
||||
|
||||
func HasFullscreenRenderer() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (a Attr) Merge(b Attr) Attr {
|
||||
return a | b
|
||||
}
|
||||
|
||||
const (
|
||||
AttrRegular Attr = Attr(0)
|
||||
Bold = Attr(1)
|
||||
Dim = Attr(1 << 1)
|
||||
Italic = Attr(1 << 2)
|
||||
Underline = Attr(1 << 3)
|
||||
Blink = Attr(1 << 4)
|
||||
Blink2 = Attr(1 << 5)
|
||||
Reverse = Attr(1 << 6)
|
||||
)
|
||||
|
||||
func (r *FullscreenRenderer) Init() {}
|
||||
func (r *FullscreenRenderer) Pause() {}
|
||||
func (r *FullscreenRenderer) Clear() {}
|
||||
func (r *FullscreenRenderer) Refresh() {}
|
||||
func (r *FullscreenRenderer) Close() {}
|
||||
|
||||
func (r *FullscreenRenderer) Resume() bool { return false }
|
||||
func (r *FullscreenRenderer) DoesAutoWrap() bool { return false }
|
||||
func (r *FullscreenRenderer) IsOptimized() bool { return false }
|
||||
func (r *FullscreenRenderer) GetChar() Event { return Event{} }
|
||||
func (r *FullscreenRenderer) MaxX() int { return 0 }
|
||||
func (r *FullscreenRenderer) MaxY() int { return 0 }
|
||||
|
||||
func (r *FullscreenRenderer) RefreshWindows(windows []Window) {}
|
||||
|
||||
func (r *FullscreenRenderer) NewWindow(top int, left int, width int, height int, border bool) Window {
|
||||
return nil
|
||||
}
|
125
src/tui/light.go
125
src/tui/light.go
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
@@ -19,11 +20,15 @@ const (
|
||||
defaultWidth = 80
|
||||
defaultHeight = 24
|
||||
|
||||
defaultEscDelay = 100
|
||||
escPollInterval = 5
|
||||
offsetPollTries = 10
|
||||
)
|
||||
|
||||
const consoleDevice string = "/dev/tty"
|
||||
|
||||
var offsetRegexp *regexp.Regexp = regexp.MustCompile("\x1b\\[([0-9]+);([0-9]+)R")
|
||||
|
||||
func openTtyIn() *os.File {
|
||||
in, err := os.OpenFile(consoleDevice, syscall.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
@@ -79,6 +84,7 @@ type LightRenderer struct {
|
||||
yoffset int
|
||||
tabstop int
|
||||
escDelay int
|
||||
fullscreen bool
|
||||
upOneLine bool
|
||||
queued string
|
||||
y int
|
||||
@@ -106,8 +112,9 @@ func NewLightRenderer(theme *ColorTheme, forceBlack bool, mouse bool, tabstop in
|
||||
forceBlack: forceBlack,
|
||||
mouse: mouse,
|
||||
ttyin: openTtyIn(),
|
||||
yoffset: -1,
|
||||
yoffset: 0,
|
||||
tabstop: tabstop,
|
||||
fullscreen: false,
|
||||
upOneLine: false,
|
||||
maxHeightFunc: maxHeightFunc}
|
||||
return &r
|
||||
@@ -131,18 +138,14 @@ func (r *LightRenderer) defaultTheme() *ColorTheme {
|
||||
func (r *LightRenderer) findOffset() (row int, col int) {
|
||||
r.csi("6n")
|
||||
r.flush()
|
||||
bytes := r.getBytesInternal([]byte{})
|
||||
|
||||
// ^[[*;*R
|
||||
if len(bytes) > 5 && bytes[0] == 27 && bytes[1] == 91 && bytes[len(bytes)-1] == 'R' {
|
||||
nums := strings.Split(string(bytes[2:len(bytes)-1]), ";")
|
||||
if len(nums) == 2 {
|
||||
return atoi(nums[0], 0) - 1, atoi(nums[1], 0) - 1
|
||||
bytes := []byte{}
|
||||
for tries := 0; tries < offsetPollTries; tries++ {
|
||||
bytes = r.getBytesInternal(bytes, tries > 0)
|
||||
offsets := offsetRegexp.FindSubmatch(bytes)
|
||||
if len(offsets) > 2 {
|
||||
return atoi(string(offsets[1]), 0) - 1, atoi(string(offsets[2]), 0) - 1
|
||||
}
|
||||
return -1, -1
|
||||
}
|
||||
|
||||
// No idea
|
||||
return -1, -1
|
||||
}
|
||||
|
||||
@@ -162,15 +165,7 @@ func atoi(s string, defaultValue int) int {
|
||||
}
|
||||
|
||||
func (r *LightRenderer) Init() {
|
||||
delay := 100
|
||||
delayEnv := os.Getenv("ESCDELAY")
|
||||
if len(delayEnv) > 0 {
|
||||
num, err := strconv.Atoi(delayEnv)
|
||||
if err == nil && num >= 0 {
|
||||
delay = num
|
||||
}
|
||||
}
|
||||
r.escDelay = delay
|
||||
r.escDelay = atoi(os.Getenv("ESCDELAY"), defaultEscDelay)
|
||||
|
||||
fd := r.fd()
|
||||
origState, err := terminal.GetState(fd)
|
||||
@@ -179,17 +174,26 @@ func (r *LightRenderer) Init() {
|
||||
}
|
||||
r.origState = origState
|
||||
terminal.MakeRaw(fd)
|
||||
r.updateTerminalSize()
|
||||
terminalHeight, capHeight := r.updateTerminalSize()
|
||||
if capHeight == terminalHeight {
|
||||
r.fullscreen = true
|
||||
r.height = terminalHeight
|
||||
}
|
||||
initTheme(r.theme, r.defaultTheme(), r.forceBlack)
|
||||
|
||||
_, x := r.findOffset()
|
||||
if x > 0 {
|
||||
r.upOneLine = true
|
||||
r.stderr("\n")
|
||||
}
|
||||
for i := 1; i < r.MaxY(); i++ {
|
||||
r.stderr("\n")
|
||||
r.csi("G")
|
||||
if r.fullscreen {
|
||||
r.smcup()
|
||||
} else {
|
||||
r.csi("J")
|
||||
y, x := r.findOffset()
|
||||
r.mouse = r.mouse && y >= 0
|
||||
if x > 0 {
|
||||
r.upOneLine = true
|
||||
r.makeSpace()
|
||||
}
|
||||
for i := 1; i < r.MaxY(); i++ {
|
||||
r.makeSpace()
|
||||
}
|
||||
}
|
||||
|
||||
if r.mouse {
|
||||
@@ -197,12 +201,18 @@ func (r *LightRenderer) Init() {
|
||||
}
|
||||
r.csi(fmt.Sprintf("%dA", r.MaxY()-1))
|
||||
r.csi("G")
|
||||
r.csi("K")
|
||||
// r.csi("s")
|
||||
if r.mouse {
|
||||
if !r.fullscreen && r.mouse {
|
||||
r.yoffset, _ = r.findOffset()
|
||||
}
|
||||
}
|
||||
|
||||
func (r *LightRenderer) makeSpace() {
|
||||
r.stderr("\n")
|
||||
r.csi("G")
|
||||
}
|
||||
|
||||
func (r *LightRenderer) move(y int, x int) {
|
||||
// w.csi("u")
|
||||
if r.y < y {
|
||||
@@ -230,15 +240,20 @@ func getEnv(name string, defaultValue int) int {
|
||||
return atoi(env, defaultValue)
|
||||
}
|
||||
|
||||
func (r *LightRenderer) updateTerminalSize() {
|
||||
func (r *LightRenderer) updateTerminalSize() (int, int) {
|
||||
width, height, err := terminal.GetSize(r.fd())
|
||||
if err == nil {
|
||||
r.width = width
|
||||
r.height = r.maxHeightFunc(height)
|
||||
if r.fullscreen {
|
||||
r.height = height
|
||||
} else {
|
||||
r.height = r.maxHeightFunc(height)
|
||||
}
|
||||
} else {
|
||||
r.width = getEnv("COLUMNS", defaultWidth)
|
||||
r.height = r.maxHeightFunc(getEnv("LINES", defaultHeight))
|
||||
}
|
||||
return height, r.height
|
||||
}
|
||||
|
||||
func (r *LightRenderer) getch(nonblock bool) (int, bool) {
|
||||
@@ -252,18 +267,18 @@ func (r *LightRenderer) getch(nonblock bool) (int, bool) {
|
||||
}
|
||||
|
||||
func (r *LightRenderer) getBytes() []byte {
|
||||
return r.getBytesInternal(r.buffer)
|
||||
return r.getBytesInternal(r.buffer, false)
|
||||
}
|
||||
|
||||
func (r *LightRenderer) getBytesInternal(buffer []byte) []byte {
|
||||
c, ok := r.getch(false)
|
||||
if !ok {
|
||||
func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) []byte {
|
||||
c, ok := r.getch(nonblock)
|
||||
if !nonblock && !ok {
|
||||
r.Close()
|
||||
errorExit("Failed to read " + consoleDevice)
|
||||
}
|
||||
|
||||
retries := 0
|
||||
if c == ESC {
|
||||
if c == ESC || nonblock {
|
||||
retries = r.escDelay / escPollInterval
|
||||
}
|
||||
buffer = append(buffer, byte(c))
|
||||
@@ -464,7 +479,7 @@ func (r *LightRenderer) escSequence(sz *int) Event {
|
||||
}
|
||||
|
||||
func (r *LightRenderer) mouseSequence(sz *int) Event {
|
||||
if len(r.buffer) < 6 || r.yoffset < 0 {
|
||||
if len(r.buffer) < 6 || !r.mouse {
|
||||
return Event{Invalid, 0, nil}
|
||||
}
|
||||
*sz = 6
|
||||
@@ -503,15 +518,31 @@ func (r *LightRenderer) mouseSequence(sz *int) Event {
|
||||
return Event{Invalid, 0, nil}
|
||||
}
|
||||
|
||||
func (r *LightRenderer) smcup() {
|
||||
r.csi("?1049h")
|
||||
}
|
||||
|
||||
func (r *LightRenderer) rmcup() {
|
||||
r.csi("?1049l")
|
||||
}
|
||||
|
||||
func (r *LightRenderer) Pause() {
|
||||
terminal.Restore(r.fd(), r.origState)
|
||||
r.csi("?1049h")
|
||||
if r.fullscreen {
|
||||
r.rmcup()
|
||||
} else {
|
||||
r.smcup()
|
||||
}
|
||||
r.flush()
|
||||
}
|
||||
|
||||
func (r *LightRenderer) Resume() bool {
|
||||
terminal.MakeRaw(r.fd())
|
||||
r.csi("?1049l")
|
||||
if r.fullscreen {
|
||||
r.smcup()
|
||||
} else {
|
||||
r.rmcup()
|
||||
}
|
||||
r.flush()
|
||||
// Should redraw
|
||||
return true
|
||||
@@ -534,14 +565,18 @@ func (r *LightRenderer) Refresh() {
|
||||
|
||||
func (r *LightRenderer) Close() {
|
||||
// r.csi("u")
|
||||
r.origin()
|
||||
r.csi("J")
|
||||
if r.fullscreen {
|
||||
r.rmcup()
|
||||
} else {
|
||||
r.origin()
|
||||
if r.upOneLine {
|
||||
r.csi("A")
|
||||
}
|
||||
r.csi("J")
|
||||
}
|
||||
if r.mouse {
|
||||
r.csi("?1000l")
|
||||
}
|
||||
if r.upOneLine {
|
||||
r.csi("A")
|
||||
}
|
||||
r.flush()
|
||||
terminal.Restore(r.fd(), r.origState)
|
||||
}
|
||||
|
@@ -1,3 +1,4 @@
|
||||
// +build ncurses
|
||||
// +build !windows
|
||||
// +build !tcell
|
||||
|
||||
@@ -32,6 +33,10 @@ import (
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
func HasFullscreenRenderer() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
type Attr C.uint
|
||||
|
||||
type CursesWindow struct {
|
||||
|
@@ -15,6 +15,10 @@ import (
|
||||
"github.com/junegunn/go-runewidth"
|
||||
)
|
||||
|
||||
func HasFullscreenRenderer() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (p ColorPair) style() tcell.Style {
|
||||
style := tcell.StyleDefault
|
||||
return style.Foreground(tcell.Color(p.Fg())).Background(tcell.Color(p.Bg()))
|
||||
|
@@ -23,21 +23,23 @@ end
|
||||
# List assets
|
||||
assets = Hash[rel['assets'].map { |a| a.values_at *%w[name id] }]
|
||||
|
||||
files.select { |f| File.exists? f }.each do |file|
|
||||
name = File.basename file
|
||||
files.select { |f| File.exists? f }.map do |file|
|
||||
Thread.new do
|
||||
name = File.basename file
|
||||
|
||||
if asset_id = assets[name]
|
||||
puts "#{name} found. Deleting asset id #{asset_id}."
|
||||
RestClient.delete "#{base}/assets/#{asset_id}",
|
||||
:authorization => "token #{token}"
|
||||
else
|
||||
puts "#{name} not found"
|
||||
if asset_id = assets[name]
|
||||
puts "#{name} found. Deleting asset id #{asset_id}."
|
||||
RestClient.delete "#{base}/assets/#{asset_id}",
|
||||
:authorization => "token #{token}"
|
||||
else
|
||||
puts "#{name} not found"
|
||||
end
|
||||
|
||||
puts "Uploading #{name}"
|
||||
RestClient.post(
|
||||
"#{base.sub 'api', 'uploads'}/#{rel['id']}/assets?name=#{name}",
|
||||
File.read(file),
|
||||
:authorization => "token #{token}",
|
||||
:content_type => "application/octet-stream")
|
||||
end
|
||||
|
||||
puts "Uploading #{name}"
|
||||
RestClient.post(
|
||||
"#{base.sub 'api', 'uploads'}/#{rel['id']}/assets?name=#{name}",
|
||||
File.read(file),
|
||||
:authorization => "token #{token}",
|
||||
:content_type => "application/octet-stream")
|
||||
end
|
||||
end.each(&:join)
|
||||
|
@@ -111,7 +111,7 @@ class Tmux
|
||||
File.read(TEMPNAME).split($/)[0, @lines].reverse.drop_while(&:empty?).reverse
|
||||
end
|
||||
|
||||
def until pane = 0
|
||||
def until refresh = false, pane = 0
|
||||
lines = nil
|
||||
begin
|
||||
wait do
|
||||
@@ -141,7 +141,9 @@ class Tmux
|
||||
self.select { |line| line.send method, val }.first
|
||||
end
|
||||
end
|
||||
yield lines
|
||||
yield(lines).tap do |ok|
|
||||
send_keys 'C-l' if refresh && !ok
|
||||
end
|
||||
end
|
||||
rescue Exception
|
||||
puts $!.backtrace
|
||||
@@ -1067,7 +1069,7 @@ class TestGoFZF < TestBase
|
||||
}.each do |ts, exp|
|
||||
tmux.prepare
|
||||
tmux.send_keys %[cat #{tempname} | fzf --tabstop=#{ts}], :Enter
|
||||
tmux.until { |lines| exp.start_with? lines[-3].to_s.strip.sub(/\.\.$/, '') }
|
||||
tmux.until(true) { |lines| exp.start_with? lines[-3].to_s.strip.sub(/\.\.$/, '') }
|
||||
tmux.send_keys :Enter
|
||||
end
|
||||
end
|
||||
@@ -1093,12 +1095,6 @@ class TestGoFZF < TestBase
|
||||
assert_equal 1, $?.exitstatus
|
||||
end
|
||||
|
||||
def test_invalid_term
|
||||
lines = `TERM=xxx #{FZF} 2>&1`
|
||||
assert_equal 2, $?.exitstatus
|
||||
assert lines.include?('Invalid $TERM: xxx') || lines.include?('terminal entry not found')
|
||||
end
|
||||
|
||||
def test_invalid_option
|
||||
lines = `#{FZF} --foobar 2>&1`
|
||||
assert_equal 2, $?.exitstatus
|
||||
@@ -1228,6 +1224,19 @@ class TestGoFZF < TestBase
|
||||
tmux.send_keys '?'
|
||||
tmux.until { |lines| lines[-1] == '> 555' }
|
||||
end
|
||||
|
||||
def test_preview_size_0
|
||||
File.unlink tempname rescue nil
|
||||
tmux.send_keys %[seq 100 | #{FZF} --reverse --preview 'echo {} >> #{tempname}; echo ' --preview-window 0], :Enter
|
||||
tmux.until { |lines| lines.item_count == 100 && lines[1] == ' 100/100' && lines[2] == '> 1' }
|
||||
tmux.until { |_| %w[1] == File.readlines(tempname).map(&:chomp) }
|
||||
tmux.send_keys :Down
|
||||
tmux.until { |lines| lines[3] == '> 2' }
|
||||
tmux.until { |_| %w[1 2] == File.readlines(tempname).map(&:chomp) }
|
||||
tmux.send_keys :Down
|
||||
tmux.until { |lines| lines[4] == '> 3' }
|
||||
tmux.until { |_| %w[1 2 3] == File.readlines(tempname).map(&:chomp) }
|
||||
end
|
||||
end
|
||||
|
||||
module TestShell
|
||||
@@ -1375,8 +1384,7 @@ module CompletionTest
|
||||
tmux.send_keys :Tab, :Tab
|
||||
tmux.until { |lines| lines.select_count == 2 }
|
||||
tmux.send_keys :Enter
|
||||
tmux.until do |lines|
|
||||
tmux.send_keys 'C-L'
|
||||
tmux.until(true) do |lines|
|
||||
lines[-1].include?('/tmp/fzf-test/10') &&
|
||||
lines[-1].include?('/tmp/fzf-test/100')
|
||||
end
|
||||
@@ -1388,20 +1396,16 @@ module CompletionTest
|
||||
tmux.send_keys "'.fzf-home"
|
||||
tmux.until { |lines| lines.select { |l| l.include? '.fzf-home' }.count > 1 }
|
||||
tmux.send_keys :Enter
|
||||
tmux.until do |lines|
|
||||
tmux.send_keys 'C-L'
|
||||
tmux.until(true) do |lines|
|
||||
lines[-1].end_with?('.fzf-home')
|
||||
end
|
||||
|
||||
# ~INVALID_USERNAME**<TAB>
|
||||
tmux.send_keys 'C-u'
|
||||
tmux.send_keys "cat ~such**", :Tab
|
||||
tmux.until { |lines| lines.any_include? 'no~such~user' }
|
||||
tmux.until(true) { |lines| lines.any_include? 'no~such~user' }
|
||||
tmux.send_keys :Enter
|
||||
tmux.until do |lines|
|
||||
tmux.send_keys 'C-L'
|
||||
lines[-1].end_with?('no~such~user')
|
||||
end
|
||||
tmux.until(true) { |lines| lines[-1].end_with?('no~such~user') }
|
||||
|
||||
# /tmp/fzf\ test**<TAB>
|
||||
tmux.send_keys 'C-u'
|
||||
@@ -1410,19 +1414,13 @@ module CompletionTest
|
||||
tmux.send_keys 'foobar$'
|
||||
tmux.until { |lines| lines.match_count == 1 }
|
||||
tmux.send_keys :Enter
|
||||
tmux.until do |lines|
|
||||
tmux.send_keys 'C-L'
|
||||
lines[-1].end_with?('/tmp/fzf\ test/foobar')
|
||||
end
|
||||
tmux.until(true) { |lines| lines[-1].end_with?('/tmp/fzf\ test/foobar') }
|
||||
|
||||
# Should include hidden files
|
||||
(1..100).each { |i| FileUtils.touch "/tmp/fzf-test/.hidden-#{i}" }
|
||||
tmux.send_keys 'C-u'
|
||||
tmux.send_keys 'cat /tmp/fzf-test/hidden**', :Tab
|
||||
tmux.until do |lines|
|
||||
tmux.send_keys 'C-L'
|
||||
lines.match_count == 100 && lines.any_include?('/tmp/fzf-test/.hidden-')
|
||||
end
|
||||
tmux.until(true) { |lines| lines.match_count == 100 && lines.any_include?('/tmp/fzf-test/.hidden-') }
|
||||
tmux.send_keys :Enter
|
||||
ensure
|
||||
['/tmp/fzf-test', '/tmp/fzf test', '~/.fzf-home', 'no~such~user'].each do |f|
|
||||
@@ -1448,10 +1446,7 @@ module CompletionTest
|
||||
tmux.send_keys 55
|
||||
tmux.until { |lines| lines.match_count == 1 }
|
||||
tmux.send_keys :Enter
|
||||
tmux.until do |lines|
|
||||
tmux.send_keys 'C-L'
|
||||
lines[-1] == 'cd /tmp/fzf-test/d55/'
|
||||
end
|
||||
tmux.until(true) { |lines| lines[-1] == 'cd /tmp/fzf-test/d55/' }
|
||||
tmux.send_keys :xx
|
||||
tmux.until { |lines| lines[-1] == 'cd /tmp/fzf-test/d55/xx' }
|
||||
|
||||
@@ -1479,10 +1474,7 @@ module CompletionTest
|
||||
tmux.send_keys 'sleep12345'
|
||||
tmux.until { |lines| lines.any_include? 'sleep 12345' }
|
||||
tmux.send_keys :Enter
|
||||
tmux.until do |lines|
|
||||
tmux.send_keys 'C-L'
|
||||
lines[-1].include? "kill #{pid}"
|
||||
end
|
||||
tmux.until(true) { |lines| lines[-1].include? "kill #{pid}" }
|
||||
ensure
|
||||
Process.kill 'KILL', pid.to_i rescue nil if pid
|
||||
end
|
||||
@@ -1495,10 +1487,7 @@ module CompletionTest
|
||||
tmux.send_keys :Tab, :Tab, :Tab
|
||||
tmux.until { |lines| lines.select_count == 3 }
|
||||
tmux.send_keys :Enter
|
||||
tmux.until do |lines|
|
||||
tmux.send_keys 'C-L'
|
||||
lines[-1] == "ls /tmp 1 2"
|
||||
end
|
||||
tmux.until(true) { |lines| lines[-1] == "ls /tmp 1 2" }
|
||||
end
|
||||
|
||||
def test_unset_completion
|
||||
@@ -1515,7 +1504,7 @@ module CompletionTest
|
||||
# FZF_TMUX=1
|
||||
new_shell
|
||||
tmux.send_keys 'unset FZFFO**', :Tab, pane: 0
|
||||
tmux.until(1) { |lines| lines.match_count == 1 }
|
||||
tmux.until(false, 1) { |lines| lines.match_count == 1 }
|
||||
tmux.send_keys :Enter
|
||||
tmux.until { |lines| lines[-1].include? 'unset FZFFOOBAR' }
|
||||
end
|
||||
@@ -1541,10 +1530,7 @@ module CompletionTest
|
||||
tmux.until { |lines| lines.select_count == 2 }
|
||||
|
||||
tmux.send_keys :Enter
|
||||
tmux.until do |lines|
|
||||
tmux.send_keys 'C-l'
|
||||
lines.any_include? 'cat'
|
||||
end
|
||||
tmux.until(true) { |lines| lines.any_include? 'cat' }
|
||||
tmux.send_keys :Enter
|
||||
tmux.until { |lines| lines[-1].include? 'test3test4' }
|
||||
end
|
||||
|
Reference in New Issue
Block a user