summaryrefslogtreecommitdiffstats
path: root/src/core.go
blob: 2601397a39e5c5db587b667a998cd2647a93f12f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
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
		for looping {
			eventBox.Wait(func(events *Events) {
				for evt, _ := range *events {
					switch evt {
					case EVT_READ_FIN:
						looping = false
						return
					}
				}
			})
			time.Sleep(COORDINATOR_DELAY)
		}

		matches, cancelled := matcher.scan(MatchRequest{
			chunks:  chunkList.Snapshot(),
			pattern: pattern}, limit)

		if !cancelled && (filtering || opts.Exit0) {
			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
	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
					terminal.UpdateCount(chunkList.Count(), !reading)
					matcher.Reset(chunkList.Snapshot(), terminal.Input(), false)

				case EVT_SEARCH_NEW:
					matcher.Reset(chunkList.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)
		}
	}
}