doing 2.0.20 → 2.0.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/Gemfile.lock +1 -1
- data/README.md +1 -1
- data/doing.rdoc +1 -1
- data/lib/doing/version.rb +1 -1
- data/lib/helpers/fzf/.goreleaser.yml +119 -0
- data/lib/helpers/fzf/.rubocop.yml +28 -0
- data/lib/helpers/fzf/ADVANCED.md +565 -0
- data/lib/helpers/fzf/BUILD.md +49 -0
- data/lib/helpers/fzf/CHANGELOG.md +1193 -0
- data/lib/helpers/fzf/Dockerfile +11 -0
- data/lib/helpers/fzf/LICENSE +21 -0
- data/lib/helpers/fzf/Makefile +166 -0
- data/lib/helpers/fzf/README-VIM.md +486 -0
- data/lib/helpers/fzf/README.md +712 -0
- data/lib/helpers/fzf/bin/fzf-tmux +233 -0
- data/lib/helpers/fzf/doc/fzf.txt +512 -0
- data/lib/helpers/fzf/go.mod +17 -0
- data/lib/helpers/fzf/go.sum +31 -0
- data/lib/helpers/fzf/install +382 -0
- data/lib/helpers/fzf/install.ps1 +65 -0
- data/lib/helpers/fzf/main.go +14 -0
- data/lib/helpers/fzf/man/man1/fzf-tmux.1 +68 -0
- data/lib/helpers/fzf/man/man1/fzf.1 +1001 -0
- data/lib/helpers/fzf/plugin/fzf.vim +1048 -0
- data/lib/helpers/fzf/shell/completion.bash +381 -0
- data/lib/helpers/fzf/shell/completion.zsh +329 -0
- data/lib/helpers/fzf/shell/key-bindings.bash +96 -0
- data/lib/helpers/fzf/shell/key-bindings.fish +172 -0
- data/lib/helpers/fzf/shell/key-bindings.zsh +114 -0
- data/lib/helpers/fzf/src/LICENSE +21 -0
- data/lib/helpers/fzf/src/algo/algo.go +884 -0
- data/lib/helpers/fzf/src/algo/algo_test.go +197 -0
- data/lib/helpers/fzf/src/algo/normalize.go +492 -0
- data/lib/helpers/fzf/src/ansi.go +409 -0
- data/lib/helpers/fzf/src/ansi_test.go +427 -0
- data/lib/helpers/fzf/src/cache.go +81 -0
- data/lib/helpers/fzf/src/cache_test.go +39 -0
- data/lib/helpers/fzf/src/chunklist.go +89 -0
- data/lib/helpers/fzf/src/chunklist_test.go +80 -0
- data/lib/helpers/fzf/src/constants.go +85 -0
- data/lib/helpers/fzf/src/core.go +351 -0
- data/lib/helpers/fzf/src/history.go +96 -0
- data/lib/helpers/fzf/src/history_test.go +68 -0
- data/lib/helpers/fzf/src/item.go +44 -0
- data/lib/helpers/fzf/src/item_test.go +23 -0
- data/lib/helpers/fzf/src/matcher.go +235 -0
- data/lib/helpers/fzf/src/merger.go +120 -0
- data/lib/helpers/fzf/src/merger_test.go +88 -0
- data/lib/helpers/fzf/src/options.go +1691 -0
- data/lib/helpers/fzf/src/options_test.go +457 -0
- data/lib/helpers/fzf/src/pattern.go +425 -0
- data/lib/helpers/fzf/src/pattern_test.go +209 -0
- data/lib/helpers/fzf/src/protector/protector.go +8 -0
- data/lib/helpers/fzf/src/protector/protector_openbsd.go +10 -0
- data/lib/helpers/fzf/src/reader.go +201 -0
- data/lib/helpers/fzf/src/reader_test.go +63 -0
- data/lib/helpers/fzf/src/result.go +243 -0
- data/lib/helpers/fzf/src/result_others.go +16 -0
- data/lib/helpers/fzf/src/result_test.go +159 -0
- data/lib/helpers/fzf/src/result_x86.go +16 -0
- data/lib/helpers/fzf/src/terminal.go +2832 -0
- data/lib/helpers/fzf/src/terminal_test.go +638 -0
- data/lib/helpers/fzf/src/terminal_unix.go +26 -0
- data/lib/helpers/fzf/src/terminal_windows.go +45 -0
- data/lib/helpers/fzf/src/tokenizer.go +253 -0
- data/lib/helpers/fzf/src/tokenizer_test.go +112 -0
- data/lib/helpers/fzf/src/tui/dummy.go +46 -0
- data/lib/helpers/fzf/src/tui/light.go +987 -0
- data/lib/helpers/fzf/src/tui/light_unix.go +110 -0
- data/lib/helpers/fzf/src/tui/light_windows.go +145 -0
- data/lib/helpers/fzf/src/tui/tcell.go +721 -0
- data/lib/helpers/fzf/src/tui/tcell_test.go +392 -0
- data/lib/helpers/fzf/src/tui/ttyname_unix.go +47 -0
- data/lib/helpers/fzf/src/tui/ttyname_windows.go +14 -0
- data/lib/helpers/fzf/src/tui/tui.go +625 -0
- data/lib/helpers/fzf/src/tui/tui_test.go +20 -0
- data/lib/helpers/fzf/src/util/atomicbool.go +34 -0
- data/lib/helpers/fzf/src/util/atomicbool_test.go +17 -0
- data/lib/helpers/fzf/src/util/chars.go +198 -0
- data/lib/helpers/fzf/src/util/chars_test.go +46 -0
- data/lib/helpers/fzf/src/util/eventbox.go +96 -0
- data/lib/helpers/fzf/src/util/eventbox_test.go +61 -0
- data/lib/helpers/fzf/src/util/slab.go +12 -0
- data/lib/helpers/fzf/src/util/util.go +138 -0
- data/lib/helpers/fzf/src/util/util_test.go +40 -0
- data/lib/helpers/fzf/src/util/util_unix.go +47 -0
- data/lib/helpers/fzf/src/util/util_windows.go +83 -0
- data/lib/helpers/fzf/test/fzf.vader +175 -0
- data/lib/helpers/fzf/test/test_go.rb +2626 -0
- data/lib/helpers/fzf/uninstall +117 -0
- metadata +87 -1
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
package util
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
"unicode"
|
|
6
|
+
"unicode/utf8"
|
|
7
|
+
"unsafe"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
const (
|
|
11
|
+
overflow64 uint64 = 0x8080808080808080
|
|
12
|
+
overflow32 uint32 = 0x80808080
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
type Chars struct {
|
|
16
|
+
slice []byte // or []rune
|
|
17
|
+
inBytes bool
|
|
18
|
+
trimLengthKnown bool
|
|
19
|
+
trimLength uint16
|
|
20
|
+
|
|
21
|
+
// XXX Piggybacking item index here is a horrible idea. But I'm trying to
|
|
22
|
+
// minimize the memory footprint by not wasting padded spaces.
|
|
23
|
+
Index int32
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
func checkAscii(bytes []byte) (bool, int) {
|
|
27
|
+
i := 0
|
|
28
|
+
for ; i <= len(bytes)-8; i += 8 {
|
|
29
|
+
if (overflow64 & *(*uint64)(unsafe.Pointer(&bytes[i]))) > 0 {
|
|
30
|
+
return false, i
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
for ; i <= len(bytes)-4; i += 4 {
|
|
34
|
+
if (overflow32 & *(*uint32)(unsafe.Pointer(&bytes[i]))) > 0 {
|
|
35
|
+
return false, i
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
for ; i < len(bytes); i++ {
|
|
39
|
+
if bytes[i] >= utf8.RuneSelf {
|
|
40
|
+
return false, i
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return true, 0
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ToChars converts byte array into rune array
|
|
47
|
+
func ToChars(bytes []byte) Chars {
|
|
48
|
+
inBytes, bytesUntil := checkAscii(bytes)
|
|
49
|
+
if inBytes {
|
|
50
|
+
return Chars{slice: bytes, inBytes: inBytes}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
runes := make([]rune, bytesUntil, len(bytes))
|
|
54
|
+
for i := 0; i < bytesUntil; i++ {
|
|
55
|
+
runes[i] = rune(bytes[i])
|
|
56
|
+
}
|
|
57
|
+
for i := bytesUntil; i < len(bytes); {
|
|
58
|
+
r, sz := utf8.DecodeRune(bytes[i:])
|
|
59
|
+
i += sz
|
|
60
|
+
runes = append(runes, r)
|
|
61
|
+
}
|
|
62
|
+
return RunesToChars(runes)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
func RunesToChars(runes []rune) Chars {
|
|
66
|
+
return Chars{slice: *(*[]byte)(unsafe.Pointer(&runes)), inBytes: false}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
func (chars *Chars) IsBytes() bool {
|
|
70
|
+
return chars.inBytes
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
func (chars *Chars) Bytes() []byte {
|
|
74
|
+
return chars.slice
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
func (chars *Chars) optionalRunes() []rune {
|
|
78
|
+
if chars.inBytes {
|
|
79
|
+
return nil
|
|
80
|
+
}
|
|
81
|
+
return *(*[]rune)(unsafe.Pointer(&chars.slice))
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
func (chars *Chars) Get(i int) rune {
|
|
85
|
+
if runes := chars.optionalRunes(); runes != nil {
|
|
86
|
+
return runes[i]
|
|
87
|
+
}
|
|
88
|
+
return rune(chars.slice[i])
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
func (chars *Chars) Length() int {
|
|
92
|
+
if runes := chars.optionalRunes(); runes != nil {
|
|
93
|
+
return len(runes)
|
|
94
|
+
}
|
|
95
|
+
return len(chars.slice)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// String returns the string representation of a Chars object.
|
|
99
|
+
func (chars *Chars) String() string {
|
|
100
|
+
return fmt.Sprintf("Chars{slice: []byte(%q), inBytes: %v, trimLengthKnown: %v, trimLength: %d, Index: %d}", chars.slice, chars.inBytes, chars.trimLengthKnown, chars.trimLength, chars.Index)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// TrimLength returns the length after trimming leading and trailing whitespaces
|
|
104
|
+
func (chars *Chars) TrimLength() uint16 {
|
|
105
|
+
if chars.trimLengthKnown {
|
|
106
|
+
return chars.trimLength
|
|
107
|
+
}
|
|
108
|
+
chars.trimLengthKnown = true
|
|
109
|
+
var i int
|
|
110
|
+
len := chars.Length()
|
|
111
|
+
for i = len - 1; i >= 0; i-- {
|
|
112
|
+
char := chars.Get(i)
|
|
113
|
+
if !unicode.IsSpace(char) {
|
|
114
|
+
break
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// Completely empty
|
|
118
|
+
if i < 0 {
|
|
119
|
+
return 0
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
var j int
|
|
123
|
+
for j = 0; j < len; j++ {
|
|
124
|
+
char := chars.Get(j)
|
|
125
|
+
if !unicode.IsSpace(char) {
|
|
126
|
+
break
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
chars.trimLength = AsUint16(i - j + 1)
|
|
130
|
+
return chars.trimLength
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
func (chars *Chars) LeadingWhitespaces() int {
|
|
134
|
+
whitespaces := 0
|
|
135
|
+
for i := 0; i < chars.Length(); i++ {
|
|
136
|
+
char := chars.Get(i)
|
|
137
|
+
if !unicode.IsSpace(char) {
|
|
138
|
+
break
|
|
139
|
+
}
|
|
140
|
+
whitespaces++
|
|
141
|
+
}
|
|
142
|
+
return whitespaces
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
func (chars *Chars) TrailingWhitespaces() int {
|
|
146
|
+
whitespaces := 0
|
|
147
|
+
for i := chars.Length() - 1; i >= 0; i-- {
|
|
148
|
+
char := chars.Get(i)
|
|
149
|
+
if !unicode.IsSpace(char) {
|
|
150
|
+
break
|
|
151
|
+
}
|
|
152
|
+
whitespaces++
|
|
153
|
+
}
|
|
154
|
+
return whitespaces
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
func (chars *Chars) TrimTrailingWhitespaces() {
|
|
158
|
+
whitespaces := chars.TrailingWhitespaces()
|
|
159
|
+
chars.slice = chars.slice[0 : len(chars.slice)-whitespaces]
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
func (chars *Chars) ToString() string {
|
|
163
|
+
if runes := chars.optionalRunes(); runes != nil {
|
|
164
|
+
return string(runes)
|
|
165
|
+
}
|
|
166
|
+
return string(chars.slice)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
func (chars *Chars) ToRunes() []rune {
|
|
170
|
+
if runes := chars.optionalRunes(); runes != nil {
|
|
171
|
+
return runes
|
|
172
|
+
}
|
|
173
|
+
bytes := chars.slice
|
|
174
|
+
runes := make([]rune, len(bytes))
|
|
175
|
+
for idx, b := range bytes {
|
|
176
|
+
runes[idx] = rune(b)
|
|
177
|
+
}
|
|
178
|
+
return runes
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
func (chars *Chars) CopyRunes(dest []rune) {
|
|
182
|
+
if runes := chars.optionalRunes(); runes != nil {
|
|
183
|
+
copy(dest, runes)
|
|
184
|
+
return
|
|
185
|
+
}
|
|
186
|
+
for idx, b := range chars.slice[:len(dest)] {
|
|
187
|
+
dest[idx] = rune(b)
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
func (chars *Chars) Prepend(prefix string) {
|
|
192
|
+
if runes := chars.optionalRunes(); runes != nil {
|
|
193
|
+
runes = append([]rune(prefix), runes...)
|
|
194
|
+
chars.slice = *(*[]byte)(unsafe.Pointer(&runes))
|
|
195
|
+
} else {
|
|
196
|
+
chars.slice = append([]byte(prefix), chars.slice...)
|
|
197
|
+
}
|
|
198
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
package util
|
|
2
|
+
|
|
3
|
+
import "testing"
|
|
4
|
+
|
|
5
|
+
func TestToCharsAscii(t *testing.T) {
|
|
6
|
+
chars := ToChars([]byte("foobar"))
|
|
7
|
+
if !chars.inBytes || chars.ToString() != "foobar" || !chars.inBytes {
|
|
8
|
+
t.Error()
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
func TestCharsLength(t *testing.T) {
|
|
13
|
+
chars := ToChars([]byte("\tabc한글 "))
|
|
14
|
+
if chars.inBytes || chars.Length() != 8 || chars.TrimLength() != 5 {
|
|
15
|
+
t.Error()
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
func TestCharsToString(t *testing.T) {
|
|
20
|
+
text := "\tabc한글 "
|
|
21
|
+
chars := ToChars([]byte(text))
|
|
22
|
+
if chars.ToString() != text {
|
|
23
|
+
t.Error()
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
func TestTrimLength(t *testing.T) {
|
|
28
|
+
check := func(str string, exp uint16) {
|
|
29
|
+
chars := ToChars([]byte(str))
|
|
30
|
+
trimmed := chars.TrimLength()
|
|
31
|
+
if trimmed != exp {
|
|
32
|
+
t.Errorf("Invalid TrimLength result for '%s': %d (expected %d)",
|
|
33
|
+
str, trimmed, exp)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
check("hello", 5)
|
|
37
|
+
check("hello ", 5)
|
|
38
|
+
check("hello ", 5)
|
|
39
|
+
check(" hello", 5)
|
|
40
|
+
check(" hello", 5)
|
|
41
|
+
check(" hello ", 5)
|
|
42
|
+
check(" hello ", 5)
|
|
43
|
+
check("h o", 5)
|
|
44
|
+
check(" h o ", 5)
|
|
45
|
+
check(" ", 0)
|
|
46
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
package util
|
|
2
|
+
|
|
3
|
+
import "sync"
|
|
4
|
+
|
|
5
|
+
// EventType is the type for fzf events
|
|
6
|
+
type EventType int
|
|
7
|
+
|
|
8
|
+
// Events is a type that associates EventType to any data
|
|
9
|
+
type Events map[EventType]interface{}
|
|
10
|
+
|
|
11
|
+
// EventBox is used for coordinating events
|
|
12
|
+
type EventBox struct {
|
|
13
|
+
events Events
|
|
14
|
+
cond *sync.Cond
|
|
15
|
+
ignore map[EventType]bool
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// NewEventBox returns a new EventBox
|
|
19
|
+
func NewEventBox() *EventBox {
|
|
20
|
+
return &EventBox{
|
|
21
|
+
events: make(Events),
|
|
22
|
+
cond: sync.NewCond(&sync.Mutex{}),
|
|
23
|
+
ignore: make(map[EventType]bool)}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Wait blocks the goroutine until signaled
|
|
27
|
+
func (b *EventBox) Wait(callback func(*Events)) {
|
|
28
|
+
b.cond.L.Lock()
|
|
29
|
+
|
|
30
|
+
if len(b.events) == 0 {
|
|
31
|
+
b.cond.Wait()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
callback(&b.events)
|
|
35
|
+
b.cond.L.Unlock()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Set turns on the event type on the box
|
|
39
|
+
func (b *EventBox) Set(event EventType, value interface{}) {
|
|
40
|
+
b.cond.L.Lock()
|
|
41
|
+
b.events[event] = value
|
|
42
|
+
if _, found := b.ignore[event]; !found {
|
|
43
|
+
b.cond.Broadcast()
|
|
44
|
+
}
|
|
45
|
+
b.cond.L.Unlock()
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Clear clears the events
|
|
49
|
+
// Unsynchronized; should be called within Wait routine
|
|
50
|
+
func (events *Events) Clear() {
|
|
51
|
+
for event := range *events {
|
|
52
|
+
delete(*events, event)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Peek peeks at the event box if the given event is set
|
|
57
|
+
func (b *EventBox) Peek(event EventType) bool {
|
|
58
|
+
b.cond.L.Lock()
|
|
59
|
+
_, ok := b.events[event]
|
|
60
|
+
b.cond.L.Unlock()
|
|
61
|
+
return ok
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Watch deletes the events from the ignore list
|
|
65
|
+
func (b *EventBox) Watch(events ...EventType) {
|
|
66
|
+
b.cond.L.Lock()
|
|
67
|
+
for _, event := range events {
|
|
68
|
+
delete(b.ignore, event)
|
|
69
|
+
}
|
|
70
|
+
b.cond.L.Unlock()
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Unwatch adds the events to the ignore list
|
|
74
|
+
func (b *EventBox) Unwatch(events ...EventType) {
|
|
75
|
+
b.cond.L.Lock()
|
|
76
|
+
for _, event := range events {
|
|
77
|
+
b.ignore[event] = true
|
|
78
|
+
}
|
|
79
|
+
b.cond.L.Unlock()
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// WaitFor blocks the execution until the event is received
|
|
83
|
+
func (b *EventBox) WaitFor(event EventType) {
|
|
84
|
+
looping := true
|
|
85
|
+
for looping {
|
|
86
|
+
b.Wait(func(events *Events) {
|
|
87
|
+
for evt := range *events {
|
|
88
|
+
switch evt {
|
|
89
|
+
case event:
|
|
90
|
+
looping = false
|
|
91
|
+
return
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
package util
|
|
2
|
+
|
|
3
|
+
import "testing"
|
|
4
|
+
|
|
5
|
+
// fzf events
|
|
6
|
+
const (
|
|
7
|
+
EvtReadNew EventType = iota
|
|
8
|
+
EvtReadFin
|
|
9
|
+
EvtSearchNew
|
|
10
|
+
EvtSearchProgress
|
|
11
|
+
EvtSearchFin
|
|
12
|
+
EvtClose
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
func TestEventBox(t *testing.T) {
|
|
16
|
+
eb := NewEventBox()
|
|
17
|
+
|
|
18
|
+
// Wait should return immediately
|
|
19
|
+
ch := make(chan bool)
|
|
20
|
+
|
|
21
|
+
go func() {
|
|
22
|
+
eb.Set(EvtReadNew, 10)
|
|
23
|
+
ch <- true
|
|
24
|
+
<-ch
|
|
25
|
+
eb.Set(EvtSearchNew, 10)
|
|
26
|
+
eb.Set(EvtSearchNew, 15)
|
|
27
|
+
eb.Set(EvtSearchNew, 20)
|
|
28
|
+
eb.Set(EvtSearchProgress, 30)
|
|
29
|
+
ch <- true
|
|
30
|
+
<-ch
|
|
31
|
+
eb.Set(EvtSearchFin, 40)
|
|
32
|
+
ch <- true
|
|
33
|
+
<-ch
|
|
34
|
+
}()
|
|
35
|
+
|
|
36
|
+
count := 0
|
|
37
|
+
sum := 0
|
|
38
|
+
looping := true
|
|
39
|
+
for looping {
|
|
40
|
+
<-ch
|
|
41
|
+
eb.Wait(func(events *Events) {
|
|
42
|
+
for _, value := range *events {
|
|
43
|
+
switch val := value.(type) {
|
|
44
|
+
case int:
|
|
45
|
+
sum += val
|
|
46
|
+
looping = sum < 100
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
events.Clear()
|
|
50
|
+
})
|
|
51
|
+
ch <- true
|
|
52
|
+
count++
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if count != 3 {
|
|
56
|
+
t.Error("Invalid number of events", count)
|
|
57
|
+
}
|
|
58
|
+
if sum != 100 {
|
|
59
|
+
t.Error("Invalid sum", sum)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
package util
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"math"
|
|
5
|
+
"os"
|
|
6
|
+
"strings"
|
|
7
|
+
"time"
|
|
8
|
+
|
|
9
|
+
"github.com/mattn/go-isatty"
|
|
10
|
+
"github.com/mattn/go-runewidth"
|
|
11
|
+
"github.com/rivo/uniseg"
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
// RunesWidth returns runes width
|
|
15
|
+
func RunesWidth(runes []rune, prefixWidth int, tabstop int, limit int) (int, int) {
|
|
16
|
+
width := 0
|
|
17
|
+
gr := uniseg.NewGraphemes(string(runes))
|
|
18
|
+
idx := 0
|
|
19
|
+
for gr.Next() {
|
|
20
|
+
rs := gr.Runes()
|
|
21
|
+
var w int
|
|
22
|
+
if len(rs) == 1 && rs[0] == '\t' {
|
|
23
|
+
w = tabstop - (prefixWidth+width)%tabstop
|
|
24
|
+
} else {
|
|
25
|
+
s := string(rs)
|
|
26
|
+
w = runewidth.StringWidth(s) + strings.Count(s, "\n")
|
|
27
|
+
}
|
|
28
|
+
width += w
|
|
29
|
+
if limit > 0 && width > limit {
|
|
30
|
+
return width, idx
|
|
31
|
+
}
|
|
32
|
+
idx += len(rs)
|
|
33
|
+
}
|
|
34
|
+
return width, -1
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Max returns the largest integer
|
|
38
|
+
func Max(first int, second int) int {
|
|
39
|
+
if first >= second {
|
|
40
|
+
return first
|
|
41
|
+
}
|
|
42
|
+
return second
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Max16 returns the largest integer
|
|
46
|
+
func Max16(first int16, second int16) int16 {
|
|
47
|
+
if first >= second {
|
|
48
|
+
return first
|
|
49
|
+
}
|
|
50
|
+
return second
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Max32 returns the largest 32-bit integer
|
|
54
|
+
func Max32(first int32, second int32) int32 {
|
|
55
|
+
if first > second {
|
|
56
|
+
return first
|
|
57
|
+
}
|
|
58
|
+
return second
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Min returns the smallest integer
|
|
62
|
+
func Min(first int, second int) int {
|
|
63
|
+
if first <= second {
|
|
64
|
+
return first
|
|
65
|
+
}
|
|
66
|
+
return second
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Min32 returns the smallest 32-bit integer
|
|
70
|
+
func Min32(first int32, second int32) int32 {
|
|
71
|
+
if first <= second {
|
|
72
|
+
return first
|
|
73
|
+
}
|
|
74
|
+
return second
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Constrain32 limits the given 32-bit integer with the upper and lower bounds
|
|
78
|
+
func Constrain32(val int32, min int32, max int32) int32 {
|
|
79
|
+
if val < min {
|
|
80
|
+
return min
|
|
81
|
+
}
|
|
82
|
+
if val > max {
|
|
83
|
+
return max
|
|
84
|
+
}
|
|
85
|
+
return val
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Constrain limits the given integer with the upper and lower bounds
|
|
89
|
+
func Constrain(val int, min int, max int) int {
|
|
90
|
+
if val < min {
|
|
91
|
+
return min
|
|
92
|
+
}
|
|
93
|
+
if val > max {
|
|
94
|
+
return max
|
|
95
|
+
}
|
|
96
|
+
return val
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
func AsUint16(val int) uint16 {
|
|
100
|
+
if val > math.MaxUint16 {
|
|
101
|
+
return math.MaxUint16
|
|
102
|
+
} else if val < 0 {
|
|
103
|
+
return 0
|
|
104
|
+
}
|
|
105
|
+
return uint16(val)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// DurWithin limits the given time.Duration with the upper and lower bounds
|
|
109
|
+
func DurWithin(
|
|
110
|
+
val time.Duration, min time.Duration, max time.Duration) time.Duration {
|
|
111
|
+
if val < min {
|
|
112
|
+
return min
|
|
113
|
+
}
|
|
114
|
+
if val > max {
|
|
115
|
+
return max
|
|
116
|
+
}
|
|
117
|
+
return val
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// IsTty returns true if stdin is a terminal
|
|
121
|
+
func IsTty() bool {
|
|
122
|
+
return isatty.IsTerminal(os.Stdin.Fd())
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ToTty returns true if stdout is a terminal
|
|
126
|
+
func ToTty() bool {
|
|
127
|
+
return isatty.IsTerminal(os.Stdout.Fd())
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Once returns a function that returns the specified boolean value only once
|
|
131
|
+
func Once(nextResponse bool) func() bool {
|
|
132
|
+
state := nextResponse
|
|
133
|
+
return func() bool {
|
|
134
|
+
prevState := state
|
|
135
|
+
state = false
|
|
136
|
+
return prevState
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
package util
|
|
2
|
+
|
|
3
|
+
import "testing"
|
|
4
|
+
|
|
5
|
+
func TestMax(t *testing.T) {
|
|
6
|
+
if Max(-2, 5) != 5 {
|
|
7
|
+
t.Error("Invalid result")
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
func TestContrain(t *testing.T) {
|
|
12
|
+
if Constrain(-3, -1, 3) != -1 {
|
|
13
|
+
t.Error("Expected", -1)
|
|
14
|
+
}
|
|
15
|
+
if Constrain(2, -1, 3) != 2 {
|
|
16
|
+
t.Error("Expected", 2)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if Constrain(5, -1, 3) != 3 {
|
|
20
|
+
t.Error("Expected", 3)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
func TestOnce(t *testing.T) {
|
|
25
|
+
o := Once(false)
|
|
26
|
+
if o() {
|
|
27
|
+
t.Error("Expected: false")
|
|
28
|
+
}
|
|
29
|
+
if o() {
|
|
30
|
+
t.Error("Expected: false")
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
o = Once(true)
|
|
34
|
+
if !o() {
|
|
35
|
+
t.Error("Expected: true")
|
|
36
|
+
}
|
|
37
|
+
if o() {
|
|
38
|
+
t.Error("Expected: false")
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// +build !windows
|
|
2
|
+
|
|
3
|
+
package util
|
|
4
|
+
|
|
5
|
+
import (
|
|
6
|
+
"os"
|
|
7
|
+
"os/exec"
|
|
8
|
+
"syscall"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
// ExecCommand executes the given command with $SHELL
|
|
12
|
+
func ExecCommand(command string, setpgid bool) *exec.Cmd {
|
|
13
|
+
shell := os.Getenv("SHELL")
|
|
14
|
+
if len(shell) == 0 {
|
|
15
|
+
shell = "sh"
|
|
16
|
+
}
|
|
17
|
+
return ExecCommandWith(shell, command, setpgid)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// ExecCommandWith executes the given command with the specified shell
|
|
21
|
+
func ExecCommandWith(shell string, command string, setpgid bool) *exec.Cmd {
|
|
22
|
+
cmd := exec.Command(shell, "-c", command)
|
|
23
|
+
if setpgid {
|
|
24
|
+
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
|
25
|
+
}
|
|
26
|
+
return cmd
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// KillCommand kills the process for the given command
|
|
30
|
+
func KillCommand(cmd *exec.Cmd) error {
|
|
31
|
+
return syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// IsWindows returns true on Windows
|
|
35
|
+
func IsWindows() bool {
|
|
36
|
+
return false
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// SetNonblock executes syscall.SetNonblock on file descriptor
|
|
40
|
+
func SetNonblock(file *os.File, nonblock bool) {
|
|
41
|
+
syscall.SetNonblock(int(file.Fd()), nonblock)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Read executes syscall.Read on file descriptor
|
|
45
|
+
func Read(fd int, b []byte) (int, error) {
|
|
46
|
+
return syscall.Read(int(fd), b)
|
|
47
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// +build windows
|
|
2
|
+
|
|
3
|
+
package util
|
|
4
|
+
|
|
5
|
+
import (
|
|
6
|
+
"fmt"
|
|
7
|
+
"os"
|
|
8
|
+
"os/exec"
|
|
9
|
+
"strings"
|
|
10
|
+
"sync/atomic"
|
|
11
|
+
"syscall"
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
var shellPath atomic.Value
|
|
15
|
+
|
|
16
|
+
// ExecCommand executes the given command with $SHELL
|
|
17
|
+
func ExecCommand(command string, setpgid bool) *exec.Cmd {
|
|
18
|
+
var shell string
|
|
19
|
+
if cached := shellPath.Load(); cached != nil {
|
|
20
|
+
shell = cached.(string)
|
|
21
|
+
} else {
|
|
22
|
+
shell = os.Getenv("SHELL")
|
|
23
|
+
if len(shell) == 0 {
|
|
24
|
+
shell = "cmd"
|
|
25
|
+
} else if strings.Contains(shell, "/") {
|
|
26
|
+
out, err := exec.Command("cygpath", "-w", shell).Output()
|
|
27
|
+
if err == nil {
|
|
28
|
+
shell = strings.Trim(string(out), "\n")
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
shellPath.Store(shell)
|
|
32
|
+
}
|
|
33
|
+
return ExecCommandWith(shell, command, setpgid)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ExecCommandWith executes the given command with the specified shell
|
|
37
|
+
// FIXME: setpgid is unused. We set it in the Unix implementation so that we
|
|
38
|
+
// can kill preview process with its child processes at once.
|
|
39
|
+
// NOTE: For "powershell", we should ideally set output encoding to UTF8,
|
|
40
|
+
// but it is left as is now because no adverse effect has been observed.
|
|
41
|
+
func ExecCommandWith(shell string, command string, setpgid bool) *exec.Cmd {
|
|
42
|
+
var cmd *exec.Cmd
|
|
43
|
+
if strings.Contains(shell, "cmd") {
|
|
44
|
+
cmd = exec.Command(shell)
|
|
45
|
+
cmd.SysProcAttr = &syscall.SysProcAttr{
|
|
46
|
+
HideWindow: false,
|
|
47
|
+
CmdLine: fmt.Sprintf(` /v:on/s/c "%s"`, command),
|
|
48
|
+
CreationFlags: 0,
|
|
49
|
+
}
|
|
50
|
+
return cmd
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if strings.Contains(shell, "pwsh") || strings.Contains(shell, "powershell") {
|
|
54
|
+
cmd = exec.Command(shell, "-NoProfile", "-Command", command)
|
|
55
|
+
} else {
|
|
56
|
+
cmd = exec.Command(shell, "-c", command)
|
|
57
|
+
}
|
|
58
|
+
cmd.SysProcAttr = &syscall.SysProcAttr{
|
|
59
|
+
HideWindow: false,
|
|
60
|
+
CreationFlags: 0,
|
|
61
|
+
}
|
|
62
|
+
return cmd
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// KillCommand kills the process for the given command
|
|
66
|
+
func KillCommand(cmd *exec.Cmd) error {
|
|
67
|
+
return cmd.Process.Kill()
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// IsWindows returns true on Windows
|
|
71
|
+
func IsWindows() bool {
|
|
72
|
+
return true
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// SetNonblock executes syscall.SetNonblock on file descriptor
|
|
76
|
+
func SetNonblock(file *os.File, nonblock bool) {
|
|
77
|
+
syscall.SetNonblock(syscall.Handle(file.Fd()), nonblock)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Read executes syscall.Read on file descriptor
|
|
81
|
+
func Read(fd int, b []byte) (int, error) {
|
|
82
|
+
return syscall.Read(syscall.Handle(fd), b)
|
|
83
|
+
}
|