doing 2.0.18 → 2.0.22

Sign up to get free protection for your applications and to get access to all the features.
Files changed (107) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -0
  3. data/Gemfile.lock +15 -5
  4. data/README.md +1 -1
  5. data/bin/doing +2 -18
  6. data/doing.gemspec +5 -4
  7. data/doing.rdoc +2 -2
  8. data/generate_completions.sh +3 -3
  9. data/lib/doing/cli_status.rb +6 -2
  10. data/lib/doing/completion/bash_completion.rb +185 -0
  11. data/lib/doing/completion/fish_completion.rb +175 -0
  12. data/lib/doing/completion/string.rb +17 -0
  13. data/lib/doing/completion/zsh_completion.rb +140 -0
  14. data/lib/doing/completion.rb +39 -0
  15. data/lib/doing/version.rb +1 -1
  16. data/lib/doing/wwid.rb +18 -6
  17. data/lib/doing.rb +1 -1
  18. data/lib/helpers/fzf/.goreleaser.yml +119 -0
  19. data/lib/helpers/fzf/.rubocop.yml +28 -0
  20. data/lib/helpers/fzf/ADVANCED.md +565 -0
  21. data/lib/helpers/fzf/BUILD.md +49 -0
  22. data/lib/helpers/fzf/CHANGELOG.md +1193 -0
  23. data/lib/helpers/fzf/Dockerfile +11 -0
  24. data/lib/helpers/fzf/LICENSE +21 -0
  25. data/lib/helpers/fzf/Makefile +166 -0
  26. data/lib/helpers/fzf/README-VIM.md +486 -0
  27. data/lib/helpers/fzf/README.md +712 -0
  28. data/lib/helpers/fzf/bin/fzf-tmux +233 -0
  29. data/lib/helpers/fzf/doc/fzf.txt +512 -0
  30. data/lib/helpers/fzf/go.mod +17 -0
  31. data/lib/helpers/fzf/go.sum +31 -0
  32. data/lib/helpers/fzf/install +382 -0
  33. data/lib/helpers/fzf/install.ps1 +65 -0
  34. data/lib/helpers/fzf/main.go +14 -0
  35. data/lib/helpers/fzf/man/man1/fzf-tmux.1 +68 -0
  36. data/lib/helpers/fzf/man/man1/fzf.1 +1001 -0
  37. data/lib/helpers/fzf/plugin/fzf.vim +1048 -0
  38. data/lib/helpers/fzf/shell/completion.bash +381 -0
  39. data/lib/helpers/fzf/shell/completion.zsh +329 -0
  40. data/lib/helpers/fzf/shell/key-bindings.bash +96 -0
  41. data/lib/helpers/fzf/shell/key-bindings.fish +172 -0
  42. data/lib/helpers/fzf/shell/key-bindings.zsh +114 -0
  43. data/lib/helpers/fzf/src/LICENSE +21 -0
  44. data/lib/helpers/fzf/src/algo/algo.go +884 -0
  45. data/lib/helpers/fzf/src/algo/algo_test.go +197 -0
  46. data/lib/helpers/fzf/src/algo/normalize.go +492 -0
  47. data/lib/helpers/fzf/src/ansi.go +409 -0
  48. data/lib/helpers/fzf/src/ansi_test.go +427 -0
  49. data/lib/helpers/fzf/src/cache.go +81 -0
  50. data/lib/helpers/fzf/src/cache_test.go +39 -0
  51. data/lib/helpers/fzf/src/chunklist.go +89 -0
  52. data/lib/helpers/fzf/src/chunklist_test.go +80 -0
  53. data/lib/helpers/fzf/src/constants.go +85 -0
  54. data/lib/helpers/fzf/src/core.go +351 -0
  55. data/lib/helpers/fzf/src/history.go +96 -0
  56. data/lib/helpers/fzf/src/history_test.go +68 -0
  57. data/lib/helpers/fzf/src/item.go +44 -0
  58. data/lib/helpers/fzf/src/item_test.go +23 -0
  59. data/lib/helpers/fzf/src/matcher.go +235 -0
  60. data/lib/helpers/fzf/src/merger.go +120 -0
  61. data/lib/helpers/fzf/src/merger_test.go +88 -0
  62. data/lib/helpers/fzf/src/options.go +1691 -0
  63. data/lib/helpers/fzf/src/options_test.go +457 -0
  64. data/lib/helpers/fzf/src/pattern.go +425 -0
  65. data/lib/helpers/fzf/src/pattern_test.go +209 -0
  66. data/lib/helpers/fzf/src/protector/protector.go +8 -0
  67. data/lib/helpers/fzf/src/protector/protector_openbsd.go +10 -0
  68. data/lib/helpers/fzf/src/reader.go +201 -0
  69. data/lib/helpers/fzf/src/reader_test.go +63 -0
  70. data/lib/helpers/fzf/src/result.go +243 -0
  71. data/lib/helpers/fzf/src/result_others.go +16 -0
  72. data/lib/helpers/fzf/src/result_test.go +159 -0
  73. data/lib/helpers/fzf/src/result_x86.go +16 -0
  74. data/lib/helpers/fzf/src/terminal.go +2832 -0
  75. data/lib/helpers/fzf/src/terminal_test.go +638 -0
  76. data/lib/helpers/fzf/src/terminal_unix.go +26 -0
  77. data/lib/helpers/fzf/src/terminal_windows.go +45 -0
  78. data/lib/helpers/fzf/src/tokenizer.go +253 -0
  79. data/lib/helpers/fzf/src/tokenizer_test.go +112 -0
  80. data/lib/helpers/fzf/src/tui/dummy.go +46 -0
  81. data/lib/helpers/fzf/src/tui/light.go +987 -0
  82. data/lib/helpers/fzf/src/tui/light_unix.go +110 -0
  83. data/lib/helpers/fzf/src/tui/light_windows.go +145 -0
  84. data/lib/helpers/fzf/src/tui/tcell.go +721 -0
  85. data/lib/helpers/fzf/src/tui/tcell_test.go +392 -0
  86. data/lib/helpers/fzf/src/tui/ttyname_unix.go +47 -0
  87. data/lib/helpers/fzf/src/tui/ttyname_windows.go +14 -0
  88. data/lib/helpers/fzf/src/tui/tui.go +625 -0
  89. data/lib/helpers/fzf/src/tui/tui_test.go +20 -0
  90. data/lib/helpers/fzf/src/util/atomicbool.go +34 -0
  91. data/lib/helpers/fzf/src/util/atomicbool_test.go +17 -0
  92. data/lib/helpers/fzf/src/util/chars.go +198 -0
  93. data/lib/helpers/fzf/src/util/chars_test.go +46 -0
  94. data/lib/helpers/fzf/src/util/eventbox.go +96 -0
  95. data/lib/helpers/fzf/src/util/eventbox_test.go +61 -0
  96. data/lib/helpers/fzf/src/util/slab.go +12 -0
  97. data/lib/helpers/fzf/src/util/util.go +138 -0
  98. data/lib/helpers/fzf/src/util/util_test.go +40 -0
  99. data/lib/helpers/fzf/src/util/util_unix.go +47 -0
  100. data/lib/helpers/fzf/src/util/util_windows.go +83 -0
  101. data/lib/helpers/fzf/test/fzf.vader +175 -0
  102. data/lib/helpers/fzf/test/test_go.rb +2626 -0
  103. data/lib/helpers/fzf/uninstall +117 -0
  104. data/scripts/generate_bash_completions.rb +6 -12
  105. data/scripts/generate_fish_completions.rb +7 -16
  106. data/scripts/generate_zsh_completions.rb +6 -15
  107. 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,12 @@
1
+ package util
2
+
3
+ type Slab struct {
4
+ I16 []int16
5
+ I32 []int32
6
+ }
7
+
8
+ func MakeSlab(size16 int, size32 int) *Slab {
9
+ return &Slab{
10
+ I16: make([]int16, size16),
11
+ I32: make([]int32, size32)}
12
+ }
@@ -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
+ }