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