summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJunegunn Choi <junegunn.c@gmail.com>2015-03-31 22:05:02 +0900
committerJunegunn Choi <junegunn.c@gmail.com>2015-03-31 22:05:16 +0900
commit50292adacbad70f9561bc1e22ccbd3adea22481a (patch)
tree72a9ba657933585b7c56798b71b64a206144421b /src
parent84a7499ae357bc3b3a82890d2e44d9c300af0c13 (diff)
Implement --toggle-sort option (#173)
Diffstat (limited to 'src')
-rw-r--r--src/constants.go2
-rw-r--r--src/core.go14
-rw-r--r--src/matcher.go11
-rw-r--r--src/options.go35
-rw-r--r--src/options_test.go45
-rw-r--r--src/pattern.go12
-rw-r--r--src/terminal.go17
7 files changed, 116 insertions, 20 deletions
diff --git a/src/constants.go b/src/constants.go
index 006a1bdc..5cd6d80b 100644
--- a/src/constants.go
+++ b/src/constants.go
@@ -5,7 +5,7 @@ import (
)
// Current version
-const Version = "0.9.6"
+const Version = "0.9.7"
// fzf events
const (
diff --git a/src/core.go b/src/core.go
index 204532b2..9f33b41d 100644
--- a/src/core.go
+++ b/src/core.go
@@ -44,7 +44,7 @@ func initProcs() {
/*
Reader -> EvtReadFin
Reader -> EvtReadNew -> Matcher (restart)
-Terminal -> EvtSearchNew -> Matcher (restart)
+Terminal -> EvtSearchNew:bool -> Matcher (restart)
Matcher -> EvtSearchProgress -> Terminal (update info)
Matcher -> EvtSearchFin -> Terminal (update list)
*/
@@ -54,6 +54,7 @@ func Run(options *Options) {
initProcs()
opts := ParseOptions()
+ sort := opts.Sort > 0
if opts.Version {
fmt.Println(Version)
@@ -112,7 +113,7 @@ func Run(options *Options) {
}
// Reader
- streamingFilter := opts.Filter != nil && opts.Sort == 0 && !opts.Tac && !opts.Sync
+ streamingFilter := opts.Filter != nil && !sort && !opts.Tac && !opts.Sync
if !streamingFilter {
reader := Reader{func(str string) { chunkList.Push(str) }, eventBox}
go reader.ReadSource()
@@ -123,7 +124,7 @@ func Run(options *Options) {
return BuildPattern(
opts.Mode, opts.Case, opts.Nth, opts.Delimiter, runes)
}
- matcher := NewMatcher(patternBuilder, opts.Sort > 0, opts.Tac, eventBox)
+ matcher := NewMatcher(patternBuilder, sort, opts.Tac, eventBox)
// Filtering mode
if opts.Filter != nil {
@@ -190,11 +191,14 @@ func Run(options *Options) {
reading = reading && evt == EvtReadNew
snapshot, count := chunkList.Snapshot()
terminal.UpdateCount(count, !reading)
- matcher.Reset(snapshot, terminal.Input(), false, !reading)
+ matcher.Reset(snapshot, terminal.Input(), false, !reading, sort)
case EvtSearchNew:
+ if value.(bool) {
+ sort = !sort
+ }
snapshot, _ := chunkList.Snapshot()
- matcher.Reset(snapshot, terminal.Input(), true, !reading)
+ matcher.Reset(snapshot, terminal.Input(), true, !reading, sort)
delay = false
case EvtSearchProgress:
diff --git a/src/matcher.go b/src/matcher.go
index a3a9bd0e..0f3b409e 100644
--- a/src/matcher.go
+++ b/src/matcher.go
@@ -15,6 +15,7 @@ type MatchRequest struct {
chunks []*Chunk
pattern *Pattern
final bool
+ sort bool
}
// Matcher is responsible for performing search
@@ -69,6 +70,12 @@ func (m *Matcher) Loop() {
events.Clear()
})
+ if request.sort != m.sort {
+ m.sort = request.sort
+ m.mergerCache = make(map[string]*Merger)
+ clearChunkCache()
+ }
+
// Restart search
patternString := request.pattern.AsString()
var merger *Merger
@@ -203,7 +210,7 @@ func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
}
// Reset is called to interrupt/signal the ongoing search
-func (m *Matcher) Reset(chunks []*Chunk, patternRunes []rune, cancel bool, final bool) {
+func (m *Matcher) Reset(chunks []*Chunk, patternRunes []rune, cancel bool, final bool, sort bool) {
pattern := m.patternBuilder(patternRunes)
var event util.EventType
@@ -212,5 +219,5 @@ func (m *Matcher) Reset(chunks []*Chunk, patternRunes []rune, cancel bool, final
} else {
event = reqRetry
}
- m.reqBox.Set(event, MatchRequest{chunks, pattern, final})
+ m.reqBox.Set(event, MatchRequest{chunks, pattern, final, sort})
}
diff --git a/src/options.go b/src/options.go
index 89b1c368..fcf30979 100644
--- a/src/options.go
+++ b/src/options.go
@@ -47,6 +47,7 @@ const usage = `usage: fzf [options]
-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
+ --toggle-sort=KEY Key to toggle sort
--sync Synchronous search for multi-staged filtering
(e.g. 'fzf --multi | fzf --sync')
@@ -97,6 +98,7 @@ type Options struct {
Select1 bool
Exit0 bool
Filter *string
+ ToggleSort int
Expect []int
PrintQuery bool
Sync bool
@@ -124,6 +126,7 @@ func defaultOptions() *Options {
Select1: false,
Exit0: false,
Filter: nil,
+ ToggleSort: 0,
Expect: []int{},
PrintQuery: false,
Sync: false,
@@ -201,9 +204,21 @@ func isAlphabet(char uint8) bool {
return char >= 'a' && char <= 'z'
}
-func parseKeyChords(str string) []int {
+func parseKeyChords(str string, message string) []int {
+ if len(str) == 0 {
+ errorExit(message)
+ }
+
+ tokens := strings.Split(str, ",")
+ if str == "," || strings.HasPrefix(str, ",,") || strings.HasSuffix(str, ",,") || strings.Index(str, ",,,") >= 0 {
+ tokens = append(tokens, ",")
+ }
+
var chords []int
- for _, key := range strings.Split(str, ",") {
+ for _, key := range tokens {
+ if len(key) == 0 {
+ continue // ignore
+ }
lkey := strings.ToLower(key)
if len(key) == 6 && strings.HasPrefix(lkey, "ctrl-") && isAlphabet(lkey[5]) {
chords = append(chords, curses.CtrlA+int(lkey[5])-'a')
@@ -220,6 +235,14 @@ func parseKeyChords(str string) []int {
return chords
}
+func checkToggleSort(str string) int {
+ keys := parseKeyChords(str, "key name required")
+ if len(keys) != 1 {
+ errorExit("multiple keys specified")
+ }
+ return keys[0]
+}
+
func parseOptions(opts *Options, allArgs []string) {
for i := 0; i < len(allArgs); i++ {
arg := allArgs[i]
@@ -238,7 +261,9 @@ func parseOptions(opts *Options, allArgs []string) {
filter := nextString(allArgs, &i, "query string required")
opts.Filter = &filter
case "--expect":
- opts.Expect = parseKeyChords(nextString(allArgs, &i, "key names required"))
+ opts.Expect = parseKeyChords(nextString(allArgs, &i, "key names required"), "key names required")
+ case "--toggle-sort":
+ opts.ToggleSort = checkToggleSort(nextString(allArgs, &i, "key name required"))
case "-d", "--delimiter":
opts.Delimiter = delimiterRegexp(nextString(allArgs, &i, "delimiter required"))
case "-n", "--nth":
@@ -316,8 +341,10 @@ 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, "--toggle-sort="); match {
+ opts.ToggleSort = checkToggleSort(value)
} else if match, value := optString(arg, "--expect="); match {
- opts.Expect = parseKeyChords(value)
+ opts.Expect = parseKeyChords(value, "key names required")
} else {
errorExit("unknown option: " + arg)
}
diff --git a/src/options_test.go b/src/options_test.go
index b20cd6a3..36959da4 100644
--- a/src/options_test.go
+++ b/src/options_test.go
@@ -70,8 +70,8 @@ func TestIrrelevantNth(t *testing.T) {
}
}
-func TestExpectKeys(t *testing.T) {
- keys := parseKeyChords("ctrl-z,alt-z,f2,@,Alt-a,!,ctrl-G,J,g")
+func TestParseKeys(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)
@@ -88,3 +88,44 @@ func TestExpectKeys(t *testing.T) {
check(keys[7], curses.AltZ+'J')
check(keys[8], curses.AltZ+'g')
}
+
+func TestParseKeysWithComma(t *testing.T) {
+ check := func(key int, expected int) {
+ if key != expected {
+ t.Errorf("%d != %d", key, expected)
+ }
+ }
+
+ keys := parseKeyChords(",", "")
+ check(len(keys), 1)
+ check(keys[0], curses.AltZ+',')
+
+ keys = parseKeyChords(",,a,b", "")
+ check(len(keys), 3)
+ check(keys[0], curses.AltZ+'a')
+ check(keys[1], curses.AltZ+'b')
+ check(keys[2], curses.AltZ+',')
+
+ keys = parseKeyChords("a,b,,", "")
+ check(len(keys), 3)
+ check(keys[0], curses.AltZ+'a')
+ check(keys[1], curses.AltZ+'b')
+ check(keys[2], curses.AltZ+',')
+
+ keys = parseKeyChords("a,,,b", "")
+ check(len(keys), 3)
+ check(keys[0], curses.AltZ+'a')
+ check(keys[1], curses.AltZ+'b')
+ check(keys[2], curses.AltZ+',')
+
+ keys = parseKeyChords("a,,,b,c", "")
+ check(len(keys), 4)
+ check(keys[0], curses.AltZ+'a')
+ check(keys[1], curses.AltZ+'b')
+ check(keys[2], curses.AltZ+'c')
+ check(keys[3], curses.AltZ+',')
+
+ keys = parseKeyChords(",,,", "")
+ check(len(keys), 1)
+ check(keys[0], curses.AltZ+',')
+}
diff --git a/src/pattern.go b/src/pattern.go
index 7acdbcfa..fbb70c5f 100644
--- a/src/pattern.go
+++ b/src/pattern.go
@@ -54,17 +54,21 @@ var (
)
func init() {
- // We can uniquely identify the pattern for a given string since
- // mode and caseMode do not change while the program is running
- _patternCache = make(map[string]*Pattern)
_splitRegex = regexp.MustCompile("\\s+")
- _cache = NewChunkCache()
+ clearPatternCache()
+ clearChunkCache()
}
func clearPatternCache() {
+ // We can uniquely identify the pattern for a given string since
+ // mode and caseMode do not change while the program is running
_patternCache = make(map[string]*Pattern)
}
+func clearChunkCache() {
+ _cache = NewChunkCache()
+}
+
// BuildPattern builds Pattern object from the given arguments
func BuildPattern(mode Mode, caseMode Case,
nth []Range, delimiter *regexp.Regexp, runes []rune) *Pattern {
diff --git a/src/terminal.go b/src/terminal.go
index 2d191a90..d027d761 100644
--- a/src/terminal.go
+++ b/src/terminal.go
@@ -28,6 +28,7 @@ type Terminal struct {
yanked []rune
input []rune
multi bool
+ toggleSort int
expect []int
pressed int
printQuery bool
@@ -93,6 +94,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
yanked: []rune{},
input: input,
multi: opts.Multi,
+ toggleSort: opts.ToggleSort,
expect: opts.Expect,
pressed: 0,
printQuery: opts.PrintQuery,
@@ -457,6 +459,10 @@ func (t *Terminal) rubout(pattern string) {
t.input = append(t.input[:t.cx], after...)
}
+func keyMatch(key int, event C.Event) bool {
+ return event.Type == key || event.Type == C.Rune && int(event.Char) == key-C.AltZ
+}
+
// Loop is called to start Terminal I/O
func (t *Terminal) Loop() {
<-t.startChan
@@ -553,12 +559,19 @@ func (t *Terminal) Loop() {
}
}
for _, key := range t.expect {
- if event.Type == key || event.Type == C.Rune && int(event.Char) == key-C.AltZ {
+ if keyMatch(key, event) {
t.pressed = key
req(reqClose)
break
}
}
+ if t.toggleSort > 0 {
+ if keyMatch(t.toggleSort, event) {
+ t.eventBox.Set(EvtSearchNew, true)
+ t.mutex.Unlock()
+ continue
+ }
+ }
switch event.Type {
case C.Invalid:
t.mutex.Unlock()
@@ -688,7 +701,7 @@ func (t *Terminal) Loop() {
t.mutex.Unlock() // Must be unlocked before touching reqBox
if changed {
- t.eventBox.Set(EvtSearchNew, nil)
+ t.eventBox.Set(EvtSearchNew, false)
}
for _, event := range events {
t.reqBox.Set(event, nil)