doing 2.0.18 → 2.0.22
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -0
- data/Gemfile.lock +15 -5
- data/README.md +1 -1
- data/bin/doing +2 -18
- data/doing.gemspec +5 -4
- data/doing.rdoc +2 -2
- data/generate_completions.sh +3 -3
- data/lib/doing/cli_status.rb +6 -2
- data/lib/doing/completion/bash_completion.rb +185 -0
- data/lib/doing/completion/fish_completion.rb +175 -0
- data/lib/doing/completion/string.rb +17 -0
- data/lib/doing/completion/zsh_completion.rb +140 -0
- data/lib/doing/completion.rb +39 -0
- data/lib/doing/version.rb +1 -1
- data/lib/doing/wwid.rb +18 -6
- data/lib/doing.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
- data/scripts/generate_bash_completions.rb +6 -12
- data/scripts/generate_fish_completions.rb +7 -16
- data/scripts/generate_zsh_completions.rb +6 -15
- metadata +144 -9
@@ -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
|
+
}
|