summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJunegunn Choi <junegunn.c@gmail.com>2015-04-16 14:19:28 +0900
committerJunegunn Choi <junegunn.c@gmail.com>2015-04-16 14:19:28 +0900
commitb8904a8c3e8f6d8c00c8d69b153c0d1897b1ade2 (patch)
tree7fd46744a6fd3d9f63657634d6d32a4ba09b140c /src
parent48ab87294bb2df5ff32ff35a16231991a2e0887b (diff)
Add --tiebreak option for customizing sort criteria
Close #191
Diffstat (limited to 'src')
-rw-r--r--src/core.go1
-rw-r--r--src/item.go36
-rw-r--r--src/item_test.go2
-rw-r--r--src/options.go36
4 files changed, 67 insertions, 8 deletions
diff --git a/src/core.go b/src/core.go
index 9f33b41d..4a834240 100644
--- a/src/core.go
+++ b/src/core.go
@@ -55,6 +55,7 @@ func Run(options *Options) {
opts := ParseOptions()
sort := opts.Sort > 0
+ rankTiebreak = opts.Tiebreak
if opts.Version {
fmt.Println(Version)
diff --git a/src/item.go b/src/item.go
index 9e2e1e7e..996c5e19 100644
--- a/src/item.go
+++ b/src/item.go
@@ -1,6 +1,8 @@
package fzf
import (
+ "math"
+
"github.com/junegunn/fzf/src/curses"
)
@@ -27,17 +29,21 @@ type Item struct {
// Rank is used to sort the search result
type Rank struct {
matchlen uint16
- strlen uint16
+ tiebreak uint16
index uint32
}
+// Tiebreak criterion to use. Never changes once fzf is started.
+var rankTiebreak tiebreak
+
// Rank calculates rank of the Item
func (i *Item) Rank(cache bool) Rank {
- if cache && (i.rank.matchlen > 0 || i.rank.strlen > 0) {
+ if cache && (i.rank.matchlen > 0 || i.rank.tiebreak > 0) {
return i.rank
}
matchlen := 0
prevEnd := 0
+ minBegin := math.MaxUint16
for _, offset := range i.offsets {
begin := int(offset[0])
end := int(offset[1])
@@ -48,10 +54,30 @@ func (i *Item) Rank(cache bool) Rank {
prevEnd = end
}
if end > begin {
+ if begin < minBegin {
+ minBegin = begin
+ }
matchlen += end - begin
}
}
- rank := Rank{uint16(matchlen), uint16(len(*i.text)), i.index}
+ var tiebreak uint16
+ switch rankTiebreak {
+ case byLength:
+ tiebreak = uint16(len(*i.text))
+ case byBegin:
+ // We can't just look at i.offsets[0][0] because it can be an inverse term
+ tiebreak = uint16(minBegin)
+ case byEnd:
+ if prevEnd > 0 {
+ tiebreak = uint16(1 + len(*i.text) - prevEnd)
+ } else {
+ // Empty offsets due to inverse terms.
+ tiebreak = 1
+ }
+ case byIndex:
+ tiebreak = 1
+ }
+ rank := Rank{uint16(matchlen), tiebreak, i.index}
if cache {
i.rank = rank
}
@@ -199,9 +225,9 @@ func compareRanks(irank Rank, jrank Rank, tac bool) bool {
return false
}
- if irank.strlen < jrank.strlen {
+ if irank.tiebreak < jrank.tiebreak {
return true
- } else if irank.strlen > jrank.strlen {
+ } else if irank.tiebreak > jrank.tiebreak {
return false
}
diff --git a/src/item_test.go b/src/item_test.go
index 4eea8c15..2d375e47 100644
--- a/src/item_test.go
+++ b/src/item_test.go
@@ -42,7 +42,7 @@ func TestItemRank(t *testing.T) {
strs := []string{"foo", "foobar", "bar", "baz"}
item1 := Item{text: &strs[0], index: 1, offsets: []Offset{}}
rank1 := item1.Rank(true)
- if rank1.matchlen != 0 || rank1.strlen != 3 || rank1.index != 1 {
+ if rank1.matchlen != 0 || rank1.tiebreak != 3 || rank1.index != 1 {
t.Error(item1.Rank(true))
}
// Only differ in index
diff --git a/src/options.go b/src/options.go
index c186542d..d13a53b8 100644
--- a/src/options.go
+++ b/src/options.go
@@ -28,7 +28,8 @@ const usage = `usage: fzf [options]
Search result
+s, --no-sort Do not sort the result
--tac Reverse the order of the input
- (e.g. 'history | fzf --tac --no-sort')
+ --tiebreak=CRI Sort criterion when the scores are tied;
+ [length|begin|end|index] (default: length)
Interface
-m, --multi Enable multi-select with tab/shift-tab
@@ -50,7 +51,6 @@ const usage = `usage: fzf [options]
--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')
Environment variables
FZF_DEFAULT_COMMAND Default command to use when input is tty
@@ -78,6 +78,16 @@ const (
CaseRespect
)
+// Sort criteria
+type tiebreak int
+
+const (
+ byLength tiebreak = iota
+ byBegin
+ byEnd
+ byIndex
+)
+
// Options stores the values of command-line options
type Options struct {
Mode Mode
@@ -87,6 +97,7 @@ type Options struct {
Delimiter *regexp.Regexp
Sort int
Tac bool
+ Tiebreak tiebreak
Multi bool
Ansi bool
Mouse bool
@@ -116,6 +127,7 @@ func defaultOptions() *Options {
Delimiter: nil,
Sort: 1000,
Tac: false,
+ Tiebreak: byLength,
Multi: false,
Ansi: false,
Mouse: true,
@@ -238,6 +250,22 @@ func parseKeyChords(str string, message string) []int {
return chords
}
+func parseTiebreak(str string) tiebreak {
+ switch strings.ToLower(str) {
+ case "length":
+ return byLength
+ case "index":
+ return byIndex
+ case "begin":
+ return byBegin
+ case "end":
+ return byEnd
+ default:
+ errorExit("invalid sort criterion: " + str)
+ }
+ return byLength
+}
+
func checkToggleSort(str string) int {
keys := parseKeyChords(str, "key name required")
if len(keys) != 1 {
@@ -265,6 +293,8 @@ func parseOptions(opts *Options, allArgs []string) {
opts.Filter = &filter
case "--expect":
opts.Expect = parseKeyChords(nextString(allArgs, &i, "key names required"), "key names required")
+ case "--tiebreak":
+ opts.Tiebreak = parseTiebreak(nextString(allArgs, &i, "sort criterion required"))
case "--toggle-sort":
opts.ToggleSort = checkToggleSort(nextString(allArgs, &i, "key name required"))
case "-d", "--delimiter":
@@ -352,6 +382,8 @@ func parseOptions(opts *Options, allArgs []string) {
opts.ToggleSort = checkToggleSort(value)
} else if match, value := optString(arg, "--expect="); match {
opts.Expect = parseKeyChords(value, "key names required")
+ } else if match, value := optString(arg, "--tiebreak="); match {
+ opts.Tiebreak = parseTiebreak(value)
} else {
errorExit("unknown option: " + arg)
}