diff --git a/src/algo.go b/src/algo.go index e0c173f..5f15ab3 100644 --- a/src/algo.go +++ b/src/algo.go @@ -10,6 +10,7 @@ import "strings" * 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) { runes := []rune(*input) @@ -36,7 +37,7 @@ func FuzzyMatch(caseSensitive bool, input *string, pattern []rune) (int, int) { if sidx < 0 { sidx = index } - if pidx += 1; pidx == len(pattern) { + if pidx++; pidx == len(pattern) { eidx = index + 1 break } @@ -44,14 +45,14 @@ func FuzzyMatch(caseSensitive bool, input *string, pattern []rune) (int, int) { } if sidx >= 0 && eidx >= 0 { - pidx -= 1 + pidx-- for index := eidx - 1; index >= sidx; index-- { char := runes[index] if !caseSensitive && char >= 65 && char <= 90 { char += 32 } if char == pattern[pidx] { - if pidx -= 1; pidx < 0 { + if pidx--; pidx < 0 { sidx = index break } @@ -62,6 +63,8 @@ func FuzzyMatch(caseSensitive bool, input *string, pattern []rune) (int, int) { return -1, -1 } +// ExactMatchStrings performs exact-match using strings package. +// Currently not used. func ExactMatchStrings(caseSensitive bool, input *string, pattern []rune) (int, int) { var str string if caseSensitive { @@ -77,15 +80,13 @@ func ExactMatchStrings(caseSensitive bool, input *string, pattern []rune) (int, return -1, -1 } -/* - * This is a basic string searching algorithm that handles case sensitivity. - * Although naive, it still performs better than the combination of - * strings.ToLower + strings.Index for typical fzf use cases where input - * strings and patterns are not very long. - * - * We might try to implement better algorithms in the future: - * http://en.wikipedia.org/wiki/String_searching_algorithm - */ +// ExactMatchNaive is a basic string searching algorithm that handles case +// sensitivity. Although naive, it still performs better than the combination +// of strings.ToLower + strings.Index for typical fzf use cases where input +// strings and patterns are not very long. +// +// We might try to implement better algorithms in the future: +// http://en.wikipedia.org/wiki/String_searching_algorithm func ExactMatchNaive(caseSensitive bool, input *string, pattern []rune) (int, int) { runes := []rune(*input) numRunes := len(runes) @@ -101,7 +102,7 @@ func ExactMatchNaive(caseSensitive bool, input *string, pattern []rune) (int, in char += 32 } if pattern[pidx] == char { - pidx += 1 + pidx++ if pidx == plen { return index - plen + 1, index + 1 } @@ -113,6 +114,7 @@ func ExactMatchNaive(caseSensitive bool, input *string, pattern []rune) (int, in return -1, -1 } +// PrefixMatch performs prefix-match func PrefixMatch(caseSensitive bool, input *string, pattern []rune) (int, int) { runes := []rune(*input) if len(runes) < len(pattern) { @@ -131,6 +133,7 @@ func PrefixMatch(caseSensitive bool, input *string, pattern []rune) (int, int) { return 0, len(pattern) } +// SuffixMatch performs suffix-match func SuffixMatch(caseSensitive bool, input *string, pattern []rune) (int, int) { runes := []rune(strings.TrimRight(*input, " ")) trimmedLen := len(runes) diff --git a/src/atomicbool.go b/src/atomicbool.go index f2f4894..b264724 100644 --- a/src/atomicbool.go +++ b/src/atomicbool.go @@ -2,23 +2,28 @@ package fzf import "sync" +// AtomicBool is a boxed-class that provides synchronized access to the +// underlying boolean value type AtomicBool struct { mutex sync.Mutex state bool } +// NewAtomicBool returns a new AtomicBool func NewAtomicBool(initialState bool) *AtomicBool { return &AtomicBool{ mutex: sync.Mutex{}, state: initialState} } +// Get returns the current boolean value synchronously func (a *AtomicBool) Get() bool { a.mutex.Lock() defer a.mutex.Unlock() return a.state } +// Set updates the boolean value synchronously func (a *AtomicBool) Set(newState bool) bool { a.mutex.Lock() defer a.mutex.Unlock() diff --git a/src/cache.go b/src/cache.go index 340f325..f2f84a0 100644 --- a/src/cache.go +++ b/src/cache.go @@ -2,16 +2,21 @@ package fzf import "sync" +// QueryCache associates strings to lists of items type QueryCache map[string][]*Item + +// ChunkCache associates Chunk and query string to lists of items type ChunkCache struct { mutex sync.Mutex cache map[*Chunk]*QueryCache } +// NewChunkCache returns a new ChunkCache func NewChunkCache() ChunkCache { 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) { if len(key) == 0 || !chunk.IsFull() { return @@ -28,6 +33,7 @@ func (cc *ChunkCache) Add(chunk *Chunk, key string, list []*Item) { (*qc)[key] = list } +// Find is called to lookup ChunkCache func (cc *ChunkCache) Find(chunk *Chunk, key string) ([]*Item, bool) { if len(key) == 0 || !chunk.IsFull() { return nil, false diff --git a/src/cache_test.go b/src/cache_test.go index 2a8b048..3975eaa 100644 --- a/src/cache_test.go +++ b/src/cache_test.go @@ -4,7 +4,7 @@ import "testing" func TestChunkCache(t *testing.T) { cache := NewChunkCache() - chunk2 := make(Chunk, CHUNK_SIZE) + chunk2 := make(Chunk, ChunkSize) chunk1p := &Chunk{} chunk2p := &chunk2 items1 := []*Item{&Item{}} diff --git a/src/chunklist.go b/src/chunklist.go index 5bca6da..73983b1 100644 --- a/src/chunklist.go +++ b/src/chunklist.go @@ -2,12 +2,17 @@ package fzf 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 +// Transformer is a closure type that builds Item object from a pointer to a +// string and an integer type Transformer func(*string, int) *Item +// ChunkList is a list of Chunks type ChunkList struct { chunks []*Chunk count int @@ -15,6 +20,7 @@ type ChunkList struct { trans Transformer } +// NewChunkList returns a new ChunkList func NewChunkList(trans Transformer) *ChunkList { return &ChunkList{ chunks: []*Chunk{}, @@ -27,34 +33,38 @@ func (c *Chunk) push(trans Transformer, data *string, index int) { *c = append(*c, trans(data, index)) } +// IsFull returns true if the Chunk is full func (c *Chunk) IsFull() bool { - return len(*c) == CHUNK_SIZE + return len(*c) == ChunkSize } func (cl *ChunkList) lastChunk() *Chunk { return cl.chunks[len(cl.chunks)-1] } +// CountItems returns the total number of Items func CountItems(cs []*Chunk) int { if len(cs) == 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) { cl.mutex.Lock() defer cl.mutex.Unlock() 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.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) { cl.mutex.Lock() defer cl.mutex.Unlock() diff --git a/src/chunklist_test.go b/src/chunklist_test.go index 09e4aad..02288d9 100644 --- a/src/chunklist_test.go +++ b/src/chunklist_test.go @@ -45,7 +45,7 @@ func TestChunkList(t *testing.T) { } // 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)) } @@ -57,7 +57,7 @@ func TestChunkList(t *testing.T) { // New snapshot snapshot, count = cl.Snapshot() 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") } if len(*snapshot[2]) != 2 { diff --git a/src/constants.go b/src/constants.go index b0b64db..80eb634 100644 --- a/src/constants.go +++ b/src/constants.go @@ -1,12 +1,17 @@ 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 ( - EVT_READ_NEW EventType = iota - EVT_READ_FIN - EVT_SEARCH_NEW - EVT_SEARCH_PROGRESS - EVT_SEARCH_FIN - EVT_CLOSE + EvtReadNew EventType = iota + EvtReadFin + EvtSearchNew + EvtSearchProgress + EvtSearchFin + EvtClose ) diff --git a/src/core.go b/src/core.go index ab2a48f..65e641c 100644 --- a/src/core.go +++ b/src/core.go @@ -32,28 +32,29 @@ import ( "time" ) -const COORDINATOR_DELAY_MAX time.Duration = 100 * time.Millisecond -const COORDINATOR_DELAY_STEP time.Duration = 10 * time.Millisecond +const coordinatorDelayMax time.Duration = 100 * time.Millisecond +const coordinatorDelayStep time.Duration = 10 * time.Millisecond func initProcs() { runtime.GOMAXPROCS(runtime.NumCPU()) } /* -Reader -> EVT_READ_FIN -Reader -> EVT_READ_NEW -> Matcher (restart) -Terminal -> EVT_SEARCH_NEW -> Matcher (restart) -Matcher -> EVT_SEARCH_PROGRESS -> Terminal (update info) -Matcher -> EVT_SEARCH_FIN -> Terminal (update list) +Reader -> EvtReadFin +Reader -> EvtReadNew -> Matcher (restart) +Terminal -> EvtSearchNew -> Matcher (restart) +Matcher -> EvtSearchProgress -> Terminal (update info) +Matcher -> EvtSearchFin -> Terminal (update list) */ +// Run starts fzf func Run(options *Options) { initProcs() opts := ParseOptions() if opts.Version { - fmt.Println(VERSION) + fmt.Println(Version) os.Exit(0) } @@ -108,12 +109,12 @@ func Run(options *Options) { pattern := patternBuilder([]rune(patternString)) looping := true - eventBox.Unwatch(EVT_READ_NEW) + eventBox.Unwatch(EvtReadNew) for looping { eventBox.Wait(func(events *Events) { - for evt, _ := range *events { + for evt := range *events { switch evt { - case EVT_READ_FIN: + case EvtReadFin: looping = false return } @@ -133,7 +134,7 @@ func Run(options *Options) { fmt.Println(patternString) } for i := 0; i < merger.Length(); i++ { - merger.Get(i).Print() + fmt.Println(merger.Get(i).AsString()) } os.Exit(0) } @@ -149,33 +150,33 @@ func Run(options *Options) { // Event coordination reading := true ticks := 0 - eventBox.Watch(EVT_READ_NEW) + eventBox.Watch(EvtReadNew) for { delay := true - ticks += 1 + ticks++ eventBox.Wait(func(events *Events) { defer events.Clear() for evt, value := range *events { switch evt { - case EVT_READ_NEW, EVT_READ_FIN: - reading = reading && evt == EVT_READ_NEW + case EvtReadNew, EvtReadFin: + reading = reading && evt == EvtReadNew snapshot, count := chunkList.Snapshot() terminal.UpdateCount(count, !reading) matcher.Reset(snapshot, terminal.Input(), false) - case EVT_SEARCH_NEW: + case EvtSearchNew: snapshot, _ := chunkList.Snapshot() matcher.Reset(snapshot, terminal.Input(), true) delay = false - case EVT_SEARCH_PROGRESS: + case EvtSearchProgress: switch val := value.(type) { case float32: terminal.UpdateProgress(val) } - case EVT_SEARCH_FIN: + case EvtSearchFin: switch val := value.(type) { case *Merger: terminal.UpdateList(val) @@ -185,8 +186,8 @@ func Run(options *Options) { }) if delay && reading { dur := DurWithin( - time.Duration(ticks)*COORDINATOR_DELAY_STEP, - 0, COORDINATOR_DELAY_MAX) + time.Duration(ticks)*coordinatorDelayStep, + 0, coordinatorDelayMax) time.Sleep(dur) } } diff --git a/src/curses/curses.go b/src/curses/curses.go index 736ccf6..8ebb583 100644 --- a/src/curses/curses.go +++ b/src/curses/curses.go @@ -20,66 +20,68 @@ import ( "unicode/utf8" ) +// Types of user action const ( - RUNE = iota + Rune = iota - CTRL_A - CTRL_B - CTRL_C - CTRL_D - CTRL_E - CTRL_F - CTRL_G - CTRL_H - TAB - CTRL_J - CTRL_K - CTRL_L - CTRL_M - CTRL_N - CTRL_O - CTRL_P - CTRL_Q - CTRL_R - CTRL_S - CTRL_T - CTRL_U - CTRL_V - CTRL_W - CTRL_X - CTRL_Y - CTRL_Z + CtrlA + CtrlB + CtrlC + CtrlD + CtrlE + CtrlF + CtrlG + CtrlH + Tab + CtrlJ + CtrlK + CtrlL + CtrlM + CtrlN + CtrlO + CtrlP + CtrlQ + CtrlR + CtrlS + CtrlT + CtrlU + CtrlV + CtrlW + CtrlX + CtrlY + CtrlZ ESC - INVALID - MOUSE + Invalid + Mouse - BTAB + BTab - DEL - PGUP - PGDN + Del + PgUp + PgDn - ALT_B - ALT_F - ALT_D - ALT_BS + AltB + AltF + AltD + AltBS +) + +// Pallete +const ( + ColNormal = iota + ColPrompt + ColMatch + ColCurrent + ColCurrentMatch + ColSpinner + ColInfo + ColCursor + ColSelected ) const ( - COL_NORMAL = iota - COL_PROMPT - COL_MATCH - COL_CURRENT - COL_CURRENT_MATCH - COL_SPINNER - COL_INFO - COL_CURSOR - COL_SELECTED -) - -const ( - DOUBLE_CLICK_DURATION = 500 * time.Millisecond + doubleClickDuration = 500 * time.Millisecond ) type Event struct { @@ -112,8 +114,8 @@ func init() { } func attrColored(pair int, bold bool) C.int { - var attr C.int = 0 - if pair > COL_NORMAL { + var attr C.int + if pair > ColNormal { attr = C.COLOR_PAIR(C.int(pair)) } if bold { @@ -123,15 +125,15 @@ func attrColored(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 { - case COL_CURRENT: + case ColCurrent: if bold { attr = C.A_REVERSE } - case COL_MATCH: + case ColMatch: attr = C.A_UNDERLINE - case COL_CURRENT_MATCH: + case ColCurrentMatch: attr = C.A_UNDERLINE | C.A_REVERSE } if bold { @@ -198,23 +200,23 @@ func Init(color bool, color256 bool, black bool, mouse bool) { bg = -1 } if color256 { - C.init_pair(COL_PROMPT, 110, bg) - C.init_pair(COL_MATCH, 108, bg) - C.init_pair(COL_CURRENT, 254, 236) - C.init_pair(COL_CURRENT_MATCH, 151, 236) - C.init_pair(COL_SPINNER, 148, bg) - C.init_pair(COL_INFO, 144, bg) - C.init_pair(COL_CURSOR, 161, 236) - C.init_pair(COL_SELECTED, 168, 236) + C.init_pair(ColPrompt, 110, bg) + C.init_pair(ColMatch, 108, bg) + C.init_pair(ColCurrent, 254, 236) + C.init_pair(ColCurrentMatch, 151, 236) + C.init_pair(ColSpinner, 148, bg) + C.init_pair(ColInfo, 144, bg) + C.init_pair(ColCursor, 161, 236) + C.init_pair(ColSelected, 168, 236) } else { - C.init_pair(COL_PROMPT, C.COLOR_BLUE, bg) - C.init_pair(COL_MATCH, C.COLOR_GREEN, bg) - C.init_pair(COL_CURRENT, C.COLOR_YELLOW, C.COLOR_BLACK) - C.init_pair(COL_CURRENT_MATCH, C.COLOR_GREEN, C.COLOR_BLACK) - C.init_pair(COL_SPINNER, C.COLOR_GREEN, bg) - C.init_pair(COL_INFO, C.COLOR_WHITE, bg) - C.init_pair(COL_CURSOR, C.COLOR_RED, C.COLOR_BLACK) - C.init_pair(COL_SELECTED, C.COLOR_MAGENTA, C.COLOR_BLACK) + C.init_pair(ColPrompt, C.COLOR_BLUE, bg) + C.init_pair(ColMatch, C.COLOR_GREEN, bg) + C.init_pair(ColCurrent, C.COLOR_YELLOW, C.COLOR_BLACK) + C.init_pair(ColCurrentMatch, C.COLOR_GREEN, C.COLOR_BLACK) + C.init_pair(ColSpinner, C.COLOR_GREEN, bg) + C.init_pair(ColInfo, C.COLOR_WHITE, bg) + C.init_pair(ColCursor, C.COLOR_RED, C.COLOR_BLACK) + C.init_pair(ColSelected, C.COLOR_MAGENTA, C.COLOR_BLACK) } _color = attrColored } else { @@ -245,7 +247,7 @@ func GetBytes() []byte { // 27 (91 79) 77 type x y func mouseSequence(sz *int) Event { if len(_buf) < 6 { - return Event{INVALID, 0, nil} + return Event{Invalid, 0, nil} } *sz = 6 switch _buf[3] { @@ -258,7 +260,7 @@ func mouseSequence(sz *int) Event { double := false if down { now := time.Now() - if now.Sub(_prevDownTime) < DOUBLE_CLICK_DURATION { + if now.Sub(_prevDownTime) < doubleClickDuration { _clickY = append(_clickY, y) } else { _clickY = []int{y} @@ -266,18 +268,18 @@ func mouseSequence(sz *int) Event { _prevDownTime = now } else { if len(_clickY) > 1 && _clickY[0] == _clickY[1] && - time.Now().Sub(_prevDownTime) < DOUBLE_CLICK_DURATION { + time.Now().Sub(_prevDownTime) < doubleClickDuration { 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 97, 101, 105, 113: // scroll-down / shift / cmd / ctrl mod := _buf[3] >= 100 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 { @@ -287,81 +289,81 @@ func escSequence(sz *int) Event { *sz = 2 switch _buf[1] { case 98: - return Event{ALT_B, 0, nil} + return Event{AltB, 0, nil} case 100: - return Event{ALT_D, 0, nil} + return Event{AltD, 0, nil} case 102: - return Event{ALT_F, 0, nil} + return Event{AltF, 0, nil} case 127: - return Event{ALT_BS, 0, nil} + return Event{AltBS, 0, nil} case 91, 79: if len(_buf) < 3 { - return Event{INVALID, 0, nil} + return Event{Invalid, 0, nil} } *sz = 3 switch _buf[2] { case 68: - return Event{CTRL_B, 0, nil} + return Event{CtrlB, 0, nil} case 67: - return Event{CTRL_F, 0, nil} + return Event{CtrlF, 0, nil} case 66: - return Event{CTRL_J, 0, nil} + return Event{CtrlJ, 0, nil} case 65: - return Event{CTRL_K, 0, nil} + return Event{CtrlK, 0, nil} case 90: - return Event{BTAB, 0, nil} + return Event{BTab, 0, nil} case 72: - return Event{CTRL_A, 0, nil} + return Event{CtrlA, 0, nil} case 70: - return Event{CTRL_E, 0, nil} + return Event{CtrlE, 0, nil} case 77: return mouseSequence(sz) case 49, 50, 51, 52, 53, 54: if len(_buf) < 4 { - return Event{INVALID, 0, nil} + return Event{Invalid, 0, nil} } *sz = 4 switch _buf[2] { case 50: - return Event{INVALID, 0, nil} // INS + return Event{Invalid, 0, nil} // INS case 51: - return Event{DEL, 0, nil} + return Event{Del, 0, nil} case 52: - return Event{CTRL_E, 0, nil} + return Event{CtrlE, 0, nil} case 53: - return Event{PGUP, 0, nil} + return Event{PgUp, 0, nil} case 54: - return Event{PGDN, 0, nil} + return Event{PgDn, 0, nil} case 49: switch _buf[3] { case 126: - return Event{CTRL_A, 0, nil} + return Event{CtrlA, 0, nil} case 59: if len(_buf) != 6 { - return Event{INVALID, 0, nil} + return Event{Invalid, 0, nil} } *sz = 6 switch _buf[4] { case 50: switch _buf[5] { case 68: - return Event{CTRL_A, 0, nil} + return Event{CtrlA, 0, nil} case 67: - return Event{CTRL_E, 0, nil} + return Event{CtrlE, 0, nil} } case 53: switch _buf[5] { case 68: - return Event{ALT_B, 0, nil} + return Event{AltB, 0, nil} case 67: - return Event{ALT_F, 0, nil} + return Event{AltF, 0, nil} } } // _buf[4] } // _buf[3] } // _buf[2] } // _buf[2] } // _buf[1] - return Event{INVALID, 0, nil} + return Event{Invalid, 0, nil} } func GetChar() Event { @@ -378,21 +380,21 @@ func GetChar() Event { }() switch _buf[0] { - case CTRL_C, CTRL_G, CTRL_Q: - return Event{CTRL_C, 0, nil} + case CtrlC, CtrlG, CtrlQ: + return Event{CtrlC, 0, nil} case 127: - return Event{CTRL_H, 0, nil} + return Event{CtrlH, 0, nil} case ESC: return escSequence(&sz) } // CTRL-A ~ CTRL-Z - if _buf[0] <= CTRL_Z { + if _buf[0] <= CtrlZ { return Event{int(_buf[0]), 0, nil} } r, rsz := utf8.DecodeRune(_buf) sz = rsz - return Event{RUNE, r, nil} + return Event{Rune, r, nil} } func Move(y int, x int) { diff --git a/src/eventbox.go b/src/eventbox.go index 95126cc..0c8f922 100644 --- a/src/eventbox.go +++ b/src/eventbox.go @@ -2,16 +2,17 @@ package fzf import "sync" -type EventType int - +// Events is a type that associates EventType to any data type Events map[EventType]interface{} +// EventBox is used for coordinating events type EventBox struct { events Events cond *sync.Cond ignore map[EventType]bool } +// NewEventBox returns a new EventBox func NewEventBox() *EventBox { return &EventBox{ events: make(Events), @@ -19,6 +20,7 @@ func NewEventBox() *EventBox { ignore: make(map[EventType]bool)} } +// Wait blocks the goroutine until signaled func (b *EventBox) Wait(callback func(*Events)) { b.cond.L.Lock() defer b.cond.L.Unlock() @@ -30,6 +32,7 @@ func (b *EventBox) Wait(callback func(*Events)) { callback(&b.events) } +// Set turns on the event type on the box func (b *EventBox) Set(event EventType, value interface{}) { b.cond.L.Lock() 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 func (events *Events) Clear() { 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 { b.cond.L.Lock() defer b.cond.L.Unlock() @@ -53,6 +58,7 @@ func (b *EventBox) Peak(event EventType) bool { return ok } +// Watch deletes the events from the ignore list func (b *EventBox) Watch(events ...EventType) { b.cond.L.Lock() 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) { b.cond.L.Lock() defer b.cond.L.Unlock() diff --git a/src/eventbox_test.go b/src/eventbox_test.go index fb0ceed..1cd7f22 100644 --- a/src/eventbox_test.go +++ b/src/eventbox_test.go @@ -9,16 +9,16 @@ func TestEventBox(t *testing.T) { ch := make(chan bool) go func() { - eb.Set(EVT_READ_NEW, 10) + eb.Set(EvtReadNew, 10) ch <- true <-ch - eb.Set(EVT_SEARCH_NEW, 10) - eb.Set(EVT_SEARCH_NEW, 15) - eb.Set(EVT_SEARCH_NEW, 20) - eb.Set(EVT_SEARCH_PROGRESS, 30) + eb.Set(EvtSearchNew, 10) + eb.Set(EvtSearchNew, 15) + eb.Set(EvtSearchNew, 20) + eb.Set(EvtSearchProgress, 30) ch <- true <-ch - eb.Set(EVT_SEARCH_FIN, 40) + eb.Set(EvtSearchFin, 40) ch <- true <-ch }() @@ -39,7 +39,7 @@ func TestEventBox(t *testing.T) { events.Clear() }) ch <- true - count += 1 + count++ } if count != 3 { diff --git a/src/item.go b/src/item.go index 41aa34b..4cbd3f9 100644 --- a/src/item.go +++ b/src/item.go @@ -1,9 +1,9 @@ package fzf -import "fmt" - +// Offset holds two 32-bit integers denoting the offsets of a matched substring type Offset [2]int32 +// Item represents each input line type Item struct { text *string origText *string @@ -13,12 +13,14 @@ type Item struct { rank Rank } +// Rank is used to sort the search result type Rank struct { matchlen uint16 strlen uint16 index uint32 } +// Rank calculates rank of the Item func (i *Item) Rank(cache bool) Rank { if cache && (i.rank.matchlen > 0 || i.rank.strlen > 0) { return i.rank @@ -45,14 +47,15 @@ func (i *Item) Rank(cache bool) Rank { return rank } -func (i *Item) Print() { +// AsString returns the original string +func (i *Item) AsString() string { if i.origText != nil { - fmt.Println(*i.origText) - } else { - fmt.Println(*i.text) + return *i.origText } + return *i.text } +// ByOrder is for sorting substring offsets type ByOrder []Offset 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]) } +// ByRelevance is for sorting Items type ByRelevance []*Item func (a ByRelevance) Len() int { diff --git a/src/matcher.go b/src/matcher.go index 713b4dd..b8be287 100644 --- a/src/matcher.go +++ b/src/matcher.go @@ -8,11 +8,13 @@ import ( "time" ) +// MatchRequest represents a search request type MatchRequest struct { chunks []*Chunk pattern *Pattern } +// Matcher is responsible for performing search type Matcher struct { patternBuilder func([]rune) *Pattern sort bool @@ -23,20 +25,15 @@ type Matcher struct { } const ( - REQ_RETRY EventType = iota - REQ_RESET + reqRetry EventType = iota + reqReset ) const ( - STAT_CANCELLED int = iota - STAT_QCH - STAT_CHUNKS -) - -const ( - PROGRESS_MIN_DURATION = 200 * time.Millisecond + progressMinDuration = 200 * time.Millisecond ) +// NewMatcher returns a new Matcher func NewMatcher(patternBuilder func([]rune) *Pattern, sort bool, eventBox *EventBox) *Matcher { return &Matcher{ @@ -48,6 +45,7 @@ func NewMatcher(patternBuilder func([]rune) *Pattern, mergerCache: make(map[string]*Merger)} } +// Loop puts Matcher in action func (m *Matcher) Loop() { prevCount := 0 @@ -91,7 +89,7 @@ func (m *Matcher) Loop() { if !cancelled { 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 matchCount := 0 for matchesInChunk := range countChan { - count += 1 + count++ matchCount += matchesInChunk if limit > 0 && matchCount > limit { @@ -183,12 +181,12 @@ func (m *Matcher) scan(request MatchRequest, limit int) (*Merger, bool) { break } - if !empty && m.reqBox.Peak(REQ_RESET) { + if !empty && m.reqBox.Peak(reqReset) { return nil, wait() } - if time.Now().Sub(startedAt) > PROGRESS_MIN_DURATION { - m.eventBox.Set(EVT_SEARCH_PROGRESS, float32(count)/float32(numChunks)) + if time.Now().Sub(startedAt) > progressMinDuration { + 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 } +// Reset is called to interrupt/signal the ongoing search func (m *Matcher) Reset(chunks []*Chunk, patternRunes []rune, cancel bool) { pattern := m.patternBuilder(patternRunes) var event EventType if cancel { - event = REQ_RESET + event = reqReset } else { - event = REQ_RETRY + event = reqRetry } m.reqBox.Set(event, MatchRequest{chunks, pattern}) } diff --git a/src/merger.go b/src/merger.go index 16afdaf..bd2158d 100644 --- a/src/merger.go +++ b/src/merger.go @@ -2,8 +2,11 @@ package fzf 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 { lists [][]*Item merged []*Item @@ -12,6 +15,7 @@ type Merger struct { count int } +// NewMerger returns a new Merger func NewMerger(lists [][]*Item, sorted bool) *Merger { mg := Merger{ lists: lists, @@ -26,10 +30,12 @@ func NewMerger(lists [][]*Item, sorted bool) *Merger { return &mg } +// Length returns the number of items func (mg *Merger) Length() int { return mg.count } +// Get returns the pointer to the Item object indexed by the given integer func (mg *Merger) Get(idx int) *Item { if len(mg.lists) == 1 { return mg.lists[0][idx] @@ -69,7 +75,7 @@ func (mg *Merger) mergedGet(idx int) *Item { if minIdx >= 0 { chosen := mg.lists[minIdx] mg.merged = append(mg.merged, chosen[mg.cursors[minIdx]]) - mg.cursors[minIdx] += 1 + mg.cursors[minIdx]++ } else { panic(fmt.Sprintf("Index out of bounds (sorted, %d/%d)", i, mg.count)) } diff --git a/src/options.go b/src/options.go index 4929dfd..cf0608b 100644 --- a/src/options.go +++ b/src/options.go @@ -8,7 +8,7 @@ import ( "strings" ) -const USAGE = `usage: fzf [options] +const usage = `usage: fzf [options] Search -x, --extended Extended-search mode @@ -47,22 +47,27 @@ const USAGE = `usage: fzf [options] ` +// Mode denotes the current search mode type Mode int +// Search modes const ( - MODE_FUZZY Mode = iota - MODE_EXTENDED - MODE_EXTENDED_EXACT + ModeFuzzy Mode = iota + ModeExtended + ModeExtendedExact ) +// Case denotes case-sensitivity of search type Case int +// Case-sensitivities const ( - CASE_SMART Case = iota - CASE_IGNORE - CASE_RESPECT + CaseSmart Case = iota + CaseIgnore + CaseRespect ) +// Options stores the values of command-line options type Options struct { Mode Mode Case Case @@ -85,10 +90,10 @@ type Options struct { Version bool } -func DefaultOptions() *Options { +func defaultOptions() *Options { return &Options{ - Mode: MODE_FUZZY, - Case: CASE_SMART, + Mode: ModeFuzzy, + Case: CaseSmart, Nth: make([]Range, 0), WithNth: make([]Range, 0), Delimiter: nil, @@ -109,7 +114,7 @@ func DefaultOptions() *Options { } func help(ok int) { - os.Stderr.WriteString(USAGE) + os.Stderr.WriteString(usage) os.Exit(ok) } @@ -123,9 +128,8 @@ func optString(arg string, prefix string) (bool, string) { matches := rx.FindStringSubmatch(arg) if len(matches) > 1 { return true, matches[1] - } else { - return false, "" } + return false, "" } func nextString(args []string, i *int, message string) string { @@ -183,11 +187,11 @@ func parseOptions(opts *Options, allArgs []string) { case "-h", "--help": help(0) case "-x", "--extended": - opts.Mode = MODE_EXTENDED + opts.Mode = ModeExtended case "-e", "--extended-exact": - opts.Mode = MODE_EXTENDED_EXACT + opts.Mode = ModeExtendedExact case "+x", "--no-extended", "+e", "--no-extended-exact": - opts.Mode = MODE_FUZZY + opts.Mode = ModeFuzzy case "-q", "--query": opts.Query = nextString(allArgs, &i, "query string required") case "-f", "--filter": @@ -204,9 +208,9 @@ func parseOptions(opts *Options, allArgs []string) { case "+s", "--no-sort": opts.Sort = 0 case "-i": - opts.Case = CASE_IGNORE + opts.Case = CaseIgnore case "+i": - opts.Case = CASE_RESPECT + opts.Case = CaseRespect case "-m", "--multi": opts.Multi = true case "+m", "--no-multi": @@ -263,8 +267,9 @@ func parseOptions(opts *Options, allArgs []string) { } } +// ParseOptions parses command-line options func ParseOptions() *Options { - opts := DefaultOptions() + opts := defaultOptions() // Options from Env var words, _ := shellwords.Parse(os.Getenv("FZF_DEFAULT_OPTS")) diff --git a/src/options_test.go b/src/options_test.go index f0aa3a0..e10ec56 100644 --- a/src/options_test.go +++ b/src/options_test.go @@ -15,20 +15,20 @@ func TestSplitNth(t *testing.T) { { ranges := splitNth("..") if len(ranges) != 1 || - ranges[0].begin != RANGE_ELLIPSIS || - ranges[0].end != RANGE_ELLIPSIS { + ranges[0].begin != rangeEllipsis || + ranges[0].end != rangeEllipsis { t.Errorf("%s", ranges) } } { ranges := splitNth("..3,1..,2..3,4..-1,-3..-2,..,2,-2") if len(ranges) != 8 || - ranges[0].begin != RANGE_ELLIPSIS || ranges[0].end != 3 || - ranges[1].begin != 1 || ranges[1].end != RANGE_ELLIPSIS || + ranges[0].begin != rangeEllipsis || ranges[0].end != 3 || + ranges[1].begin != 1 || ranges[1].end != rangeEllipsis || ranges[2].begin != 2 || ranges[2].end != 3 || ranges[3].begin != 4 || ranges[3].end != -1 || 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[7].begin != -2 || ranges[7].end != -2 { t.Errorf("%s", ranges) diff --git a/src/pattern.go b/src/pattern.go index 2e7d6f9..9f32de6 100644 --- a/src/pattern.go +++ b/src/pattern.go @@ -6,7 +6,7 @@ import ( "strings" ) -const UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +const uppercaseLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" // fuzzy // 'exact @@ -17,31 +17,32 @@ const UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" // !^not-exact-prefix // !not-exact-suffix$ -type TermType int +type termType int const ( - TERM_FUZZY TermType = iota - TERM_EXACT - TERM_PREFIX - TERM_SUFFIX + termFuzzy termType = iota + termExact + termPrefix + termSuffix ) -type Term struct { - typ TermType +type term struct { + typ termType inv bool text []rune origText []rune } +// Pattern represents search pattern type Pattern struct { mode Mode caseSensitive bool text []rune - terms []Term + terms []term hasInvTerm bool delimiter *regexp.Regexp nth []Range - procFun map[TermType]func(bool, *string, []rune) (int, int) + procFun map[termType]func(bool, *string, []rune) (int, int) } var ( @@ -62,12 +63,13 @@ func clearPatternCache() { _patternCache = make(map[string]*Pattern) } +// BuildPattern builds Pattern object from the given arguments func BuildPattern(mode Mode, caseMode Case, nth []Range, delimiter *regexp.Regexp, runes []rune) *Pattern { var asString string switch mode { - case MODE_EXTENDED, MODE_EXTENDED_EXACT: + case ModeExtended, ModeExtendedExact: asString = strings.Trim(string(runes), " ") default: asString = string(runes) @@ -79,19 +81,19 @@ func BuildPattern(mode Mode, caseMode Case, } caseSensitive, hasInvTerm := true, false - terms := []Term{} + terms := []term{} switch caseMode { - case CASE_SMART: - if !strings.ContainsAny(asString, UPPERCASE) { + case CaseSmart: + if !strings.ContainsAny(asString, uppercaseLetters) { runes, caseSensitive = []rune(strings.ToLower(asString)), false } - case CASE_IGNORE: + case CaseIgnore: runes, caseSensitive = []rune(strings.ToLower(asString)), false } switch mode { - case MODE_EXTENDED, MODE_EXTENDED_EXACT: + case ModeExtended, ModeExtendedExact: terms = parseTerms(mode, string(runes)) for _, term := range terms { if term.inv { @@ -108,25 +110,25 @@ func BuildPattern(mode Mode, caseMode Case, hasInvTerm: hasInvTerm, nth: nth, 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[TERM_EXACT] = ExactMatchNaive - ptr.procFun[TERM_PREFIX] = PrefixMatch - ptr.procFun[TERM_SUFFIX] = SuffixMatch + ptr.procFun[termFuzzy] = FuzzyMatch + ptr.procFun[termExact] = ExactMatchNaive + ptr.procFun[termPrefix] = PrefixMatch + ptr.procFun[termSuffix] = SuffixMatch _patternCache[asString] = ptr return ptr } -func parseTerms(mode Mode, str string) []Term { +func parseTerms(mode Mode, str string) []term { tokens := _splitRegex.Split(str, -1) - terms := []Term{} + terms := []term{} for _, token := range tokens { - typ, inv, text := TERM_FUZZY, false, token + typ, inv, text := termFuzzy, false, token origText := []rune(text) - if mode == MODE_EXTENDED_EXACT { - typ = TERM_EXACT + if mode == ModeExtendedExact { + typ = termExact } if strings.HasPrefix(text, "!") { @@ -135,20 +137,20 @@ func parseTerms(mode Mode, str string) []Term { } if strings.HasPrefix(text, "'") { - if mode == MODE_EXTENDED { - typ = TERM_EXACT + if mode == ModeExtended { + typ = termExact text = text[1:] } } else if strings.HasPrefix(text, "^") { - typ = TERM_PREFIX + typ = termPrefix text = text[1:] } else if strings.HasSuffix(text, "$") { - typ = TERM_SUFFIX + typ = termSuffix text = text[:len(text)-1] } if len(text) > 0 { - terms = append(terms, Term{ + terms = append(terms, term{ typ: typ, inv: inv, text: []rune(text), @@ -158,20 +160,22 @@ func parseTerms(mode Mode, str string) []Term { return terms } +// IsEmpty returns true if the pattern is effectively empty func (p *Pattern) IsEmpty() bool { - if p.mode == MODE_FUZZY { + if p.mode == ModeFuzzy { 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 { return string(p.text) } +// CacheKey is used to build string to be used as the key of result cache func (p *Pattern) CacheKey() string { - if p.mode == MODE_FUZZY { + if p.mode == ModeFuzzy { return p.AsString() } cacheableTerms := []string{} @@ -184,6 +188,7 @@ func (p *Pattern) CacheKey() string { return strings.Join(cacheableTerms, " ") } +// Match returns the list of matches Items in the given Chunk func (p *Pattern) Match(chunk *Chunk) []*Item { space := chunk @@ -213,7 +218,7 @@ Loop: } var matches []*Item - if p.mode == MODE_FUZZY { + if p.mode == ModeFuzzy { matches = p.fuzzyMatch(space) } else { matches = p.extendedMatch(space) diff --git a/src/pattern_test.go b/src/pattern_test.go index 2635b6c..c006c45 100644 --- a/src/pattern_test.go +++ b/src/pattern_test.go @@ -3,17 +3,17 @@ package fzf import "testing" func TestParseTermsExtended(t *testing.T) { - terms := parseTerms(MODE_EXTENDED, + terms := parseTerms(ModeExtended, "aaa 'bbb ^ccc ddd$ !eee !'fff !^ggg !hhh$") if len(terms) != 8 || - terms[0].typ != TERM_FUZZY || terms[0].inv || - terms[1].typ != TERM_EXACT || terms[1].inv || - terms[2].typ != TERM_PREFIX || terms[2].inv || - terms[3].typ != TERM_SUFFIX || terms[3].inv || - terms[4].typ != TERM_FUZZY || !terms[4].inv || - terms[5].typ != TERM_EXACT || !terms[5].inv || - terms[6].typ != TERM_PREFIX || !terms[6].inv || - terms[7].typ != TERM_SUFFIX || !terms[7].inv { + terms[0].typ != termFuzzy || terms[0].inv || + terms[1].typ != termExact || terms[1].inv || + terms[2].typ != termPrefix || terms[2].inv || + terms[3].typ != termSuffix || terms[3].inv || + terms[4].typ != termFuzzy || !terms[4].inv || + terms[5].typ != termExact || !terms[5].inv || + terms[6].typ != termPrefix || !terms[6].inv || + terms[7].typ != termSuffix || !terms[7].inv { t.Errorf("%s", terms) } for idx, term := range terms { @@ -27,23 +27,23 @@ func TestParseTermsExtended(t *testing.T) { } func TestParseTermsExtendedExact(t *testing.T) { - terms := parseTerms(MODE_EXTENDED_EXACT, + terms := parseTerms(ModeExtendedExact, "aaa 'bbb ^ccc ddd$ !eee !'fff !^ggg !hhh$") if len(terms) != 8 || - terms[0].typ != TERM_EXACT || terms[0].inv || len(terms[0].text) != 3 || - terms[1].typ != TERM_EXACT || terms[1].inv || len(terms[1].text) != 4 || - terms[2].typ != TERM_PREFIX || terms[2].inv || len(terms[2].text) != 3 || - terms[3].typ != TERM_SUFFIX || terms[3].inv || len(terms[3].text) != 3 || - terms[4].typ != TERM_EXACT || !terms[4].inv || len(terms[4].text) != 3 || - terms[5].typ != TERM_EXACT || !terms[5].inv || len(terms[5].text) != 4 || - terms[6].typ != TERM_PREFIX || !terms[6].inv || len(terms[6].text) != 3 || - terms[7].typ != TERM_SUFFIX || !terms[7].inv || len(terms[7].text) != 3 { + terms[0].typ != termExact || terms[0].inv || len(terms[0].text) != 3 || + terms[1].typ != termExact || terms[1].inv || len(terms[1].text) != 4 || + terms[2].typ != termPrefix || terms[2].inv || len(terms[2].text) != 3 || + terms[3].typ != termSuffix || terms[3].inv || len(terms[3].text) != 3 || + terms[4].typ != termExact || !terms[4].inv || len(terms[4].text) != 3 || + terms[5].typ != termExact || !terms[5].inv || len(terms[5].text) != 4 || + terms[6].typ != termPrefix || !terms[6].inv || len(terms[6].text) != 3 || + terms[7].typ != termSuffix || !terms[7].inv || len(terms[7].text) != 3 { t.Errorf("%s", terms) } } func TestParseTermsEmpty(t *testing.T) { - terms := parseTerms(MODE_EXTENDED, "' $ ^ !' !^ !$") + terms := parseTerms(ModeExtended, "' $ ^ !' !^ !$") if len(terms) != 0 { t.Errorf("%s", terms) } @@ -52,7 +52,7 @@ func TestParseTermsEmpty(t *testing.T) { func TestExact(t *testing.T) { defer clearPatternCache() clearPatternCache() - pattern := BuildPattern(MODE_EXTENDED, CASE_SMART, + pattern := BuildPattern(ModeExtended, CaseSmart, []Range{}, nil, []rune("'abc")) str := "aabbcc abc" sidx, eidx := ExactMatchNaive(pattern.caseSensitive, &str, pattern.terms[0].text) @@ -64,17 +64,17 @@ func TestExact(t *testing.T) { func TestCaseSensitivity(t *testing.T) { defer clearPatternCache() clearPatternCache() - pat1 := BuildPattern(MODE_FUZZY, CASE_SMART, []Range{}, nil, []rune("abc")) + pat1 := BuildPattern(ModeFuzzy, CaseSmart, []Range{}, nil, []rune("abc")) clearPatternCache() - pat2 := BuildPattern(MODE_FUZZY, CASE_SMART, []Range{}, nil, []rune("Abc")) + pat2 := BuildPattern(ModeFuzzy, CaseSmart, []Range{}, nil, []rune("Abc")) clearPatternCache() - pat3 := BuildPattern(MODE_FUZZY, CASE_IGNORE, []Range{}, nil, []rune("abc")) + pat3 := BuildPattern(ModeFuzzy, CaseIgnore, []Range{}, nil, []rune("abc")) clearPatternCache() - pat4 := BuildPattern(MODE_FUZZY, CASE_IGNORE, []Range{}, nil, []rune("Abc")) + pat4 := BuildPattern(ModeFuzzy, CaseIgnore, []Range{}, nil, []rune("Abc")) clearPatternCache() - pat5 := BuildPattern(MODE_FUZZY, CASE_RESPECT, []Range{}, nil, []rune("abc")) + pat5 := BuildPattern(ModeFuzzy, CaseRespect, []Range{}, nil, []rune("abc")) 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 || string(pat2.text) != "Abc" || pat2.caseSensitive != true || @@ -90,7 +90,7 @@ func TestOrigTextAndTransformed(t *testing.T) { strptr := func(str string) *string { 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) trans := Transform(tokens, []Range{Range{1, 1}}) diff --git a/src/reader.go b/src/reader.go index 39fa70c..269a2fd 100644 --- a/src/reader.go +++ b/src/reader.go @@ -10,31 +10,33 @@ import ( "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 { pusher func(string) eventBox *EventBox } +// ReadSource reads data from the default command or from standard input func (r *Reader) ReadSource() { if int(C.isatty(C.int(os.Stdin.Fd()))) != 0 { cmd := os.Getenv("FZF_DEFAULT_COMMAND") if len(cmd) == 0 { - cmd = DEFAULT_COMMAND + cmd = defaultCommand } r.readFromCommand(cmd) } else { r.readFromStdin() } - r.eventBox.Set(EVT_READ_FIN, nil) + r.eventBox.Set(EvtReadFin, nil) } func (r *Reader) feed(src io.Reader) { if scanner := bufio.NewScanner(src); scanner != nil { for scanner.Scan() { r.pusher(scanner.Text()) - r.eventBox.Set(EVT_READ_NEW, nil) + r.eventBox.Set(EvtReadNew, nil) } } } diff --git a/src/reader_test.go b/src/reader_test.go index f51ccab..630f6fa 100644 --- a/src/reader_test.go +++ b/src/reader_test.go @@ -10,8 +10,8 @@ func TestReadFromCommand(t *testing.T) { eventBox: eb} // Check EventBox - if eb.Peak(EVT_READ_NEW) { - t.Error("EVT_READ_NEW should not be set yet") + if eb.Peak(EvtReadNew) { + t.Error("EvtReadNew should not be set yet") } // Normal command @@ -21,21 +21,21 @@ func TestReadFromCommand(t *testing.T) { } // Check EventBox again - if !eb.Peak(EVT_READ_NEW) { - t.Error("EVT_READ_NEW should be set yet") + if !eb.Peak(EvtReadNew) { + t.Error("EvtReadNew should be set yet") } // Wait should return immediately eb.Wait(func(events *Events) { - if _, found := (*events)[EVT_READ_NEW]; !found { + if _, found := (*events)[EvtReadNew]; !found { t.Errorf("%s", events) } events.Clear() }) // EventBox is cleared - if eb.Peak(EVT_READ_NEW) { - t.Error("EVT_READ_NEW should not be set yet") + if eb.Peak(EvtReadNew) { + t.Error("EvtReadNew should not be set yet") } // Failing command @@ -46,7 +46,7 @@ func TestReadFromCommand(t *testing.T) { } // Check EventBox again - if eb.Peak(EVT_READ_NEW) { - t.Error("Command failed. EVT_READ_NEW should be set") + if eb.Peak(EvtReadNew) { + t.Error("Command failed. EvtReadNew should be set") } } diff --git a/src/terminal.go b/src/terminal.go index 7d8bc5b..daf63c5 100644 --- a/src/terminal.go +++ b/src/terminal.go @@ -11,6 +11,7 @@ import ( "time" ) +// Terminal represents terminal input/output type Terminal struct { prompt string reverse bool @@ -34,23 +35,24 @@ type Terminal struct { suppress bool } -var _spinner []string = []string{`-`, `\`, `|`, `/`, `-`, `\`, `|`, `/`} +var _spinner = []string{`-`, `\`, `|`, `/`, `-`, `\`, `|`, `/`} const ( - REQ_PROMPT EventType = iota - REQ_INFO - REQ_LIST - REQ_REFRESH - REQ_REDRAW - REQ_CLOSE - REQ_QUIT + reqPrompt EventType = iota + reqInfo + reqList + reqRefresh + reqRedraw + reqClose + reqQuit ) const ( - INITIAL_DELAY = 100 * time.Millisecond - SPINNER_DURATION = 200 * time.Millisecond + initialDelay = 100 * time.Millisecond + spinnerDuration = 200 * time.Millisecond ) +// NewTerminal returns new Terminal object func NewTerminal(opts *Options, eventBox *EventBox) *Terminal { input := []rune(opts.Query) return &Terminal{ @@ -75,23 +77,26 @@ func NewTerminal(opts *Options, eventBox *EventBox) *Terminal { }} } +// Input returns current query string func (t *Terminal) Input() []rune { t.mutex.Lock() defer t.mutex.Unlock() return copySlice(t.input) } +// UpdateCount updates the count information func (t *Terminal) UpdateCount(cnt int, final bool) { t.mutex.Lock() t.count = cnt t.reading = !final t.mutex.Unlock() - t.reqBox.Set(REQ_INFO, nil) + t.reqBox.Set(reqInfo, nil) if final { - t.reqBox.Set(REQ_REFRESH, nil) + t.reqBox.Set(reqRefresh, nil) } } +// UpdateProgress updates the search progress func (t *Terminal) UpdateProgress(progress float32) { t.mutex.Lock() newProgress := int(progress * 100) @@ -100,25 +105,25 @@ func (t *Terminal) UpdateProgress(progress float32) { t.mutex.Unlock() 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) { t.mutex.Lock() t.progress = 100 t.merger = merger t.mutex.Unlock() - t.reqBox.Set(REQ_INFO, nil) - t.reqBox.Set(REQ_LIST, nil) + t.reqBox.Set(reqInfo, nil) + t.reqBox.Set(reqList, nil) } func (t *Terminal) listIndex(y int) int { if t.tac { return t.merger.Length() - y - 1 - } else { - return y } + return y } func (t *Terminal) output() { @@ -127,7 +132,7 @@ func (t *Terminal) output() { } if len(t.selected) == 0 { 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 { for ptr, orig := range t.selected { @@ -167,16 +172,16 @@ func (t *Terminal) placeCursor() { func (t *Terminal) printPrompt() { t.move(0, 0, true) - C.CPrint(C.COL_PROMPT, true, t.prompt) - C.CPrint(C.COL_NORMAL, true, string(t.input)) + C.CPrint(C.ColPrompt, true, t.prompt) + C.CPrint(C.ColNormal, true, string(t.input)) } func (t *Terminal) printInfo() { t.move(1, 0, true) if t.reading { - duration := int64(SPINNER_DURATION) + duration := int64(spinnerDuration) 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) @@ -187,7 +192,7 @@ func (t *Terminal) printInfo() { if t.progress > 0 && t.progress < 100 { output += fmt.Sprintf(" (%d%%)", t.progress) } - C.CPrint(C.COL_INFO, false, output) + C.CPrint(C.ColInfo, false, output) } func (t *Terminal) printList() { @@ -206,21 +211,21 @@ func (t *Terminal) printList() { func (t *Terminal) printItem(item *Item, current bool) { _, selected := t.selected[item.text] if current { - C.CPrint(C.COL_CURSOR, true, ">") + C.CPrint(C.ColCursor, true, ">") if selected { - C.CPrint(C.COL_CURRENT, true, ">") + C.CPrint(C.ColCurrent, true, ">") } 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 { - C.CPrint(C.COL_CURSOR, true, " ") + C.CPrint(C.ColCursor, true, " ") if selected { - C.CPrint(C.COL_SELECTED, true, ">") + C.CPrint(C.ColSelected, true, ">") } else { 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) currentWidth -= runewidth.RuneWidth(runes[sz-1]) runes = runes[:sz-1] - trimmed += 1 + trimmed++ } return runes, trimmed } func trimLeft(runes []rune, width int) ([]rune, int32) { currentWidth := displayWidth(runes) - var trimmed int32 = 0 + var trimmed int32 for currentWidth > width && len(runes) > 0 { currentWidth -= runewidth.RuneWidth(runes[0]) runes = runes[1:] - trimmed += 1 + trimmed++ } return runes, trimmed } func (*Terminal) printHighlighted(item *Item, bold bool, col1 int, col2 int) { - var maxe int32 = 0 + var maxe int32 for _, offset := range item.offsets { if offset[1] > maxe { maxe = offset[1] @@ -293,7 +298,7 @@ func (*Terminal) printHighlighted(item *Item, bold bool, col1 int, col2 int) { } sort.Sort(ByOrder(offsets)) - var index int32 = 0 + var index int32 for _, offset := range offsets { b := Max32(index, offset[0]) e := Max32(index, offset[1]) @@ -364,6 +369,7 @@ func (t *Terminal) rubout(pattern string) { t.input = append(t.input[:t.cx], after...) } +// Loop is called to start Terminal I/O func (t *Terminal) Loop() { { // Late initialization t.mutex.Lock() @@ -374,9 +380,9 @@ func (t *Terminal) Loop() { t.printInfo() t.mutex.Unlock() go func() { - timer := time.NewTimer(INITIAL_DELAY) + timer := time.NewTimer(initialDelay) <-timer.C - t.reqBox.Set(REQ_REFRESH, nil) + t.reqBox.Set(reqRefresh, nil) }() } @@ -387,22 +393,22 @@ func (t *Terminal) Loop() { t.mutex.Lock() for req := range *events { switch req { - case REQ_PROMPT: + case reqPrompt: t.printPrompt() - case REQ_INFO: + case reqInfo: t.printInfo() - case REQ_LIST: + case reqList: t.printList() - case REQ_REFRESH: + case reqRefresh: t.suppress = false - case REQ_REDRAW: + case reqRedraw: C.Clear() t.printAll() - case REQ_CLOSE: + case reqClose: C.Close() t.output() os.Exit(0) - case REQ_QUIT: + case reqQuit: C.Close() os.Exit(1) } @@ -420,11 +426,11 @@ func (t *Terminal) Loop() { t.mutex.Lock() previousInput := t.input - events := []EventType{REQ_PROMPT} + events := []EventType{reqPrompt} req := func(evts ...EventType) { for _, event := range evts { events = append(events, event) - if event == REQ_CLOSE || event == REQ_QUIT { + if event == reqClose || event == reqQuit { looping = false } } @@ -438,99 +444,99 @@ func (t *Terminal) Loop() { } else { delete(t.selected, item.text) } - req(REQ_INFO) + req(reqInfo) } } switch event.Type { - case C.INVALID: + case C.Invalid: t.mutex.Unlock() continue - case C.CTRL_A: + case C.CtrlA: t.cx = 0 - case C.CTRL_B: + case C.CtrlB: if t.cx > 0 { - t.cx -= 1 + t.cx-- } - case C.CTRL_C, C.CTRL_G, C.CTRL_Q, C.ESC: - req(REQ_QUIT) - case C.CTRL_D: + case C.CtrlC, C.CtrlG, C.CtrlQ, C.ESC: + req(reqQuit) + case C.CtrlD: if !t.delChar() && t.cx == 0 { - req(REQ_QUIT) + req(reqQuit) } - case C.CTRL_E: + case C.CtrlE: t.cx = len(t.input) - case C.CTRL_F: + case C.CtrlF: if t.cx < len(t.input) { - t.cx += 1 + t.cx++ } - case C.CTRL_H: + case C.CtrlH: if t.cx > 0 { 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 { toggle() t.vmove(-1) - req(REQ_LIST) + req(reqList) } - case C.BTAB: + case C.BTab: if t.multi && t.merger.Length() > 0 { toggle() t.vmove(1) - req(REQ_LIST) + req(reqList) } - case C.CTRL_J, C.CTRL_N: + case C.CtrlJ, C.CtrlN: t.vmove(-1) - req(REQ_LIST) - case C.CTRL_K, C.CTRL_P: + req(reqList) + case C.CtrlK, C.CtrlP: t.vmove(1) - req(REQ_LIST) - case C.CTRL_M: - req(REQ_CLOSE) - case C.CTRL_L: - req(REQ_REDRAW) - case C.CTRL_U: + req(reqList) + case C.CtrlM: + req(reqClose) + case C.CtrlL: + req(reqRedraw) + case C.CtrlU: if t.cx > 0 { t.yanked = copySlice(t.input[:t.cx]) t.input = t.input[t.cx:] t.cx = 0 } - case C.CTRL_W: + case C.CtrlW: if t.cx > 0 { t.rubout("\\s\\S") } - case C.ALT_BS: + case C.AltBS: if t.cx > 0 { 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.cx += len(t.yanked) - case C.DEL: + case C.Del: t.delChar() - case C.PGUP: + case C.PgUp: t.vmove(maxItems() - 1) - req(REQ_LIST) - case C.PGDN: + req(reqList) + case C.PgDn: t.vmove(-(maxItems() - 1)) - req(REQ_LIST) - case C.ALT_B: + req(reqList) + case C.AltB: 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 - case C.ALT_D: + case C.AltD: ncx := t.cx + findFirstMatch("[[:alnum:]][^[:alnum:]]|(.$)", string(t.input[t.cx:])) + 1 if ncx > t.cx { t.yanked = copySlice(t.input[t.cx:ncx]) t.input = append(t.input[:t.cx], t.input[ncx:]...) } - case C.RUNE: + case C.Rune: prefix := copySlice(t.input[:t.cx]) t.input = append(append(prefix, event.Char), t.input[t.cx:]...) - t.cx += 1 - case C.MOUSE: + t.cx++ + case C.Mouse: me := event.MouseEvent mx, my := Min(len(t.input), Max(0, me.X-len(t.prompt))), me.Y if !t.reverse { @@ -543,13 +549,13 @@ func (t *Terminal) Loop() { toggle() } t.vmove(me.S) - req(REQ_LIST) + req(reqList) } } else if me.Double { // Double-click if my >= 2 { if t.vset(my-2) && t.listIndex(t.cy) < t.merger.Length() { - req(REQ_CLOSE) + req(reqClose) } } } else if me.Down { @@ -561,7 +567,7 @@ func (t *Terminal) Loop() { if t.vset(t.offset+my-2) && t.multi && me.Mod { toggle() } - req(REQ_LIST) + req(reqList) } } } @@ -569,7 +575,7 @@ func (t *Terminal) Loop() { t.mutex.Unlock() // Must be unlocked before touching reqBox if changed { - t.eventBox.Set(EVT_SEARCH_NEW, nil) + t.eventBox.Set(EvtSearchNew, nil) } for _, event := range events { t.reqBox.Set(event, nil) diff --git a/src/tokenizer.go b/src/tokenizer.go index d62f395..294329b 100644 --- a/src/tokenizer.go +++ b/src/tokenizer.go @@ -6,40 +6,42 @@ import ( "strings" ) -const RANGE_ELLIPSIS = 0 +const rangeEllipsis = 0 +// Range represents nth-expression type Range struct { begin int end int } +// Transformed holds the result of tokenization and transformation type Transformed struct { whole *string parts []Token } +// Token contains the tokenized part of the strings and its prefix length type Token struct { text *string prefixLength int } +// ParseRange parses nth-expression and returns the corresponding Range object func ParseRange(str *string) (Range, bool) { if (*str) == ".." { - return Range{RANGE_ELLIPSIS, RANGE_ELLIPSIS}, true + return Range{rangeEllipsis, rangeEllipsis}, true } else if strings.HasPrefix(*str, "..") { end, err := strconv.Atoi((*str)[2:]) if err != nil || end == 0 { return Range{}, false - } else { - return Range{RANGE_ELLIPSIS, end}, true } + return Range{rangeEllipsis, end}, true } else if strings.HasSuffix(*str, "..") { begin, err := strconv.Atoi((*str)[:len(*str)-2]) if err != nil || begin == 0 { return Range{}, false - } else { - return Range{begin, RANGE_ELLIPSIS}, true } + return Range{begin, rangeEllipsis}, true } else if strings.Contains(*str, "..") { ns := strings.Split(*str, "..") if len(ns) != 2 { @@ -75,9 +77,9 @@ func withPrefixLengths(tokens []string, begin int) []Token { } const ( - AWK_NIL = iota - AWK_BLACK - AWK_WHITE + awkNil = iota + awkBlack + awkWhite ) func awkTokenizer(input *string) ([]string, int) { @@ -85,28 +87,28 @@ func awkTokenizer(input *string) ([]string, int) { ret := []string{} str := []rune{} prefixLength := 0 - state := AWK_NIL + state := awkNil for _, r := range []rune(*input) { white := r == 9 || r == 32 switch state { - case AWK_NIL: + case awkNil: if white { prefixLength++ } else { - state = AWK_BLACK + state = awkBlack str = append(str, r) } - case AWK_BLACK: + case awkBlack: str = append(str, r) if white { - state = AWK_WHITE + state = awkWhite } - case AWK_WHITE: + case awkWhite: if white { str = append(str, r) } else { ret = append(ret, string(str)) - state = AWK_BLACK + state = awkBlack str = []rune{r} } } @@ -117,15 +119,15 @@ func awkTokenizer(input *string) ([]string, int) { return ret, prefixLength } +// Tokenize tokenizes the given string with the delimiter func Tokenize(str *string, delimiter *regexp.Regexp) []Token { if delimiter == nil { // AWK-style (\S+\s*) tokens, prefixLength := awkTokenizer(str) return withPrefixLengths(tokens, prefixLength) - } else { - tokens := delimiter.FindAllString(*str, -1) - return withPrefixLengths(tokens, 0) } + tokens := delimiter.FindAllString(*str, -1) + return withPrefixLengths(tokens, 0) } func joinTokens(tokens []Token) string { @@ -136,6 +138,7 @@ func joinTokens(tokens []Token) string { return ret } +// Transform is used to transform the input when --with-nth option is given func Transform(tokens []Token, withNth []Range) *Transformed { transTokens := make([]Token, len(withNth)) numTokens := len(tokens) @@ -145,7 +148,7 @@ func Transform(tokens []Token, withNth []Range) *Transformed { minIdx := 0 if r.begin == r.end { idx := r.begin - if idx == RANGE_ELLIPSIS { + if idx == rangeEllipsis { part += joinTokens(tokens) } else { if idx < 0 { @@ -158,12 +161,12 @@ func Transform(tokens []Token, withNth []Range) *Transformed { } } else { var begin, end int - if r.begin == RANGE_ELLIPSIS { // ..N + if r.begin == rangeEllipsis { // ..N begin, end = 1, r.end if end < 0 { end += numTokens + 1 } - } else if r.end == RANGE_ELLIPSIS { // N.. + } else if r.end == rangeEllipsis { // N.. begin, end = r.begin, numTokens if begin < 0 { begin += numTokens + 1 diff --git a/src/tokenizer_test.go b/src/tokenizer_test.go index 1ae0c7e..5195a1b 100644 --- a/src/tokenizer_test.go +++ b/src/tokenizer_test.go @@ -6,14 +6,14 @@ func TestParseRange(t *testing.T) { { i := ".." r, _ := ParseRange(&i) - if r.begin != RANGE_ELLIPSIS || r.end != RANGE_ELLIPSIS { + if r.begin != rangeEllipsis || r.end != rangeEllipsis { t.Errorf("%s", r) } } { i := "3.." r, _ := ParseRange(&i) - if r.begin != 3 || r.end != RANGE_ELLIPSIS { + if r.begin != 3 || r.end != rangeEllipsis { t.Errorf("%s", r) } } diff --git a/src/util.go b/src/util.go index de6f365..5461705 100644 --- a/src/util.go +++ b/src/util.go @@ -2,6 +2,7 @@ package fzf import "time" +// Max returns the largest integer func Max(first int, items ...int) int { max := first for _, item := range items { @@ -12,6 +13,7 @@ func Max(first int, items ...int) int { return max } +// Max32 returns the largest 32-bit integer func Max32(first int32, second int32) int32 { if first > second { return first @@ -19,6 +21,7 @@ func Max32(first int32, second int32) int32 { return second } +// Min returns the smallest integer func Min(first int, items ...int) int { min := first for _, item := range items { @@ -29,6 +32,7 @@ func Min(first int, items ...int) int { return min } +// DurWithin limits the given time.Duration with the upper and lower bounds func DurWithin( val time.Duration, min time.Duration, max time.Duration) time.Duration { if val < min {