package fzf import ( "fmt" "os" "runtime" "time" ) const COORDINATOR_DELAY time.Duration = 100 * 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) */ func Run(options *Options) { initProcs() opts := ParseOptions() if opts.Version { fmt.Println(VERSION) os.Exit(0) } // Event channel eventBox := NewEventBox() // Chunk list var chunkList *ChunkList if len(opts.WithNth) == 0 { chunkList = NewChunkList(func(data *string, index int) *Item { return &Item{text: data, index: index} }) } else { chunkList = NewChunkList(func(data *string, index int) *Item { item := Item{text: data, index: index} tokens := Tokenize(item.text, opts.Delimiter) item.origText = item.text item.text = Transform(tokens, opts.WithNth).whole return &item }) } // Reader reader := Reader{func(str string) { chunkList.Push(str) }, eventBox} go reader.ReadSource() // Matcher patternBuilder := func(runes []rune) *Pattern { return BuildPattern( opts.Mode, opts.Case, opts.Nth, opts.Delimiter, runes) } matcher := NewMatcher(patternBuilder, opts.Sort > 0, eventBox) // Defered-interactive / Non-interactive // --select-1 | --exit-0 | --filter if filtering := opts.Filter != nil; filtering || opts.Select1 || opts.Exit0 { limit := 0 var patternString string if filtering { patternString = *opts.Filter } else { if opts.Select1 || opts.Exit0 { limit = 1 } patternString = opts.Query } pattern := patternBuilder([]rune(patternString)) looping := true eventBox.Unwatch(EVT_READ_NEW) for looping { eventBox.Wait(func(events *Events) { for evt, _ := range *events { switch evt { case EVT_READ_FIN: looping = false return } } }) } snapshot, _ := chunkList.Snapshot() matches, cancelled := matcher.scan(MatchRequest{ chunks: snapshot, pattern: pattern}, limit) if !cancelled && (filtering || opts.Exit0 && len(matches) == 0 || opts.Select1 && len(matches) == 1) { if opts.PrintQuery { fmt.Println(patternString) } for _, item := range matches { item.Print() } os.Exit(0) } } // Go interactive go matcher.Loop() // Terminal I/O terminal := NewTerminal(opts, eventBox) go terminal.Loop() // Event coordination reading := true ticks := 0 eventBox.Watch(EVT_READ_NEW) for { delay := true ticks += 1 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 snapshot, count := chunkList.Snapshot() terminal.UpdateCount(count, !reading) matcher.Reset(snapshot, terminal.Input(), false) case EVT_SEARCH_NEW: snapshot, _ := chunkList.Snapshot() matcher.Reset(snapshot, terminal.Input(), true) delay = false case EVT_SEARCH_PROGRESS: switch val := value.(type) { case float32: terminal.UpdateProgress(val) } case EVT_SEARCH_FIN: switch val := value.(type) { case []*Item: terminal.UpdateList(val) } } } }) if ticks > 3 && delay && reading { time.Sleep(COORDINATOR_DELAY) } } }