mirror of
https://github.com/Llewellynvdm/fzf.git
synced 2024-11-29 00:06:29 +00:00
Add --tac option and reverse display order of --no-sort
DISCLAIMER: This is a backward incompatible change
This commit is contained in:
parent
4a1752d3fc
commit
c1aa5c5f33
32
CHANGELOG.md
Normal file
32
CHANGELOG.md
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
CHANGELOG
|
||||||
|
=========
|
||||||
|
|
||||||
|
0.9.4
|
||||||
|
-----
|
||||||
|
|
||||||
|
#### New features
|
||||||
|
|
||||||
|
- Added `--tac` option to reverse the order of the input.
|
||||||
|
- One might argue that this option is unnecessary since we can already put
|
||||||
|
`tac` or `tail -r` in the command pipeline to achieve the same result.
|
||||||
|
However, the advantage of `--tac` is that it does not block until the
|
||||||
|
input is complete.
|
||||||
|
|
||||||
|
#### *Backward incompatible changes*
|
||||||
|
|
||||||
|
- `--no-sort` option will no longer reverse the display order. You may want to
|
||||||
|
use the new `--tac` option with `--no-sort`.
|
||||||
|
```
|
||||||
|
history | fzf +s --tac
|
||||||
|
```
|
||||||
|
|
||||||
|
0.9.3
|
||||||
|
-----
|
||||||
|
|
||||||
|
#### New features
|
||||||
|
- Added `--sync` option for multi-staged filtering
|
||||||
|
|
||||||
|
#### Improvements
|
||||||
|
- `--select-1` and `--exit-0` will start finder immediately when the condition
|
||||||
|
cannot be met
|
||||||
|
|
16
README.md
16
README.md
@ -75,7 +75,7 @@ Usage
|
|||||||
```
|
```
|
||||||
usage: fzf [options]
|
usage: fzf [options]
|
||||||
|
|
||||||
Search
|
Search mode
|
||||||
-x, --extended Extended-search mode
|
-x, --extended Extended-search mode
|
||||||
-e, --extended-exact Extended-search mode (exact match)
|
-e, --extended-exact Extended-search mode (exact match)
|
||||||
-i Case-insensitive match (default: smart-case match)
|
-i Case-insensitive match (default: smart-case match)
|
||||||
@ -87,8 +87,9 @@ usage: fzf [options]
|
|||||||
-d, --delimiter=STR Field delimiter regex for --nth (default: AWK-style)
|
-d, --delimiter=STR Field delimiter regex for --nth (default: AWK-style)
|
||||||
|
|
||||||
Search result
|
Search result
|
||||||
-s, --sort Sort the result
|
+s, --no-sort Do not sort the result
|
||||||
+s, --no-sort Do not sort the result. Keep the sequence unchanged.
|
--tac Reverse the order of the input
|
||||||
|
(e.g. 'history | fzf --tac --no-sort')
|
||||||
|
|
||||||
Interface
|
Interface
|
||||||
-m, --multi Enable multi-select with tab/shift-tab
|
-m, --multi Enable multi-select with tab/shift-tab
|
||||||
@ -128,13 +129,6 @@ files excluding hidden ones. (You can override the default command with
|
|||||||
vim $(fzf)
|
vim $(fzf)
|
||||||
```
|
```
|
||||||
|
|
||||||
If you want to preserve the exact sequence of the input, provide `--no-sort` (or
|
|
||||||
`+s`) option.
|
|
||||||
|
|
||||||
```sh
|
|
||||||
history | fzf +s
|
|
||||||
```
|
|
||||||
|
|
||||||
### Keys
|
### Keys
|
||||||
|
|
||||||
Use CTRL-J and CTRL-K (or CTRL-N and CTRL-P) to change the selection, press
|
Use CTRL-J and CTRL-K (or CTRL-N and CTRL-P) to change the selection, press
|
||||||
@ -197,7 +191,7 @@ fd() {
|
|||||||
|
|
||||||
# fh - repeat history
|
# fh - repeat history
|
||||||
fh() {
|
fh() {
|
||||||
eval $(([ -n "$ZSH_NAME" ] && fc -l 1 || history) | fzf +s | sed 's/ *[0-9]* *//')
|
eval $(([ -n "$ZSH_NAME" ] && fc -l 1 || history) | fzf +s --tac | sed 's/ *[0-9]* *//')
|
||||||
}
|
}
|
||||||
|
|
||||||
# fkill - kill process
|
# fkill - kill process
|
||||||
|
10
install
10
install
@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
version=0.9.3
|
version=0.9.4
|
||||||
|
|
||||||
cd $(dirname $BASH_SOURCE)
|
cd $(dirname $BASH_SOURCE)
|
||||||
fzf_base=$(pwd)
|
fzf_base=$(pwd)
|
||||||
@ -245,7 +245,7 @@ if [ -z "$(set -o | \grep '^vi.*on')" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# CTRL-R - Paste the selected command from history into the command line
|
# CTRL-R - Paste the selected command from history into the command line
|
||||||
bind '"\C-r": " \C-e\C-u$(HISTTIMEFORMAT= history | fzf +s +m -n2..,.. | sed \"s/ *[0-9]* *//\")\e\C-e\er"'
|
bind '"\C-r": " \C-e\C-u$(HISTTIMEFORMAT= history | fzf +s --tac +m -n2..,.. | sed \"s/ *[0-9]* *//\")\e\C-e\er"'
|
||||||
|
|
||||||
# ALT-C - cd into the selected directory
|
# ALT-C - cd into the selected directory
|
||||||
bind '"\ec": " \C-e\C-u$(__fcd)\e\C-e\er\C-m"'
|
bind '"\ec": " \C-e\C-u$(__fcd)\e\C-e\er\C-m"'
|
||||||
@ -263,7 +263,7 @@ else
|
|||||||
bind -m vi-command '"\C-t": "i\C-t"'
|
bind -m vi-command '"\C-t": "i\C-t"'
|
||||||
|
|
||||||
# CTRL-R - Paste the selected command from history into the command line
|
# CTRL-R - Paste the selected command from history into the command line
|
||||||
bind '"\C-r": "\eddi$(HISTTIMEFORMAT= history | fzf +s +m -n2..,.. | sed \"s/ *[0-9]* *//\")\C-x\C-e\e$a\C-x\C-r"'
|
bind '"\C-r": "\eddi$(HISTTIMEFORMAT= history | fzf +s --tac +m -n2..,.. | sed \"s/ *[0-9]* *//\")\C-x\C-e\e$a\C-x\C-r"'
|
||||||
bind -m vi-command '"\C-r": "i\C-r"'
|
bind -m vi-command '"\C-r": "i\C-r"'
|
||||||
|
|
||||||
# ALT-C - cd into the selected directory
|
# ALT-C - cd into the selected directory
|
||||||
@ -323,7 +323,7 @@ bindkey '\ec' fzf-cd-widget
|
|||||||
|
|
||||||
# CTRL-R - Paste the selected command from history into the command line
|
# CTRL-R - Paste the selected command from history into the command line
|
||||||
fzf-history-widget() {
|
fzf-history-widget() {
|
||||||
LBUFFER=$(fc -l 1 | fzf +s +m -n2..,.. | sed "s/ *[0-9*]* *//")
|
LBUFFER=$(fc -l 1 | fzf +s --tac +m -n2..,.. | sed "s/ *[0-9*]* *//")
|
||||||
zle redisplay
|
zle redisplay
|
||||||
}
|
}
|
||||||
zle -N fzf-history-widget
|
zle -N fzf-history-widget
|
||||||
@ -412,7 +412,7 @@ function fzf_key_bindings
|
|||||||
end
|
end
|
||||||
|
|
||||||
function __fzf_ctrl_r
|
function __fzf_ctrl_r
|
||||||
history | __fzf_reverse | fzf +s +m > $TMPDIR/fzf.result
|
history | __fzf_reverse | fzf +s --tac +m > $TMPDIR/fzf.result
|
||||||
and commandline (cat $TMPDIR/fzf.result)
|
and commandline (cat $TMPDIR/fzf.result)
|
||||||
commandline -f repaint
|
commandline -f repaint
|
||||||
rm -f $TMPDIR/fzf.result
|
rm -f $TMPDIR/fzf.result
|
||||||
|
@ -5,7 +5,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Current version
|
// Current version
|
||||||
const Version = "0.9.3"
|
const Version = "0.9.4"
|
||||||
|
|
||||||
// fzf events
|
// fzf events
|
||||||
const (
|
const (
|
||||||
|
@ -93,7 +93,7 @@ func Run(options *Options) {
|
|||||||
return BuildPattern(
|
return BuildPattern(
|
||||||
opts.Mode, opts.Case, opts.Nth, opts.Delimiter, runes)
|
opts.Mode, opts.Case, opts.Nth, opts.Delimiter, runes)
|
||||||
}
|
}
|
||||||
matcher := NewMatcher(patternBuilder, opts.Sort > 0, eventBox)
|
matcher := NewMatcher(patternBuilder, opts.Sort > 0, opts.Tac, eventBox)
|
||||||
|
|
||||||
// Filtering mode
|
// Filtering mode
|
||||||
if opts.Filter != nil {
|
if opts.Filter != nil {
|
||||||
|
27
src/item.go
27
src/item.go
@ -87,10 +87,28 @@ func (a ByRelevance) Less(i, j int) bool {
|
|||||||
irank := a[i].Rank(true)
|
irank := a[i].Rank(true)
|
||||||
jrank := a[j].Rank(true)
|
jrank := a[j].Rank(true)
|
||||||
|
|
||||||
return compareRanks(irank, jrank)
|
return compareRanks(irank, jrank, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func compareRanks(irank Rank, jrank Rank) bool {
|
// ByRelevanceTac is for sorting Items
|
||||||
|
type ByRelevanceTac []*Item
|
||||||
|
|
||||||
|
func (a ByRelevanceTac) Len() int {
|
||||||
|
return len(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a ByRelevanceTac) Swap(i, j int) {
|
||||||
|
a[i], a[j] = a[j], a[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a ByRelevanceTac) Less(i, j int) bool {
|
||||||
|
irank := a[i].Rank(true)
|
||||||
|
jrank := a[j].Rank(true)
|
||||||
|
|
||||||
|
return compareRanks(irank, jrank, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func compareRanks(irank Rank, jrank Rank, tac bool) bool {
|
||||||
if irank.matchlen < jrank.matchlen {
|
if irank.matchlen < jrank.matchlen {
|
||||||
return true
|
return true
|
||||||
} else if irank.matchlen > jrank.matchlen {
|
} else if irank.matchlen > jrank.matchlen {
|
||||||
@ -103,8 +121,5 @@ func compareRanks(irank Rank, jrank Rank) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if irank.index <= jrank.index {
|
return (irank.index <= jrank.index) != tac
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
@ -20,12 +20,19 @@ func TestOffsetSort(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRankComparison(t *testing.T) {
|
func TestRankComparison(t *testing.T) {
|
||||||
if compareRanks(Rank{3, 0, 5}, Rank{2, 0, 7}) ||
|
if compareRanks(Rank{3, 0, 5}, Rank{2, 0, 7}, false) ||
|
||||||
!compareRanks(Rank{3, 0, 5}, Rank{3, 0, 6}) ||
|
!compareRanks(Rank{3, 0, 5}, Rank{3, 0, 6}, false) ||
|
||||||
!compareRanks(Rank{1, 2, 3}, Rank{1, 3, 2}) ||
|
!compareRanks(Rank{1, 2, 3}, Rank{1, 3, 2}, false) ||
|
||||||
!compareRanks(Rank{0, 0, 0}, Rank{0, 0, 0}) {
|
!compareRanks(Rank{0, 0, 0}, Rank{0, 0, 0}, false) {
|
||||||
t.Error("Invalid order")
|
t.Error("Invalid order")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if compareRanks(Rank{3, 0, 5}, Rank{2, 0, 7}, true) ||
|
||||||
|
!compareRanks(Rank{3, 0, 5}, Rank{3, 0, 6}, false) ||
|
||||||
|
!compareRanks(Rank{1, 2, 3}, Rank{1, 3, 2}, true) ||
|
||||||
|
!compareRanks(Rank{0, 0, 0}, Rank{0, 0, 0}, false) {
|
||||||
|
t.Error("Invalid order (tac)")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Match length, string length, index
|
// Match length, string length, index
|
||||||
|
@ -21,6 +21,7 @@ type MatchRequest struct {
|
|||||||
type Matcher struct {
|
type Matcher struct {
|
||||||
patternBuilder func([]rune) *Pattern
|
patternBuilder func([]rune) *Pattern
|
||||||
sort bool
|
sort bool
|
||||||
|
tac bool
|
||||||
eventBox *util.EventBox
|
eventBox *util.EventBox
|
||||||
reqBox *util.EventBox
|
reqBox *util.EventBox
|
||||||
partitions int
|
partitions int
|
||||||
@ -38,10 +39,11 @@ const (
|
|||||||
|
|
||||||
// NewMatcher returns a new Matcher
|
// NewMatcher returns a new Matcher
|
||||||
func NewMatcher(patternBuilder func([]rune) *Pattern,
|
func NewMatcher(patternBuilder func([]rune) *Pattern,
|
||||||
sort bool, eventBox *util.EventBox) *Matcher {
|
sort bool, tac bool, eventBox *util.EventBox) *Matcher {
|
||||||
return &Matcher{
|
return &Matcher{
|
||||||
patternBuilder: patternBuilder,
|
patternBuilder: patternBuilder,
|
||||||
sort: sort,
|
sort: sort,
|
||||||
|
tac: tac,
|
||||||
eventBox: eventBox,
|
eventBox: eventBox,
|
||||||
reqBox: util.NewEventBox(),
|
reqBox: util.NewEventBox(),
|
||||||
partitions: runtime.NumCPU(),
|
partitions: runtime.NumCPU(),
|
||||||
@ -159,8 +161,12 @@ func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
|
|||||||
countChan <- len(matches)
|
countChan <- len(matches)
|
||||||
}
|
}
|
||||||
if !empty && m.sort {
|
if !empty && m.sort {
|
||||||
|
if m.tac {
|
||||||
|
sort.Sort(ByRelevanceTac(sliceMatches))
|
||||||
|
} else {
|
||||||
sort.Sort(ByRelevance(sliceMatches))
|
sort.Sort(ByRelevance(sliceMatches))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
resultChan <- partialResult{idx, sliceMatches}
|
resultChan <- partialResult{idx, sliceMatches}
|
||||||
}(idx, chunks)
|
}(idx, chunks)
|
||||||
}
|
}
|
||||||
@ -195,7 +201,7 @@ func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
|
|||||||
partialResult := <-resultChan
|
partialResult := <-resultChan
|
||||||
partialResults[partialResult.index] = partialResult.matches
|
partialResults[partialResult.index] = partialResult.matches
|
||||||
}
|
}
|
||||||
return NewMerger(partialResults, !empty && m.sort), false
|
return NewMerger(partialResults, !empty && m.sort, m.tac), false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset is called to interrupt/signal the ongoing search
|
// Reset is called to interrupt/signal the ongoing search
|
||||||
|
@ -3,7 +3,7 @@ package fzf
|
|||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
// Merger with no data
|
// Merger with no data
|
||||||
var EmptyMerger = NewMerger([][]*Item{}, false)
|
var EmptyMerger = NewMerger([][]*Item{}, false, false)
|
||||||
|
|
||||||
// Merger holds a set of locally sorted lists of items and provides the view of
|
// Merger holds a set of locally sorted lists of items and provides the view of
|
||||||
// a single, globally-sorted list
|
// a single, globally-sorted list
|
||||||
@ -12,17 +12,19 @@ type Merger struct {
|
|||||||
merged []*Item
|
merged []*Item
|
||||||
cursors []int
|
cursors []int
|
||||||
sorted bool
|
sorted bool
|
||||||
|
tac bool
|
||||||
final bool
|
final bool
|
||||||
count int
|
count int
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMerger returns a new Merger
|
// NewMerger returns a new Merger
|
||||||
func NewMerger(lists [][]*Item, sorted bool) *Merger {
|
func NewMerger(lists [][]*Item, sorted bool, tac bool) *Merger {
|
||||||
mg := Merger{
|
mg := Merger{
|
||||||
lists: lists,
|
lists: lists,
|
||||||
merged: []*Item{},
|
merged: []*Item{},
|
||||||
cursors: make([]int, len(lists)),
|
cursors: make([]int, len(lists)),
|
||||||
sorted: sorted,
|
sorted: sorted,
|
||||||
|
tac: tac,
|
||||||
final: false,
|
final: false,
|
||||||
count: 0}
|
count: 0}
|
||||||
|
|
||||||
@ -39,9 +41,13 @@ func (mg *Merger) Length() int {
|
|||||||
|
|
||||||
// Get returns the pointer to the Item object indexed by the given integer
|
// Get returns the pointer to the Item object indexed by the given integer
|
||||||
func (mg *Merger) Get(idx int) *Item {
|
func (mg *Merger) Get(idx int) *Item {
|
||||||
if len(mg.lists) == 1 {
|
if mg.sorted {
|
||||||
return mg.lists[0][idx]
|
return mg.mergedGet(idx)
|
||||||
} else if !mg.sorted {
|
}
|
||||||
|
|
||||||
|
if mg.tac {
|
||||||
|
idx = mg.Length() - idx - 1
|
||||||
|
}
|
||||||
for _, list := range mg.lists {
|
for _, list := range mg.lists {
|
||||||
numItems := len(list)
|
numItems := len(list)
|
||||||
if idx < numItems {
|
if idx < numItems {
|
||||||
@ -51,8 +57,6 @@ func (mg *Merger) Get(idx int) *Item {
|
|||||||
}
|
}
|
||||||
panic(fmt.Sprintf("Index out of bounds (unsorted, %d/%d)", idx, mg.count))
|
panic(fmt.Sprintf("Index out of bounds (unsorted, %d/%d)", idx, mg.count))
|
||||||
}
|
}
|
||||||
return mg.mergedGet(idx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mg *Merger) mergedGet(idx int) *Item {
|
func (mg *Merger) mergedGet(idx int) *Item {
|
||||||
for i := len(mg.merged); i <= idx; i++ {
|
for i := len(mg.merged); i <= idx; i++ {
|
||||||
@ -66,7 +70,7 @@ func (mg *Merger) mergedGet(idx int) *Item {
|
|||||||
}
|
}
|
||||||
if cursor >= 0 {
|
if cursor >= 0 {
|
||||||
rank := list[cursor].Rank(false)
|
rank := list[cursor].Rank(false)
|
||||||
if minIdx < 0 || compareRanks(rank, minRank) {
|
if minIdx < 0 || compareRanks(rank, minRank, mg.tac) {
|
||||||
minRank = rank
|
minRank = rank
|
||||||
minIdx = listIdx
|
minIdx = listIdx
|
||||||
}
|
}
|
||||||
|
@ -62,7 +62,7 @@ func TestMergerUnsorted(t *testing.T) {
|
|||||||
cnt := len(items)
|
cnt := len(items)
|
||||||
|
|
||||||
// Not sorted: same order
|
// Not sorted: same order
|
||||||
mg := NewMerger(lists, false)
|
mg := NewMerger(lists, false, false)
|
||||||
assert(t, cnt == mg.Length(), "Invalid Length")
|
assert(t, cnt == mg.Length(), "Invalid Length")
|
||||||
for i := 0; i < cnt; i++ {
|
for i := 0; i < cnt; i++ {
|
||||||
assert(t, items[i] == mg.Get(i), "Invalid Get")
|
assert(t, items[i] == mg.Get(i), "Invalid Get")
|
||||||
@ -74,7 +74,7 @@ func TestMergerSorted(t *testing.T) {
|
|||||||
cnt := len(items)
|
cnt := len(items)
|
||||||
|
|
||||||
// Sorted sorted order
|
// Sorted sorted order
|
||||||
mg := NewMerger(lists, true)
|
mg := NewMerger(lists, true, false)
|
||||||
assert(t, cnt == mg.Length(), "Invalid Length")
|
assert(t, cnt == mg.Length(), "Invalid Length")
|
||||||
sort.Sort(ByRelevance(items))
|
sort.Sort(ByRelevance(items))
|
||||||
for i := 0; i < cnt; i++ {
|
for i := 0; i < cnt; i++ {
|
||||||
@ -84,7 +84,7 @@ func TestMergerSorted(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Inverse order
|
// Inverse order
|
||||||
mg2 := NewMerger(lists, true)
|
mg2 := NewMerger(lists, true, false)
|
||||||
for i := cnt - 1; i >= 0; i-- {
|
for i := cnt - 1; i >= 0; i-- {
|
||||||
if items[i] != mg2.Get(i) {
|
if items[i] != mg2.Get(i) {
|
||||||
t.Error("Not sorted", items[i], mg2.Get(i))
|
t.Error("Not sorted", items[i], mg2.Get(i))
|
||||||
|
@ -11,7 +11,7 @@ import (
|
|||||||
|
|
||||||
const usage = `usage: fzf [options]
|
const usage = `usage: fzf [options]
|
||||||
|
|
||||||
Search
|
Search mode
|
||||||
-x, --extended Extended-search mode
|
-x, --extended Extended-search mode
|
||||||
-e, --extended-exact Extended-search mode (exact match)
|
-e, --extended-exact Extended-search mode (exact match)
|
||||||
-i Case-insensitive match (default: smart-case match)
|
-i Case-insensitive match (default: smart-case match)
|
||||||
@ -23,8 +23,9 @@ const usage = `usage: fzf [options]
|
|||||||
-d, --delimiter=STR Field delimiter regex for --nth (default: AWK-style)
|
-d, --delimiter=STR Field delimiter regex for --nth (default: AWK-style)
|
||||||
|
|
||||||
Search result
|
Search result
|
||||||
-s, --sort Sort the result
|
+s, --no-sort Do not sort the result
|
||||||
+s, --no-sort Do not sort the result. Keep the sequence unchanged.
|
--tac Reverse the order of the input
|
||||||
|
(e.g. 'history | fzf --tac --no-sort')
|
||||||
|
|
||||||
Interface
|
Interface
|
||||||
-m, --multi Enable multi-select with tab/shift-tab
|
-m, --multi Enable multi-select with tab/shift-tab
|
||||||
@ -78,6 +79,7 @@ type Options struct {
|
|||||||
WithNth []Range
|
WithNth []Range
|
||||||
Delimiter *regexp.Regexp
|
Delimiter *regexp.Regexp
|
||||||
Sort int
|
Sort int
|
||||||
|
Tac bool
|
||||||
Multi bool
|
Multi bool
|
||||||
Mouse bool
|
Mouse bool
|
||||||
Color bool
|
Color bool
|
||||||
@ -102,6 +104,7 @@ func defaultOptions() *Options {
|
|||||||
WithNth: make([]Range, 0),
|
WithNth: make([]Range, 0),
|
||||||
Delimiter: nil,
|
Delimiter: nil,
|
||||||
Sort: 1000,
|
Sort: 1000,
|
||||||
|
Tac: false,
|
||||||
Multi: false,
|
Multi: false,
|
||||||
Mouse: true,
|
Mouse: true,
|
||||||
Color: true,
|
Color: true,
|
||||||
@ -212,6 +215,10 @@ func parseOptions(opts *Options, allArgs []string) {
|
|||||||
opts.Sort = optionalNumeric(allArgs, &i)
|
opts.Sort = optionalNumeric(allArgs, &i)
|
||||||
case "+s", "--no-sort":
|
case "+s", "--no-sort":
|
||||||
opts.Sort = 0
|
opts.Sort = 0
|
||||||
|
case "--tac":
|
||||||
|
opts.Tac = true
|
||||||
|
case "--no-tac":
|
||||||
|
opts.Tac = false
|
||||||
case "-i":
|
case "-i":
|
||||||
opts.Case = CaseIgnore
|
opts.Case = CaseIgnore
|
||||||
case "+i":
|
case "+i":
|
||||||
|
@ -22,7 +22,6 @@ import (
|
|||||||
type Terminal struct {
|
type Terminal struct {
|
||||||
prompt string
|
prompt string
|
||||||
reverse bool
|
reverse bool
|
||||||
tac bool
|
|
||||||
cx int
|
cx int
|
||||||
cy int
|
cy int
|
||||||
offset int
|
offset int
|
||||||
@ -85,7 +84,6 @@ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
|
|||||||
input := []rune(opts.Query)
|
input := []rune(opts.Query)
|
||||||
return &Terminal{
|
return &Terminal{
|
||||||
prompt: opts.Prompt,
|
prompt: opts.Prompt,
|
||||||
tac: opts.Sort == 0,
|
|
||||||
reverse: opts.Reverse,
|
reverse: opts.Reverse,
|
||||||
cx: len(input),
|
cx: len(input),
|
||||||
cy: 0,
|
cy: 0,
|
||||||
@ -148,13 +146,6 @@ func (t *Terminal) UpdateList(merger *Merger) {
|
|||||||
t.reqBox.Set(reqList, nil)
|
t.reqBox.Set(reqList, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Terminal) listIndex(y int) int {
|
|
||||||
if t.tac {
|
|
||||||
return t.merger.Length() - y - 1
|
|
||||||
}
|
|
||||||
return y
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Terminal) output() {
|
func (t *Terminal) output() {
|
||||||
if t.printQuery {
|
if t.printQuery {
|
||||||
fmt.Println(string(t.input))
|
fmt.Println(string(t.input))
|
||||||
@ -162,7 +153,7 @@ func (t *Terminal) output() {
|
|||||||
if len(t.selected) == 0 {
|
if len(t.selected) == 0 {
|
||||||
cnt := t.merger.Length()
|
cnt := t.merger.Length()
|
||||||
if cnt > 0 && cnt > t.cy {
|
if cnt > 0 && cnt > t.cy {
|
||||||
fmt.Println(t.merger.Get(t.listIndex(t.cy)).AsString())
|
fmt.Println(t.merger.Get(t.cy).AsString())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
sels := make([]selectedItem, 0, len(t.selected))
|
sels := make([]selectedItem, 0, len(t.selected))
|
||||||
@ -246,7 +237,7 @@ func (t *Terminal) printList() {
|
|||||||
for i := 0; i < maxy; i++ {
|
for i := 0; i < maxy; i++ {
|
||||||
t.move(i+2, 0, true)
|
t.move(i+2, 0, true)
|
||||||
if i < count {
|
if i < count {
|
||||||
t.printItem(t.merger.Get(t.listIndex(i+t.offset)), i == t.cy-t.offset)
|
t.printItem(t.merger.Get(i+t.offset), i == t.cy-t.offset)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -525,9 +516,8 @@ func (t *Terminal) Loop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
toggle := func() {
|
toggle := func() {
|
||||||
idx := t.listIndex(t.cy)
|
if t.cy < t.merger.Length() {
|
||||||
if idx < t.merger.Length() {
|
item := t.merger.Get(t.cy)
|
||||||
item := t.merger.Get(idx)
|
|
||||||
if _, found := t.selected[item.text]; !found {
|
if _, found := t.selected[item.text]; !found {
|
||||||
var strptr *string
|
var strptr *string
|
||||||
if item.origText != nil {
|
if item.origText != nil {
|
||||||
@ -650,7 +640,7 @@ func (t *Terminal) Loop() {
|
|||||||
} else if me.Double {
|
} else if me.Double {
|
||||||
// Double-click
|
// Double-click
|
||||||
if my >= 2 {
|
if my >= 2 {
|
||||||
if t.vset(my-2) && t.listIndex(t.cy) < t.merger.Length() {
|
if t.vset(my-2) && t.cy < t.merger.Length() {
|
||||||
req(reqClose)
|
req(reqClose)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ module Temp
|
|||||||
waited = 0
|
waited = 0
|
||||||
while waited < 5
|
while waited < 5
|
||||||
begin
|
begin
|
||||||
data = File.read(name)
|
data = `cat #{name}`
|
||||||
return data unless data.empty?
|
return data unless data.empty?
|
||||||
rescue
|
rescue
|
||||||
sleep 0.1
|
sleep 0.1
|
||||||
@ -93,7 +93,7 @@ private
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class TestGoFZF < MiniTest::Unit::TestCase
|
class TestGoFZF < Minitest::Test
|
||||||
include Temp
|
include Temp
|
||||||
|
|
||||||
FIN = 'FIN'
|
FIN = 'FIN'
|
||||||
@ -322,5 +322,28 @@ class TestGoFZF < MiniTest::Unit::TestCase
|
|||||||
tmux.send_keys 'C-K', :Enter
|
tmux.send_keys 'C-K', :Enter
|
||||||
assert_equal ['1919'], readonce.split($/)
|
assert_equal ['1919'], readonce.split($/)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_tac
|
||||||
|
tmux.send_keys "seq 1 1000 | #{fzf :tac, :multi}", :Enter
|
||||||
|
tmux.until { |lines| lines[-2].include? '1000/1000' }
|
||||||
|
tmux.send_keys :BTab, :BTab, :BTab, :Enter
|
||||||
|
assert_equal %w[1000 999 998], readonce.split($/)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_tac_sort
|
||||||
|
tmux.send_keys "seq 1 1000 | #{fzf :tac, :multi}", :Enter
|
||||||
|
tmux.until { |lines| lines[-2].include? '1000/1000' }
|
||||||
|
tmux.send_keys '99'
|
||||||
|
tmux.send_keys :BTab, :BTab, :BTab, :Enter
|
||||||
|
assert_equal %w[99 999 998], readonce.split($/)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_tac_nosort
|
||||||
|
tmux.send_keys "seq 1 1000 | #{fzf :tac, :no_sort, :multi}", :Enter
|
||||||
|
tmux.until { |lines| lines[-2].include? '1000/1000' }
|
||||||
|
tmux.send_keys '00'
|
||||||
|
tmux.send_keys :BTab, :BTab, :BTab, :Enter
|
||||||
|
assert_equal %w[1000 900 800], readonce.split($/)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ class MockTTY
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class TestRubyFZF < MiniTest::Unit::TestCase
|
class TestRubyFZF < Minitest::Test
|
||||||
def setup
|
def setup
|
||||||
ENV.delete 'FZF_DEFAULT_SORT'
|
ENV.delete 'FZF_DEFAULT_SORT'
|
||||||
ENV.delete 'FZF_DEFAULT_OPTS'
|
ENV.delete 'FZF_DEFAULT_OPTS'
|
||||||
|
Loading…
Reference in New Issue
Block a user