diff options
author | Junegunn Choi <junegunn.c@gmail.com> | 2015-03-31 22:05:02 +0900 |
---|---|---|
committer | Junegunn Choi <junegunn.c@gmail.com> | 2015-03-31 22:05:16 +0900 |
commit | 50292adacbad70f9561bc1e22ccbd3adea22481a (patch) | |
tree | 72a9ba657933585b7c56798b71b64a206144421b /src | |
parent | 84a7499ae357bc3b3a82890d2e44d9c300af0c13 (diff) |
Implement --toggle-sort option (#173)
Diffstat (limited to 'src')
-rw-r--r-- | src/constants.go | 2 | ||||
-rw-r--r-- | src/core.go | 14 | ||||
-rw-r--r-- | src/matcher.go | 11 | ||||
-rw-r--r-- | src/options.go | 35 | ||||
-rw-r--r-- | src/options_test.go | 45 | ||||
-rw-r--r-- | src/pattern.go | 12 | ||||
-rw-r--r-- | src/terminal.go | 17 |
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) |