doing 2.0.18 → 2.0.22

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 (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,85 @@
1
+ package fzf
2
+
3
+ import (
4
+ "math"
5
+ "os"
6
+ "time"
7
+
8
+ "github.com/junegunn/fzf/src/util"
9
+ )
10
+
11
+ const (
12
+ // Core
13
+ coordinatorDelayMax time.Duration = 100 * time.Millisecond
14
+ coordinatorDelayStep time.Duration = 10 * time.Millisecond
15
+
16
+ // Reader
17
+ readerBufferSize = 64 * 1024
18
+ readerPollIntervalMin = 10 * time.Millisecond
19
+ readerPollIntervalStep = 5 * time.Millisecond
20
+ readerPollIntervalMax = 50 * time.Millisecond
21
+
22
+ // Terminal
23
+ initialDelay = 20 * time.Millisecond
24
+ initialDelayTac = 100 * time.Millisecond
25
+ spinnerDuration = 100 * time.Millisecond
26
+ previewCancelWait = 500 * time.Millisecond
27
+ previewChunkDelay = 100 * time.Millisecond
28
+ previewDelayed = 500 * time.Millisecond
29
+ maxPatternLength = 300
30
+ maxMulti = math.MaxInt32
31
+
32
+ // Matcher
33
+ numPartitionsMultiplier = 8
34
+ maxPartitions = 32
35
+ progressMinDuration = 200 * time.Millisecond
36
+
37
+ // Capacity of each chunk
38
+ chunkSize int = 100
39
+
40
+ // Pre-allocated memory slices to minimize GC
41
+ slab16Size int = 100 * 1024 // 200KB * 32 = 12.8MB
42
+ slab32Size int = 2048 // 8KB * 32 = 256KB
43
+
44
+ // Do not cache results of low selectivity queries
45
+ queryCacheMax int = chunkSize / 5
46
+
47
+ // Not to cache mergers with large lists
48
+ mergerCacheMax int = 100000
49
+
50
+ // History
51
+ defaultHistoryMax int = 1000
52
+
53
+ // Jump labels
54
+ defaultJumpLabels string = "asdfghjklqwertyuiopzxcvbnm1234567890ASDFGHJKLQWERTYUIOPZXCVBNM`~;:,<.>/?'\"!@#$%^&*()[{]}-_=+"
55
+ )
56
+
57
+ var defaultCommand string
58
+
59
+ func init() {
60
+ if !util.IsWindows() {
61
+ defaultCommand = `set -o pipefail; command find -L . -mindepth 1 \( -path '*/\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \) -prune -o -type f -print -o -type l -print 2> /dev/null | cut -b3-`
62
+ } else if os.Getenv("TERM") == "cygwin" {
63
+ defaultCommand = `sh -c "command find -L . -mindepth 1 -path '*/\.*' -prune -o -type f -print -o -type l -print 2> /dev/null | cut -b3-"`
64
+ }
65
+ }
66
+
67
+ // fzf events
68
+ const (
69
+ EvtReadNew util.EventType = iota
70
+ EvtReadFin
71
+ EvtSearchNew
72
+ EvtSearchProgress
73
+ EvtSearchFin
74
+ EvtHeader
75
+ EvtReady
76
+ EvtQuit
77
+ )
78
+
79
+ const (
80
+ exitCancel = -1
81
+ exitOk = 0
82
+ exitNoMatch = 1
83
+ exitError = 2
84
+ exitInterrupt = 130
85
+ )
@@ -0,0 +1,351 @@
1
+ /*
2
+ Package fzf implements fzf, a command-line fuzzy finder.
3
+
4
+ The MIT License (MIT)
5
+
6
+ Copyright (c) 2013-2021 Junegunn Choi
7
+
8
+ Permission is hereby granted, free of charge, to any person obtaining a copy
9
+ of this software and associated documentation files (the "Software"), to deal
10
+ in the Software without restriction, including without limitation the rights
11
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12
+ copies of the Software, and to permit persons to whom the Software is
13
+ furnished to do so, subject to the following conditions:
14
+
15
+ The above copyright notice and this permission notice shall be included in
16
+ all copies or substantial portions of the Software.
17
+
18
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24
+ THE SOFTWARE.
25
+ */
26
+ package fzf
27
+
28
+ import (
29
+ "fmt"
30
+ "os"
31
+ "time"
32
+
33
+ "github.com/junegunn/fzf/src/util"
34
+ )
35
+
36
+ /*
37
+ Reader -> EvtReadFin
38
+ Reader -> EvtReadNew -> Matcher (restart)
39
+ Terminal -> EvtSearchNew:bool -> Matcher (restart)
40
+ Matcher -> EvtSearchProgress -> Terminal (update info)
41
+ Matcher -> EvtSearchFin -> Terminal (update list)
42
+ Matcher -> EvtHeader -> Terminal (update header)
43
+ */
44
+
45
+ // Run starts fzf
46
+ func Run(opts *Options, version string, revision string) {
47
+ sort := opts.Sort > 0
48
+ sortCriteria = opts.Criteria
49
+
50
+ if opts.Version {
51
+ if len(revision) > 0 {
52
+ fmt.Printf("%s (%s)\n", version, revision)
53
+ } else {
54
+ fmt.Println(version)
55
+ }
56
+ os.Exit(exitOk)
57
+ }
58
+
59
+ // Event channel
60
+ eventBox := util.NewEventBox()
61
+
62
+ // ANSI code processor
63
+ ansiProcessor := func(data []byte) (util.Chars, *[]ansiOffset) {
64
+ return util.ToChars(data), nil
65
+ }
66
+
67
+ var lineAnsiState, prevLineAnsiState *ansiState
68
+ if opts.Ansi {
69
+ if opts.Theme.Colored {
70
+ ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
71
+ prevLineAnsiState = lineAnsiState
72
+ trimmed, offsets, newState := extractColor(string(data), lineAnsiState, nil)
73
+ lineAnsiState = newState
74
+ return util.ToChars([]byte(trimmed)), offsets
75
+ }
76
+ } else {
77
+ // When color is disabled but ansi option is given,
78
+ // we simply strip out ANSI codes from the input
79
+ ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
80
+ trimmed, _, _ := extractColor(string(data), nil, nil)
81
+ return util.ToChars([]byte(trimmed)), nil
82
+ }
83
+ }
84
+ }
85
+
86
+ // Chunk list
87
+ var chunkList *ChunkList
88
+ var itemIndex int32
89
+ header := make([]string, 0, opts.HeaderLines)
90
+ if len(opts.WithNth) == 0 {
91
+ chunkList = NewChunkList(func(item *Item, data []byte) bool {
92
+ if len(header) < opts.HeaderLines {
93
+ header = append(header, string(data))
94
+ eventBox.Set(EvtHeader, header)
95
+ return false
96
+ }
97
+ item.text, item.colors = ansiProcessor(data)
98
+ item.text.Index = itemIndex
99
+ itemIndex++
100
+ return true
101
+ })
102
+ } else {
103
+ chunkList = NewChunkList(func(item *Item, data []byte) bool {
104
+ tokens := Tokenize(string(data), opts.Delimiter)
105
+ if opts.Ansi && opts.Theme.Colored && len(tokens) > 1 {
106
+ var ansiState *ansiState
107
+ if prevLineAnsiState != nil {
108
+ ansiStateDup := *prevLineAnsiState
109
+ ansiState = &ansiStateDup
110
+ }
111
+ for _, token := range tokens {
112
+ prevAnsiState := ansiState
113
+ _, _, ansiState = extractColor(token.text.ToString(), ansiState, nil)
114
+ if prevAnsiState != nil {
115
+ token.text.Prepend("\x1b[m" + prevAnsiState.ToString())
116
+ } else {
117
+ token.text.Prepend("\x1b[m")
118
+ }
119
+ }
120
+ }
121
+ trans := Transform(tokens, opts.WithNth)
122
+ transformed := joinTokens(trans)
123
+ if len(header) < opts.HeaderLines {
124
+ header = append(header, transformed)
125
+ eventBox.Set(EvtHeader, header)
126
+ return false
127
+ }
128
+ item.text, item.colors = ansiProcessor([]byte(transformed))
129
+ item.text.TrimTrailingWhitespaces()
130
+ item.text.Index = itemIndex
131
+ item.origText = &data
132
+ itemIndex++
133
+ return true
134
+ })
135
+ }
136
+
137
+ // Reader
138
+ streamingFilter := opts.Filter != nil && !sort && !opts.Tac && !opts.Sync
139
+ var reader *Reader
140
+ if !streamingFilter {
141
+ reader = NewReader(func(data []byte) bool {
142
+ return chunkList.Push(data)
143
+ }, eventBox, opts.ReadZero, opts.Filter == nil)
144
+ go reader.ReadSource()
145
+ }
146
+
147
+ // Matcher
148
+ forward := true
149
+ for _, cri := range opts.Criteria[1:] {
150
+ if cri == byEnd {
151
+ forward = false
152
+ break
153
+ }
154
+ if cri == byBegin {
155
+ break
156
+ }
157
+ }
158
+ patternBuilder := func(runes []rune) *Pattern {
159
+ return BuildPattern(
160
+ opts.Fuzzy, opts.FuzzyAlgo, opts.Extended, opts.Case, opts.Normalize, forward,
161
+ opts.Filter == nil, opts.Nth, opts.Delimiter, runes)
162
+ }
163
+ matcher := NewMatcher(patternBuilder, sort, opts.Tac, eventBox)
164
+
165
+ // Filtering mode
166
+ if opts.Filter != nil {
167
+ if opts.PrintQuery {
168
+ opts.Printer(*opts.Filter)
169
+ }
170
+
171
+ pattern := patternBuilder([]rune(*opts.Filter))
172
+ matcher.sort = pattern.sortable
173
+
174
+ found := false
175
+ if streamingFilter {
176
+ slab := util.MakeSlab(slab16Size, slab32Size)
177
+ reader := NewReader(
178
+ func(runes []byte) bool {
179
+ item := Item{}
180
+ if chunkList.trans(&item, runes) {
181
+ if result, _, _ := pattern.MatchItem(&item, false, slab); result != nil {
182
+ opts.Printer(item.text.ToString())
183
+ found = true
184
+ }
185
+ }
186
+ return false
187
+ }, eventBox, opts.ReadZero, false)
188
+ reader.ReadSource()
189
+ } else {
190
+ eventBox.Unwatch(EvtReadNew)
191
+ eventBox.WaitFor(EvtReadFin)
192
+
193
+ snapshot, _ := chunkList.Snapshot()
194
+ merger, _ := matcher.scan(MatchRequest{
195
+ chunks: snapshot,
196
+ pattern: pattern})
197
+ for i := 0; i < merger.Length(); i++ {
198
+ opts.Printer(merger.Get(i).item.AsString(opts.Ansi))
199
+ found = true
200
+ }
201
+ }
202
+ if found {
203
+ os.Exit(exitOk)
204
+ }
205
+ os.Exit(exitNoMatch)
206
+ }
207
+
208
+ // Synchronous search
209
+ if opts.Sync {
210
+ eventBox.Unwatch(EvtReadNew)
211
+ eventBox.WaitFor(EvtReadFin)
212
+ }
213
+
214
+ // Go interactive
215
+ go matcher.Loop()
216
+
217
+ // Terminal I/O
218
+ terminal := NewTerminal(opts, eventBox)
219
+ deferred := opts.Select1 || opts.Exit0
220
+ go terminal.Loop()
221
+ if !deferred {
222
+ terminal.startChan <- true
223
+ }
224
+
225
+ // Event coordination
226
+ reading := true
227
+ clearCache := util.Once(false)
228
+ clearSelection := util.Once(false)
229
+ ticks := 0
230
+ var nextCommand *string
231
+ restart := func(command string) {
232
+ reading = true
233
+ clearCache = util.Once(true)
234
+ clearSelection = util.Once(true)
235
+ chunkList.Clear()
236
+ itemIndex = 0
237
+ header = make([]string, 0, opts.HeaderLines)
238
+ go reader.restart(command)
239
+ }
240
+ eventBox.Watch(EvtReadNew)
241
+ query := []rune{}
242
+ for {
243
+ delay := true
244
+ ticks++
245
+ input := func() []rune {
246
+ paused, input := terminal.Input()
247
+ if !paused {
248
+ query = input
249
+ }
250
+ return query
251
+ }
252
+ eventBox.Wait(func(events *util.Events) {
253
+ if _, fin := (*events)[EvtReadFin]; fin {
254
+ delete(*events, EvtReadNew)
255
+ }
256
+ for evt, value := range *events {
257
+ switch evt {
258
+ case EvtQuit:
259
+ if reading {
260
+ reader.terminate()
261
+ }
262
+ os.Exit(value.(int))
263
+ case EvtReadNew, EvtReadFin:
264
+ if evt == EvtReadFin && nextCommand != nil {
265
+ restart(*nextCommand)
266
+ nextCommand = nil
267
+ break
268
+ } else {
269
+ reading = reading && evt == EvtReadNew
270
+ }
271
+ snapshot, count := chunkList.Snapshot()
272
+ terminal.UpdateCount(count, !reading, value.(*string))
273
+ if opts.Sync {
274
+ opts.Sync = false
275
+ terminal.UpdateList(PassMerger(&snapshot, opts.Tac), false)
276
+ }
277
+ matcher.Reset(snapshot, input(), false, !reading, sort, clearCache())
278
+
279
+ case EvtSearchNew:
280
+ var command *string
281
+ switch val := value.(type) {
282
+ case searchRequest:
283
+ sort = val.sort
284
+ command = val.command
285
+ }
286
+ if command != nil {
287
+ if reading {
288
+ reader.terminate()
289
+ nextCommand = command
290
+ } else {
291
+ restart(*command)
292
+ }
293
+ break
294
+ }
295
+ snapshot, _ := chunkList.Snapshot()
296
+ matcher.Reset(snapshot, input(), true, !reading, sort, clearCache())
297
+ delay = false
298
+
299
+ case EvtSearchProgress:
300
+ switch val := value.(type) {
301
+ case float32:
302
+ terminal.UpdateProgress(val)
303
+ }
304
+
305
+ case EvtHeader:
306
+ headerPadded := make([]string, opts.HeaderLines)
307
+ copy(headerPadded, value.([]string))
308
+ terminal.UpdateHeader(headerPadded)
309
+
310
+ case EvtSearchFin:
311
+ switch val := value.(type) {
312
+ case *Merger:
313
+ if deferred {
314
+ count := val.Length()
315
+ if opts.Select1 && count > 1 || opts.Exit0 && !opts.Select1 && count > 0 {
316
+ deferred = false
317
+ terminal.startChan <- true
318
+ } else if val.final {
319
+ if opts.Exit0 && count == 0 || opts.Select1 && count == 1 {
320
+ if opts.PrintQuery {
321
+ opts.Printer(opts.Query)
322
+ }
323
+ if len(opts.Expect) > 0 {
324
+ opts.Printer("")
325
+ }
326
+ for i := 0; i < count; i++ {
327
+ opts.Printer(val.Get(i).item.AsString(opts.Ansi))
328
+ }
329
+ if count > 0 {
330
+ os.Exit(exitOk)
331
+ }
332
+ os.Exit(exitNoMatch)
333
+ }
334
+ deferred = false
335
+ terminal.startChan <- true
336
+ }
337
+ }
338
+ terminal.UpdateList(val, clearSelection())
339
+ }
340
+ }
341
+ }
342
+ events.Clear()
343
+ })
344
+ if delay && reading {
345
+ dur := util.DurWithin(
346
+ time.Duration(ticks)*coordinatorDelayStep,
347
+ 0, coordinatorDelayMax)
348
+ time.Sleep(dur)
349
+ }
350
+ }
351
+ }
@@ -0,0 +1,96 @@
1
+ package fzf
2
+
3
+ import (
4
+ "errors"
5
+ "io/ioutil"
6
+ "os"
7
+ "strings"
8
+ )
9
+
10
+ // History struct represents input history
11
+ type History struct {
12
+ path string
13
+ lines []string
14
+ modified map[int]string
15
+ maxSize int
16
+ cursor int
17
+ }
18
+
19
+ // NewHistory returns the pointer to a new History struct
20
+ func NewHistory(path string, maxSize int) (*History, error) {
21
+ fmtError := func(e error) error {
22
+ if os.IsPermission(e) {
23
+ return errors.New("permission denied: " + path)
24
+ }
25
+ return errors.New("invalid history file: " + e.Error())
26
+ }
27
+
28
+ // Read history file
29
+ data, err := ioutil.ReadFile(path)
30
+ if err != nil {
31
+ // If it doesn't exist, check if we can create a file with the name
32
+ if os.IsNotExist(err) {
33
+ data = []byte{}
34
+ if err := ioutil.WriteFile(path, data, 0600); err != nil {
35
+ return nil, fmtError(err)
36
+ }
37
+ } else {
38
+ return nil, fmtError(err)
39
+ }
40
+ }
41
+ // Split lines and limit the maximum number of lines
42
+ lines := strings.Split(strings.Trim(string(data), "\n"), "\n")
43
+ if len(lines[len(lines)-1]) > 0 {
44
+ lines = append(lines, "")
45
+ }
46
+ return &History{
47
+ path: path,
48
+ maxSize: maxSize,
49
+ lines: lines,
50
+ modified: make(map[int]string),
51
+ cursor: len(lines) - 1}, nil
52
+ }
53
+
54
+ func (h *History) append(line string) error {
55
+ // We don't append empty lines
56
+ if len(line) == 0 {
57
+ return nil
58
+ }
59
+
60
+ lines := append(h.lines[:len(h.lines)-1], line)
61
+ if len(lines) > h.maxSize {
62
+ lines = lines[len(lines)-h.maxSize:]
63
+ }
64
+ h.lines = append(lines, "")
65
+ return ioutil.WriteFile(h.path, []byte(strings.Join(h.lines, "\n")), 0600)
66
+ }
67
+
68
+ func (h *History) override(str string) {
69
+ // You can update the history but they're not written to the file
70
+ if h.cursor == len(h.lines)-1 {
71
+ h.lines[h.cursor] = str
72
+ } else if h.cursor < len(h.lines)-1 {
73
+ h.modified[h.cursor] = str
74
+ }
75
+ }
76
+
77
+ func (h *History) current() string {
78
+ if str, prs := h.modified[h.cursor]; prs {
79
+ return str
80
+ }
81
+ return h.lines[h.cursor]
82
+ }
83
+
84
+ func (h *History) previous() string {
85
+ if h.cursor > 0 {
86
+ h.cursor--
87
+ }
88
+ return h.current()
89
+ }
90
+
91
+ func (h *History) next() string {
92
+ if h.cursor < len(h.lines)-1 {
93
+ h.cursor++
94
+ }
95
+ return h.current()
96
+ }
@@ -0,0 +1,68 @@
1
+ package fzf
2
+
3
+ import (
4
+ "io/ioutil"
5
+ "os"
6
+ "runtime"
7
+ "testing"
8
+ )
9
+
10
+ func TestHistory(t *testing.T) {
11
+ maxHistory := 50
12
+
13
+ // Invalid arguments
14
+ var paths []string
15
+ if runtime.GOOS == "windows" {
16
+ // GOPATH should exist, so we shouldn't be able to override it
17
+ paths = []string{os.Getenv("GOPATH")}
18
+ } else {
19
+ paths = []string{"/etc", "/proc"}
20
+ }
21
+
22
+ for _, path := range paths {
23
+ if _, e := NewHistory(path, maxHistory); e == nil {
24
+ t.Error("Error expected for: " + path)
25
+ }
26
+ }
27
+
28
+ f, _ := ioutil.TempFile("", "fzf-history")
29
+ f.Close()
30
+
31
+ { // Append lines
32
+ h, _ := NewHistory(f.Name(), maxHistory)
33
+ for i := 0; i < maxHistory+10; i++ {
34
+ h.append("foobar")
35
+ }
36
+ }
37
+ { // Read lines
38
+ h, _ := NewHistory(f.Name(), maxHistory)
39
+ if len(h.lines) != maxHistory+1 {
40
+ t.Errorf("Expected: %d, actual: %d\n", maxHistory+1, len(h.lines))
41
+ }
42
+ for i := 0; i < maxHistory; i++ {
43
+ if h.lines[i] != "foobar" {
44
+ t.Error("Expected: foobar, actual: " + h.lines[i])
45
+ }
46
+ }
47
+ }
48
+ { // Append lines
49
+ h, _ := NewHistory(f.Name(), maxHistory)
50
+ h.append("barfoo")
51
+ h.append("")
52
+ h.append("foobarbaz")
53
+ }
54
+ { // Read lines again
55
+ h, _ := NewHistory(f.Name(), maxHistory)
56
+ if len(h.lines) != maxHistory+1 {
57
+ t.Errorf("Expected: %d, actual: %d\n", maxHistory+1, len(h.lines))
58
+ }
59
+ compare := func(idx int, exp string) {
60
+ if h.lines[idx] != exp {
61
+ t.Errorf("Expected: %s, actual: %s\n", exp, h.lines[idx])
62
+ }
63
+ }
64
+ compare(maxHistory-3, "foobar")
65
+ compare(maxHistory-2, "barfoo")
66
+ compare(maxHistory-1, "foobarbaz")
67
+ }
68
+ }
@@ -0,0 +1,44 @@
1
+ package fzf
2
+
3
+ import (
4
+ "github.com/junegunn/fzf/src/util"
5
+ )
6
+
7
+ // Item represents each input line. 56 bytes.
8
+ type Item struct {
9
+ text util.Chars // 32 = 24 + 1 + 1 + 2 + 4
10
+ transformed *[]Token // 8
11
+ origText *[]byte // 8
12
+ colors *[]ansiOffset // 8
13
+ }
14
+
15
+ // Index returns ordinal index of the Item
16
+ func (item *Item) Index() int32 {
17
+ return item.text.Index
18
+ }
19
+
20
+ var minItem = Item{text: util.Chars{Index: -1}}
21
+
22
+ func (item *Item) TrimLength() uint16 {
23
+ return item.text.TrimLength()
24
+ }
25
+
26
+ // Colors returns ansiOffsets of the Item
27
+ func (item *Item) Colors() []ansiOffset {
28
+ if item.colors == nil {
29
+ return []ansiOffset{}
30
+ }
31
+ return *item.colors
32
+ }
33
+
34
+ // AsString returns the original string
35
+ func (item *Item) AsString(stripAnsi bool) string {
36
+ if item.origText != nil {
37
+ if stripAnsi {
38
+ trimmed, _, _ := extractColor(string(*item.origText), nil, nil)
39
+ return trimmed
40
+ }
41
+ return string(*item.origText)
42
+ }
43
+ return item.text.ToString()
44
+ }
@@ -0,0 +1,23 @@
1
+ package fzf
2
+
3
+ import (
4
+ "testing"
5
+
6
+ "github.com/junegunn/fzf/src/util"
7
+ )
8
+
9
+ func TestStringPtr(t *testing.T) {
10
+ orig := []byte("\x1b[34mfoo")
11
+ text := []byte("\x1b[34mbar")
12
+ item := Item{origText: &orig, text: util.ToChars(text)}
13
+ if item.AsString(true) != "foo" || item.AsString(false) != string(orig) {
14
+ t.Fail()
15
+ }
16
+ if item.AsString(true) != "foo" {
17
+ t.Fail()
18
+ }
19
+ item.origText = nil
20
+ if item.AsString(true) != string(text) || item.AsString(false) != string(text) {
21
+ t.Fail()
22
+ }
23
+ }