m/fzf
1
0
mirror of https://github.com/junegunn/fzf.git synced 2025-11-18 16:45:38 -05:00

Implement --toggle-sort option (#173)

This commit is contained in:
Junegunn Choi
2015-03-31 22:05:02 +09:00
parent 84a7499ae3
commit 50292adacb
16 changed files with 156 additions and 26 deletions

View File

@@ -5,7 +5,7 @@ import (
)
// Current version
const Version = "0.9.6"
const Version = "0.9.7"
// fzf events
const (

View File

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

View File

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

View File

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

View File

@@ -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+',')
}

View File

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

View File

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