This commit is contained in:
Junegunn Choi 2015-01-12 03:01:24 +09:00
parent 9dbf6b02d2
commit 7a2bc2cada
24 changed files with 478 additions and 405 deletions

View File

@ -10,6 +10,7 @@ import "strings"
* In short: They try to do as little work as possible. * In short: They try to do as little work as possible.
*/ */
// FuzzyMatch performs fuzzy-match
func FuzzyMatch(caseSensitive bool, input *string, pattern []rune) (int, int) { func FuzzyMatch(caseSensitive bool, input *string, pattern []rune) (int, int) {
runes := []rune(*input) runes := []rune(*input)
@ -36,7 +37,7 @@ func FuzzyMatch(caseSensitive bool, input *string, pattern []rune) (int, int) {
if sidx < 0 { if sidx < 0 {
sidx = index sidx = index
} }
if pidx += 1; pidx == len(pattern) { if pidx++; pidx == len(pattern) {
eidx = index + 1 eidx = index + 1
break break
} }
@ -44,14 +45,14 @@ func FuzzyMatch(caseSensitive bool, input *string, pattern []rune) (int, int) {
} }
if sidx >= 0 && eidx >= 0 { if sidx >= 0 && eidx >= 0 {
pidx -= 1 pidx--
for index := eidx - 1; index >= sidx; index-- { for index := eidx - 1; index >= sidx; index-- {
char := runes[index] char := runes[index]
if !caseSensitive && char >= 65 && char <= 90 { if !caseSensitive && char >= 65 && char <= 90 {
char += 32 char += 32
} }
if char == pattern[pidx] { if char == pattern[pidx] {
if pidx -= 1; pidx < 0 { if pidx--; pidx < 0 {
sidx = index sidx = index
break break
} }
@ -62,6 +63,8 @@ func FuzzyMatch(caseSensitive bool, input *string, pattern []rune) (int, int) {
return -1, -1 return -1, -1
} }
// ExactMatchStrings performs exact-match using strings package.
// Currently not used.
func ExactMatchStrings(caseSensitive bool, input *string, pattern []rune) (int, int) { func ExactMatchStrings(caseSensitive bool, input *string, pattern []rune) (int, int) {
var str string var str string
if caseSensitive { if caseSensitive {
@ -77,15 +80,13 @@ func ExactMatchStrings(caseSensitive bool, input *string, pattern []rune) (int,
return -1, -1 return -1, -1
} }
/* // ExactMatchNaive is a basic string searching algorithm that handles case
* This is a basic string searching algorithm that handles case sensitivity. // sensitivity. Although naive, it still performs better than the combination
* Although naive, it still performs better than the combination of // of strings.ToLower + strings.Index for typical fzf use cases where input
* strings.ToLower + strings.Index for typical fzf use cases where input // strings and patterns are not very long.
* strings and patterns are not very long. //
* // We might try to implement better algorithms in the future:
* We might try to implement better algorithms in the future: // http://en.wikipedia.org/wiki/String_searching_algorithm
* http://en.wikipedia.org/wiki/String_searching_algorithm
*/
func ExactMatchNaive(caseSensitive bool, input *string, pattern []rune) (int, int) { func ExactMatchNaive(caseSensitive bool, input *string, pattern []rune) (int, int) {
runes := []rune(*input) runes := []rune(*input)
numRunes := len(runes) numRunes := len(runes)
@ -101,7 +102,7 @@ func ExactMatchNaive(caseSensitive bool, input *string, pattern []rune) (int, in
char += 32 char += 32
} }
if pattern[pidx] == char { if pattern[pidx] == char {
pidx += 1 pidx++
if pidx == plen { if pidx == plen {
return index - plen + 1, index + 1 return index - plen + 1, index + 1
} }
@ -113,6 +114,7 @@ func ExactMatchNaive(caseSensitive bool, input *string, pattern []rune) (int, in
return -1, -1 return -1, -1
} }
// PrefixMatch performs prefix-match
func PrefixMatch(caseSensitive bool, input *string, pattern []rune) (int, int) { func PrefixMatch(caseSensitive bool, input *string, pattern []rune) (int, int) {
runes := []rune(*input) runes := []rune(*input)
if len(runes) < len(pattern) { if len(runes) < len(pattern) {
@ -131,6 +133,7 @@ func PrefixMatch(caseSensitive bool, input *string, pattern []rune) (int, int) {
return 0, len(pattern) return 0, len(pattern)
} }
// SuffixMatch performs suffix-match
func SuffixMatch(caseSensitive bool, input *string, pattern []rune) (int, int) { func SuffixMatch(caseSensitive bool, input *string, pattern []rune) (int, int) {
runes := []rune(strings.TrimRight(*input, " ")) runes := []rune(strings.TrimRight(*input, " "))
trimmedLen := len(runes) trimmedLen := len(runes)

View File

@ -2,23 +2,28 @@ package fzf
import "sync" import "sync"
// AtomicBool is a boxed-class that provides synchronized access to the
// underlying boolean value
type AtomicBool struct { type AtomicBool struct {
mutex sync.Mutex mutex sync.Mutex
state bool state bool
} }
// NewAtomicBool returns a new AtomicBool
func NewAtomicBool(initialState bool) *AtomicBool { func NewAtomicBool(initialState bool) *AtomicBool {
return &AtomicBool{ return &AtomicBool{
mutex: sync.Mutex{}, mutex: sync.Mutex{},
state: initialState} state: initialState}
} }
// Get returns the current boolean value synchronously
func (a *AtomicBool) Get() bool { func (a *AtomicBool) Get() bool {
a.mutex.Lock() a.mutex.Lock()
defer a.mutex.Unlock() defer a.mutex.Unlock()
return a.state return a.state
} }
// Set updates the boolean value synchronously
func (a *AtomicBool) Set(newState bool) bool { func (a *AtomicBool) Set(newState bool) bool {
a.mutex.Lock() a.mutex.Lock()
defer a.mutex.Unlock() defer a.mutex.Unlock()

View File

@ -2,16 +2,21 @@ package fzf
import "sync" import "sync"
// QueryCache associates strings to lists of items
type QueryCache map[string][]*Item type QueryCache map[string][]*Item
// ChunkCache associates Chunk and query string to lists of items
type ChunkCache struct { type ChunkCache struct {
mutex sync.Mutex mutex sync.Mutex
cache map[*Chunk]*QueryCache cache map[*Chunk]*QueryCache
} }
// NewChunkCache returns a new ChunkCache
func NewChunkCache() ChunkCache { func NewChunkCache() ChunkCache {
return ChunkCache{sync.Mutex{}, make(map[*Chunk]*QueryCache)} return ChunkCache{sync.Mutex{}, make(map[*Chunk]*QueryCache)}
} }
// Add adds the list to the cache
func (cc *ChunkCache) Add(chunk *Chunk, key string, list []*Item) { func (cc *ChunkCache) Add(chunk *Chunk, key string, list []*Item) {
if len(key) == 0 || !chunk.IsFull() { if len(key) == 0 || !chunk.IsFull() {
return return
@ -28,6 +33,7 @@ func (cc *ChunkCache) Add(chunk *Chunk, key string, list []*Item) {
(*qc)[key] = list (*qc)[key] = list
} }
// Find is called to lookup ChunkCache
func (cc *ChunkCache) Find(chunk *Chunk, key string) ([]*Item, bool) { func (cc *ChunkCache) Find(chunk *Chunk, key string) ([]*Item, bool) {
if len(key) == 0 || !chunk.IsFull() { if len(key) == 0 || !chunk.IsFull() {
return nil, false return nil, false

View File

@ -4,7 +4,7 @@ import "testing"
func TestChunkCache(t *testing.T) { func TestChunkCache(t *testing.T) {
cache := NewChunkCache() cache := NewChunkCache()
chunk2 := make(Chunk, CHUNK_SIZE) chunk2 := make(Chunk, ChunkSize)
chunk1p := &Chunk{} chunk1p := &Chunk{}
chunk2p := &chunk2 chunk2p := &chunk2
items1 := []*Item{&Item{}} items1 := []*Item{&Item{}}

View File

@ -2,12 +2,17 @@ package fzf
import "sync" import "sync"
const CHUNK_SIZE int = 100 // Capacity of each chunk
const ChunkSize int = 100
// Chunk is a list of Item pointers whose size has the upper limit of ChunkSize
type Chunk []*Item // >>> []Item type Chunk []*Item // >>> []Item
// Transformer is a closure type that builds Item object from a pointer to a
// string and an integer
type Transformer func(*string, int) *Item type Transformer func(*string, int) *Item
// ChunkList is a list of Chunks
type ChunkList struct { type ChunkList struct {
chunks []*Chunk chunks []*Chunk
count int count int
@ -15,6 +20,7 @@ type ChunkList struct {
trans Transformer trans Transformer
} }
// NewChunkList returns a new ChunkList
func NewChunkList(trans Transformer) *ChunkList { func NewChunkList(trans Transformer) *ChunkList {
return &ChunkList{ return &ChunkList{
chunks: []*Chunk{}, chunks: []*Chunk{},
@ -27,34 +33,38 @@ func (c *Chunk) push(trans Transformer, data *string, index int) {
*c = append(*c, trans(data, index)) *c = append(*c, trans(data, index))
} }
// IsFull returns true if the Chunk is full
func (c *Chunk) IsFull() bool { func (c *Chunk) IsFull() bool {
return len(*c) == CHUNK_SIZE return len(*c) == ChunkSize
} }
func (cl *ChunkList) lastChunk() *Chunk { func (cl *ChunkList) lastChunk() *Chunk {
return cl.chunks[len(cl.chunks)-1] return cl.chunks[len(cl.chunks)-1]
} }
// CountItems returns the total number of Items
func CountItems(cs []*Chunk) int { func CountItems(cs []*Chunk) int {
if len(cs) == 0 { if len(cs) == 0 {
return 0 return 0
} }
return CHUNK_SIZE*(len(cs)-1) + len(*(cs[len(cs)-1])) return ChunkSize*(len(cs)-1) + len(*(cs[len(cs)-1]))
} }
// Push adds the item to the list
func (cl *ChunkList) Push(data string) { func (cl *ChunkList) Push(data string) {
cl.mutex.Lock() cl.mutex.Lock()
defer cl.mutex.Unlock() defer cl.mutex.Unlock()
if len(cl.chunks) == 0 || cl.lastChunk().IsFull() { if len(cl.chunks) == 0 || cl.lastChunk().IsFull() {
newChunk := Chunk(make([]*Item, 0, CHUNK_SIZE)) newChunk := Chunk(make([]*Item, 0, ChunkSize))
cl.chunks = append(cl.chunks, &newChunk) cl.chunks = append(cl.chunks, &newChunk)
} }
cl.lastChunk().push(cl.trans, &data, cl.count) cl.lastChunk().push(cl.trans, &data, cl.count)
cl.count += 1 cl.count++
} }
// Snapshot returns immutable snapshot of the ChunkList
func (cl *ChunkList) Snapshot() ([]*Chunk, int) { func (cl *ChunkList) Snapshot() ([]*Chunk, int) {
cl.mutex.Lock() cl.mutex.Lock()
defer cl.mutex.Unlock() defer cl.mutex.Unlock()

View File

@ -45,7 +45,7 @@ func TestChunkList(t *testing.T) {
} }
// Add more data // Add more data
for i := 0; i < CHUNK_SIZE*2; i++ { for i := 0; i < ChunkSize*2; i++ {
cl.Push(fmt.Sprintf("item %d", i)) cl.Push(fmt.Sprintf("item %d", i))
} }
@ -57,7 +57,7 @@ func TestChunkList(t *testing.T) {
// New snapshot // New snapshot
snapshot, count = cl.Snapshot() snapshot, count = cl.Snapshot()
if len(snapshot) != 3 || !snapshot[0].IsFull() || if len(snapshot) != 3 || !snapshot[0].IsFull() ||
!snapshot[1].IsFull() || snapshot[2].IsFull() || count != CHUNK_SIZE*2+2 { !snapshot[1].IsFull() || snapshot[2].IsFull() || count != ChunkSize*2+2 {
t.Error("Expected two full chunks and one more chunk") t.Error("Expected two full chunks and one more chunk")
} }
if len(*snapshot[2]) != 2 { if len(*snapshot[2]) != 2 {

View File

@ -1,12 +1,17 @@
package fzf package fzf
const VERSION = "0.9.0" // Current version
const Version = "0.9.0"
// EventType is the type for fzf events
type EventType int
// fzf events
const ( const (
EVT_READ_NEW EventType = iota EvtReadNew EventType = iota
EVT_READ_FIN EvtReadFin
EVT_SEARCH_NEW EvtSearchNew
EVT_SEARCH_PROGRESS EvtSearchProgress
EVT_SEARCH_FIN EvtSearchFin
EVT_CLOSE EvtClose
) )

View File

@ -32,28 +32,29 @@ import (
"time" "time"
) )
const COORDINATOR_DELAY_MAX time.Duration = 100 * time.Millisecond const coordinatorDelayMax time.Duration = 100 * time.Millisecond
const COORDINATOR_DELAY_STEP time.Duration = 10 * time.Millisecond const coordinatorDelayStep time.Duration = 10 * time.Millisecond
func initProcs() { func initProcs() {
runtime.GOMAXPROCS(runtime.NumCPU()) runtime.GOMAXPROCS(runtime.NumCPU())
} }
/* /*
Reader -> EVT_READ_FIN Reader -> EvtReadFin
Reader -> EVT_READ_NEW -> Matcher (restart) Reader -> EvtReadNew -> Matcher (restart)
Terminal -> EVT_SEARCH_NEW -> Matcher (restart) Terminal -> EvtSearchNew -> Matcher (restart)
Matcher -> EVT_SEARCH_PROGRESS -> Terminal (update info) Matcher -> EvtSearchProgress -> Terminal (update info)
Matcher -> EVT_SEARCH_FIN -> Terminal (update list) Matcher -> EvtSearchFin -> Terminal (update list)
*/ */
// Run starts fzf
func Run(options *Options) { func Run(options *Options) {
initProcs() initProcs()
opts := ParseOptions() opts := ParseOptions()
if opts.Version { if opts.Version {
fmt.Println(VERSION) fmt.Println(Version)
os.Exit(0) os.Exit(0)
} }
@ -108,12 +109,12 @@ func Run(options *Options) {
pattern := patternBuilder([]rune(patternString)) pattern := patternBuilder([]rune(patternString))
looping := true looping := true
eventBox.Unwatch(EVT_READ_NEW) eventBox.Unwatch(EvtReadNew)
for looping { for looping {
eventBox.Wait(func(events *Events) { eventBox.Wait(func(events *Events) {
for evt, _ := range *events { for evt := range *events {
switch evt { switch evt {
case EVT_READ_FIN: case EvtReadFin:
looping = false looping = false
return return
} }
@ -133,7 +134,7 @@ func Run(options *Options) {
fmt.Println(patternString) fmt.Println(patternString)
} }
for i := 0; i < merger.Length(); i++ { for i := 0; i < merger.Length(); i++ {
merger.Get(i).Print() fmt.Println(merger.Get(i).AsString())
} }
os.Exit(0) os.Exit(0)
} }
@ -149,33 +150,33 @@ func Run(options *Options) {
// Event coordination // Event coordination
reading := true reading := true
ticks := 0 ticks := 0
eventBox.Watch(EVT_READ_NEW) eventBox.Watch(EvtReadNew)
for { for {
delay := true delay := true
ticks += 1 ticks++
eventBox.Wait(func(events *Events) { eventBox.Wait(func(events *Events) {
defer events.Clear() defer events.Clear()
for evt, value := range *events { for evt, value := range *events {
switch evt { switch evt {
case EVT_READ_NEW, EVT_READ_FIN: case EvtReadNew, EvtReadFin:
reading = reading && evt == EVT_READ_NEW reading = reading && evt == EvtReadNew
snapshot, count := chunkList.Snapshot() snapshot, count := chunkList.Snapshot()
terminal.UpdateCount(count, !reading) terminal.UpdateCount(count, !reading)
matcher.Reset(snapshot, terminal.Input(), false) matcher.Reset(snapshot, terminal.Input(), false)
case EVT_SEARCH_NEW: case EvtSearchNew:
snapshot, _ := chunkList.Snapshot() snapshot, _ := chunkList.Snapshot()
matcher.Reset(snapshot, terminal.Input(), true) matcher.Reset(snapshot, terminal.Input(), true)
delay = false delay = false
case EVT_SEARCH_PROGRESS: case EvtSearchProgress:
switch val := value.(type) { switch val := value.(type) {
case float32: case float32:
terminal.UpdateProgress(val) terminal.UpdateProgress(val)
} }
case EVT_SEARCH_FIN: case EvtSearchFin:
switch val := value.(type) { switch val := value.(type) {
case *Merger: case *Merger:
terminal.UpdateList(val) terminal.UpdateList(val)
@ -185,8 +186,8 @@ func Run(options *Options) {
}) })
if delay && reading { if delay && reading {
dur := DurWithin( dur := DurWithin(
time.Duration(ticks)*COORDINATOR_DELAY_STEP, time.Duration(ticks)*coordinatorDelayStep,
0, COORDINATOR_DELAY_MAX) 0, coordinatorDelayMax)
time.Sleep(dur) time.Sleep(dur)
} }
} }

View File

@ -20,66 +20,68 @@ import (
"unicode/utf8" "unicode/utf8"
) )
// Types of user action
const ( const (
RUNE = iota Rune = iota
CTRL_A CtrlA
CTRL_B CtrlB
CTRL_C CtrlC
CTRL_D CtrlD
CTRL_E CtrlE
CTRL_F CtrlF
CTRL_G CtrlG
CTRL_H CtrlH
TAB Tab
CTRL_J CtrlJ
CTRL_K CtrlK
CTRL_L CtrlL
CTRL_M CtrlM
CTRL_N CtrlN
CTRL_O CtrlO
CTRL_P CtrlP
CTRL_Q CtrlQ
CTRL_R CtrlR
CTRL_S CtrlS
CTRL_T CtrlT
CTRL_U CtrlU
CTRL_V CtrlV
CTRL_W CtrlW
CTRL_X CtrlX
CTRL_Y CtrlY
CTRL_Z CtrlZ
ESC ESC
INVALID Invalid
MOUSE Mouse
BTAB BTab
DEL Del
PGUP PgUp
PGDN PgDn
ALT_B AltB
ALT_F AltF
ALT_D AltD
ALT_BS AltBS
)
// Pallete
const (
ColNormal = iota
ColPrompt
ColMatch
ColCurrent
ColCurrentMatch
ColSpinner
ColInfo
ColCursor
ColSelected
) )
const ( const (
COL_NORMAL = iota doubleClickDuration = 500 * time.Millisecond
COL_PROMPT
COL_MATCH
COL_CURRENT
COL_CURRENT_MATCH
COL_SPINNER
COL_INFO
COL_CURSOR
COL_SELECTED
)
const (
DOUBLE_CLICK_DURATION = 500 * time.Millisecond
) )
type Event struct { type Event struct {
@ -112,8 +114,8 @@ func init() {
} }
func attrColored(pair int, bold bool) C.int { func attrColored(pair int, bold bool) C.int {
var attr C.int = 0 var attr C.int
if pair > COL_NORMAL { if pair > ColNormal {
attr = C.COLOR_PAIR(C.int(pair)) attr = C.COLOR_PAIR(C.int(pair))
} }
if bold { if bold {
@ -123,15 +125,15 @@ func attrColored(pair int, bold bool) C.int {
} }
func attrMono(pair int, bold bool) C.int { func attrMono(pair int, bold bool) C.int {
var attr C.int = 0 var attr C.int
switch pair { switch pair {
case COL_CURRENT: case ColCurrent:
if bold { if bold {
attr = C.A_REVERSE attr = C.A_REVERSE
} }
case COL_MATCH: case ColMatch:
attr = C.A_UNDERLINE attr = C.A_UNDERLINE
case COL_CURRENT_MATCH: case ColCurrentMatch:
attr = C.A_UNDERLINE | C.A_REVERSE attr = C.A_UNDERLINE | C.A_REVERSE
} }
if bold { if bold {
@ -198,23 +200,23 @@ func Init(color bool, color256 bool, black bool, mouse bool) {
bg = -1 bg = -1
} }
if color256 { if color256 {
C.init_pair(COL_PROMPT, 110, bg) C.init_pair(ColPrompt, 110, bg)
C.init_pair(COL_MATCH, 108, bg) C.init_pair(ColMatch, 108, bg)
C.init_pair(COL_CURRENT, 254, 236) C.init_pair(ColCurrent, 254, 236)
C.init_pair(COL_CURRENT_MATCH, 151, 236) C.init_pair(ColCurrentMatch, 151, 236)
C.init_pair(COL_SPINNER, 148, bg) C.init_pair(ColSpinner, 148, bg)
C.init_pair(COL_INFO, 144, bg) C.init_pair(ColInfo, 144, bg)
C.init_pair(COL_CURSOR, 161, 236) C.init_pair(ColCursor, 161, 236)
C.init_pair(COL_SELECTED, 168, 236) C.init_pair(ColSelected, 168, 236)
} else { } else {
C.init_pair(COL_PROMPT, C.COLOR_BLUE, bg) C.init_pair(ColPrompt, C.COLOR_BLUE, bg)
C.init_pair(COL_MATCH, C.COLOR_GREEN, bg) C.init_pair(ColMatch, C.COLOR_GREEN, bg)
C.init_pair(COL_CURRENT, C.COLOR_YELLOW, C.COLOR_BLACK) C.init_pair(ColCurrent, C.COLOR_YELLOW, C.COLOR_BLACK)
C.init_pair(COL_CURRENT_MATCH, C.COLOR_GREEN, C.COLOR_BLACK) C.init_pair(ColCurrentMatch, C.COLOR_GREEN, C.COLOR_BLACK)
C.init_pair(COL_SPINNER, C.COLOR_GREEN, bg) C.init_pair(ColSpinner, C.COLOR_GREEN, bg)
C.init_pair(COL_INFO, C.COLOR_WHITE, bg) C.init_pair(ColInfo, C.COLOR_WHITE, bg)
C.init_pair(COL_CURSOR, C.COLOR_RED, C.COLOR_BLACK) C.init_pair(ColCursor, C.COLOR_RED, C.COLOR_BLACK)
C.init_pair(COL_SELECTED, C.COLOR_MAGENTA, C.COLOR_BLACK) C.init_pair(ColSelected, C.COLOR_MAGENTA, C.COLOR_BLACK)
} }
_color = attrColored _color = attrColored
} else { } else {
@ -245,7 +247,7 @@ func GetBytes() []byte {
// 27 (91 79) 77 type x y // 27 (91 79) 77 type x y
func mouseSequence(sz *int) Event { func mouseSequence(sz *int) Event {
if len(_buf) < 6 { if len(_buf) < 6 {
return Event{INVALID, 0, nil} return Event{Invalid, 0, nil}
} }
*sz = 6 *sz = 6
switch _buf[3] { switch _buf[3] {
@ -258,7 +260,7 @@ func mouseSequence(sz *int) Event {
double := false double := false
if down { if down {
now := time.Now() now := time.Now()
if now.Sub(_prevDownTime) < DOUBLE_CLICK_DURATION { if now.Sub(_prevDownTime) < doubleClickDuration {
_clickY = append(_clickY, y) _clickY = append(_clickY, y)
} else { } else {
_clickY = []int{y} _clickY = []int{y}
@ -266,18 +268,18 @@ func mouseSequence(sz *int) Event {
_prevDownTime = now _prevDownTime = now
} else { } else {
if len(_clickY) > 1 && _clickY[0] == _clickY[1] && if len(_clickY) > 1 && _clickY[0] == _clickY[1] &&
time.Now().Sub(_prevDownTime) < DOUBLE_CLICK_DURATION { time.Now().Sub(_prevDownTime) < doubleClickDuration {
double = true double = true
} }
} }
return Event{MOUSE, 0, &MouseEvent{y, x, 0, down, double, mod}} return Event{Mouse, 0, &MouseEvent{y, x, 0, down, double, mod}}
case 96, 100, 104, 112, // scroll-up / shift / cmd / ctrl case 96, 100, 104, 112, // scroll-up / shift / cmd / ctrl
97, 101, 105, 113: // scroll-down / shift / cmd / ctrl 97, 101, 105, 113: // scroll-down / shift / cmd / ctrl
mod := _buf[3] >= 100 mod := _buf[3] >= 100
s := 1 - int(_buf[3]%2)*2 s := 1 - int(_buf[3]%2)*2
return Event{MOUSE, 0, &MouseEvent{0, 0, s, false, false, mod}} return Event{Mouse, 0, &MouseEvent{0, 0, s, false, false, mod}}
} }
return Event{INVALID, 0, nil} return Event{Invalid, 0, nil}
} }
func escSequence(sz *int) Event { func escSequence(sz *int) Event {
@ -287,81 +289,81 @@ func escSequence(sz *int) Event {
*sz = 2 *sz = 2
switch _buf[1] { switch _buf[1] {
case 98: case 98:
return Event{ALT_B, 0, nil} return Event{AltB, 0, nil}
case 100: case 100:
return Event{ALT_D, 0, nil} return Event{AltD, 0, nil}
case 102: case 102:
return Event{ALT_F, 0, nil} return Event{AltF, 0, nil}
case 127: case 127:
return Event{ALT_BS, 0, nil} return Event{AltBS, 0, nil}
case 91, 79: case 91, 79:
if len(_buf) < 3 { if len(_buf) < 3 {
return Event{INVALID, 0, nil} return Event{Invalid, 0, nil}
} }
*sz = 3 *sz = 3
switch _buf[2] { switch _buf[2] {
case 68: case 68:
return Event{CTRL_B, 0, nil} return Event{CtrlB, 0, nil}
case 67: case 67:
return Event{CTRL_F, 0, nil} return Event{CtrlF, 0, nil}
case 66: case 66:
return Event{CTRL_J, 0, nil} return Event{CtrlJ, 0, nil}
case 65: case 65:
return Event{CTRL_K, 0, nil} return Event{CtrlK, 0, nil}
case 90: case 90:
return Event{BTAB, 0, nil} return Event{BTab, 0, nil}
case 72: case 72:
return Event{CTRL_A, 0, nil} return Event{CtrlA, 0, nil}
case 70: case 70:
return Event{CTRL_E, 0, nil} return Event{CtrlE, 0, nil}
case 77: case 77:
return mouseSequence(sz) return mouseSequence(sz)
case 49, 50, 51, 52, 53, 54: case 49, 50, 51, 52, 53, 54:
if len(_buf) < 4 { if len(_buf) < 4 {
return Event{INVALID, 0, nil} return Event{Invalid, 0, nil}
} }
*sz = 4 *sz = 4
switch _buf[2] { switch _buf[2] {
case 50: case 50:
return Event{INVALID, 0, nil} // INS return Event{Invalid, 0, nil} // INS
case 51: case 51:
return Event{DEL, 0, nil} return Event{Del, 0, nil}
case 52: case 52:
return Event{CTRL_E, 0, nil} return Event{CtrlE, 0, nil}
case 53: case 53:
return Event{PGUP, 0, nil} return Event{PgUp, 0, nil}
case 54: case 54:
return Event{PGDN, 0, nil} return Event{PgDn, 0, nil}
case 49: case 49:
switch _buf[3] { switch _buf[3] {
case 126: case 126:
return Event{CTRL_A, 0, nil} return Event{CtrlA, 0, nil}
case 59: case 59:
if len(_buf) != 6 { if len(_buf) != 6 {
return Event{INVALID, 0, nil} return Event{Invalid, 0, nil}
} }
*sz = 6 *sz = 6
switch _buf[4] { switch _buf[4] {
case 50: case 50:
switch _buf[5] { switch _buf[5] {
case 68: case 68:
return Event{CTRL_A, 0, nil} return Event{CtrlA, 0, nil}
case 67: case 67:
return Event{CTRL_E, 0, nil} return Event{CtrlE, 0, nil}
} }
case 53: case 53:
switch _buf[5] { switch _buf[5] {
case 68: case 68:
return Event{ALT_B, 0, nil} return Event{AltB, 0, nil}
case 67: case 67:
return Event{ALT_F, 0, nil} return Event{AltF, 0, nil}
} }
} // _buf[4] } // _buf[4]
} // _buf[3] } // _buf[3]
} // _buf[2] } // _buf[2]
} // _buf[2] } // _buf[2]
} // _buf[1] } // _buf[1]
return Event{INVALID, 0, nil} return Event{Invalid, 0, nil}
} }
func GetChar() Event { func GetChar() Event {
@ -378,21 +380,21 @@ func GetChar() Event {
}() }()
switch _buf[0] { switch _buf[0] {
case CTRL_C, CTRL_G, CTRL_Q: case CtrlC, CtrlG, CtrlQ:
return Event{CTRL_C, 0, nil} return Event{CtrlC, 0, nil}
case 127: case 127:
return Event{CTRL_H, 0, nil} return Event{CtrlH, 0, nil}
case ESC: case ESC:
return escSequence(&sz) return escSequence(&sz)
} }
// CTRL-A ~ CTRL-Z // CTRL-A ~ CTRL-Z
if _buf[0] <= CTRL_Z { if _buf[0] <= CtrlZ {
return Event{int(_buf[0]), 0, nil} return Event{int(_buf[0]), 0, nil}
} }
r, rsz := utf8.DecodeRune(_buf) r, rsz := utf8.DecodeRune(_buf)
sz = rsz sz = rsz
return Event{RUNE, r, nil} return Event{Rune, r, nil}
} }
func Move(y int, x int) { func Move(y int, x int) {

View File

@ -2,16 +2,17 @@ package fzf
import "sync" import "sync"
type EventType int // Events is a type that associates EventType to any data
type Events map[EventType]interface{} type Events map[EventType]interface{}
// EventBox is used for coordinating events
type EventBox struct { type EventBox struct {
events Events events Events
cond *sync.Cond cond *sync.Cond
ignore map[EventType]bool ignore map[EventType]bool
} }
// NewEventBox returns a new EventBox
func NewEventBox() *EventBox { func NewEventBox() *EventBox {
return &EventBox{ return &EventBox{
events: make(Events), events: make(Events),
@ -19,6 +20,7 @@ func NewEventBox() *EventBox {
ignore: make(map[EventType]bool)} ignore: make(map[EventType]bool)}
} }
// Wait blocks the goroutine until signaled
func (b *EventBox) Wait(callback func(*Events)) { func (b *EventBox) Wait(callback func(*Events)) {
b.cond.L.Lock() b.cond.L.Lock()
defer b.cond.L.Unlock() defer b.cond.L.Unlock()
@ -30,6 +32,7 @@ func (b *EventBox) Wait(callback func(*Events)) {
callback(&b.events) callback(&b.events)
} }
// Set turns on the event type on the box
func (b *EventBox) Set(event EventType, value interface{}) { func (b *EventBox) Set(event EventType, value interface{}) {
b.cond.L.Lock() b.cond.L.Lock()
defer b.cond.L.Unlock() defer b.cond.L.Unlock()
@ -39,6 +42,7 @@ func (b *EventBox) Set(event EventType, value interface{}) {
} }
} }
// Clear clears the events
// Unsynchronized; should be called within Wait routine // Unsynchronized; should be called within Wait routine
func (events *Events) Clear() { func (events *Events) Clear() {
for event := range *events { for event := range *events {
@ -46,6 +50,7 @@ func (events *Events) Clear() {
} }
} }
// Peak peaks at the event box if the given event is set
func (b *EventBox) Peak(event EventType) bool { func (b *EventBox) Peak(event EventType) bool {
b.cond.L.Lock() b.cond.L.Lock()
defer b.cond.L.Unlock() defer b.cond.L.Unlock()
@ -53,6 +58,7 @@ func (b *EventBox) Peak(event EventType) bool {
return ok return ok
} }
// Watch deletes the events from the ignore list
func (b *EventBox) Watch(events ...EventType) { func (b *EventBox) Watch(events ...EventType) {
b.cond.L.Lock() b.cond.L.Lock()
defer b.cond.L.Unlock() defer b.cond.L.Unlock()
@ -61,6 +67,7 @@ func (b *EventBox) Watch(events ...EventType) {
} }
} }
// Unwatch adds the events to the ignore list
func (b *EventBox) Unwatch(events ...EventType) { func (b *EventBox) Unwatch(events ...EventType) {
b.cond.L.Lock() b.cond.L.Lock()
defer b.cond.L.Unlock() defer b.cond.L.Unlock()

View File

@ -9,16 +9,16 @@ func TestEventBox(t *testing.T) {
ch := make(chan bool) ch := make(chan bool)
go func() { go func() {
eb.Set(EVT_READ_NEW, 10) eb.Set(EvtReadNew, 10)
ch <- true ch <- true
<-ch <-ch
eb.Set(EVT_SEARCH_NEW, 10) eb.Set(EvtSearchNew, 10)
eb.Set(EVT_SEARCH_NEW, 15) eb.Set(EvtSearchNew, 15)
eb.Set(EVT_SEARCH_NEW, 20) eb.Set(EvtSearchNew, 20)
eb.Set(EVT_SEARCH_PROGRESS, 30) eb.Set(EvtSearchProgress, 30)
ch <- true ch <- true
<-ch <-ch
eb.Set(EVT_SEARCH_FIN, 40) eb.Set(EvtSearchFin, 40)
ch <- true ch <- true
<-ch <-ch
}() }()
@ -39,7 +39,7 @@ func TestEventBox(t *testing.T) {
events.Clear() events.Clear()
}) })
ch <- true ch <- true
count += 1 count++
} }
if count != 3 { if count != 3 {

View File

@ -1,9 +1,9 @@
package fzf package fzf
import "fmt" // Offset holds two 32-bit integers denoting the offsets of a matched substring
type Offset [2]int32 type Offset [2]int32
// Item represents each input line
type Item struct { type Item struct {
text *string text *string
origText *string origText *string
@ -13,12 +13,14 @@ type Item struct {
rank Rank rank Rank
} }
// Rank is used to sort the search result
type Rank struct { type Rank struct {
matchlen uint16 matchlen uint16
strlen uint16 strlen uint16
index uint32 index uint32
} }
// Rank calculates rank of the Item
func (i *Item) Rank(cache bool) Rank { 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.strlen > 0) {
return i.rank return i.rank
@ -45,14 +47,15 @@ func (i *Item) Rank(cache bool) Rank {
return rank return rank
} }
func (i *Item) Print() { // AsString returns the original string
func (i *Item) AsString() string {
if i.origText != nil { if i.origText != nil {
fmt.Println(*i.origText) return *i.origText
} else {
fmt.Println(*i.text)
} }
return *i.text
} }
// ByOrder is for sorting substring offsets
type ByOrder []Offset type ByOrder []Offset
func (a ByOrder) Len() int { func (a ByOrder) Len() int {
@ -69,6 +72,7 @@ func (a ByOrder) Less(i, j int) bool {
return (ioff[0] < joff[0]) || (ioff[0] == joff[0]) && (ioff[1] <= joff[1]) return (ioff[0] < joff[0]) || (ioff[0] == joff[0]) && (ioff[1] <= joff[1])
} }
// ByRelevance is for sorting Items
type ByRelevance []*Item type ByRelevance []*Item
func (a ByRelevance) Len() int { func (a ByRelevance) Len() int {

View File

@ -8,11 +8,13 @@ import (
"time" "time"
) )
// MatchRequest represents a search request
type MatchRequest struct { type MatchRequest struct {
chunks []*Chunk chunks []*Chunk
pattern *Pattern pattern *Pattern
} }
// Matcher is responsible for performing search
type Matcher struct { type Matcher struct {
patternBuilder func([]rune) *Pattern patternBuilder func([]rune) *Pattern
sort bool sort bool
@ -23,20 +25,15 @@ type Matcher struct {
} }
const ( const (
REQ_RETRY EventType = iota reqRetry EventType = iota
REQ_RESET reqReset
) )
const ( const (
STAT_CANCELLED int = iota progressMinDuration = 200 * time.Millisecond
STAT_QCH
STAT_CHUNKS
)
const (
PROGRESS_MIN_DURATION = 200 * time.Millisecond
) )
// NewMatcher returns a new Matcher
func NewMatcher(patternBuilder func([]rune) *Pattern, func NewMatcher(patternBuilder func([]rune) *Pattern,
sort bool, eventBox *EventBox) *Matcher { sort bool, eventBox *EventBox) *Matcher {
return &Matcher{ return &Matcher{
@ -48,6 +45,7 @@ func NewMatcher(patternBuilder func([]rune) *Pattern,
mergerCache: make(map[string]*Merger)} mergerCache: make(map[string]*Merger)}
} }
// Loop puts Matcher in action
func (m *Matcher) Loop() { func (m *Matcher) Loop() {
prevCount := 0 prevCount := 0
@ -91,7 +89,7 @@ func (m *Matcher) Loop() {
if !cancelled { if !cancelled {
m.mergerCache[patternString] = merger m.mergerCache[patternString] = merger
m.eventBox.Set(EVT_SEARCH_FIN, merger) m.eventBox.Set(EvtSearchFin, merger)
} }
} }
} }
@ -172,7 +170,7 @@ func (m *Matcher) scan(request MatchRequest, limit int) (*Merger, bool) {
count := 0 count := 0
matchCount := 0 matchCount := 0
for matchesInChunk := range countChan { for matchesInChunk := range countChan {
count += 1 count++
matchCount += matchesInChunk matchCount += matchesInChunk
if limit > 0 && matchCount > limit { if limit > 0 && matchCount > limit {
@ -183,12 +181,12 @@ func (m *Matcher) scan(request MatchRequest, limit int) (*Merger, bool) {
break break
} }
if !empty && m.reqBox.Peak(REQ_RESET) { if !empty && m.reqBox.Peak(reqReset) {
return nil, wait() return nil, wait()
} }
if time.Now().Sub(startedAt) > PROGRESS_MIN_DURATION { if time.Now().Sub(startedAt) > progressMinDuration {
m.eventBox.Set(EVT_SEARCH_PROGRESS, float32(count)/float32(numChunks)) m.eventBox.Set(EvtSearchProgress, float32(count)/float32(numChunks))
} }
} }
@ -200,14 +198,15 @@ func (m *Matcher) scan(request MatchRequest, limit int) (*Merger, bool) {
return NewMerger(partialResults, !empty && m.sort), false return NewMerger(partialResults, !empty && m.sort), false
} }
// Reset is called to interrupt/signal the ongoing search
func (m *Matcher) Reset(chunks []*Chunk, patternRunes []rune, cancel bool) { func (m *Matcher) Reset(chunks []*Chunk, patternRunes []rune, cancel bool) {
pattern := m.patternBuilder(patternRunes) pattern := m.patternBuilder(patternRunes)
var event EventType var event EventType
if cancel { if cancel {
event = REQ_RESET event = reqReset
} else { } else {
event = REQ_RETRY event = reqRetry
} }
m.reqBox.Set(event, MatchRequest{chunks, pattern}) m.reqBox.Set(event, MatchRequest{chunks, pattern})
} }

View File

@ -2,8 +2,11 @@ package fzf
import "fmt" import "fmt"
var EmptyMerger *Merger = NewMerger([][]*Item{}, false) // Merger with no data
var EmptyMerger = NewMerger([][]*Item{}, false)
// Merger holds a set of locally sorted lists of items and provides the view of
// a single, globally-sorted list
type Merger struct { type Merger struct {
lists [][]*Item lists [][]*Item
merged []*Item merged []*Item
@ -12,6 +15,7 @@ type Merger struct {
count int count int
} }
// NewMerger returns a new Merger
func NewMerger(lists [][]*Item, sorted bool) *Merger { func NewMerger(lists [][]*Item, sorted bool) *Merger {
mg := Merger{ mg := Merger{
lists: lists, lists: lists,
@ -26,10 +30,12 @@ func NewMerger(lists [][]*Item, sorted bool) *Merger {
return &mg return &mg
} }
// Length returns the number of items
func (mg *Merger) Length() int { func (mg *Merger) Length() int {
return mg.count return mg.count
} }
// 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 len(mg.lists) == 1 {
return mg.lists[0][idx] return mg.lists[0][idx]
@ -69,7 +75,7 @@ func (mg *Merger) mergedGet(idx int) *Item {
if minIdx >= 0 { if minIdx >= 0 {
chosen := mg.lists[minIdx] chosen := mg.lists[minIdx]
mg.merged = append(mg.merged, chosen[mg.cursors[minIdx]]) mg.merged = append(mg.merged, chosen[mg.cursors[minIdx]])
mg.cursors[minIdx] += 1 mg.cursors[minIdx]++
} else { } else {
panic(fmt.Sprintf("Index out of bounds (sorted, %d/%d)", i, mg.count)) panic(fmt.Sprintf("Index out of bounds (sorted, %d/%d)", i, mg.count))
} }

View File

@ -8,7 +8,7 @@ import (
"strings" "strings"
) )
const USAGE = `usage: fzf [options] const usage = `usage: fzf [options]
Search Search
-x, --extended Extended-search mode -x, --extended Extended-search mode
@ -47,22 +47,27 @@ const USAGE = `usage: fzf [options]
` `
// Mode denotes the current search mode
type Mode int type Mode int
// Search modes
const ( const (
MODE_FUZZY Mode = iota ModeFuzzy Mode = iota
MODE_EXTENDED ModeExtended
MODE_EXTENDED_EXACT ModeExtendedExact
) )
// Case denotes case-sensitivity of search
type Case int type Case int
// Case-sensitivities
const ( const (
CASE_SMART Case = iota CaseSmart Case = iota
CASE_IGNORE CaseIgnore
CASE_RESPECT CaseRespect
) )
// Options stores the values of command-line options
type Options struct { type Options struct {
Mode Mode Mode Mode
Case Case Case Case
@ -85,10 +90,10 @@ type Options struct {
Version bool Version bool
} }
func DefaultOptions() *Options { func defaultOptions() *Options {
return &Options{ return &Options{
Mode: MODE_FUZZY, Mode: ModeFuzzy,
Case: CASE_SMART, Case: CaseSmart,
Nth: make([]Range, 0), Nth: make([]Range, 0),
WithNth: make([]Range, 0), WithNth: make([]Range, 0),
Delimiter: nil, Delimiter: nil,
@ -109,7 +114,7 @@ func DefaultOptions() *Options {
} }
func help(ok int) { func help(ok int) {
os.Stderr.WriteString(USAGE) os.Stderr.WriteString(usage)
os.Exit(ok) os.Exit(ok)
} }
@ -123,9 +128,8 @@ func optString(arg string, prefix string) (bool, string) {
matches := rx.FindStringSubmatch(arg) matches := rx.FindStringSubmatch(arg)
if len(matches) > 1 { if len(matches) > 1 {
return true, matches[1] return true, matches[1]
} else {
return false, ""
} }
return false, ""
} }
func nextString(args []string, i *int, message string) string { func nextString(args []string, i *int, message string) string {
@ -183,11 +187,11 @@ func parseOptions(opts *Options, allArgs []string) {
case "-h", "--help": case "-h", "--help":
help(0) help(0)
case "-x", "--extended": case "-x", "--extended":
opts.Mode = MODE_EXTENDED opts.Mode = ModeExtended
case "-e", "--extended-exact": case "-e", "--extended-exact":
opts.Mode = MODE_EXTENDED_EXACT opts.Mode = ModeExtendedExact
case "+x", "--no-extended", "+e", "--no-extended-exact": case "+x", "--no-extended", "+e", "--no-extended-exact":
opts.Mode = MODE_FUZZY opts.Mode = ModeFuzzy
case "-q", "--query": case "-q", "--query":
opts.Query = nextString(allArgs, &i, "query string required") opts.Query = nextString(allArgs, &i, "query string required")
case "-f", "--filter": case "-f", "--filter":
@ -204,9 +208,9 @@ func parseOptions(opts *Options, allArgs []string) {
case "+s", "--no-sort": case "+s", "--no-sort":
opts.Sort = 0 opts.Sort = 0
case "-i": case "-i":
opts.Case = CASE_IGNORE opts.Case = CaseIgnore
case "+i": case "+i":
opts.Case = CASE_RESPECT opts.Case = CaseRespect
case "-m", "--multi": case "-m", "--multi":
opts.Multi = true opts.Multi = true
case "+m", "--no-multi": case "+m", "--no-multi":
@ -263,8 +267,9 @@ func parseOptions(opts *Options, allArgs []string) {
} }
} }
// ParseOptions parses command-line options
func ParseOptions() *Options { func ParseOptions() *Options {
opts := DefaultOptions() opts := defaultOptions()
// Options from Env var // Options from Env var
words, _ := shellwords.Parse(os.Getenv("FZF_DEFAULT_OPTS")) words, _ := shellwords.Parse(os.Getenv("FZF_DEFAULT_OPTS"))

View File

@ -15,20 +15,20 @@ func TestSplitNth(t *testing.T) {
{ {
ranges := splitNth("..") ranges := splitNth("..")
if len(ranges) != 1 || if len(ranges) != 1 ||
ranges[0].begin != RANGE_ELLIPSIS || ranges[0].begin != rangeEllipsis ||
ranges[0].end != RANGE_ELLIPSIS { ranges[0].end != rangeEllipsis {
t.Errorf("%s", ranges) t.Errorf("%s", ranges)
} }
} }
{ {
ranges := splitNth("..3,1..,2..3,4..-1,-3..-2,..,2,-2") ranges := splitNth("..3,1..,2..3,4..-1,-3..-2,..,2,-2")
if len(ranges) != 8 || if len(ranges) != 8 ||
ranges[0].begin != RANGE_ELLIPSIS || ranges[0].end != 3 || ranges[0].begin != rangeEllipsis || ranges[0].end != 3 ||
ranges[1].begin != 1 || ranges[1].end != RANGE_ELLIPSIS || ranges[1].begin != 1 || ranges[1].end != rangeEllipsis ||
ranges[2].begin != 2 || ranges[2].end != 3 || ranges[2].begin != 2 || ranges[2].end != 3 ||
ranges[3].begin != 4 || ranges[3].end != -1 || ranges[3].begin != 4 || ranges[3].end != -1 ||
ranges[4].begin != -3 || ranges[4].end != -2 || ranges[4].begin != -3 || ranges[4].end != -2 ||
ranges[5].begin != RANGE_ELLIPSIS || ranges[5].end != RANGE_ELLIPSIS || ranges[5].begin != rangeEllipsis || ranges[5].end != rangeEllipsis ||
ranges[6].begin != 2 || ranges[6].end != 2 || ranges[6].begin != 2 || ranges[6].end != 2 ||
ranges[7].begin != -2 || ranges[7].end != -2 { ranges[7].begin != -2 || ranges[7].end != -2 {
t.Errorf("%s", ranges) t.Errorf("%s", ranges)

View File

@ -6,7 +6,7 @@ import (
"strings" "strings"
) )
const UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" const uppercaseLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
// fuzzy // fuzzy
// 'exact // 'exact
@ -17,31 +17,32 @@ const UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
// !^not-exact-prefix // !^not-exact-prefix
// !not-exact-suffix$ // !not-exact-suffix$
type TermType int type termType int
const ( const (
TERM_FUZZY TermType = iota termFuzzy termType = iota
TERM_EXACT termExact
TERM_PREFIX termPrefix
TERM_SUFFIX termSuffix
) )
type Term struct { type term struct {
typ TermType typ termType
inv bool inv bool
text []rune text []rune
origText []rune origText []rune
} }
// Pattern represents search pattern
type Pattern struct { type Pattern struct {
mode Mode mode Mode
caseSensitive bool caseSensitive bool
text []rune text []rune
terms []Term terms []term
hasInvTerm bool hasInvTerm bool
delimiter *regexp.Regexp delimiter *regexp.Regexp
nth []Range nth []Range
procFun map[TermType]func(bool, *string, []rune) (int, int) procFun map[termType]func(bool, *string, []rune) (int, int)
} }
var ( var (
@ -62,12 +63,13 @@ func clearPatternCache() {
_patternCache = make(map[string]*Pattern) _patternCache = make(map[string]*Pattern)
} }
// BuildPattern builds Pattern object from the given arguments
func BuildPattern(mode Mode, caseMode Case, func BuildPattern(mode Mode, caseMode Case,
nth []Range, delimiter *regexp.Regexp, runes []rune) *Pattern { nth []Range, delimiter *regexp.Regexp, runes []rune) *Pattern {
var asString string var asString string
switch mode { switch mode {
case MODE_EXTENDED, MODE_EXTENDED_EXACT: case ModeExtended, ModeExtendedExact:
asString = strings.Trim(string(runes), " ") asString = strings.Trim(string(runes), " ")
default: default:
asString = string(runes) asString = string(runes)
@ -79,19 +81,19 @@ func BuildPattern(mode Mode, caseMode Case,
} }
caseSensitive, hasInvTerm := true, false caseSensitive, hasInvTerm := true, false
terms := []Term{} terms := []term{}
switch caseMode { switch caseMode {
case CASE_SMART: case CaseSmart:
if !strings.ContainsAny(asString, UPPERCASE) { if !strings.ContainsAny(asString, uppercaseLetters) {
runes, caseSensitive = []rune(strings.ToLower(asString)), false runes, caseSensitive = []rune(strings.ToLower(asString)), false
} }
case CASE_IGNORE: case CaseIgnore:
runes, caseSensitive = []rune(strings.ToLower(asString)), false runes, caseSensitive = []rune(strings.ToLower(asString)), false
} }
switch mode { switch mode {
case MODE_EXTENDED, MODE_EXTENDED_EXACT: case ModeExtended, ModeExtendedExact:
terms = parseTerms(mode, string(runes)) terms = parseTerms(mode, string(runes))
for _, term := range terms { for _, term := range terms {
if term.inv { if term.inv {
@ -108,25 +110,25 @@ func BuildPattern(mode Mode, caseMode Case,
hasInvTerm: hasInvTerm, hasInvTerm: hasInvTerm,
nth: nth, nth: nth,
delimiter: delimiter, delimiter: delimiter,
procFun: make(map[TermType]func(bool, *string, []rune) (int, int))} procFun: make(map[termType]func(bool, *string, []rune) (int, int))}
ptr.procFun[TERM_FUZZY] = FuzzyMatch ptr.procFun[termFuzzy] = FuzzyMatch
ptr.procFun[TERM_EXACT] = ExactMatchNaive ptr.procFun[termExact] = ExactMatchNaive
ptr.procFun[TERM_PREFIX] = PrefixMatch ptr.procFun[termPrefix] = PrefixMatch
ptr.procFun[TERM_SUFFIX] = SuffixMatch ptr.procFun[termSuffix] = SuffixMatch
_patternCache[asString] = ptr _patternCache[asString] = ptr
return ptr return ptr
} }
func parseTerms(mode Mode, str string) []Term { func parseTerms(mode Mode, str string) []term {
tokens := _splitRegex.Split(str, -1) tokens := _splitRegex.Split(str, -1)
terms := []Term{} terms := []term{}
for _, token := range tokens { for _, token := range tokens {
typ, inv, text := TERM_FUZZY, false, token typ, inv, text := termFuzzy, false, token
origText := []rune(text) origText := []rune(text)
if mode == MODE_EXTENDED_EXACT { if mode == ModeExtendedExact {
typ = TERM_EXACT typ = termExact
} }
if strings.HasPrefix(text, "!") { if strings.HasPrefix(text, "!") {
@ -135,20 +137,20 @@ func parseTerms(mode Mode, str string) []Term {
} }
if strings.HasPrefix(text, "'") { if strings.HasPrefix(text, "'") {
if mode == MODE_EXTENDED { if mode == ModeExtended {
typ = TERM_EXACT typ = termExact
text = text[1:] text = text[1:]
} }
} else if strings.HasPrefix(text, "^") { } else if strings.HasPrefix(text, "^") {
typ = TERM_PREFIX typ = termPrefix
text = text[1:] text = text[1:]
} else if strings.HasSuffix(text, "$") { } else if strings.HasSuffix(text, "$") {
typ = TERM_SUFFIX typ = termSuffix
text = text[:len(text)-1] text = text[:len(text)-1]
} }
if len(text) > 0 { if len(text) > 0 {
terms = append(terms, Term{ terms = append(terms, term{
typ: typ, typ: typ,
inv: inv, inv: inv,
text: []rune(text), text: []rune(text),
@ -158,20 +160,22 @@ func parseTerms(mode Mode, str string) []Term {
return terms return terms
} }
// IsEmpty returns true if the pattern is effectively empty
func (p *Pattern) IsEmpty() bool { func (p *Pattern) IsEmpty() bool {
if p.mode == MODE_FUZZY { if p.mode == ModeFuzzy {
return len(p.text) == 0 return len(p.text) == 0
} else { }
return len(p.terms) == 0 return len(p.terms) == 0
} }
}
// AsString returns the search query in string type
func (p *Pattern) AsString() string { func (p *Pattern) AsString() string {
return string(p.text) return string(p.text)
} }
// CacheKey is used to build string to be used as the key of result cache
func (p *Pattern) CacheKey() string { func (p *Pattern) CacheKey() string {
if p.mode == MODE_FUZZY { if p.mode == ModeFuzzy {
return p.AsString() return p.AsString()
} }
cacheableTerms := []string{} cacheableTerms := []string{}
@ -184,6 +188,7 @@ func (p *Pattern) CacheKey() string {
return strings.Join(cacheableTerms, " ") return strings.Join(cacheableTerms, " ")
} }
// Match returns the list of matches Items in the given Chunk
func (p *Pattern) Match(chunk *Chunk) []*Item { func (p *Pattern) Match(chunk *Chunk) []*Item {
space := chunk space := chunk
@ -213,7 +218,7 @@ Loop:
} }
var matches []*Item var matches []*Item
if p.mode == MODE_FUZZY { if p.mode == ModeFuzzy {
matches = p.fuzzyMatch(space) matches = p.fuzzyMatch(space)
} else { } else {
matches = p.extendedMatch(space) matches = p.extendedMatch(space)

View File

@ -3,17 +3,17 @@ package fzf
import "testing" import "testing"
func TestParseTermsExtended(t *testing.T) { func TestParseTermsExtended(t *testing.T) {
terms := parseTerms(MODE_EXTENDED, terms := parseTerms(ModeExtended,
"aaa 'bbb ^ccc ddd$ !eee !'fff !^ggg !hhh$") "aaa 'bbb ^ccc ddd$ !eee !'fff !^ggg !hhh$")
if len(terms) != 8 || if len(terms) != 8 ||
terms[0].typ != TERM_FUZZY || terms[0].inv || terms[0].typ != termFuzzy || terms[0].inv ||
terms[1].typ != TERM_EXACT || terms[1].inv || terms[1].typ != termExact || terms[1].inv ||
terms[2].typ != TERM_PREFIX || terms[2].inv || terms[2].typ != termPrefix || terms[2].inv ||
terms[3].typ != TERM_SUFFIX || terms[3].inv || terms[3].typ != termSuffix || terms[3].inv ||
terms[4].typ != TERM_FUZZY || !terms[4].inv || terms[4].typ != termFuzzy || !terms[4].inv ||
terms[5].typ != TERM_EXACT || !terms[5].inv || terms[5].typ != termExact || !terms[5].inv ||
terms[6].typ != TERM_PREFIX || !terms[6].inv || terms[6].typ != termPrefix || !terms[6].inv ||
terms[7].typ != TERM_SUFFIX || !terms[7].inv { terms[7].typ != termSuffix || !terms[7].inv {
t.Errorf("%s", terms) t.Errorf("%s", terms)
} }
for idx, term := range terms { for idx, term := range terms {
@ -27,23 +27,23 @@ func TestParseTermsExtended(t *testing.T) {
} }
func TestParseTermsExtendedExact(t *testing.T) { func TestParseTermsExtendedExact(t *testing.T) {
terms := parseTerms(MODE_EXTENDED_EXACT, terms := parseTerms(ModeExtendedExact,
"aaa 'bbb ^ccc ddd$ !eee !'fff !^ggg !hhh$") "aaa 'bbb ^ccc ddd$ !eee !'fff !^ggg !hhh$")
if len(terms) != 8 || if len(terms) != 8 ||
terms[0].typ != TERM_EXACT || terms[0].inv || len(terms[0].text) != 3 || terms[0].typ != termExact || terms[0].inv || len(terms[0].text) != 3 ||
terms[1].typ != TERM_EXACT || terms[1].inv || len(terms[1].text) != 4 || terms[1].typ != termExact || terms[1].inv || len(terms[1].text) != 4 ||
terms[2].typ != TERM_PREFIX || terms[2].inv || len(terms[2].text) != 3 || terms[2].typ != termPrefix || terms[2].inv || len(terms[2].text) != 3 ||
terms[3].typ != TERM_SUFFIX || terms[3].inv || len(terms[3].text) != 3 || terms[3].typ != termSuffix || terms[3].inv || len(terms[3].text) != 3 ||
terms[4].typ != TERM_EXACT || !terms[4].inv || len(terms[4].text) != 3 || terms[4].typ != termExact || !terms[4].inv || len(terms[4].text) != 3 ||
terms[5].typ != TERM_EXACT || !terms[5].inv || len(terms[5].text) != 4 || terms[5].typ != termExact || !terms[5].inv || len(terms[5].text) != 4 ||
terms[6].typ != TERM_PREFIX || !terms[6].inv || len(terms[6].text) != 3 || terms[6].typ != termPrefix || !terms[6].inv || len(terms[6].text) != 3 ||
terms[7].typ != TERM_SUFFIX || !terms[7].inv || len(terms[7].text) != 3 { terms[7].typ != termSuffix || !terms[7].inv || len(terms[7].text) != 3 {
t.Errorf("%s", terms) t.Errorf("%s", terms)
} }
} }
func TestParseTermsEmpty(t *testing.T) { func TestParseTermsEmpty(t *testing.T) {
terms := parseTerms(MODE_EXTENDED, "' $ ^ !' !^ !$") terms := parseTerms(ModeExtended, "' $ ^ !' !^ !$")
if len(terms) != 0 { if len(terms) != 0 {
t.Errorf("%s", terms) t.Errorf("%s", terms)
} }
@ -52,7 +52,7 @@ func TestParseTermsEmpty(t *testing.T) {
func TestExact(t *testing.T) { func TestExact(t *testing.T) {
defer clearPatternCache() defer clearPatternCache()
clearPatternCache() clearPatternCache()
pattern := BuildPattern(MODE_EXTENDED, CASE_SMART, pattern := BuildPattern(ModeExtended, CaseSmart,
[]Range{}, nil, []rune("'abc")) []Range{}, nil, []rune("'abc"))
str := "aabbcc abc" str := "aabbcc abc"
sidx, eidx := ExactMatchNaive(pattern.caseSensitive, &str, pattern.terms[0].text) sidx, eidx := ExactMatchNaive(pattern.caseSensitive, &str, pattern.terms[0].text)
@ -64,17 +64,17 @@ func TestExact(t *testing.T) {
func TestCaseSensitivity(t *testing.T) { func TestCaseSensitivity(t *testing.T) {
defer clearPatternCache() defer clearPatternCache()
clearPatternCache() clearPatternCache()
pat1 := BuildPattern(MODE_FUZZY, CASE_SMART, []Range{}, nil, []rune("abc")) pat1 := BuildPattern(ModeFuzzy, CaseSmart, []Range{}, nil, []rune("abc"))
clearPatternCache() clearPatternCache()
pat2 := BuildPattern(MODE_FUZZY, CASE_SMART, []Range{}, nil, []rune("Abc")) pat2 := BuildPattern(ModeFuzzy, CaseSmart, []Range{}, nil, []rune("Abc"))
clearPatternCache() clearPatternCache()
pat3 := BuildPattern(MODE_FUZZY, CASE_IGNORE, []Range{}, nil, []rune("abc")) pat3 := BuildPattern(ModeFuzzy, CaseIgnore, []Range{}, nil, []rune("abc"))
clearPatternCache() clearPatternCache()
pat4 := BuildPattern(MODE_FUZZY, CASE_IGNORE, []Range{}, nil, []rune("Abc")) pat4 := BuildPattern(ModeFuzzy, CaseIgnore, []Range{}, nil, []rune("Abc"))
clearPatternCache() clearPatternCache()
pat5 := BuildPattern(MODE_FUZZY, CASE_RESPECT, []Range{}, nil, []rune("abc")) pat5 := BuildPattern(ModeFuzzy, CaseRespect, []Range{}, nil, []rune("abc"))
clearPatternCache() clearPatternCache()
pat6 := BuildPattern(MODE_FUZZY, CASE_RESPECT, []Range{}, nil, []rune("Abc")) pat6 := BuildPattern(ModeFuzzy, CaseRespect, []Range{}, nil, []rune("Abc"))
if string(pat1.text) != "abc" || pat1.caseSensitive != false || if string(pat1.text) != "abc" || pat1.caseSensitive != false ||
string(pat2.text) != "Abc" || pat2.caseSensitive != true || string(pat2.text) != "Abc" || pat2.caseSensitive != true ||
@ -90,7 +90,7 @@ func TestOrigTextAndTransformed(t *testing.T) {
strptr := func(str string) *string { strptr := func(str string) *string {
return &str return &str
} }
pattern := BuildPattern(MODE_EXTENDED, CASE_SMART, []Range{}, nil, []rune("jg")) pattern := BuildPattern(ModeExtended, CaseSmart, []Range{}, nil, []rune("jg"))
tokens := Tokenize(strptr("junegunn"), nil) tokens := Tokenize(strptr("junegunn"), nil)
trans := Transform(tokens, []Range{Range{1, 1}}) trans := Transform(tokens, []Range{Range{1, 1}})

View File

@ -10,31 +10,33 @@ import (
"os/exec" "os/exec"
) )
const DEFAULT_COMMAND = `find * -path '*/\.*' -prune -o -type f -print -o -type l -print 2> /dev/null` const defaultCommand = `find * -path '*/\.*' -prune -o -type f -print -o -type l -print 2> /dev/null`
// Reader reads from command or standard input
type Reader struct { type Reader struct {
pusher func(string) pusher func(string)
eventBox *EventBox eventBox *EventBox
} }
// ReadSource reads data from the default command or from standard input
func (r *Reader) ReadSource() { func (r *Reader) ReadSource() {
if int(C.isatty(C.int(os.Stdin.Fd()))) != 0 { if int(C.isatty(C.int(os.Stdin.Fd()))) != 0 {
cmd := os.Getenv("FZF_DEFAULT_COMMAND") cmd := os.Getenv("FZF_DEFAULT_COMMAND")
if len(cmd) == 0 { if len(cmd) == 0 {
cmd = DEFAULT_COMMAND cmd = defaultCommand
} }
r.readFromCommand(cmd) r.readFromCommand(cmd)
} else { } else {
r.readFromStdin() r.readFromStdin()
} }
r.eventBox.Set(EVT_READ_FIN, nil) r.eventBox.Set(EvtReadFin, nil)
} }
func (r *Reader) feed(src io.Reader) { func (r *Reader) feed(src io.Reader) {
if scanner := bufio.NewScanner(src); scanner != nil { if scanner := bufio.NewScanner(src); scanner != nil {
for scanner.Scan() { for scanner.Scan() {
r.pusher(scanner.Text()) r.pusher(scanner.Text())
r.eventBox.Set(EVT_READ_NEW, nil) r.eventBox.Set(EvtReadNew, nil)
} }
} }
} }

View File

@ -10,8 +10,8 @@ func TestReadFromCommand(t *testing.T) {
eventBox: eb} eventBox: eb}
// Check EventBox // Check EventBox
if eb.Peak(EVT_READ_NEW) { if eb.Peak(EvtReadNew) {
t.Error("EVT_READ_NEW should not be set yet") t.Error("EvtReadNew should not be set yet")
} }
// Normal command // Normal command
@ -21,21 +21,21 @@ func TestReadFromCommand(t *testing.T) {
} }
// Check EventBox again // Check EventBox again
if !eb.Peak(EVT_READ_NEW) { if !eb.Peak(EvtReadNew) {
t.Error("EVT_READ_NEW should be set yet") t.Error("EvtReadNew should be set yet")
} }
// Wait should return immediately // Wait should return immediately
eb.Wait(func(events *Events) { eb.Wait(func(events *Events) {
if _, found := (*events)[EVT_READ_NEW]; !found { if _, found := (*events)[EvtReadNew]; !found {
t.Errorf("%s", events) t.Errorf("%s", events)
} }
events.Clear() events.Clear()
}) })
// EventBox is cleared // EventBox is cleared
if eb.Peak(EVT_READ_NEW) { if eb.Peak(EvtReadNew) {
t.Error("EVT_READ_NEW should not be set yet") t.Error("EvtReadNew should not be set yet")
} }
// Failing command // Failing command
@ -46,7 +46,7 @@ func TestReadFromCommand(t *testing.T) {
} }
// Check EventBox again // Check EventBox again
if eb.Peak(EVT_READ_NEW) { if eb.Peak(EvtReadNew) {
t.Error("Command failed. EVT_READ_NEW should be set") t.Error("Command failed. EvtReadNew should be set")
} }
} }

View File

@ -11,6 +11,7 @@ import (
"time" "time"
) )
// Terminal represents terminal input/output
type Terminal struct { type Terminal struct {
prompt string prompt string
reverse bool reverse bool
@ -34,23 +35,24 @@ type Terminal struct {
suppress bool suppress bool
} }
var _spinner []string = []string{`-`, `\`, `|`, `/`, `-`, `\`, `|`, `/`} var _spinner = []string{`-`, `\`, `|`, `/`, `-`, `\`, `|`, `/`}
const ( const (
REQ_PROMPT EventType = iota reqPrompt EventType = iota
REQ_INFO reqInfo
REQ_LIST reqList
REQ_REFRESH reqRefresh
REQ_REDRAW reqRedraw
REQ_CLOSE reqClose
REQ_QUIT reqQuit
) )
const ( const (
INITIAL_DELAY = 100 * time.Millisecond initialDelay = 100 * time.Millisecond
SPINNER_DURATION = 200 * time.Millisecond spinnerDuration = 200 * time.Millisecond
) )
// NewTerminal returns new Terminal object
func NewTerminal(opts *Options, eventBox *EventBox) *Terminal { func NewTerminal(opts *Options, eventBox *EventBox) *Terminal {
input := []rune(opts.Query) input := []rune(opts.Query)
return &Terminal{ return &Terminal{
@ -75,23 +77,26 @@ func NewTerminal(opts *Options, eventBox *EventBox) *Terminal {
}} }}
} }
// Input returns current query string
func (t *Terminal) Input() []rune { func (t *Terminal) Input() []rune {
t.mutex.Lock() t.mutex.Lock()
defer t.mutex.Unlock() defer t.mutex.Unlock()
return copySlice(t.input) return copySlice(t.input)
} }
// UpdateCount updates the count information
func (t *Terminal) UpdateCount(cnt int, final bool) { func (t *Terminal) UpdateCount(cnt int, final bool) {
t.mutex.Lock() t.mutex.Lock()
t.count = cnt t.count = cnt
t.reading = !final t.reading = !final
t.mutex.Unlock() t.mutex.Unlock()
t.reqBox.Set(REQ_INFO, nil) t.reqBox.Set(reqInfo, nil)
if final { if final {
t.reqBox.Set(REQ_REFRESH, nil) t.reqBox.Set(reqRefresh, nil)
} }
} }
// UpdateProgress updates the search progress
func (t *Terminal) UpdateProgress(progress float32) { func (t *Terminal) UpdateProgress(progress float32) {
t.mutex.Lock() t.mutex.Lock()
newProgress := int(progress * 100) newProgress := int(progress * 100)
@ -100,25 +105,25 @@ func (t *Terminal) UpdateProgress(progress float32) {
t.mutex.Unlock() t.mutex.Unlock()
if changed { if changed {
t.reqBox.Set(REQ_INFO, nil) t.reqBox.Set(reqInfo, nil)
} }
} }
// UpdateList updates Merger to display the list
func (t *Terminal) UpdateList(merger *Merger) { func (t *Terminal) UpdateList(merger *Merger) {
t.mutex.Lock() t.mutex.Lock()
t.progress = 100 t.progress = 100
t.merger = merger t.merger = merger
t.mutex.Unlock() t.mutex.Unlock()
t.reqBox.Set(REQ_INFO, nil) t.reqBox.Set(reqInfo, nil)
t.reqBox.Set(REQ_LIST, nil) t.reqBox.Set(reqList, nil)
} }
func (t *Terminal) listIndex(y int) int { func (t *Terminal) listIndex(y int) int {
if t.tac { if t.tac {
return t.merger.Length() - y - 1 return t.merger.Length() - y - 1
} else {
return y
} }
return y
} }
func (t *Terminal) output() { func (t *Terminal) output() {
@ -127,7 +132,7 @@ func (t *Terminal) output() {
} }
if len(t.selected) == 0 { if len(t.selected) == 0 {
if t.merger.Length() > t.cy { if t.merger.Length() > t.cy {
t.merger.Get(t.listIndex(t.cy)).Print() fmt.Println(t.merger.Get(t.listIndex(t.cy)).AsString())
} }
} else { } else {
for ptr, orig := range t.selected { for ptr, orig := range t.selected {
@ -167,16 +172,16 @@ func (t *Terminal) placeCursor() {
func (t *Terminal) printPrompt() { func (t *Terminal) printPrompt() {
t.move(0, 0, true) t.move(0, 0, true)
C.CPrint(C.COL_PROMPT, true, t.prompt) C.CPrint(C.ColPrompt, true, t.prompt)
C.CPrint(C.COL_NORMAL, true, string(t.input)) C.CPrint(C.ColNormal, true, string(t.input))
} }
func (t *Terminal) printInfo() { func (t *Terminal) printInfo() {
t.move(1, 0, true) t.move(1, 0, true)
if t.reading { if t.reading {
duration := int64(SPINNER_DURATION) duration := int64(spinnerDuration)
idx := (time.Now().UnixNano() % (duration * int64(len(_spinner)))) / duration idx := (time.Now().UnixNano() % (duration * int64(len(_spinner)))) / duration
C.CPrint(C.COL_SPINNER, true, _spinner[idx]) C.CPrint(C.ColSpinner, true, _spinner[idx])
} }
t.move(1, 2, false) t.move(1, 2, false)
@ -187,7 +192,7 @@ func (t *Terminal) printInfo() {
if t.progress > 0 && t.progress < 100 { if t.progress > 0 && t.progress < 100 {
output += fmt.Sprintf(" (%d%%)", t.progress) output += fmt.Sprintf(" (%d%%)", t.progress)
} }
C.CPrint(C.COL_INFO, false, output) C.CPrint(C.ColInfo, false, output)
} }
func (t *Terminal) printList() { func (t *Terminal) printList() {
@ -206,21 +211,21 @@ func (t *Terminal) printList() {
func (t *Terminal) printItem(item *Item, current bool) { func (t *Terminal) printItem(item *Item, current bool) {
_, selected := t.selected[item.text] _, selected := t.selected[item.text]
if current { if current {
C.CPrint(C.COL_CURSOR, true, ">") C.CPrint(C.ColCursor, true, ">")
if selected { if selected {
C.CPrint(C.COL_CURRENT, true, ">") C.CPrint(C.ColCurrent, true, ">")
} else { } else {
C.CPrint(C.COL_CURRENT, true, " ") C.CPrint(C.ColCurrent, true, " ")
} }
t.printHighlighted(item, true, C.COL_CURRENT, C.COL_CURRENT_MATCH) t.printHighlighted(item, true, C.ColCurrent, C.ColCurrentMatch)
} else { } else {
C.CPrint(C.COL_CURSOR, true, " ") C.CPrint(C.ColCursor, true, " ")
if selected { if selected {
C.CPrint(C.COL_SELECTED, true, ">") C.CPrint(C.ColSelected, true, ">")
} else { } else {
C.Print(" ") C.Print(" ")
} }
t.printHighlighted(item, false, 0, C.COL_MATCH) t.printHighlighted(item, false, 0, C.ColMatch)
} }
} }
@ -232,25 +237,25 @@ func trimRight(runes []rune, width int) ([]rune, int) {
sz := len(runes) sz := len(runes)
currentWidth -= runewidth.RuneWidth(runes[sz-1]) currentWidth -= runewidth.RuneWidth(runes[sz-1])
runes = runes[:sz-1] runes = runes[:sz-1]
trimmed += 1 trimmed++
} }
return runes, trimmed return runes, trimmed
} }
func trimLeft(runes []rune, width int) ([]rune, int32) { func trimLeft(runes []rune, width int) ([]rune, int32) {
currentWidth := displayWidth(runes) currentWidth := displayWidth(runes)
var trimmed int32 = 0 var trimmed int32
for currentWidth > width && len(runes) > 0 { for currentWidth > width && len(runes) > 0 {
currentWidth -= runewidth.RuneWidth(runes[0]) currentWidth -= runewidth.RuneWidth(runes[0])
runes = runes[1:] runes = runes[1:]
trimmed += 1 trimmed++
} }
return runes, trimmed return runes, trimmed
} }
func (*Terminal) printHighlighted(item *Item, bold bool, col1 int, col2 int) { func (*Terminal) printHighlighted(item *Item, bold bool, col1 int, col2 int) {
var maxe int32 = 0 var maxe int32
for _, offset := range item.offsets { for _, offset := range item.offsets {
if offset[1] > maxe { if offset[1] > maxe {
maxe = offset[1] maxe = offset[1]
@ -293,7 +298,7 @@ func (*Terminal) printHighlighted(item *Item, bold bool, col1 int, col2 int) {
} }
sort.Sort(ByOrder(offsets)) sort.Sort(ByOrder(offsets))
var index int32 = 0 var index int32
for _, offset := range offsets { for _, offset := range offsets {
b := Max32(index, offset[0]) b := Max32(index, offset[0])
e := Max32(index, offset[1]) e := Max32(index, offset[1])
@ -364,6 +369,7 @@ func (t *Terminal) rubout(pattern string) {
t.input = append(t.input[:t.cx], after...) t.input = append(t.input[:t.cx], after...)
} }
// Loop is called to start Terminal I/O
func (t *Terminal) Loop() { func (t *Terminal) Loop() {
{ // Late initialization { // Late initialization
t.mutex.Lock() t.mutex.Lock()
@ -374,9 +380,9 @@ func (t *Terminal) Loop() {
t.printInfo() t.printInfo()
t.mutex.Unlock() t.mutex.Unlock()
go func() { go func() {
timer := time.NewTimer(INITIAL_DELAY) timer := time.NewTimer(initialDelay)
<-timer.C <-timer.C
t.reqBox.Set(REQ_REFRESH, nil) t.reqBox.Set(reqRefresh, nil)
}() }()
} }
@ -387,22 +393,22 @@ func (t *Terminal) Loop() {
t.mutex.Lock() t.mutex.Lock()
for req := range *events { for req := range *events {
switch req { switch req {
case REQ_PROMPT: case reqPrompt:
t.printPrompt() t.printPrompt()
case REQ_INFO: case reqInfo:
t.printInfo() t.printInfo()
case REQ_LIST: case reqList:
t.printList() t.printList()
case REQ_REFRESH: case reqRefresh:
t.suppress = false t.suppress = false
case REQ_REDRAW: case reqRedraw:
C.Clear() C.Clear()
t.printAll() t.printAll()
case REQ_CLOSE: case reqClose:
C.Close() C.Close()
t.output() t.output()
os.Exit(0) os.Exit(0)
case REQ_QUIT: case reqQuit:
C.Close() C.Close()
os.Exit(1) os.Exit(1)
} }
@ -420,11 +426,11 @@ func (t *Terminal) Loop() {
t.mutex.Lock() t.mutex.Lock()
previousInput := t.input previousInput := t.input
events := []EventType{REQ_PROMPT} events := []EventType{reqPrompt}
req := func(evts ...EventType) { req := func(evts ...EventType) {
for _, event := range evts { for _, event := range evts {
events = append(events, event) events = append(events, event)
if event == REQ_CLOSE || event == REQ_QUIT { if event == reqClose || event == reqQuit {
looping = false looping = false
} }
} }
@ -438,99 +444,99 @@ func (t *Terminal) Loop() {
} else { } else {
delete(t.selected, item.text) delete(t.selected, item.text)
} }
req(REQ_INFO) req(reqInfo)
} }
} }
switch event.Type { switch event.Type {
case C.INVALID: case C.Invalid:
t.mutex.Unlock() t.mutex.Unlock()
continue continue
case C.CTRL_A: case C.CtrlA:
t.cx = 0 t.cx = 0
case C.CTRL_B: case C.CtrlB:
if t.cx > 0 { if t.cx > 0 {
t.cx -= 1 t.cx--
} }
case C.CTRL_C, C.CTRL_G, C.CTRL_Q, C.ESC: case C.CtrlC, C.CtrlG, C.CtrlQ, C.ESC:
req(REQ_QUIT) req(reqQuit)
case C.CTRL_D: case C.CtrlD:
if !t.delChar() && t.cx == 0 { if !t.delChar() && t.cx == 0 {
req(REQ_QUIT) req(reqQuit)
} }
case C.CTRL_E: case C.CtrlE:
t.cx = len(t.input) t.cx = len(t.input)
case C.CTRL_F: case C.CtrlF:
if t.cx < len(t.input) { if t.cx < len(t.input) {
t.cx += 1 t.cx++
} }
case C.CTRL_H: case C.CtrlH:
if t.cx > 0 { if t.cx > 0 {
t.input = append(t.input[:t.cx-1], t.input[t.cx:]...) t.input = append(t.input[:t.cx-1], t.input[t.cx:]...)
t.cx -= 1 t.cx--
} }
case C.TAB: case C.Tab:
if t.multi && t.merger.Length() > 0 { if t.multi && t.merger.Length() > 0 {
toggle() toggle()
t.vmove(-1) t.vmove(-1)
req(REQ_LIST) req(reqList)
} }
case C.BTAB: case C.BTab:
if t.multi && t.merger.Length() > 0 { if t.multi && t.merger.Length() > 0 {
toggle() toggle()
t.vmove(1) t.vmove(1)
req(REQ_LIST) req(reqList)
} }
case C.CTRL_J, C.CTRL_N: case C.CtrlJ, C.CtrlN:
t.vmove(-1) t.vmove(-1)
req(REQ_LIST) req(reqList)
case C.CTRL_K, C.CTRL_P: case C.CtrlK, C.CtrlP:
t.vmove(1) t.vmove(1)
req(REQ_LIST) req(reqList)
case C.CTRL_M: case C.CtrlM:
req(REQ_CLOSE) req(reqClose)
case C.CTRL_L: case C.CtrlL:
req(REQ_REDRAW) req(reqRedraw)
case C.CTRL_U: case C.CtrlU:
if t.cx > 0 { if t.cx > 0 {
t.yanked = copySlice(t.input[:t.cx]) t.yanked = copySlice(t.input[:t.cx])
t.input = t.input[t.cx:] t.input = t.input[t.cx:]
t.cx = 0 t.cx = 0
} }
case C.CTRL_W: case C.CtrlW:
if t.cx > 0 { if t.cx > 0 {
t.rubout("\\s\\S") t.rubout("\\s\\S")
} }
case C.ALT_BS: case C.AltBS:
if t.cx > 0 { if t.cx > 0 {
t.rubout("[^[:alnum:]][[:alnum:]]") t.rubout("[^[:alnum:]][[:alnum:]]")
} }
case C.CTRL_Y: case C.CtrlY:
t.input = append(append(t.input[:t.cx], t.yanked...), t.input[t.cx:]...) t.input = append(append(t.input[:t.cx], t.yanked...), t.input[t.cx:]...)
t.cx += len(t.yanked) t.cx += len(t.yanked)
case C.DEL: case C.Del:
t.delChar() t.delChar()
case C.PGUP: case C.PgUp:
t.vmove(maxItems() - 1) t.vmove(maxItems() - 1)
req(REQ_LIST) req(reqList)
case C.PGDN: case C.PgDn:
t.vmove(-(maxItems() - 1)) t.vmove(-(maxItems() - 1))
req(REQ_LIST) req(reqList)
case C.ALT_B: case C.AltB:
t.cx = findLastMatch("[^[:alnum:]][[:alnum:]]", string(t.input[:t.cx])) + 1 t.cx = findLastMatch("[^[:alnum:]][[:alnum:]]", string(t.input[:t.cx])) + 1
case C.ALT_F: case C.AltF:
t.cx += findFirstMatch("[[:alnum:]][^[:alnum:]]|(.$)", string(t.input[t.cx:])) + 1 t.cx += findFirstMatch("[[:alnum:]][^[:alnum:]]|(.$)", string(t.input[t.cx:])) + 1
case C.ALT_D: case C.AltD:
ncx := t.cx + ncx := t.cx +
findFirstMatch("[[:alnum:]][^[:alnum:]]|(.$)", string(t.input[t.cx:])) + 1 findFirstMatch("[[:alnum:]][^[:alnum:]]|(.$)", string(t.input[t.cx:])) + 1
if ncx > t.cx { if ncx > t.cx {
t.yanked = copySlice(t.input[t.cx:ncx]) t.yanked = copySlice(t.input[t.cx:ncx])
t.input = append(t.input[:t.cx], t.input[ncx:]...) t.input = append(t.input[:t.cx], t.input[ncx:]...)
} }
case C.RUNE: case C.Rune:
prefix := copySlice(t.input[:t.cx]) prefix := copySlice(t.input[:t.cx])
t.input = append(append(prefix, event.Char), t.input[t.cx:]...) t.input = append(append(prefix, event.Char), t.input[t.cx:]...)
t.cx += 1 t.cx++
case C.MOUSE: case C.Mouse:
me := event.MouseEvent me := event.MouseEvent
mx, my := Min(len(t.input), Max(0, me.X-len(t.prompt))), me.Y mx, my := Min(len(t.input), Max(0, me.X-len(t.prompt))), me.Y
if !t.reverse { if !t.reverse {
@ -543,13 +549,13 @@ func (t *Terminal) Loop() {
toggle() toggle()
} }
t.vmove(me.S) t.vmove(me.S)
req(REQ_LIST) req(reqList)
} }
} 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.listIndex(t.cy) < t.merger.Length() {
req(REQ_CLOSE) req(reqClose)
} }
} }
} else if me.Down { } else if me.Down {
@ -561,7 +567,7 @@ func (t *Terminal) Loop() {
if t.vset(t.offset+my-2) && t.multi && me.Mod { if t.vset(t.offset+my-2) && t.multi && me.Mod {
toggle() toggle()
} }
req(REQ_LIST) req(reqList)
} }
} }
} }
@ -569,7 +575,7 @@ func (t *Terminal) Loop() {
t.mutex.Unlock() // Must be unlocked before touching reqBox t.mutex.Unlock() // Must be unlocked before touching reqBox
if changed { if changed {
t.eventBox.Set(EVT_SEARCH_NEW, nil) t.eventBox.Set(EvtSearchNew, nil)
} }
for _, event := range events { for _, event := range events {
t.reqBox.Set(event, nil) t.reqBox.Set(event, nil)

View File

@ -6,40 +6,42 @@ import (
"strings" "strings"
) )
const RANGE_ELLIPSIS = 0 const rangeEllipsis = 0
// Range represents nth-expression
type Range struct { type Range struct {
begin int begin int
end int end int
} }
// Transformed holds the result of tokenization and transformation
type Transformed struct { type Transformed struct {
whole *string whole *string
parts []Token parts []Token
} }
// Token contains the tokenized part of the strings and its prefix length
type Token struct { type Token struct {
text *string text *string
prefixLength int prefixLength int
} }
// ParseRange parses nth-expression and returns the corresponding Range object
func ParseRange(str *string) (Range, bool) { func ParseRange(str *string) (Range, bool) {
if (*str) == ".." { if (*str) == ".." {
return Range{RANGE_ELLIPSIS, RANGE_ELLIPSIS}, true return Range{rangeEllipsis, rangeEllipsis}, true
} else if strings.HasPrefix(*str, "..") { } else if strings.HasPrefix(*str, "..") {
end, err := strconv.Atoi((*str)[2:]) end, err := strconv.Atoi((*str)[2:])
if err != nil || end == 0 { if err != nil || end == 0 {
return Range{}, false return Range{}, false
} else {
return Range{RANGE_ELLIPSIS, end}, true
} }
return Range{rangeEllipsis, end}, true
} else if strings.HasSuffix(*str, "..") { } else if strings.HasSuffix(*str, "..") {
begin, err := strconv.Atoi((*str)[:len(*str)-2]) begin, err := strconv.Atoi((*str)[:len(*str)-2])
if err != nil || begin == 0 { if err != nil || begin == 0 {
return Range{}, false return Range{}, false
} else {
return Range{begin, RANGE_ELLIPSIS}, true
} }
return Range{begin, rangeEllipsis}, true
} else if strings.Contains(*str, "..") { } else if strings.Contains(*str, "..") {
ns := strings.Split(*str, "..") ns := strings.Split(*str, "..")
if len(ns) != 2 { if len(ns) != 2 {
@ -75,9 +77,9 @@ func withPrefixLengths(tokens []string, begin int) []Token {
} }
const ( const (
AWK_NIL = iota awkNil = iota
AWK_BLACK awkBlack
AWK_WHITE awkWhite
) )
func awkTokenizer(input *string) ([]string, int) { func awkTokenizer(input *string) ([]string, int) {
@ -85,28 +87,28 @@ func awkTokenizer(input *string) ([]string, int) {
ret := []string{} ret := []string{}
str := []rune{} str := []rune{}
prefixLength := 0 prefixLength := 0
state := AWK_NIL state := awkNil
for _, r := range []rune(*input) { for _, r := range []rune(*input) {
white := r == 9 || r == 32 white := r == 9 || r == 32
switch state { switch state {
case AWK_NIL: case awkNil:
if white { if white {
prefixLength++ prefixLength++
} else { } else {
state = AWK_BLACK state = awkBlack
str = append(str, r) str = append(str, r)
} }
case AWK_BLACK: case awkBlack:
str = append(str, r) str = append(str, r)
if white { if white {
state = AWK_WHITE state = awkWhite
} }
case AWK_WHITE: case awkWhite:
if white { if white {
str = append(str, r) str = append(str, r)
} else { } else {
ret = append(ret, string(str)) ret = append(ret, string(str))
state = AWK_BLACK state = awkBlack
str = []rune{r} str = []rune{r}
} }
} }
@ -117,16 +119,16 @@ func awkTokenizer(input *string) ([]string, int) {
return ret, prefixLength return ret, prefixLength
} }
// Tokenize tokenizes the given string with the delimiter
func Tokenize(str *string, delimiter *regexp.Regexp) []Token { func Tokenize(str *string, delimiter *regexp.Regexp) []Token {
if delimiter == nil { if delimiter == nil {
// AWK-style (\S+\s*) // AWK-style (\S+\s*)
tokens, prefixLength := awkTokenizer(str) tokens, prefixLength := awkTokenizer(str)
return withPrefixLengths(tokens, prefixLength) return withPrefixLengths(tokens, prefixLength)
} else { }
tokens := delimiter.FindAllString(*str, -1) tokens := delimiter.FindAllString(*str, -1)
return withPrefixLengths(tokens, 0) return withPrefixLengths(tokens, 0)
} }
}
func joinTokens(tokens []Token) string { func joinTokens(tokens []Token) string {
ret := "" ret := ""
@ -136,6 +138,7 @@ func joinTokens(tokens []Token) string {
return ret return ret
} }
// Transform is used to transform the input when --with-nth option is given
func Transform(tokens []Token, withNth []Range) *Transformed { func Transform(tokens []Token, withNth []Range) *Transformed {
transTokens := make([]Token, len(withNth)) transTokens := make([]Token, len(withNth))
numTokens := len(tokens) numTokens := len(tokens)
@ -145,7 +148,7 @@ func Transform(tokens []Token, withNth []Range) *Transformed {
minIdx := 0 minIdx := 0
if r.begin == r.end { if r.begin == r.end {
idx := r.begin idx := r.begin
if idx == RANGE_ELLIPSIS { if idx == rangeEllipsis {
part += joinTokens(tokens) part += joinTokens(tokens)
} else { } else {
if idx < 0 { if idx < 0 {
@ -158,12 +161,12 @@ func Transform(tokens []Token, withNth []Range) *Transformed {
} }
} else { } else {
var begin, end int var begin, end int
if r.begin == RANGE_ELLIPSIS { // ..N if r.begin == rangeEllipsis { // ..N
begin, end = 1, r.end begin, end = 1, r.end
if end < 0 { if end < 0 {
end += numTokens + 1 end += numTokens + 1
} }
} else if r.end == RANGE_ELLIPSIS { // N.. } else if r.end == rangeEllipsis { // N..
begin, end = r.begin, numTokens begin, end = r.begin, numTokens
if begin < 0 { if begin < 0 {
begin += numTokens + 1 begin += numTokens + 1

View File

@ -6,14 +6,14 @@ func TestParseRange(t *testing.T) {
{ {
i := ".." i := ".."
r, _ := ParseRange(&i) r, _ := ParseRange(&i)
if r.begin != RANGE_ELLIPSIS || r.end != RANGE_ELLIPSIS { if r.begin != rangeEllipsis || r.end != rangeEllipsis {
t.Errorf("%s", r) t.Errorf("%s", r)
} }
} }
{ {
i := "3.." i := "3.."
r, _ := ParseRange(&i) r, _ := ParseRange(&i)
if r.begin != 3 || r.end != RANGE_ELLIPSIS { if r.begin != 3 || r.end != rangeEllipsis {
t.Errorf("%s", r) t.Errorf("%s", r)
} }
} }

View File

@ -2,6 +2,7 @@ package fzf
import "time" import "time"
// Max returns the largest integer
func Max(first int, items ...int) int { func Max(first int, items ...int) int {
max := first max := first
for _, item := range items { for _, item := range items {
@ -12,6 +13,7 @@ func Max(first int, items ...int) int {
return max return max
} }
// Max32 returns the largest 32-bit integer
func Max32(first int32, second int32) int32 { func Max32(first int32, second int32) int32 {
if first > second { if first > second {
return first return first
@ -19,6 +21,7 @@ func Max32(first int32, second int32) int32 {
return second return second
} }
// Min returns the smallest integer
func Min(first int, items ...int) int { func Min(first int, items ...int) int {
min := first min := first
for _, item := range items { for _, item := range items {
@ -29,6 +32,7 @@ func Min(first int, items ...int) int {
return min return min
} }
// DurWithin limits the given time.Duration with the upper and lower bounds
func DurWithin( func DurWithin(
val time.Duration, min time.Duration, max time.Duration) time.Duration { val time.Duration, min time.Duration, max time.Duration) time.Duration {
if val < min { if val < min {