Compare commits

...

24 Commits

Author SHA1 Message Date
Junegunn Choi
f2ce233a6d 0.16.2 2017-01-24 00:37:47 +09:00
Junegunn Choi
6a75e30941 Allow invisible preview window (--preview-window 0)
Close #820
2017-01-24 00:23:16 +09:00
Junegunn Choi
a3244c4892 Delete every line below the cursor 2017-01-23 22:07:18 +09:00
Junegunn Choi
a5ad8fd3bd Minor refactoring 2017-01-23 12:55:13 +09:00
Junegunn Choi
deccdb1ec5 Cursor postition response can be preceded by user key strokes 2017-01-23 12:55:11 +09:00
Junegunn Choi
12a43b5e62 Disable mouse if failed to query cursor position 2017-01-23 12:55:04 +09:00
Junegunn Choi
e1291aa6d2 Fix make deps to see the right git dir 2017-01-23 12:10:43 +09:00
Junegunn Choi
bb26f32ac7 Allow build on OpenBSD/FreeBSD/Android
Close #497
2017-01-22 18:51:04 +09:00
Junegunn Choi
4d928001b8 Update release script to upload assets in parallel 2017-01-22 18:33:30 +09:00
Junegunn Choi
c4baa6a10c Update man page: 24-bit color 2017-01-22 18:33:03 +09:00
Junegunn Choi
71dec3dc5e Fix bug where screen is not properly cleared on toggle-preview 2017-01-22 17:43:27 +09:00
Junegunn Choi
e5017c0431 Remove unnecesasry test case 2017-01-22 17:41:47 +09:00
Junegunn Choi
cbb5134874 [vim] Use 24-bit colors if termguicolors is set 2017-01-22 14:40:30 +09:00
Junegunn Choi
ff248d566d Drop ncurses dependency
Close #818
2017-01-22 14:13:37 +09:00
Junegunn Choi
6ccc12c332 Use alternate screen if --height needs the entire screen
- Remove unnecessary scrolling
- Allow us to use `--height 100%` under Neovim terminal for 24-bit colors

Related:
- #789
- https://github.com/neovim/neovim/issues/4151
2017-01-22 05:26:38 +09:00
Junegunn Choi
2a669e9a17 Clear lines even when background color is not set
Also revert the workaround in Vim plugin introduced in fa7c897.

Related: #814
2017-01-22 03:19:50 +09:00
Junegunn Choi
5130abe76f Merge branch 'master' into devel 2017-01-22 03:10:06 +09:00
Junegunn Choi
fa7c8977a8 [vim] tput el to clear the last line
Close #814

Not grouping commands to avoid errors on non-standard shells.
2017-01-22 03:03:26 +09:00
Junegunn Choi
24fa183297 make deps 2017-01-22 02:54:19 +09:00
Junegunn Choi
131aa5dd15 Composable actions in --bind
Close #816
2017-01-22 02:32:49 +09:00
Junegunn Choi
a06ccc928f Fix flakies 2017-01-21 04:17:51 +09:00
Junegunn Choi
d09ad13208 [zsh] Workaround trailing esacped space bug in go-shellwords
https://github.com/mattn/go-shellwords/issues/3

Close #812
2017-01-21 03:59:36 +09:00
Junegunn Choi
8ac37d5927 [shell] Do not override --reverse in CTRL-R
Close #807
2017-01-17 18:09:29 +09:00
Junegunn Choi
7ef0e50507 [bash/zsh] Remove unused --reverse in CTRL-R binding
Related #807
2017-01-17 11:58:25 +09:00
21 changed files with 623 additions and 386 deletions

View File

@@ -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
View File

@@ -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

View File

@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
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

View File

@@ -21,7 +21,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
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.

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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]

View File

@@ -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)

View File

@@ -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)

View File

@@ -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
View 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

View File

@@ -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

View File

@@ -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()
}
}

View File

@@ -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
View 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
}

View File

@@ -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)
}

View File

@@ -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 {

View File

@@ -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()))

View File

@@ -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)

View File

@@ -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