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,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
+ }