summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md24
-rwxr-xr-xinstall2
-rw-r--r--man/man1/fzf.111
-rw-r--r--src/curses/curses.go25
-rw-r--r--src/options.go33
-rw-r--r--src/options_test.go25
-rw-r--r--src/terminal.go24
-rw-r--r--src/util/util.go4
-rw-r--r--test/test_go.rb27
9 files changed, 171 insertions, 4 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cb004226..ef2c957b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,30 @@
CHANGELOG
=========
+0.9.6
+-----
+
+### New features
+
+#### Added `--expect` option (#163)
+
+If you provide a comma-separated list of keys with `--expect` option, fzf will
+allow you to select the match and complete the finder when any of the keys is
+pressed. Additionally, fzf will print the name of the key pressed as the first
+line of the output so that your script can decide what to do next based on the
+information.
+
+```sh
+fzf --expect=ctrl-v,ctrl-t,alt-s,f1,f2,~,@
+```
+
+The updated vim plugin uses this option to implement
+[ctrlp](https://github.com/kien/ctrlp.vim)-compatible key bindings.
+
+### Bug fixes
+
+- Fixed to ignore ANSI escape code `\e[K` (#162)
+
0.9.5
-----
diff --git a/install b/install
index 8f9d355c..eb85a8bc 100755
--- a/install
+++ b/install
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
-version=0.9.5
+version=0.9.6
cd $(dirname $BASH_SOURCE)
fzf_base=$(pwd)
diff --git a/man/man1/fzf.1 b/man/man1/fzf.1
index bfb358f4..c6bf054e 100644
--- a/man/man1/fzf.1
+++ b/man/man1/fzf.1
@@ -108,6 +108,17 @@ Filter mode. Do not start interactive finder.
.B "--print-query"
Print query as the first line
.TP
+.BI "--expect=" "KEY[,..]"
+Comma-separated list of keys (\fIctrl-[a-z]\fR, \fIalt-[a-z]\fR, \fIf[1-4]\fR,
+or a single character) that can be used to complete fzf in addition to the
+default enter key. When this option is set, fzf will print the name of the key
+pressed as the first line of its output (or as the second line if
+\fB--print-query\fR is also used). The line will be empty if fzf is completed
+with the default enter key.
+.RS
+e.g. \fBfzf --expect=ctrl-v,ctrl-t,alt-s,f1,f2,~,@\fR
+.RE
+.TP
.B "--sync"
Synchronous search for multi-staged filtering
.RS
diff --git a/src/curses/curses.go b/src/curses/curses.go
index dfd7cf51..d6aafd71 100644
--- a/src/curses/curses.go
+++ b/src/curses/curses.go
@@ -61,10 +61,20 @@ const (
PgUp
PgDn
+ F1
+ F2
+ F3
+ F4
+
+ AltBS
+ AltA
AltB
- AltF
+ AltC
AltD
- AltBS
+ AltE
+ AltF
+
+ AltZ = AltA + 'z' - 'a'
)
// Pallete
@@ -324,6 +334,14 @@ func escSequence(sz *int) Event {
return Event{CtrlE, 0, nil}
case 77:
return mouseSequence(sz)
+ case 80:
+ return Event{F1, 0, nil}
+ case 81:
+ return Event{F2, 0, nil}
+ case 82:
+ return Event{F3, 0, nil}
+ case 83:
+ return Event{F4, 0, nil}
case 49, 50, 51, 52, 53, 54:
if len(_buf) < 4 {
return Event{Invalid, 0, nil}
@@ -369,6 +387,9 @@ func escSequence(sz *int) Event {
} // _buf[2]
} // _buf[2]
} // _buf[1]
+ if _buf[1] >= 'a' && _buf[1] <= 'z' {
+ return Event{AltA + int(_buf[1]) - 'a', 0, nil}
+ }
return Event{Invalid, 0, nil}
}
diff --git a/src/options.go b/src/options.go
index 73c9e188..89b1c368 100644
--- a/src/options.go
+++ b/src/options.go
@@ -5,6 +5,9 @@ import (
"os"
"regexp"
"strings"
+ "unicode/utf8"
+
+ "github.com/junegunn/fzf/src/curses"
"github.com/junegunn/go-shellwords"
)
@@ -43,6 +46,7 @@ const usage = `usage: fzf [options]
-0, --exit-0 Exit immediately when there's no match
-f, --filter=STR Filter mode. Do not start interactive finder.
--print-query Print query as the first line
+ --expect=KEYS Comma-separated list of keys to complete fzf
--sync Synchronous search for multi-staged filtering
(e.g. 'fzf --multi | fzf --sync')
@@ -93,6 +97,7 @@ type Options struct {
Select1 bool
Exit0 bool
Filter *string
+ Expect []int
PrintQuery bool
Sync bool
Version bool
@@ -119,6 +124,7 @@ func defaultOptions() *Options {
Select1: false,
Exit0: false,
Filter: nil,
+ Expect: []int{},
PrintQuery: false,
Sync: false,
Version: false}
@@ -191,6 +197,29 @@ func delimiterRegexp(str string) *regexp.Regexp {
return rx
}
+func isAlphabet(char uint8) bool {
+ return char >= 'a' && char <= 'z'
+}
+
+func parseKeyChords(str string) []int {
+ var chords []int
+ for _, key := range strings.Split(str, ",") {
+ lkey := strings.ToLower(key)
+ if len(key) == 6 && strings.HasPrefix(lkey, "ctrl-") && isAlphabet(lkey[5]) {
+ chords = append(chords, curses.CtrlA+int(lkey[5])-'a')
+ } else if len(key) == 5 && strings.HasPrefix(lkey, "alt-") && isAlphabet(lkey[4]) {
+ chords = append(chords, curses.AltA+int(lkey[4])-'a')
+ } else if len(key) == 2 && strings.HasPrefix(lkey, "f") && key[1] >= '1' && key[1] <= '4' {
+ chords = append(chords, curses.F1+int(key[1])-'1')
+ } else if utf8.RuneCountInString(key) == 1 {
+ chords = append(chords, curses.AltZ+int([]rune(key)[0]))
+ } else {
+ errorExit("unsupported key: " + key)
+ }
+ }
+ return chords
+}
+
func parseOptions(opts *Options, allArgs []string) {
for i := 0; i < len(allArgs); i++ {
arg := allArgs[i]
@@ -208,6 +237,8 @@ func parseOptions(opts *Options, allArgs []string) {
case "-f", "--filter":
filter := nextString(allArgs, &i, "query string required")
opts.Filter = &filter
+ case "--expect":
+ opts.Expect = parseKeyChords(nextString(allArgs, &i, "key names required"))
case "-d", "--delimiter":
opts.Delimiter = delimiterRegexp(nextString(allArgs, &i, "delimiter required"))
case "-n", "--nth":
@@ -285,6 +316,8 @@ func parseOptions(opts *Options, allArgs []string) {
opts.WithNth = splitNth(value)
} else if match, _ := optString(arg, "-s|--sort="); match {
opts.Sort = 1 // Don't care
+ } else if match, value := optString(arg, "--expect="); match {
+ opts.Expect = parseKeyChords(value)
} else {
errorExit("unknown option: " + arg)
}
diff --git a/src/options_test.go b/src/options_test.go
index 782ad791..b20cd6a3 100644
--- a/src/options_test.go
+++ b/src/options_test.go
@@ -1,6 +1,10 @@
package fzf
-import "testing"
+import (
+ "testing"
+
+ "github.com/junegunn/fzf/src/curses"
+)
func TestDelimiterRegex(t *testing.T) {
rx := delimiterRegexp("*")
@@ -65,3 +69,22 @@ func TestIrrelevantNth(t *testing.T) {
}
}
}
+
+func TestExpectKeys(t *testing.T) {
+ keys := parseKeyChords("ctrl-z,alt-z,f2,@,Alt-a,!,ctrl-G,J,g")
+ check := func(key int, expected int) {
+ if key != expected {
+ t.Errorf("%d != %d", key, expected)
+ }
+ }
+ check(len(keys), 9)
+ check(keys[0], curses.CtrlZ)
+ check(keys[1], curses.AltZ)
+ check(keys[2], curses.F2)
+ check(keys[3], curses.AltZ+'@')
+ check(keys[4], curses.AltA)
+ check(keys[5], curses.AltZ+'!')
+ check(keys[6], curses.CtrlA+'g'-'a')
+ check(keys[7], curses.AltZ+'J')
+ check(keys[8], curses.AltZ+'g')
+}
diff --git a/src/terminal.go b/src/terminal.go
index 5570f8d1..2d191a90 100644
--- a/src/terminal.go
+++ b/src/terminal.go
@@ -28,6 +28,8 @@ type Terminal struct {
yanked []rune
input []rune
multi bool
+ expect []int
+ pressed int
printQuery bool
count int
progress int
@@ -91,6 +93,8 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
yanked: []rune{},
input: input,
multi: opts.Multi,
+ expect: opts.Expect,
+ pressed: 0,
printQuery: opts.PrintQuery,
merger: EmptyMerger,
selected: make(map[*string]selectedItem),
@@ -150,6 +154,19 @@ func (t *Terminal) output() {
if t.printQuery {
fmt.Println(string(t.input))
}
+ if len(t.expect) > 0 {
+ if t.pressed == 0 {
+ fmt.Println()
+ } else if util.Between(t.pressed, C.AltA, C.AltZ) {
+ fmt.Printf("alt-%c\n", t.pressed+'a'-C.AltA)
+ } else if util.Between(t.pressed, C.F1, C.F4) {
+ fmt.Printf("f%c\n", t.pressed+'1'-C.F1)
+ } else if util.Between(t.pressed, C.CtrlA, C.CtrlZ) {
+ fmt.Printf("ctrl-%c\n", t.pressed+'a'-C.CtrlA)
+ } else {
+ fmt.Printf("%c\n", t.pressed-C.AltZ)
+ }
+ }
if len(t.selected) == 0 {
cnt := t.merger.Length()
if cnt > 0 && cnt > t.cy {
@@ -535,6 +552,13 @@ func (t *Terminal) Loop() {
req(reqInfo)
}
}
+ for _, key := range t.expect {
+ if event.Type == key || event.Type == C.Rune && int(event.Char) == key-C.AltZ {
+ t.pressed = key
+ req(reqClose)
+ break
+ }
+ }
switch event.Type {
case C.Invalid:
t.mutex.Unlock()
diff --git a/src/util/util.go b/src/util/util.go
index 1f53cc76..2d680b1a 100644
--- a/src/util/util.go
+++ b/src/util/util.go
@@ -61,6 +61,10 @@ func DurWithin(
return val
}
+func Between(val int, min int, max int) bool {
+ return val >= min && val <= max
+}
+
// IsTty returns true is stdin is a terminal
func IsTty() bool {
return int(C.isatty(C.int(os.Stdin.Fd()))) != 0
diff --git a/test/test_go.rb b/test/test_go.rb
index 6f67da74..adfc0d01 100644
--- a/test/test_go.rb
+++ b/test/test_go.rb
@@ -425,6 +425,33 @@ class TestGoFZF < TestBase
tmux.send_keys :BTab, :BTab, :BTab, :Enter
assert_equal %w[1000 900 800], readonce.split($/)
end
+
+ def test_expect
+ test = lambda do |key, feed, expected = key|
+ tmux.send_keys "seq 1 100 | #{fzf :expect, key}", :Enter
+ tmux.until { |lines| lines[-2].include? '100/100' }
+ tmux.send_keys '55'
+ tmux.send_keys *feed
+ assert_equal [expected, '55'], readonce.split($/)
+ end
+ test.call 'ctrl-t', 'C-T'
+ test.call 'ctrl-t', 'Enter', ''
+ test.call 'alt-c', [:Escape, :c]
+ test.call 'f1', 'f1'
+ test.call 'f2', 'f2'
+ test.call 'f3', 'f3'
+ test.call 'f2,f4', 'f2', 'f2'
+ test.call 'f2,f4', 'f4', 'f4'
+ test.call '@', '@'
+ end
+
+ def test_expect_print_query
+ tmux.send_keys "seq 1 100 | #{fzf '--expect=alt-z', :print_query}", :Enter
+ tmux.until { |lines| lines[-2].include? '100/100' }
+ tmux.send_keys '55'
+ tmux.send_keys :Escape, :z
+ assert_equal ['55', 'alt-z', '55'], readonce.split($/)
+ end
end
module TestShell