Reorganize source code

This commit is contained in:
Junegunn Choi 2015-01-12 12:56:17 +09:00
parent 7a2bc2cada
commit cd847affb7
21 changed files with 139 additions and 91 deletions

View File

@ -33,7 +33,7 @@ build: fzf/$(BINARY32) fzf/$(BINARY64)
test: test:
go get go get
go test -v go test -v ./...
install: $(BINDIR)/fzf install: $(BINDIR)/fzf

View File

@ -1,4 +1,4 @@
package fzf package algo
import "strings" import "strings"

View File

@ -1,4 +1,4 @@
package fzf package algo
import ( import (
"strings" "strings"

View File

@ -8,20 +8,20 @@ const ChunkSize int = 100
// Chunk is a list of Item pointers whose size has the upper limit of ChunkSize // 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 // ItemBuilder is a closure type that builds Item object from a pointer to a
// string and an integer // string and an integer
type Transformer func(*string, int) *Item type ItemBuilder func(*string, int) *Item
// ChunkList is a list of Chunks // ChunkList is a list of Chunks
type ChunkList struct { type ChunkList struct {
chunks []*Chunk chunks []*Chunk
count int count int
mutex sync.Mutex mutex sync.Mutex
trans Transformer trans ItemBuilder
} }
// NewChunkList returns a new ChunkList // NewChunkList returns a new ChunkList
func NewChunkList(trans Transformer) *ChunkList { func NewChunkList(trans ItemBuilder) *ChunkList {
return &ChunkList{ return &ChunkList{
chunks: []*Chunk{}, chunks: []*Chunk{},
count: 0, count: 0,
@ -29,7 +29,7 @@ func NewChunkList(trans Transformer) *ChunkList {
trans: trans} trans: trans}
} }
func (c *Chunk) push(trans Transformer, data *string, index int) { func (c *Chunk) push(trans ItemBuilder, data *string, index int) {
*c = append(*c, trans(data, index)) *c = append(*c, trans(data, index))
} }

View File

@ -1,14 +1,15 @@
package fzf package fzf
import (
"github.com/junegunn/fzf/src/util"
)
// Current version // Current version
const Version = "0.9.0" const Version = "0.9.0"
// EventType is the type for fzf events
type EventType int
// fzf events // fzf events
const ( const (
EvtReadNew EventType = iota EvtReadNew util.EventType = iota
EvtReadFin EvtReadFin
EvtSearchNew EvtSearchNew
EvtSearchProgress EvtSearchProgress

View File

@ -30,6 +30,8 @@ import (
"os" "os"
"runtime" "runtime"
"time" "time"
"github.com/junegunn/fzf/src/util"
) )
const coordinatorDelayMax time.Duration = 100 * time.Millisecond const coordinatorDelayMax time.Duration = 100 * time.Millisecond
@ -59,7 +61,7 @@ func Run(options *Options) {
} }
// Event channel // Event channel
eventBox := NewEventBox() eventBox := util.NewEventBox()
// Chunk list // Chunk list
var chunkList *ChunkList var chunkList *ChunkList
@ -111,7 +113,7 @@ func Run(options *Options) {
looping := true looping := true
eventBox.Unwatch(EvtReadNew) eventBox.Unwatch(EvtReadNew)
for looping { for looping {
eventBox.Wait(func(events *Events) { eventBox.Wait(func(events *util.Events) {
for evt := range *events { for evt := range *events {
switch evt { switch evt {
case EvtReadFin: case EvtReadFin:
@ -154,7 +156,7 @@ func Run(options *Options) {
for { for {
delay := true delay := true
ticks++ ticks++
eventBox.Wait(func(events *Events) { eventBox.Wait(func(events *util.Events) {
defer events.Clear() defer events.Clear()
for evt, value := range *events { for evt, value := range *events {
switch evt { switch evt {
@ -185,7 +187,7 @@ func Run(options *Options) {
} }
}) })
if delay && reading { if delay && reading {
dur := DurWithin( dur := util.DurWithin(
time.Duration(ticks)*coordinatorDelayStep, time.Duration(ticks)*coordinatorDelayStep,
0, coordinatorDelayMax) 0, coordinatorDelayMax)
time.Sleep(dur) time.Sleep(dur)

View File

@ -6,6 +6,8 @@ import (
"sort" "sort"
"sync" "sync"
"time" "time"
"github.com/junegunn/fzf/src/util"
) )
// MatchRequest represents a search request // MatchRequest represents a search request
@ -18,14 +20,14 @@ type MatchRequest struct {
type Matcher struct { type Matcher struct {
patternBuilder func([]rune) *Pattern patternBuilder func([]rune) *Pattern
sort bool sort bool
eventBox *EventBox eventBox *util.EventBox
reqBox *EventBox reqBox *util.EventBox
partitions int partitions int
mergerCache map[string]*Merger mergerCache map[string]*Merger
} }
const ( const (
reqRetry EventType = iota reqRetry util.EventType = iota
reqReset reqReset
) )
@ -35,12 +37,12 @@ const (
// NewMatcher returns a new Matcher // NewMatcher returns a new Matcher
func NewMatcher(patternBuilder func([]rune) *Pattern, func NewMatcher(patternBuilder func([]rune) *Pattern,
sort bool, eventBox *EventBox) *Matcher { sort bool, eventBox *util.EventBox) *Matcher {
return &Matcher{ return &Matcher{
patternBuilder: patternBuilder, patternBuilder: patternBuilder,
sort: sort, sort: sort,
eventBox: eventBox, eventBox: eventBox,
reqBox: NewEventBox(), reqBox: util.NewEventBox(),
partitions: runtime.NumCPU(), partitions: runtime.NumCPU(),
mergerCache: make(map[string]*Merger)} mergerCache: make(map[string]*Merger)}
} }
@ -52,7 +54,7 @@ func (m *Matcher) Loop() {
for { for {
var request MatchRequest var request MatchRequest
m.reqBox.Wait(func(events *Events) { m.reqBox.Wait(func(events *util.Events) {
for _, val := range *events { for _, val := range *events {
switch val := val.(type) { switch val := val.(type) {
case MatchRequest: case MatchRequest:
@ -128,7 +130,7 @@ func (m *Matcher) scan(request MatchRequest, limit int) (*Merger, bool) {
} }
pattern := request.pattern pattern := request.pattern
empty := pattern.IsEmpty() empty := pattern.IsEmpty()
cancelled := NewAtomicBool(false) cancelled := util.NewAtomicBool(false)
slices := m.sliceChunks(request.chunks) slices := m.sliceChunks(request.chunks)
numSlices := len(slices) numSlices := len(slices)
@ -202,7 +204,7 @@ func (m *Matcher) scan(request MatchRequest, limit int) (*Merger, bool) {
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 util.EventType
if cancel { if cancel {
event = reqReset event = reqReset
} else { } else {

View File

@ -2,10 +2,11 @@ package fzf
import ( import (
"fmt" "fmt"
"github.com/junegunn/go-shellwords"
"os" "os"
"regexp" "regexp"
"strings" "strings"
"github.com/junegunn/go-shellwords"
) )
const usage = `usage: fzf [options] const usage = `usage: fzf [options]

View File

@ -4,6 +4,8 @@ import (
"regexp" "regexp"
"sort" "sort"
"strings" "strings"
"github.com/junegunn/fzf/src/algo"
) )
const uppercaseLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" const uppercaseLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
@ -112,10 +114,10 @@ func BuildPattern(mode Mode, caseMode Case,
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[termFuzzy] = FuzzyMatch ptr.procFun[termFuzzy] = algo.FuzzyMatch
ptr.procFun[termExact] = ExactMatchNaive ptr.procFun[termExact] = algo.ExactMatchNaive
ptr.procFun[termPrefix] = PrefixMatch ptr.procFun[termPrefix] = algo.PrefixMatch
ptr.procFun[termSuffix] = SuffixMatch ptr.procFun[termSuffix] = algo.SuffixMatch
_patternCache[asString] = ptr _patternCache[asString] = ptr
return ptr return ptr
@ -245,7 +247,7 @@ func (p *Pattern) fuzzyMatch(chunk *Chunk) []*Item {
matches := []*Item{} matches := []*Item{}
for _, item := range *chunk { for _, item := range *chunk {
input := p.prepareInput(item) input := p.prepareInput(item)
if sidx, eidx := p.iter(FuzzyMatch, input, p.text); sidx >= 0 { if sidx, eidx := p.iter(algo.FuzzyMatch, input, p.text); sidx >= 0 {
matches = append(matches, matches = append(matches,
dupItem(item, []Offset{Offset{int32(sidx), int32(eidx)}})) dupItem(item, []Offset{Offset{int32(sidx), int32(eidx)}}))
} }

View File

@ -1,6 +1,10 @@
package fzf package fzf
import "testing" import (
"testing"
"github.com/junegunn/fzf/src/algo"
)
func TestParseTermsExtended(t *testing.T) { func TestParseTermsExtended(t *testing.T) {
terms := parseTerms(ModeExtended, terms := parseTerms(ModeExtended,
@ -55,7 +59,7 @@ func TestExact(t *testing.T) {
pattern := BuildPattern(ModeExtended, CaseSmart, 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 := algo.ExactMatchNaive(pattern.caseSensitive, &str, pattern.terms[0].text)
if sidx != 7 || eidx != 10 { if sidx != 7 || eidx != 10 {
t.Errorf("%s / %d / %d", pattern.terms, sidx, eidx) t.Errorf("%s / %d / %d", pattern.terms, sidx, eidx)
} }

View File

@ -1,13 +1,12 @@
package fzf package fzf
// #include <unistd.h>
import "C"
import ( import (
"bufio" "bufio"
"io" "io"
"os" "os"
"os/exec" "os/exec"
"github.com/junegunn/fzf/src/util"
) )
const defaultCommand = `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`
@ -15,12 +14,12 @@ const defaultCommand = `find * -path '*/\.*' -prune -o -type f -print -o -type l
// Reader reads from command or standard input // Reader reads from command or standard input
type Reader struct { type Reader struct {
pusher func(string) pusher func(string)
eventBox *EventBox eventBox *util.EventBox
} }
// ReadSource reads data from the default command or from standard input // 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 util.IsTty() {
cmd := os.Getenv("FZF_DEFAULT_COMMAND") cmd := os.Getenv("FZF_DEFAULT_COMMAND")
if len(cmd) == 0 { if len(cmd) == 0 {
cmd = defaultCommand cmd = defaultCommand

View File

@ -1,10 +1,14 @@
package fzf package fzf
import "testing" import (
"testing"
"github.com/junegunn/fzf/src/util"
)
func TestReadFromCommand(t *testing.T) { func TestReadFromCommand(t *testing.T) {
strs := []string{} strs := []string{}
eb := NewEventBox() eb := util.NewEventBox()
reader := Reader{ reader := Reader{
pusher: func(s string) { strs = append(strs, s) }, pusher: func(s string) { strs = append(strs, s) },
eventBox: eb} eventBox: eb}
@ -26,7 +30,7 @@ func TestReadFromCommand(t *testing.T) {
} }
// Wait should return immediately // Wait should return immediately
eb.Wait(func(events *Events) { eb.Wait(func(events *util.Events) {
if _, found := (*events)[EvtReadNew]; !found { if _, found := (*events)[EvtReadNew]; !found {
t.Errorf("%s", events) t.Errorf("%s", events)
} }

View File

@ -2,13 +2,16 @@ package fzf
import ( import (
"fmt" "fmt"
C "github.com/junegunn/fzf/src/curses"
"github.com/junegunn/go-runewidth"
"os" "os"
"regexp" "regexp"
"sort" "sort"
"sync" "sync"
"time" "time"
C "github.com/junegunn/fzf/src/curses"
"github.com/junegunn/fzf/src/util"
"github.com/junegunn/go-runewidth"
) )
// Terminal represents terminal input/output // Terminal represents terminal input/output
@ -28,8 +31,8 @@ type Terminal struct {
reading bool reading bool
merger *Merger merger *Merger
selected map[*string]*string selected map[*string]*string
reqBox *EventBox reqBox *util.EventBox
eventBox *EventBox eventBox *util.EventBox
mutex sync.Mutex mutex sync.Mutex
initFunc func() initFunc func()
suppress bool suppress bool
@ -38,7 +41,7 @@ type Terminal struct {
var _spinner = []string{`-`, `\`, `|`, `/`, `-`, `\`, `|`, `/`} var _spinner = []string{`-`, `\`, `|`, `/`, `-`, `\`, `|`, `/`}
const ( const (
reqPrompt EventType = iota reqPrompt util.EventType = iota
reqInfo reqInfo
reqList reqList
reqRefresh reqRefresh
@ -53,7 +56,7 @@ const (
) )
// NewTerminal returns new Terminal object // NewTerminal returns new Terminal object
func NewTerminal(opts *Options, eventBox *EventBox) *Terminal { func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
input := []rune(opts.Query) input := []rune(opts.Query)
return &Terminal{ return &Terminal{
prompt: opts.Prompt, prompt: opts.Prompt,
@ -68,7 +71,7 @@ func NewTerminal(opts *Options, eventBox *EventBox) *Terminal {
printQuery: opts.PrintQuery, printQuery: opts.PrintQuery,
merger: EmptyMerger, merger: EmptyMerger,
selected: make(map[*string]*string), selected: make(map[*string]*string),
reqBox: NewEventBox(), reqBox: util.NewEventBox(),
eventBox: eventBox, eventBox: eventBox,
mutex: sync.Mutex{}, mutex: sync.Mutex{},
suppress: true, suppress: true,
@ -288,7 +291,7 @@ func (*Terminal) printHighlighted(item *Item, bold bool, col1 int, col2 int) {
b, e := offset[0], offset[1] b, e := offset[0], offset[1]
b += 2 - diff b += 2 - diff
e += 2 - diff e += 2 - diff
b = Max32(b, 2) b = util.Max32(b, 2)
if b < e { if b < e {
offsets[idx] = Offset{b, e} offsets[idx] = Offset{b, e}
} }
@ -300,8 +303,8 @@ func (*Terminal) printHighlighted(item *Item, bold bool, col1 int, col2 int) {
sort.Sort(ByOrder(offsets)) sort.Sort(ByOrder(offsets))
var index int32 var index int32
for _, offset := range offsets { for _, offset := range offsets {
b := Max32(index, offset[0]) b := util.Max32(index, offset[0])
e := Max32(index, offset[1]) e := util.Max32(index, offset[1])
C.CPrint(col1, bold, string(text[index:b])) C.CPrint(col1, bold, string(text[index:b]))
C.CPrint(col2, bold, string(text[b:e])) C.CPrint(col2, bold, string(text[b:e]))
index = e index = e
@ -388,7 +391,7 @@ func (t *Terminal) Loop() {
go func() { go func() {
for { for {
t.reqBox.Wait(func(events *Events) { t.reqBox.Wait(func(events *util.Events) {
defer events.Clear() defer events.Clear()
t.mutex.Lock() t.mutex.Lock()
for req := range *events { for req := range *events {
@ -426,8 +429,8 @@ func (t *Terminal) Loop() {
t.mutex.Lock() t.mutex.Lock()
previousInput := t.input previousInput := t.input
events := []EventType{reqPrompt} events := []util.EventType{reqPrompt}
req := func(evts ...EventType) { req := func(evts ...util.EventType) {
for _, event := range evts { for _, event := range evts {
events = append(events, event) events = append(events, event)
if event == reqClose || event == reqQuit { if event == reqClose || event == reqQuit {
@ -538,7 +541,7 @@ func (t *Terminal) Loop() {
t.cx++ 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 := util.Constrain(me.X-len(t.prompt), 0, len(t.input)), me.Y
if !t.reverse { if !t.reverse {
my = C.MaxY() - my - 1 my = C.MaxY() - my - 1
} }
@ -588,7 +591,7 @@ func (t *Terminal) constrain() {
height := C.MaxY() - 2 height := C.MaxY() - 2
diffpos := t.cy - t.offset diffpos := t.cy - t.offset
t.cy = Max(0, Min(t.cy, count-1)) t.cy = util.Constrain(t.cy, 0, count-1)
if t.cy > t.offset+(height-1) { if t.cy > t.offset+(height-1) {
// Ceil // Ceil
@ -600,8 +603,8 @@ func (t *Terminal) constrain() {
// Adjustment // Adjustment
if count-t.offset < height { if count-t.offset < height {
t.offset = Max(0, count-height) t.offset = util.Max(0, count-height)
t.cy = Max(0, Min(t.offset+diffpos, count-1)) t.cy = util.Constrain(t.offset+diffpos, 0, count-1)
} }
} }
@ -614,7 +617,7 @@ func (t *Terminal) vmove(o int) {
} }
func (t *Terminal) vset(o int) bool { func (t *Terminal) vset(o int) bool {
t.cy = Max(0, Min(o, t.merger.Length()-1)) t.cy = util.Constrain(o, 0, t.merger.Length()-1)
return t.cy == o return t.cy == o
} }

View File

@ -4,6 +4,8 @@ import (
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
"github.com/junegunn/fzf/src/util"
) )
const rangeEllipsis = 0 const rangeEllipsis = 0
@ -180,7 +182,7 @@ func Transform(tokens []Token, withNth []Range) *Transformed {
end += numTokens + 1 end += numTokens + 1
} }
} }
minIdx = Max(0, begin-1) minIdx = util.Max(0, begin-1)
for idx := begin; idx <= end; idx++ { for idx := begin; idx <= end; idx++ {
if idx >= 1 && idx <= numTokens { if idx >= 1 && idx <= numTokens {
part += *tokens[idx-1].text part += *tokens[idx-1].text

View File

@ -1,4 +1,4 @@
package fzf package util
import "sync" import "sync"

View File

@ -1,4 +1,4 @@
package fzf package util
import "testing" import "testing"

View File

@ -1,7 +1,10 @@
package fzf package util
import "sync" import "sync"
// EventType is the type for fzf events
type EventType int
// Events is a type that associates EventType to any data // Events is a type that associates EventType to any data
type Events map[EventType]interface{} type Events map[EventType]interface{}

View File

@ -1,7 +1,17 @@
package fzf package util
import "testing" import "testing"
// fzf events
const (
EvtReadNew EventType = iota
EvtReadFin
EvtSearchNew
EvtSearchProgress
EvtSearchFin
EvtClose
)
func TestEventBox(t *testing.T) { func TestEventBox(t *testing.T) {
eb := NewEventBox() eb := NewEventBox()

View File

@ -1,6 +1,12 @@
package fzf package util
import "time" // #include <unistd.h>
import "C"
import (
"os"
"time"
)
// Max returns the largest integer // Max returns the largest integer
func Max(first int, items ...int) int { func Max(first int, items ...int) int {
@ -21,15 +27,15 @@ func Max32(first int32, second int32) int32 {
return second return second
} }
// Min returns the smallest integer // Constrain limits the given integer with the upper and lower bounds
func Min(first int, items ...int) int { func Constrain(val int, min int, max int) int {
min := first if val < min {
for _, item := range items { return min
if item < min {
min = item
}
} }
return min if val > max {
return max
}
return val
} }
// DurWithin limits the given time.Duration with the upper and lower bounds // DurWithin limits the given time.Duration with the upper and lower bounds
@ -43,3 +49,8 @@ func DurWithin(
} }
return val return val
} }
// IsTty returns true is stdin is a terminal
func IsTty() bool {
return int(C.isatty(C.int(os.Stdin.Fd()))) != 0
}

22
src/util/util_test.go Normal file
View File

@ -0,0 +1,22 @@
package util
import "testing"
func TestMax(t *testing.T) {
if Max(-2, 5, 1, 4, 3) != 5 {
t.Error("Invalid result")
}
}
func TestContrain(t *testing.T) {
if Constrain(-3, -1, 3) != -1 {
t.Error("Expected", -1)
}
if Constrain(2, -1, 3) != 2 {
t.Error("Expected", 2)
}
if Constrain(5, -1, 3) != 3 {
t.Error("Expected", 3)
}
}

View File

@ -1,18 +0,0 @@
package fzf
import "testing"
func TestMax(t *testing.T) {
if Max(-2, 5, 1, 4, 3) != 5 {
t.Error("Invalid result")
}
}
func TestMin(t *testing.T) {
if Min(2, -3) != -3 {
t.Error("Invalid result")
}
if Min(-2, 5, 1, 4, 3) != -2 {
t.Error("Invalid result")
}
}