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,2832 @@
1
+ package fzf
2
+
3
+ import (
4
+ "bufio"
5
+ "fmt"
6
+ "io/ioutil"
7
+ "os"
8
+ "os/signal"
9
+ "regexp"
10
+ "sort"
11
+ "strconv"
12
+ "strings"
13
+ "sync"
14
+ "syscall"
15
+ "time"
16
+
17
+ "github.com/mattn/go-runewidth"
18
+ "github.com/rivo/uniseg"
19
+
20
+ "github.com/junegunn/fzf/src/tui"
21
+ "github.com/junegunn/fzf/src/util"
22
+ )
23
+
24
+ // import "github.com/pkg/profile"
25
+
26
+ /*
27
+ Placeholder regex is used to extract placeholders from fzf's template
28
+ strings. Acts as input validation for parsePlaceholder function.
29
+ Describes the syntax, but it is fairly lenient.
30
+
31
+ The following pseudo regex has been reverse engineered from the
32
+ implementation. It is overly strict, but better describes whats possible.
33
+ As such it is not useful for validation, but rather to generate test
34
+ cases for example.
35
+
36
+ \\?(?: # escaped type
37
+ {\+?s?f?RANGE(?:,RANGE)*} # token type
38
+ |{q} # query type
39
+ |{\+?n?f?} # item type (notice no mandatory element inside brackets)
40
+ )
41
+ RANGE = (?:
42
+ (?:-?[0-9]+)?\.\.(?:-?[0-9]+)? # ellipsis syntax for token range (x..y)
43
+ |-?[0-9]+ # shorthand syntax (x..x)
44
+ )
45
+ */
46
+ var placeholder *regexp.Regexp
47
+ var whiteSuffix *regexp.Regexp
48
+ var offsetComponentRegex *regexp.Regexp
49
+ var offsetTrimCharsRegex *regexp.Regexp
50
+ var activeTempFiles []string
51
+
52
+ const ellipsis string = ".."
53
+ const clearCode string = "\x1b[2J"
54
+
55
+ func init() {
56
+ placeholder = regexp.MustCompile(`\\?(?:{[+sf]*[0-9,-.]*}|{q}|{\+?f?nf?})`)
57
+ whiteSuffix = regexp.MustCompile(`\s*$`)
58
+ offsetComponentRegex = regexp.MustCompile(`([+-][0-9]+)|(-?/[1-9][0-9]*)`)
59
+ offsetTrimCharsRegex = regexp.MustCompile(`[^0-9/+-]`)
60
+ activeTempFiles = []string{}
61
+ }
62
+
63
+ type jumpMode int
64
+
65
+ const (
66
+ jumpDisabled jumpMode = iota
67
+ jumpEnabled
68
+ jumpAcceptEnabled
69
+ )
70
+
71
+ type previewer struct {
72
+ version int64
73
+ lines []string
74
+ offset int
75
+ enabled bool
76
+ scrollable bool
77
+ final bool
78
+ following bool
79
+ spinner string
80
+ }
81
+
82
+ type previewed struct {
83
+ version int64
84
+ numLines int
85
+ offset int
86
+ filled bool
87
+ }
88
+
89
+ type eachLine struct {
90
+ line string
91
+ err error
92
+ }
93
+
94
+ type itemLine struct {
95
+ current bool
96
+ selected bool
97
+ label string
98
+ queryLen int
99
+ width int
100
+ result Result
101
+ }
102
+
103
+ var emptyLine = itemLine{}
104
+
105
+ // Terminal represents terminal input/output
106
+ type Terminal struct {
107
+ initDelay time.Duration
108
+ infoStyle infoStyle
109
+ spinner []string
110
+ prompt func()
111
+ promptLen int
112
+ pointer string
113
+ pointerLen int
114
+ pointerEmpty string
115
+ marker string
116
+ markerLen int
117
+ markerEmpty string
118
+ queryLen [2]int
119
+ layout layoutType
120
+ fullscreen bool
121
+ keepRight bool
122
+ hscroll bool
123
+ hscrollOff int
124
+ scrollOff int
125
+ wordRubout string
126
+ wordNext string
127
+ cx int
128
+ cy int
129
+ offset int
130
+ xoffset int
131
+ yanked []rune
132
+ input []rune
133
+ multi int
134
+ sort bool
135
+ toggleSort bool
136
+ delimiter Delimiter
137
+ expect map[tui.Event]string
138
+ keymap map[tui.Event][]action
139
+ pressed string
140
+ printQuery bool
141
+ history *History
142
+ cycle bool
143
+ headerFirst bool
144
+ headerLines int
145
+ header []string
146
+ header0 []string
147
+ ansi bool
148
+ tabstop int
149
+ margin [4]sizeSpec
150
+ padding [4]sizeSpec
151
+ strong tui.Attr
152
+ unicode bool
153
+ borderShape tui.BorderShape
154
+ cleanExit bool
155
+ paused bool
156
+ border tui.Window
157
+ window tui.Window
158
+ pborder tui.Window
159
+ pwindow tui.Window
160
+ count int
161
+ progress int
162
+ reading bool
163
+ running bool
164
+ failed *string
165
+ jumping jumpMode
166
+ jumpLabels string
167
+ printer func(string)
168
+ printsep string
169
+ merger *Merger
170
+ selected map[int32]selectedItem
171
+ version int64
172
+ reqBox *util.EventBox
173
+ previewOpts previewOpts
174
+ previewer previewer
175
+ previewed previewed
176
+ previewBox *util.EventBox
177
+ eventBox *util.EventBox
178
+ mutex sync.Mutex
179
+ initFunc func()
180
+ prevLines []itemLine
181
+ suppress bool
182
+ sigstop bool
183
+ startChan chan bool
184
+ killChan chan int
185
+ slab *util.Slab
186
+ theme *tui.ColorTheme
187
+ tui tui.Renderer
188
+ executing *util.AtomicBool
189
+ }
190
+
191
+ type selectedItem struct {
192
+ at time.Time
193
+ item *Item
194
+ }
195
+
196
+ type byTimeOrder []selectedItem
197
+
198
+ func (a byTimeOrder) Len() int {
199
+ return len(a)
200
+ }
201
+
202
+ func (a byTimeOrder) Swap(i, j int) {
203
+ a[i], a[j] = a[j], a[i]
204
+ }
205
+
206
+ func (a byTimeOrder) Less(i, j int) bool {
207
+ return a[i].at.Before(a[j].at)
208
+ }
209
+
210
+ const (
211
+ reqPrompt util.EventType = iota
212
+ reqInfo
213
+ reqHeader
214
+ reqList
215
+ reqJump
216
+ reqRefresh
217
+ reqReinit
218
+ reqRedraw
219
+ reqClose
220
+ reqPrintQuery
221
+ reqPreviewEnqueue
222
+ reqPreviewDisplay
223
+ reqPreviewRefresh
224
+ reqPreviewDelayed
225
+ reqQuit
226
+ )
227
+
228
+ type action struct {
229
+ t actionType
230
+ a string
231
+ }
232
+
233
+ type actionType int
234
+
235
+ const (
236
+ actIgnore actionType = iota
237
+ actInvalid
238
+ actRune
239
+ actMouse
240
+ actBeginningOfLine
241
+ actAbort
242
+ actAccept
243
+ actAcceptNonEmpty
244
+ actBackwardChar
245
+ actBackwardDeleteChar
246
+ actBackwardDeleteCharEOF
247
+ actBackwardWord
248
+ actCancel
249
+ actChangePrompt
250
+ actClearScreen
251
+ actClearQuery
252
+ actClearSelection
253
+ actClose
254
+ actDeleteChar
255
+ actDeleteCharEOF
256
+ actEndOfLine
257
+ actForwardChar
258
+ actForwardWord
259
+ actKillLine
260
+ actKillWord
261
+ actUnixLineDiscard
262
+ actUnixWordRubout
263
+ actYank
264
+ actBackwardKillWord
265
+ actSelectAll
266
+ actDeselectAll
267
+ actToggle
268
+ actToggleSearch
269
+ actToggleAll
270
+ actToggleDown
271
+ actToggleUp
272
+ actToggleIn
273
+ actToggleOut
274
+ actDown
275
+ actUp
276
+ actPageUp
277
+ actPageDown
278
+ actHalfPageUp
279
+ actHalfPageDown
280
+ actJump
281
+ actJumpAccept
282
+ actPrintQuery
283
+ actRefreshPreview
284
+ actReplaceQuery
285
+ actToggleSort
286
+ actTogglePreview
287
+ actTogglePreviewWrap
288
+ actPreview
289
+ actPreviewTop
290
+ actPreviewBottom
291
+ actPreviewUp
292
+ actPreviewDown
293
+ actPreviewPageUp
294
+ actPreviewPageDown
295
+ actPreviewHalfPageUp
296
+ actPreviewHalfPageDown
297
+ actPreviousHistory
298
+ actNextHistory
299
+ actExecute
300
+ actExecuteSilent
301
+ actExecuteMulti // Deprecated
302
+ actSigStop
303
+ actFirst
304
+ actLast
305
+ actReload
306
+ actDisableSearch
307
+ actEnableSearch
308
+ actSelect
309
+ actDeselect
310
+ actUnbind
311
+ )
312
+
313
+ type placeholderFlags struct {
314
+ plus bool
315
+ preserveSpace bool
316
+ number bool
317
+ query bool
318
+ file bool
319
+ }
320
+
321
+ type searchRequest struct {
322
+ sort bool
323
+ command *string
324
+ }
325
+
326
+ type previewRequest struct {
327
+ template string
328
+ pwindow tui.Window
329
+ list []*Item
330
+ }
331
+
332
+ type previewResult struct {
333
+ version int64
334
+ lines []string
335
+ offset int
336
+ spinner string
337
+ }
338
+
339
+ func toActions(types ...actionType) []action {
340
+ actions := make([]action, len(types))
341
+ for idx, t := range types {
342
+ actions[idx] = action{t: t, a: ""}
343
+ }
344
+ return actions
345
+ }
346
+
347
+ func defaultKeymap() map[tui.Event][]action {
348
+ keymap := make(map[tui.Event][]action)
349
+ add := func(e tui.EventType, a actionType) {
350
+ keymap[e.AsEvent()] = toActions(a)
351
+ }
352
+ addEvent := func(e tui.Event, a actionType) {
353
+ keymap[e] = toActions(a)
354
+ }
355
+
356
+ add(tui.Invalid, actInvalid)
357
+ add(tui.Resize, actClearScreen)
358
+ add(tui.CtrlA, actBeginningOfLine)
359
+ add(tui.CtrlB, actBackwardChar)
360
+ add(tui.CtrlC, actAbort)
361
+ add(tui.CtrlG, actAbort)
362
+ add(tui.CtrlQ, actAbort)
363
+ add(tui.ESC, actAbort)
364
+ add(tui.CtrlD, actDeleteCharEOF)
365
+ add(tui.CtrlE, actEndOfLine)
366
+ add(tui.CtrlF, actForwardChar)
367
+ add(tui.CtrlH, actBackwardDeleteChar)
368
+ add(tui.BSpace, actBackwardDeleteChar)
369
+ add(tui.Tab, actToggleDown)
370
+ add(tui.BTab, actToggleUp)
371
+ add(tui.CtrlJ, actDown)
372
+ add(tui.CtrlK, actUp)
373
+ add(tui.CtrlL, actClearScreen)
374
+ add(tui.CtrlM, actAccept)
375
+ add(tui.CtrlN, actDown)
376
+ add(tui.CtrlP, actUp)
377
+ add(tui.CtrlU, actUnixLineDiscard)
378
+ add(tui.CtrlW, actUnixWordRubout)
379
+ add(tui.CtrlY, actYank)
380
+ if !util.IsWindows() {
381
+ add(tui.CtrlZ, actSigStop)
382
+ }
383
+
384
+ addEvent(tui.AltKey('b'), actBackwardWord)
385
+ add(tui.SLeft, actBackwardWord)
386
+ addEvent(tui.AltKey('f'), actForwardWord)
387
+ add(tui.SRight, actForwardWord)
388
+ addEvent(tui.AltKey('d'), actKillWord)
389
+ add(tui.AltBS, actBackwardKillWord)
390
+
391
+ add(tui.Up, actUp)
392
+ add(tui.Down, actDown)
393
+ add(tui.Left, actBackwardChar)
394
+ add(tui.Right, actForwardChar)
395
+
396
+ add(tui.Home, actBeginningOfLine)
397
+ add(tui.End, actEndOfLine)
398
+ add(tui.Del, actDeleteChar)
399
+ add(tui.PgUp, actPageUp)
400
+ add(tui.PgDn, actPageDown)
401
+
402
+ add(tui.SUp, actPreviewUp)
403
+ add(tui.SDown, actPreviewDown)
404
+
405
+ add(tui.Mouse, actMouse)
406
+ add(tui.DoubleClick, actAccept)
407
+ add(tui.LeftClick, actIgnore)
408
+ add(tui.RightClick, actToggle)
409
+ return keymap
410
+ }
411
+
412
+ func trimQuery(query string) []rune {
413
+ return []rune(strings.Replace(query, "\t", " ", -1))
414
+ }
415
+
416
+ func hasPreviewAction(opts *Options) bool {
417
+ for _, actions := range opts.Keymap {
418
+ for _, action := range actions {
419
+ if action.t == actPreview {
420
+ return true
421
+ }
422
+ }
423
+ }
424
+ return false
425
+ }
426
+
427
+ func makeSpinner(unicode bool) []string {
428
+ if unicode {
429
+ return []string{`⠋`, `⠙`, `⠹`, `⠸`, `⠼`, `⠴`, `⠦`, `⠧`, `⠇`, `⠏`}
430
+ }
431
+ return []string{`-`, `\`, `|`, `/`, `-`, `\`, `|`, `/`}
432
+ }
433
+
434
+ // NewTerminal returns new Terminal object
435
+ func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
436
+ input := trimQuery(opts.Query)
437
+ var header []string
438
+ switch opts.Layout {
439
+ case layoutDefault, layoutReverseList:
440
+ header = reverseStringArray(opts.Header)
441
+ default:
442
+ header = opts.Header
443
+ }
444
+ var delay time.Duration
445
+ if opts.Tac {
446
+ delay = initialDelayTac
447
+ } else {
448
+ delay = initialDelay
449
+ }
450
+ var previewBox *util.EventBox
451
+ showPreviewWindow := len(opts.Preview.command) > 0 && !opts.Preview.hidden
452
+ if len(opts.Preview.command) > 0 || hasPreviewAction(opts) {
453
+ previewBox = util.NewEventBox()
454
+ }
455
+ strongAttr := tui.Bold
456
+ if !opts.Bold {
457
+ strongAttr = tui.AttrRegular
458
+ }
459
+ var renderer tui.Renderer
460
+ fullscreen := opts.Height.size == 0 || opts.Height.percent && opts.Height.size == 100
461
+ if fullscreen {
462
+ if tui.HasFullscreenRenderer() {
463
+ renderer = tui.NewFullscreenRenderer(opts.Theme, opts.Black, opts.Mouse)
464
+ } else {
465
+ renderer = tui.NewLightRenderer(opts.Theme, opts.Black, opts.Mouse, opts.Tabstop, opts.ClearOnExit,
466
+ true, func(h int) int { return h })
467
+ }
468
+ } else {
469
+ maxHeightFunc := func(termHeight int) int {
470
+ var maxHeight int
471
+ if opts.Height.percent {
472
+ maxHeight = util.Max(int(opts.Height.size*float64(termHeight)/100.0), opts.MinHeight)
473
+ } else {
474
+ maxHeight = int(opts.Height.size)
475
+ }
476
+
477
+ effectiveMinHeight := minHeight
478
+ if previewBox != nil && (opts.Preview.position == posUp || opts.Preview.position == posDown) {
479
+ effectiveMinHeight *= 2
480
+ }
481
+ if opts.InfoStyle != infoDefault {
482
+ effectiveMinHeight--
483
+ }
484
+ if opts.BorderShape != tui.BorderNone {
485
+ effectiveMinHeight += 2
486
+ }
487
+ return util.Min(termHeight, util.Max(maxHeight, effectiveMinHeight))
488
+ }
489
+ renderer = tui.NewLightRenderer(opts.Theme, opts.Black, opts.Mouse, opts.Tabstop, opts.ClearOnExit, false, maxHeightFunc)
490
+ }
491
+ wordRubout := "[^\\pL\\pN][\\pL\\pN]"
492
+ wordNext := "[\\pL\\pN][^\\pL\\pN]|(.$)"
493
+ if opts.FileWord {
494
+ sep := regexp.QuoteMeta(string(os.PathSeparator))
495
+ wordRubout = fmt.Sprintf("%s[^%s]", sep, sep)
496
+ wordNext = fmt.Sprintf("[^%s]%s|(.$)", sep, sep)
497
+ }
498
+ t := Terminal{
499
+ initDelay: delay,
500
+ infoStyle: opts.InfoStyle,
501
+ spinner: makeSpinner(opts.Unicode),
502
+ queryLen: [2]int{0, 0},
503
+ layout: opts.Layout,
504
+ fullscreen: fullscreen,
505
+ keepRight: opts.KeepRight,
506
+ hscroll: opts.Hscroll,
507
+ hscrollOff: opts.HscrollOff,
508
+ scrollOff: opts.ScrollOff,
509
+ wordRubout: wordRubout,
510
+ wordNext: wordNext,
511
+ cx: len(input),
512
+ cy: 0,
513
+ offset: 0,
514
+ xoffset: 0,
515
+ yanked: []rune{},
516
+ input: input,
517
+ multi: opts.Multi,
518
+ sort: opts.Sort > 0,
519
+ toggleSort: opts.ToggleSort,
520
+ delimiter: opts.Delimiter,
521
+ expect: opts.Expect,
522
+ keymap: opts.Keymap,
523
+ pressed: "",
524
+ printQuery: opts.PrintQuery,
525
+ history: opts.History,
526
+ margin: opts.Margin,
527
+ padding: opts.Padding,
528
+ unicode: opts.Unicode,
529
+ borderShape: opts.BorderShape,
530
+ cleanExit: opts.ClearOnExit,
531
+ paused: opts.Phony,
532
+ strong: strongAttr,
533
+ cycle: opts.Cycle,
534
+ headerFirst: opts.HeaderFirst,
535
+ headerLines: opts.HeaderLines,
536
+ header: header,
537
+ header0: header,
538
+ ansi: opts.Ansi,
539
+ tabstop: opts.Tabstop,
540
+ reading: true,
541
+ running: true,
542
+ failed: nil,
543
+ jumping: jumpDisabled,
544
+ jumpLabels: opts.JumpLabels,
545
+ printer: opts.Printer,
546
+ printsep: opts.PrintSep,
547
+ merger: EmptyMerger,
548
+ selected: make(map[int32]selectedItem),
549
+ reqBox: util.NewEventBox(),
550
+ previewOpts: opts.Preview,
551
+ previewer: previewer{0, []string{}, 0, showPreviewWindow, false, true, false, ""},
552
+ previewed: previewed{0, 0, 0, false},
553
+ previewBox: previewBox,
554
+ eventBox: eventBox,
555
+ mutex: sync.Mutex{},
556
+ suppress: true,
557
+ sigstop: false,
558
+ slab: util.MakeSlab(slab16Size, slab32Size),
559
+ theme: opts.Theme,
560
+ startChan: make(chan bool, 1),
561
+ killChan: make(chan int),
562
+ tui: renderer,
563
+ initFunc: func() { renderer.Init() },
564
+ executing: util.NewAtomicBool(false)}
565
+ t.prompt, t.promptLen = t.parsePrompt(opts.Prompt)
566
+ t.pointer, t.pointerLen = t.processTabs([]rune(opts.Pointer), 0)
567
+ t.marker, t.markerLen = t.processTabs([]rune(opts.Marker), 0)
568
+ // Pre-calculated empty pointer and marker signs
569
+ t.pointerEmpty = strings.Repeat(" ", t.pointerLen)
570
+ t.markerEmpty = strings.Repeat(" ", t.markerLen)
571
+
572
+ return &t
573
+ }
574
+
575
+ func (t *Terminal) parsePrompt(prompt string) (func(), int) {
576
+ var state *ansiState
577
+ trimmed, colors, _ := extractColor(prompt, state, nil)
578
+ item := &Item{text: util.ToChars([]byte(trimmed)), colors: colors}
579
+
580
+ // "Prompt> "
581
+ // ------- // Do not apply ANSI attributes to the trailing whitespaces
582
+ // // unless the part has a non-default ANSI state
583
+ loc := whiteSuffix.FindStringIndex(trimmed)
584
+ if loc != nil {
585
+ blankState := ansiOffset{[2]int32{int32(loc[0]), int32(loc[1])}, ansiState{-1, -1, tui.AttrClear, -1}}
586
+ if item.colors != nil {
587
+ lastColor := (*item.colors)[len(*item.colors)-1]
588
+ if lastColor.offset[1] < int32(loc[1]) {
589
+ blankState.offset[0] = lastColor.offset[1]
590
+ colors := append(*item.colors, blankState)
591
+ item.colors = &colors
592
+ }
593
+ } else {
594
+ colors := []ansiOffset{blankState}
595
+ item.colors = &colors
596
+ }
597
+ }
598
+ output := func() {
599
+ t.printHighlighted(
600
+ Result{item: item}, tui.ColPrompt, tui.ColPrompt, false, false)
601
+ }
602
+ _, promptLen := t.processTabs([]rune(trimmed), 0)
603
+
604
+ return output, promptLen
605
+ }
606
+
607
+ func (t *Terminal) noInfoLine() bool {
608
+ return t.infoStyle != infoDefault
609
+ }
610
+
611
+ // Input returns current query string
612
+ func (t *Terminal) Input() (bool, []rune) {
613
+ t.mutex.Lock()
614
+ defer t.mutex.Unlock()
615
+ return t.paused, copySlice(t.input)
616
+ }
617
+
618
+ // UpdateCount updates the count information
619
+ func (t *Terminal) UpdateCount(cnt int, final bool, failedCommand *string) {
620
+ t.mutex.Lock()
621
+ t.count = cnt
622
+ t.reading = !final
623
+ t.failed = failedCommand
624
+ t.mutex.Unlock()
625
+ t.reqBox.Set(reqInfo, nil)
626
+ if final {
627
+ t.reqBox.Set(reqRefresh, nil)
628
+ }
629
+ }
630
+
631
+ func reverseStringArray(input []string) []string {
632
+ size := len(input)
633
+ reversed := make([]string, size)
634
+ for idx, str := range input {
635
+ reversed[size-idx-1] = str
636
+ }
637
+ return reversed
638
+ }
639
+
640
+ // UpdateHeader updates the header
641
+ func (t *Terminal) UpdateHeader(header []string) {
642
+ t.mutex.Lock()
643
+ t.header = append(append([]string{}, t.header0...), header...)
644
+ t.mutex.Unlock()
645
+ t.reqBox.Set(reqHeader, nil)
646
+ }
647
+
648
+ // UpdateProgress updates the search progress
649
+ func (t *Terminal) UpdateProgress(progress float32) {
650
+ t.mutex.Lock()
651
+ newProgress := int(progress * 100)
652
+ changed := t.progress != newProgress
653
+ t.progress = newProgress
654
+ t.mutex.Unlock()
655
+
656
+ if changed {
657
+ t.reqBox.Set(reqInfo, nil)
658
+ }
659
+ }
660
+
661
+ // UpdateList updates Merger to display the list
662
+ func (t *Terminal) UpdateList(merger *Merger, reset bool) {
663
+ t.mutex.Lock()
664
+ t.progress = 100
665
+ t.merger = merger
666
+ if reset {
667
+ t.selected = make(map[int32]selectedItem)
668
+ }
669
+ t.mutex.Unlock()
670
+ t.reqBox.Set(reqInfo, nil)
671
+ t.reqBox.Set(reqList, nil)
672
+ }
673
+
674
+ func (t *Terminal) output() bool {
675
+ if t.printQuery {
676
+ t.printer(string(t.input))
677
+ }
678
+ if len(t.expect) > 0 {
679
+ t.printer(t.pressed)
680
+ }
681
+ found := len(t.selected) > 0
682
+ if !found {
683
+ current := t.currentItem()
684
+ if current != nil {
685
+ t.printer(current.AsString(t.ansi))
686
+ found = true
687
+ }
688
+ } else {
689
+ for _, sel := range t.sortSelected() {
690
+ t.printer(sel.item.AsString(t.ansi))
691
+ }
692
+ }
693
+ return found
694
+ }
695
+
696
+ func (t *Terminal) sortSelected() []selectedItem {
697
+ sels := make([]selectedItem, 0, len(t.selected))
698
+ for _, sel := range t.selected {
699
+ sels = append(sels, sel)
700
+ }
701
+ sort.Sort(byTimeOrder(sels))
702
+ return sels
703
+ }
704
+
705
+ func (t *Terminal) displayWidth(runes []rune) int {
706
+ width, _ := util.RunesWidth(runes, 0, t.tabstop, 0)
707
+ return width
708
+ }
709
+
710
+ const (
711
+ minWidth = 4
712
+ minHeight = 4
713
+ )
714
+
715
+ func calculateSize(base int, size sizeSpec, occupied int, minSize int, pad int) int {
716
+ max := base - occupied
717
+ if size.percent {
718
+ return util.Constrain(int(float64(base)*0.01*size.size), minSize, max)
719
+ }
720
+ return util.Constrain(int(size.size)+pad, minSize, max)
721
+ }
722
+
723
+ func (t *Terminal) resizeWindows() {
724
+ screenWidth := t.tui.MaxX()
725
+ screenHeight := t.tui.MaxY()
726
+ t.prevLines = make([]itemLine, screenHeight)
727
+
728
+ marginInt := [4]int{} // TRBL
729
+ paddingInt := [4]int{} // TRBL
730
+ sizeSpecToInt := func(index int, spec sizeSpec) int {
731
+ if spec.percent {
732
+ var max float64
733
+ if index%2 == 0 {
734
+ max = float64(screenHeight)
735
+ } else {
736
+ max = float64(screenWidth)
737
+ }
738
+ return int(max * spec.size * 0.01)
739
+ }
740
+ return int(spec.size)
741
+ }
742
+ for idx, sizeSpec := range t.padding {
743
+ paddingInt[idx] = sizeSpecToInt(idx, sizeSpec)
744
+ }
745
+
746
+ extraMargin := [4]int{} // TRBL
747
+ for idx, sizeSpec := range t.margin {
748
+ switch t.borderShape {
749
+ case tui.BorderHorizontal:
750
+ extraMargin[idx] += 1 - idx%2
751
+ case tui.BorderVertical:
752
+ extraMargin[idx] += 2 * (idx % 2)
753
+ case tui.BorderTop:
754
+ if idx == 0 {
755
+ extraMargin[idx]++
756
+ }
757
+ case tui.BorderRight:
758
+ if idx == 1 {
759
+ extraMargin[idx] += 2
760
+ }
761
+ case tui.BorderBottom:
762
+ if idx == 2 {
763
+ extraMargin[idx]++
764
+ }
765
+ case tui.BorderLeft:
766
+ if idx == 3 {
767
+ extraMargin[idx] += 2
768
+ }
769
+ case tui.BorderRounded, tui.BorderSharp:
770
+ extraMargin[idx] += 1 + idx%2
771
+ }
772
+ marginInt[idx] = sizeSpecToInt(idx, sizeSpec) + extraMargin[idx]
773
+ }
774
+
775
+ adjust := func(idx1 int, idx2 int, max int, min int) {
776
+ if max >= min {
777
+ margin := marginInt[idx1] + marginInt[idx2] + paddingInt[idx1] + paddingInt[idx2]
778
+ if max-margin < min {
779
+ desired := max - min
780
+ paddingInt[idx1] = desired * paddingInt[idx1] / margin
781
+ paddingInt[idx2] = desired * paddingInt[idx2] / margin
782
+ marginInt[idx1] = util.Max(extraMargin[idx1], desired*marginInt[idx1]/margin)
783
+ marginInt[idx2] = util.Max(extraMargin[idx2], desired*marginInt[idx2]/margin)
784
+ }
785
+ }
786
+ }
787
+
788
+ previewVisible := t.isPreviewEnabled() && t.previewOpts.size.size > 0
789
+ minAreaWidth := minWidth
790
+ minAreaHeight := minHeight
791
+ if previewVisible {
792
+ switch t.previewOpts.position {
793
+ case posUp, posDown:
794
+ minAreaHeight *= 2
795
+ case posLeft, posRight:
796
+ minAreaWidth *= 2
797
+ }
798
+ }
799
+ adjust(1, 3, screenWidth, minAreaWidth)
800
+ adjust(0, 2, screenHeight, minAreaHeight)
801
+ if t.border != nil {
802
+ t.border.Close()
803
+ }
804
+ if t.window != nil {
805
+ t.window.Close()
806
+ }
807
+ if t.pborder != nil {
808
+ t.pborder.Close()
809
+ }
810
+ if t.pwindow != nil {
811
+ t.pwindow.Close()
812
+ }
813
+ // Reset preview version so that full redraw occurs
814
+ t.previewed.version = 0
815
+
816
+ width := screenWidth - marginInt[1] - marginInt[3]
817
+ height := screenHeight - marginInt[0] - marginInt[2]
818
+ switch t.borderShape {
819
+ case tui.BorderHorizontal:
820
+ t.border = t.tui.NewWindow(
821
+ marginInt[0]-1, marginInt[3], width, height+2,
822
+ false, tui.MakeBorderStyle(tui.BorderHorizontal, t.unicode))
823
+ case tui.BorderVertical:
824
+ t.border = t.tui.NewWindow(
825
+ marginInt[0], marginInt[3]-2, width+4, height,
826
+ false, tui.MakeBorderStyle(tui.BorderVertical, t.unicode))
827
+ case tui.BorderTop:
828
+ t.border = t.tui.NewWindow(
829
+ marginInt[0]-1, marginInt[3], width, height+1,
830
+ false, tui.MakeBorderStyle(tui.BorderTop, t.unicode))
831
+ case tui.BorderBottom:
832
+ t.border = t.tui.NewWindow(
833
+ marginInt[0], marginInt[3], width, height+1,
834
+ false, tui.MakeBorderStyle(tui.BorderBottom, t.unicode))
835
+ case tui.BorderLeft:
836
+ t.border = t.tui.NewWindow(
837
+ marginInt[0], marginInt[3]-2, width+2, height,
838
+ false, tui.MakeBorderStyle(tui.BorderLeft, t.unicode))
839
+ case tui.BorderRight:
840
+ t.border = t.tui.NewWindow(
841
+ marginInt[0], marginInt[3], width+2, height,
842
+ false, tui.MakeBorderStyle(tui.BorderRight, t.unicode))
843
+ case tui.BorderRounded, tui.BorderSharp:
844
+ t.border = t.tui.NewWindow(
845
+ marginInt[0]-1, marginInt[3]-2, width+4, height+2,
846
+ false, tui.MakeBorderStyle(t.borderShape, t.unicode))
847
+ }
848
+
849
+ // Add padding
850
+ for idx, val := range paddingInt {
851
+ marginInt[idx] += val
852
+ }
853
+ width = screenWidth - marginInt[1] - marginInt[3]
854
+ height = screenHeight - marginInt[0] - marginInt[2]
855
+
856
+ noBorder := tui.MakeBorderStyle(tui.BorderNone, t.unicode)
857
+ if previewVisible {
858
+ createPreviewWindow := func(y int, x int, w int, h int) {
859
+ pwidth := w
860
+ pheight := h
861
+ var previewBorder tui.BorderStyle
862
+ if t.previewOpts.border == tui.BorderNone {
863
+ previewBorder = tui.MakeTransparentBorder()
864
+ } else {
865
+ previewBorder = tui.MakeBorderStyle(t.previewOpts.border, t.unicode)
866
+ }
867
+ t.pborder = t.tui.NewWindow(y, x, w, h, true, previewBorder)
868
+ switch t.previewOpts.border {
869
+ case tui.BorderSharp, tui.BorderRounded:
870
+ pwidth -= 4
871
+ pheight -= 2
872
+ x += 2
873
+ y += 1
874
+ case tui.BorderLeft:
875
+ pwidth -= 2
876
+ x += 2
877
+ case tui.BorderRight:
878
+ pwidth -= 2
879
+ case tui.BorderTop:
880
+ pheight -= 1
881
+ y += 1
882
+ case tui.BorderBottom:
883
+ pheight -= 1
884
+ case tui.BorderHorizontal:
885
+ pheight -= 2
886
+ y += 1
887
+ case tui.BorderVertical:
888
+ pwidth -= 4
889
+ x += 2
890
+ }
891
+ t.pwindow = t.tui.NewWindow(y, x, pwidth, pheight, true, noBorder)
892
+ }
893
+ verticalPad := 2
894
+ minPreviewHeight := 3
895
+ switch t.previewOpts.border {
896
+ case tui.BorderNone, tui.BorderVertical, tui.BorderLeft, tui.BorderRight:
897
+ verticalPad = 0
898
+ minPreviewHeight = 1
899
+ case tui.BorderTop, tui.BorderBottom:
900
+ verticalPad = 1
901
+ minPreviewHeight = 2
902
+ }
903
+ switch t.previewOpts.position {
904
+ case posUp:
905
+ pheight := calculateSize(height, t.previewOpts.size, minHeight, minPreviewHeight, verticalPad)
906
+ t.window = t.tui.NewWindow(
907
+ marginInt[0]+pheight, marginInt[3], width, height-pheight, false, noBorder)
908
+ createPreviewWindow(marginInt[0], marginInt[3], width, pheight)
909
+ case posDown:
910
+ pheight := calculateSize(height, t.previewOpts.size, minHeight, minPreviewHeight, verticalPad)
911
+ t.window = t.tui.NewWindow(
912
+ marginInt[0], marginInt[3], width, height-pheight, false, noBorder)
913
+ createPreviewWindow(marginInt[0]+height-pheight, marginInt[3], width, pheight)
914
+ case posLeft:
915
+ pwidth := calculateSize(width, t.previewOpts.size, minWidth, 5, 4)
916
+ t.window = t.tui.NewWindow(
917
+ marginInt[0], marginInt[3]+pwidth, width-pwidth, height, false, noBorder)
918
+ createPreviewWindow(marginInt[0], marginInt[3], pwidth, height)
919
+ case posRight:
920
+ pwidth := calculateSize(width, t.previewOpts.size, minWidth, 5, 4)
921
+ t.window = t.tui.NewWindow(
922
+ marginInt[0], marginInt[3], width-pwidth, height, false, noBorder)
923
+ createPreviewWindow(marginInt[0], marginInt[3]+width-pwidth, pwidth, height)
924
+ }
925
+ } else {
926
+ t.window = t.tui.NewWindow(
927
+ marginInt[0],
928
+ marginInt[3],
929
+ width,
930
+ height, false, noBorder)
931
+ }
932
+ for i := 0; i < t.window.Height(); i++ {
933
+ t.window.MoveAndClear(i, 0)
934
+ }
935
+ }
936
+
937
+ func (t *Terminal) move(y int, x int, clear bool) {
938
+ h := t.window.Height()
939
+
940
+ switch t.layout {
941
+ case layoutDefault:
942
+ y = h - y - 1
943
+ case layoutReverseList:
944
+ n := 2 + len(t.header)
945
+ if t.noInfoLine() {
946
+ n--
947
+ }
948
+ if y < n {
949
+ y = h - y - 1
950
+ } else {
951
+ y -= n
952
+ }
953
+ }
954
+
955
+ if clear {
956
+ t.window.MoveAndClear(y, x)
957
+ } else {
958
+ t.window.Move(y, x)
959
+ }
960
+ }
961
+
962
+ func (t *Terminal) truncateQuery() {
963
+ t.input, _ = t.trimRight(t.input, maxPatternLength)
964
+ t.cx = util.Constrain(t.cx, 0, len(t.input))
965
+ }
966
+
967
+ func (t *Terminal) updatePromptOffset() ([]rune, []rune) {
968
+ maxWidth := util.Max(1, t.window.Width()-t.promptLen-1)
969
+
970
+ _, overflow := t.trimLeft(t.input[:t.cx], maxWidth)
971
+ minOffset := int(overflow)
972
+ maxOffset := util.Min(util.Min(len(t.input), minOffset+maxWidth), t.cx)
973
+
974
+ t.xoffset = util.Constrain(t.xoffset, minOffset, maxOffset)
975
+ before, _ := t.trimLeft(t.input[t.xoffset:t.cx], maxWidth)
976
+ beforeLen := t.displayWidth(before)
977
+ after, _ := t.trimRight(t.input[t.cx:], maxWidth-beforeLen)
978
+ afterLen := t.displayWidth(after)
979
+ t.queryLen = [2]int{beforeLen, afterLen}
980
+ return before, after
981
+ }
982
+
983
+ func (t *Terminal) promptLine() int {
984
+ if t.headerFirst {
985
+ max := t.window.Height() - 1
986
+ if !t.noInfoLine() {
987
+ max--
988
+ }
989
+ return util.Min(len(t.header0)+t.headerLines, max)
990
+ }
991
+ return 0
992
+ }
993
+
994
+ func (t *Terminal) placeCursor() {
995
+ t.move(t.promptLine(), t.promptLen+t.queryLen[0], false)
996
+ }
997
+
998
+ func (t *Terminal) printPrompt() {
999
+ t.move(t.promptLine(), 0, true)
1000
+ t.prompt()
1001
+
1002
+ before, after := t.updatePromptOffset()
1003
+ color := tui.ColInput
1004
+ if t.paused {
1005
+ color = tui.ColDisabled
1006
+ }
1007
+ t.window.CPrint(color, string(before))
1008
+ t.window.CPrint(color, string(after))
1009
+ }
1010
+
1011
+ func (t *Terminal) trimMessage(message string, maxWidth int) string {
1012
+ if len(message) <= maxWidth {
1013
+ return message
1014
+ }
1015
+ runes, _ := t.trimRight([]rune(message), maxWidth-2)
1016
+ return string(runes) + strings.Repeat(".", util.Constrain(maxWidth, 0, 2))
1017
+ }
1018
+
1019
+ func (t *Terminal) printInfo() {
1020
+ pos := 0
1021
+ line := t.promptLine()
1022
+ switch t.infoStyle {
1023
+ case infoDefault:
1024
+ t.move(line+1, 0, true)
1025
+ if t.reading {
1026
+ duration := int64(spinnerDuration)
1027
+ idx := (time.Now().UnixNano() % (duration * int64(len(t.spinner)))) / duration
1028
+ t.window.CPrint(tui.ColSpinner, t.spinner[idx])
1029
+ }
1030
+ t.move(line+1, 2, false)
1031
+ pos = 2
1032
+ case infoInline:
1033
+ pos = t.promptLen + t.queryLen[0] + t.queryLen[1] + 1
1034
+ if pos+len(" < ") > t.window.Width() {
1035
+ return
1036
+ }
1037
+ t.move(line, pos, true)
1038
+ if t.reading {
1039
+ t.window.CPrint(tui.ColSpinner, " < ")
1040
+ } else {
1041
+ t.window.CPrint(tui.ColPrompt, " < ")
1042
+ }
1043
+ pos += len(" < ")
1044
+ case infoHidden:
1045
+ return
1046
+ }
1047
+
1048
+ found := t.merger.Length()
1049
+ total := util.Max(found, t.count)
1050
+ output := fmt.Sprintf("%d/%d", found, total)
1051
+ if t.toggleSort {
1052
+ if t.sort {
1053
+ output += " +S"
1054
+ } else {
1055
+ output += " -S"
1056
+ }
1057
+ }
1058
+ if t.multi > 0 {
1059
+ if t.multi == maxMulti {
1060
+ output += fmt.Sprintf(" (%d)", len(t.selected))
1061
+ } else {
1062
+ output += fmt.Sprintf(" (%d/%d)", len(t.selected), t.multi)
1063
+ }
1064
+ }
1065
+ if t.progress > 0 && t.progress < 100 {
1066
+ output += fmt.Sprintf(" (%d%%)", t.progress)
1067
+ }
1068
+ if t.failed != nil && t.count == 0 {
1069
+ output = fmt.Sprintf("[Command failed: %s]", *t.failed)
1070
+ }
1071
+ output = t.trimMessage(output, t.window.Width()-pos)
1072
+ t.window.CPrint(tui.ColInfo, output)
1073
+ }
1074
+
1075
+ func (t *Terminal) printHeader() {
1076
+ if len(t.header) == 0 {
1077
+ return
1078
+ }
1079
+ max := t.window.Height()
1080
+ if t.headerFirst {
1081
+ max--
1082
+ if !t.noInfoLine() {
1083
+ max--
1084
+ }
1085
+ }
1086
+ var state *ansiState
1087
+ for idx, lineStr := range t.header {
1088
+ line := idx
1089
+ if !t.headerFirst {
1090
+ line++
1091
+ if !t.noInfoLine() {
1092
+ line++
1093
+ }
1094
+ }
1095
+ if line >= max {
1096
+ continue
1097
+ }
1098
+ trimmed, colors, newState := extractColor(lineStr, state, nil)
1099
+ state = newState
1100
+ item := &Item{
1101
+ text: util.ToChars([]byte(trimmed)),
1102
+ colors: colors}
1103
+
1104
+ t.move(line, 2, true)
1105
+ t.printHighlighted(Result{item: item},
1106
+ tui.ColHeader, tui.ColHeader, false, false)
1107
+ }
1108
+ }
1109
+
1110
+ func (t *Terminal) printList() {
1111
+ t.constrain()
1112
+
1113
+ maxy := t.maxItems()
1114
+ count := t.merger.Length() - t.offset
1115
+ for j := 0; j < maxy; j++ {
1116
+ i := j
1117
+ if t.layout == layoutDefault {
1118
+ i = maxy - 1 - j
1119
+ }
1120
+ line := i + 2 + len(t.header)
1121
+ if t.noInfoLine() {
1122
+ line--
1123
+ }
1124
+ if i < count {
1125
+ t.printItem(t.merger.Get(i+t.offset), line, i, i == t.cy-t.offset)
1126
+ } else if t.prevLines[i] != emptyLine {
1127
+ t.prevLines[i] = emptyLine
1128
+ t.move(line, 0, true)
1129
+ }
1130
+ }
1131
+ }
1132
+
1133
+ func (t *Terminal) printItem(result Result, line int, i int, current bool) {
1134
+ item := result.item
1135
+ _, selected := t.selected[item.Index()]
1136
+ label := ""
1137
+ if t.jumping != jumpDisabled {
1138
+ if i < len(t.jumpLabels) {
1139
+ // Striped
1140
+ current = i%2 == 0
1141
+ label = t.jumpLabels[i:i+1] + strings.Repeat(" ", t.pointerLen-1)
1142
+ }
1143
+ } else if current {
1144
+ label = t.pointer
1145
+ }
1146
+
1147
+ // Avoid unnecessary redraw
1148
+ newLine := itemLine{current: current, selected: selected, label: label,
1149
+ result: result, queryLen: len(t.input), width: 0}
1150
+ prevLine := t.prevLines[i]
1151
+ if prevLine.current == newLine.current &&
1152
+ prevLine.selected == newLine.selected &&
1153
+ prevLine.label == newLine.label &&
1154
+ prevLine.queryLen == newLine.queryLen &&
1155
+ prevLine.result == newLine.result {
1156
+ return
1157
+ }
1158
+
1159
+ t.move(line, 0, false)
1160
+ if current {
1161
+ if len(label) == 0 {
1162
+ t.window.CPrint(tui.ColCurrentCursorEmpty, t.pointerEmpty)
1163
+ } else {
1164
+ t.window.CPrint(tui.ColCurrentCursor, label)
1165
+ }
1166
+ if selected {
1167
+ t.window.CPrint(tui.ColCurrentSelected, t.marker)
1168
+ } else {
1169
+ t.window.CPrint(tui.ColCurrentSelectedEmpty, t.markerEmpty)
1170
+ }
1171
+ newLine.width = t.printHighlighted(result, tui.ColCurrent, tui.ColCurrentMatch, true, true)
1172
+ } else {
1173
+ if len(label) == 0 {
1174
+ t.window.CPrint(tui.ColCursorEmpty, t.pointerEmpty)
1175
+ } else {
1176
+ t.window.CPrint(tui.ColCursor, label)
1177
+ }
1178
+ if selected {
1179
+ t.window.CPrint(tui.ColSelected, t.marker)
1180
+ } else {
1181
+ t.window.Print(t.markerEmpty)
1182
+ }
1183
+ newLine.width = t.printHighlighted(result, tui.ColNormal, tui.ColMatch, false, true)
1184
+ }
1185
+ fillSpaces := prevLine.width - newLine.width
1186
+ if fillSpaces > 0 {
1187
+ t.window.Print(strings.Repeat(" ", fillSpaces))
1188
+ }
1189
+ t.prevLines[i] = newLine
1190
+ }
1191
+
1192
+ func (t *Terminal) trimRight(runes []rune, width int) ([]rune, bool) {
1193
+ // We start from the beginning to handle tab characters
1194
+ width, overflowIdx := util.RunesWidth(runes, 0, t.tabstop, width)
1195
+ if overflowIdx >= 0 {
1196
+ return runes[:overflowIdx], true
1197
+ }
1198
+ return runes, false
1199
+ }
1200
+
1201
+ func (t *Terminal) displayWidthWithLimit(runes []rune, prefixWidth int, limit int) int {
1202
+ width, _ := util.RunesWidth(runes, prefixWidth, t.tabstop, limit)
1203
+ return width
1204
+ }
1205
+
1206
+ func (t *Terminal) trimLeft(runes []rune, width int) ([]rune, int32) {
1207
+ width = util.Max(0, width)
1208
+ var trimmed int32
1209
+ // Assume that each rune takes at least one column on screen
1210
+ if len(runes) > width+2 {
1211
+ diff := len(runes) - width - 2
1212
+ trimmed = int32(diff)
1213
+ runes = runes[diff:]
1214
+ }
1215
+
1216
+ currentWidth := t.displayWidth(runes)
1217
+
1218
+ for currentWidth > width && len(runes) > 0 {
1219
+ runes = runes[1:]
1220
+ trimmed++
1221
+ currentWidth = t.displayWidthWithLimit(runes, 2, width)
1222
+ }
1223
+ return runes, trimmed
1224
+ }
1225
+
1226
+ func (t *Terminal) overflow(runes []rune, max int) bool {
1227
+ return t.displayWidthWithLimit(runes, 0, max) > max
1228
+ }
1229
+
1230
+ func (t *Terminal) printHighlighted(result Result, colBase tui.ColorPair, colMatch tui.ColorPair, current bool, match bool) int {
1231
+ item := result.item
1232
+
1233
+ // Overflow
1234
+ text := make([]rune, item.text.Length())
1235
+ copy(text, item.text.ToRunes())
1236
+ matchOffsets := []Offset{}
1237
+ var pos *[]int
1238
+ if match && t.merger.pattern != nil {
1239
+ _, matchOffsets, pos = t.merger.pattern.MatchItem(item, true, t.slab)
1240
+ }
1241
+ charOffsets := matchOffsets
1242
+ if pos != nil {
1243
+ charOffsets = make([]Offset, len(*pos))
1244
+ for idx, p := range *pos {
1245
+ offset := Offset{int32(p), int32(p + 1)}
1246
+ charOffsets[idx] = offset
1247
+ }
1248
+ sort.Sort(ByOrder(charOffsets))
1249
+ }
1250
+ var maxe int
1251
+ for _, offset := range charOffsets {
1252
+ maxe = util.Max(maxe, int(offset[1]))
1253
+ }
1254
+
1255
+ offsets := result.colorOffsets(charOffsets, t.theme, colBase, colMatch, current)
1256
+ maxWidth := t.window.Width() - (t.pointerLen + t.markerLen + 1)
1257
+ maxe = util.Constrain(maxe+util.Min(maxWidth/2-2, t.hscrollOff), 0, len(text))
1258
+ displayWidth := t.displayWidthWithLimit(text, 0, maxWidth)
1259
+ if displayWidth > maxWidth {
1260
+ transformOffsets := func(diff int32) {
1261
+ for idx, offset := range offsets {
1262
+ b, e := offset.offset[0], offset.offset[1]
1263
+ b += 2 - diff
1264
+ e += 2 - diff
1265
+ b = util.Max32(b, 2)
1266
+ offsets[idx].offset[0] = b
1267
+ offsets[idx].offset[1] = util.Max32(b, e)
1268
+ }
1269
+ }
1270
+ if t.hscroll {
1271
+ if t.keepRight && pos == nil {
1272
+ trimmed, diff := t.trimLeft(text, maxWidth-2)
1273
+ transformOffsets(diff)
1274
+ text = append([]rune(ellipsis), trimmed...)
1275
+ } else if !t.overflow(text[:maxe], maxWidth-2) {
1276
+ // Stri..
1277
+ text, _ = t.trimRight(text, maxWidth-2)
1278
+ text = append(text, []rune(ellipsis)...)
1279
+ } else {
1280
+ // Stri..
1281
+ if t.overflow(text[maxe:], 2) {
1282
+ text = append(text[:maxe], []rune(ellipsis)...)
1283
+ }
1284
+ // ..ri..
1285
+ var diff int32
1286
+ text, diff = t.trimLeft(text, maxWidth-2)
1287
+
1288
+ // Transform offsets
1289
+ transformOffsets(diff)
1290
+ text = append([]rune(ellipsis), text...)
1291
+ }
1292
+ } else {
1293
+ text, _ = t.trimRight(text, maxWidth-2)
1294
+ text = append(text, []rune(ellipsis)...)
1295
+
1296
+ for idx, offset := range offsets {
1297
+ offsets[idx].offset[0] = util.Min32(offset.offset[0], int32(maxWidth-2))
1298
+ offsets[idx].offset[1] = util.Min32(offset.offset[1], int32(maxWidth))
1299
+ }
1300
+ }
1301
+ displayWidth = t.displayWidthWithLimit(text, 0, displayWidth)
1302
+ }
1303
+
1304
+ var index int32
1305
+ var substr string
1306
+ var prefixWidth int
1307
+ maxOffset := int32(len(text))
1308
+ for _, offset := range offsets {
1309
+ b := util.Constrain32(offset.offset[0], index, maxOffset)
1310
+ e := util.Constrain32(offset.offset[1], index, maxOffset)
1311
+
1312
+ substr, prefixWidth = t.processTabs(text[index:b], prefixWidth)
1313
+ t.window.CPrint(colBase, substr)
1314
+
1315
+ if b < e {
1316
+ substr, prefixWidth = t.processTabs(text[b:e], prefixWidth)
1317
+ t.window.CPrint(offset.color, substr)
1318
+ }
1319
+
1320
+ index = e
1321
+ if index >= maxOffset {
1322
+ break
1323
+ }
1324
+ }
1325
+ if index < maxOffset {
1326
+ substr, _ = t.processTabs(text[index:], prefixWidth)
1327
+ t.window.CPrint(colBase, substr)
1328
+ }
1329
+ return displayWidth
1330
+ }
1331
+
1332
+ func (t *Terminal) renderPreviewSpinner() {
1333
+ numLines := len(t.previewer.lines)
1334
+ spin := t.previewer.spinner
1335
+ if len(spin) > 0 || t.previewer.scrollable {
1336
+ maxWidth := t.pwindow.Width()
1337
+ if !t.previewer.scrollable {
1338
+ if maxWidth > 0 {
1339
+ t.pwindow.Move(0, maxWidth-1)
1340
+ t.pwindow.CPrint(tui.ColSpinner, spin)
1341
+ }
1342
+ } else {
1343
+ offsetString := fmt.Sprintf("%d/%d", t.previewer.offset+1, numLines)
1344
+ if len(spin) > 0 {
1345
+ spin += " "
1346
+ maxWidth -= 2
1347
+ }
1348
+ offsetRunes, _ := t.trimRight([]rune(offsetString), maxWidth)
1349
+ pos := maxWidth - t.displayWidth(offsetRunes)
1350
+ t.pwindow.Move(0, pos)
1351
+ if maxWidth > 0 {
1352
+ t.pwindow.CPrint(tui.ColSpinner, spin)
1353
+ t.pwindow.CPrint(tui.ColInfo.WithAttr(tui.Reverse), string(offsetRunes))
1354
+ }
1355
+ }
1356
+ }
1357
+ }
1358
+
1359
+ func (t *Terminal) renderPreviewArea(unchanged bool) {
1360
+ if unchanged {
1361
+ t.pwindow.MoveAndClear(0, 0) // Clear scroll offset display
1362
+ } else {
1363
+ t.previewed.filled = false
1364
+ t.pwindow.Erase()
1365
+ }
1366
+
1367
+ height := t.pwindow.Height()
1368
+ header := []string{}
1369
+ body := t.previewer.lines
1370
+ headerLines := t.previewOpts.headerLines
1371
+ // Do not enable preview header lines if it's value is too large
1372
+ if headerLines > 0 && headerLines < util.Min(len(body), height) {
1373
+ header = t.previewer.lines[0:headerLines]
1374
+ body = t.previewer.lines[headerLines:]
1375
+ // Always redraw header
1376
+ t.renderPreviewText(height, header, 0, false)
1377
+ t.pwindow.MoveAndClear(t.pwindow.Y(), 0)
1378
+ }
1379
+ t.renderPreviewText(height, body, -t.previewer.offset+headerLines, unchanged)
1380
+
1381
+ if !unchanged {
1382
+ t.pwindow.FinishFill()
1383
+ }
1384
+ }
1385
+
1386
+ func (t *Terminal) renderPreviewText(height int, lines []string, lineNo int, unchanged bool) {
1387
+ maxWidth := t.pwindow.Width()
1388
+ var ansi *ansiState
1389
+ for _, line := range lines {
1390
+ var lbg tui.Color = -1
1391
+ if ansi != nil {
1392
+ ansi.lbg = -1
1393
+ }
1394
+ line = strings.TrimSuffix(line, "\n")
1395
+ if lineNo >= height || t.pwindow.Y() == height-1 && t.pwindow.X() > 0 {
1396
+ t.previewed.filled = true
1397
+ break
1398
+ } else if lineNo >= 0 {
1399
+ var fillRet tui.FillReturn
1400
+ prefixWidth := 0
1401
+ _, _, ansi = extractColor(line, ansi, func(str string, ansi *ansiState) bool {
1402
+ trimmed := []rune(str)
1403
+ isTrimmed := false
1404
+ if !t.previewOpts.wrap {
1405
+ trimmed, isTrimmed = t.trimRight(trimmed, maxWidth-t.pwindow.X())
1406
+ }
1407
+ str, width := t.processTabs(trimmed, prefixWidth)
1408
+ prefixWidth += width
1409
+ if t.theme.Colored && ansi != nil && ansi.colored() {
1410
+ lbg = ansi.lbg
1411
+ fillRet = t.pwindow.CFill(ansi.fg, ansi.bg, ansi.attr, str)
1412
+ } else {
1413
+ fillRet = t.pwindow.CFill(tui.ColPreview.Fg(), tui.ColPreview.Bg(), tui.AttrRegular, str)
1414
+ }
1415
+ return !isTrimmed &&
1416
+ (fillRet == tui.FillContinue || t.previewOpts.wrap && fillRet == tui.FillNextLine)
1417
+ })
1418
+ t.previewer.scrollable = t.previewer.scrollable || t.pwindow.Y() == height-1 && t.pwindow.X() == t.pwindow.Width()
1419
+ if fillRet == tui.FillNextLine {
1420
+ continue
1421
+ } else if fillRet == tui.FillSuspend {
1422
+ t.previewed.filled = true
1423
+ break
1424
+ }
1425
+ if unchanged && lineNo == 0 {
1426
+ break
1427
+ }
1428
+ if lbg >= 0 {
1429
+ t.pwindow.CFill(-1, lbg, tui.AttrRegular,
1430
+ strings.Repeat(" ", t.pwindow.Width()-t.pwindow.X())+"\n")
1431
+ } else {
1432
+ t.pwindow.Fill("\n")
1433
+ }
1434
+ }
1435
+ lineNo++
1436
+ }
1437
+ }
1438
+
1439
+ func (t *Terminal) printPreview() {
1440
+ if !t.hasPreviewWindow() {
1441
+ return
1442
+ }
1443
+ numLines := len(t.previewer.lines)
1444
+ height := t.pwindow.Height()
1445
+ unchanged := (t.previewed.filled || numLines == t.previewed.numLines) &&
1446
+ t.previewer.version == t.previewed.version &&
1447
+ t.previewer.offset == t.previewed.offset
1448
+ t.previewer.scrollable = t.previewer.offset > 0 || numLines > height
1449
+ t.renderPreviewArea(unchanged)
1450
+ t.renderPreviewSpinner()
1451
+ t.previewed.numLines = numLines
1452
+ t.previewed.version = t.previewer.version
1453
+ t.previewed.offset = t.previewer.offset
1454
+ }
1455
+
1456
+ func (t *Terminal) printPreviewDelayed() {
1457
+ if !t.hasPreviewWindow() || len(t.previewer.lines) > 0 && t.previewed.version == t.previewer.version {
1458
+ return
1459
+ }
1460
+
1461
+ t.previewer.scrollable = false
1462
+ t.renderPreviewArea(true)
1463
+
1464
+ message := t.trimMessage("Loading ..", t.pwindow.Width())
1465
+ pos := t.pwindow.Width() - len(message)
1466
+ t.pwindow.Move(0, pos)
1467
+ t.pwindow.CPrint(tui.ColInfo.WithAttr(tui.Reverse), message)
1468
+ }
1469
+
1470
+ func (t *Terminal) processTabs(runes []rune, prefixWidth int) (string, int) {
1471
+ var strbuf strings.Builder
1472
+ l := prefixWidth
1473
+ gr := uniseg.NewGraphemes(string(runes))
1474
+ for gr.Next() {
1475
+ rs := gr.Runes()
1476
+ str := string(rs)
1477
+ var w int
1478
+ if len(rs) == 1 && rs[0] == '\t' {
1479
+ w = t.tabstop - l%t.tabstop
1480
+ strbuf.WriteString(strings.Repeat(" ", w))
1481
+ } else {
1482
+ w = runewidth.StringWidth(str)
1483
+ strbuf.WriteString(str)
1484
+ }
1485
+ l += w
1486
+ }
1487
+ return strbuf.String(), l
1488
+ }
1489
+
1490
+ func (t *Terminal) printAll() {
1491
+ t.resizeWindows()
1492
+ t.printList()
1493
+ t.printPrompt()
1494
+ t.printInfo()
1495
+ t.printHeader()
1496
+ t.printPreview()
1497
+ }
1498
+
1499
+ func (t *Terminal) refresh() {
1500
+ t.placeCursor()
1501
+ if !t.suppress {
1502
+ windows := make([]tui.Window, 0, 4)
1503
+ if t.borderShape != tui.BorderNone {
1504
+ windows = append(windows, t.border)
1505
+ }
1506
+ if t.hasPreviewWindow() {
1507
+ if t.pborder != nil {
1508
+ windows = append(windows, t.pborder)
1509
+ }
1510
+ windows = append(windows, t.pwindow)
1511
+ }
1512
+ windows = append(windows, t.window)
1513
+ t.tui.RefreshWindows(windows)
1514
+ }
1515
+ }
1516
+
1517
+ func (t *Terminal) delChar() bool {
1518
+ if len(t.input) > 0 && t.cx < len(t.input) {
1519
+ t.input = append(t.input[:t.cx], t.input[t.cx+1:]...)
1520
+ return true
1521
+ }
1522
+ return false
1523
+ }
1524
+
1525
+ func findLastMatch(pattern string, str string) int {
1526
+ rx, err := regexp.Compile(pattern)
1527
+ if err != nil {
1528
+ return -1
1529
+ }
1530
+ locs := rx.FindAllStringIndex(str, -1)
1531
+ if locs == nil {
1532
+ return -1
1533
+ }
1534
+ prefix := []rune(str[:locs[len(locs)-1][0]])
1535
+ return len(prefix)
1536
+ }
1537
+
1538
+ func findFirstMatch(pattern string, str string) int {
1539
+ rx, err := regexp.Compile(pattern)
1540
+ if err != nil {
1541
+ return -1
1542
+ }
1543
+ loc := rx.FindStringIndex(str)
1544
+ if loc == nil {
1545
+ return -1
1546
+ }
1547
+ prefix := []rune(str[:loc[0]])
1548
+ return len(prefix)
1549
+ }
1550
+
1551
+ func copySlice(slice []rune) []rune {
1552
+ ret := make([]rune, len(slice))
1553
+ copy(ret, slice)
1554
+ return ret
1555
+ }
1556
+
1557
+ func (t *Terminal) rubout(pattern string) {
1558
+ pcx := t.cx
1559
+ after := t.input[t.cx:]
1560
+ t.cx = findLastMatch(pattern, string(t.input[:t.cx])) + 1
1561
+ t.yanked = copySlice(t.input[t.cx:pcx])
1562
+ t.input = append(t.input[:t.cx], after...)
1563
+ }
1564
+
1565
+ func keyMatch(key tui.Event, event tui.Event) bool {
1566
+ return event.Type == key.Type && event.Char == key.Char ||
1567
+ key.Type == tui.DoubleClick && event.Type == tui.Mouse && event.MouseEvent.Double
1568
+ }
1569
+
1570
+ func parsePlaceholder(match string) (bool, string, placeholderFlags) {
1571
+ flags := placeholderFlags{}
1572
+
1573
+ if match[0] == '\\' {
1574
+ // Escaped placeholder pattern
1575
+ return true, match[1:], flags
1576
+ }
1577
+
1578
+ skipChars := 1
1579
+ for _, char := range match[1:] {
1580
+ switch char {
1581
+ case '+':
1582
+ flags.plus = true
1583
+ skipChars++
1584
+ case 's':
1585
+ flags.preserveSpace = true
1586
+ skipChars++
1587
+ case 'n':
1588
+ flags.number = true
1589
+ skipChars++
1590
+ case 'f':
1591
+ flags.file = true
1592
+ skipChars++
1593
+ case 'q':
1594
+ flags.query = true
1595
+ // query flag is not skipped
1596
+ default:
1597
+ break
1598
+ }
1599
+ }
1600
+
1601
+ matchWithoutFlags := "{" + match[skipChars:]
1602
+
1603
+ return false, matchWithoutFlags, flags
1604
+ }
1605
+
1606
+ func hasPreviewFlags(template string) (slot bool, plus bool, query bool) {
1607
+ for _, match := range placeholder.FindAllString(template, -1) {
1608
+ _, _, flags := parsePlaceholder(match)
1609
+ if flags.plus {
1610
+ plus = true
1611
+ }
1612
+ if flags.query {
1613
+ query = true
1614
+ }
1615
+ slot = true
1616
+ }
1617
+ return
1618
+ }
1619
+
1620
+ func writeTemporaryFile(data []string, printSep string) string {
1621
+ f, err := ioutil.TempFile("", "fzf-preview-*")
1622
+ if err != nil {
1623
+ errorExit("Unable to create temporary file")
1624
+ }
1625
+ defer f.Close()
1626
+
1627
+ f.WriteString(strings.Join(data, printSep))
1628
+ f.WriteString(printSep)
1629
+ activeTempFiles = append(activeTempFiles, f.Name())
1630
+ return f.Name()
1631
+ }
1632
+
1633
+ func cleanTemporaryFiles() {
1634
+ for _, filename := range activeTempFiles {
1635
+ os.Remove(filename)
1636
+ }
1637
+ activeTempFiles = []string{}
1638
+ }
1639
+
1640
+ func (t *Terminal) replacePlaceholder(template string, forcePlus bool, input string, list []*Item) string {
1641
+ return replacePlaceholder(
1642
+ template, t.ansi, t.delimiter, t.printsep, forcePlus, input, list)
1643
+ }
1644
+
1645
+ func (t *Terminal) evaluateScrollOffset(list []*Item, height int) int {
1646
+ offsetExpr := offsetTrimCharsRegex.ReplaceAllString(
1647
+ t.replacePlaceholder(t.previewOpts.scroll, false, "", list), "")
1648
+
1649
+ atoi := func(s string) int {
1650
+ n, e := strconv.Atoi(s)
1651
+ if e != nil {
1652
+ return 0
1653
+ }
1654
+ return n
1655
+ }
1656
+
1657
+ base := -1
1658
+ for _, component := range offsetComponentRegex.FindAllString(offsetExpr, -1) {
1659
+ if strings.HasPrefix(component, "-/") {
1660
+ component = component[1:]
1661
+ }
1662
+ if component[0] == '/' {
1663
+ denom := atoi(component[1:])
1664
+ if denom == 0 {
1665
+ return base
1666
+ }
1667
+ return base - height/denom
1668
+ }
1669
+ base += atoi(component)
1670
+ }
1671
+ return base
1672
+ }
1673
+
1674
+ func replacePlaceholder(template string, stripAnsi bool, delimiter Delimiter, printsep string, forcePlus bool, query string, allItems []*Item) string {
1675
+ current := allItems[:1]
1676
+ selected := allItems[1:]
1677
+ if current[0] == nil {
1678
+ current = []*Item{}
1679
+ }
1680
+ if selected[0] == nil {
1681
+ selected = []*Item{}
1682
+ }
1683
+
1684
+ // replace placeholders one by one
1685
+ return placeholder.ReplaceAllStringFunc(template, func(match string) string {
1686
+ escaped, match, flags := parsePlaceholder(match)
1687
+
1688
+ // this function implements the effects a placeholder has on items
1689
+ var replace func(*Item) string
1690
+
1691
+ // placeholder types (escaped, query type, item type, token type)
1692
+ switch {
1693
+ case escaped:
1694
+ return match
1695
+ case match == "{q}":
1696
+ return quoteEntry(query)
1697
+ case match == "{}":
1698
+ replace = func(item *Item) string {
1699
+ switch {
1700
+ case flags.number:
1701
+ n := int(item.text.Index)
1702
+ if n < 0 {
1703
+ return ""
1704
+ }
1705
+ return strconv.Itoa(n)
1706
+ case flags.file:
1707
+ return item.AsString(stripAnsi)
1708
+ default:
1709
+ return quoteEntry(item.AsString(stripAnsi))
1710
+ }
1711
+ }
1712
+ default:
1713
+ // token type and also failover (below)
1714
+ rangeExpressions := strings.Split(match[1:len(match)-1], ",")
1715
+ ranges := make([]Range, len(rangeExpressions))
1716
+ for idx, s := range rangeExpressions {
1717
+ r, ok := ParseRange(&s) // ellipsis (x..y) and shorthand (x..x) range syntax
1718
+ if !ok {
1719
+ // Invalid expression, just return the original string in the template
1720
+ return match
1721
+ }
1722
+ ranges[idx] = r
1723
+ }
1724
+
1725
+ replace = func(item *Item) string {
1726
+ tokens := Tokenize(item.AsString(stripAnsi), delimiter)
1727
+ trans := Transform(tokens, ranges)
1728
+ str := joinTokens(trans)
1729
+
1730
+ // trim the last delimiter
1731
+ if delimiter.str != nil {
1732
+ str = strings.TrimSuffix(str, *delimiter.str)
1733
+ } else if delimiter.regex != nil {
1734
+ delims := delimiter.regex.FindAllStringIndex(str, -1)
1735
+ // make sure the delimiter is at the very end of the string
1736
+ if len(delims) > 0 && delims[len(delims)-1][1] == len(str) {
1737
+ str = str[:delims[len(delims)-1][0]]
1738
+ }
1739
+ }
1740
+
1741
+ if !flags.preserveSpace {
1742
+ str = strings.TrimSpace(str)
1743
+ }
1744
+ if !flags.file {
1745
+ str = quoteEntry(str)
1746
+ }
1747
+ return str
1748
+ }
1749
+ }
1750
+
1751
+ // apply 'replace' function over proper set of items and return result
1752
+
1753
+ items := current
1754
+ if flags.plus || forcePlus {
1755
+ items = selected
1756
+ }
1757
+ replacements := make([]string, len(items))
1758
+
1759
+ for idx, item := range items {
1760
+ replacements[idx] = replace(item)
1761
+ }
1762
+
1763
+ if flags.file {
1764
+ return writeTemporaryFile(replacements, printsep)
1765
+ }
1766
+ return strings.Join(replacements, " ")
1767
+ })
1768
+ }
1769
+
1770
+ func (t *Terminal) redraw() {
1771
+ t.tui.Clear()
1772
+ t.tui.Refresh()
1773
+ t.printAll()
1774
+ }
1775
+
1776
+ func (t *Terminal) executeCommand(template string, forcePlus bool, background bool) {
1777
+ valid, list := t.buildPlusList(template, forcePlus)
1778
+ if !valid {
1779
+ return
1780
+ }
1781
+ command := t.replacePlaceholder(template, forcePlus, string(t.input), list)
1782
+ cmd := util.ExecCommand(command, false)
1783
+ t.executing.Set(true)
1784
+ if !background {
1785
+ cmd.Stdin = tui.TtyIn()
1786
+ cmd.Stdout = os.Stdout
1787
+ cmd.Stderr = os.Stderr
1788
+ t.tui.Pause(true)
1789
+ cmd.Run()
1790
+ t.tui.Resume(true, false)
1791
+ t.redraw()
1792
+ t.refresh()
1793
+ } else {
1794
+ t.tui.Pause(false)
1795
+ cmd.Run()
1796
+ t.tui.Resume(false, false)
1797
+ }
1798
+ t.executing.Set(false)
1799
+ cleanTemporaryFiles()
1800
+ }
1801
+
1802
+ func (t *Terminal) hasPreviewer() bool {
1803
+ return t.previewBox != nil
1804
+ }
1805
+
1806
+ func (t *Terminal) isPreviewEnabled() bool {
1807
+ return t.hasPreviewer() && t.previewer.enabled
1808
+ }
1809
+
1810
+ func (t *Terminal) hasPreviewWindow() bool {
1811
+ return t.pwindow != nil && t.isPreviewEnabled()
1812
+ }
1813
+
1814
+ func (t *Terminal) currentItem() *Item {
1815
+ cnt := t.merger.Length()
1816
+ if t.cy >= 0 && cnt > 0 && cnt > t.cy {
1817
+ return t.merger.Get(t.cy).item
1818
+ }
1819
+ return nil
1820
+ }
1821
+
1822
+ func (t *Terminal) buildPlusList(template string, forcePlus bool) (bool, []*Item) {
1823
+ current := t.currentItem()
1824
+ slot, plus, query := hasPreviewFlags(template)
1825
+ if !(!slot || query && len(t.input) > 0 || (forcePlus || plus) && len(t.selected) > 0) {
1826
+ return current != nil, []*Item{current, current}
1827
+ }
1828
+
1829
+ // We would still want to update preview window even if there is no match if
1830
+ // 1. command template contains {q} and the query string is not empty
1831
+ // 2. or it contains {+} and we have more than one item already selected.
1832
+ // To do so, we pass an empty Item instead of nil to trigger an update.
1833
+ if current == nil {
1834
+ current = &minItem
1835
+ }
1836
+
1837
+ var sels []*Item
1838
+ if len(t.selected) == 0 {
1839
+ sels = []*Item{current, current}
1840
+ } else {
1841
+ sels = make([]*Item, len(t.selected)+1)
1842
+ sels[0] = current
1843
+ for i, sel := range t.sortSelected() {
1844
+ sels[i+1] = sel.item
1845
+ }
1846
+ }
1847
+ return true, sels
1848
+ }
1849
+
1850
+ func (t *Terminal) selectItem(item *Item) bool {
1851
+ if len(t.selected) >= t.multi {
1852
+ return false
1853
+ }
1854
+ if _, found := t.selected[item.Index()]; found {
1855
+ return true
1856
+ }
1857
+
1858
+ t.selected[item.Index()] = selectedItem{time.Now(), item}
1859
+ t.version++
1860
+
1861
+ return true
1862
+ }
1863
+
1864
+ func (t *Terminal) selectItemChanged(item *Item) bool {
1865
+ if _, found := t.selected[item.Index()]; found {
1866
+ return false
1867
+ }
1868
+ return t.selectItem(item)
1869
+ }
1870
+
1871
+ func (t *Terminal) deselectItem(item *Item) {
1872
+ delete(t.selected, item.Index())
1873
+ t.version++
1874
+ }
1875
+
1876
+ func (t *Terminal) deselectItemChanged(item *Item) bool {
1877
+ if _, found := t.selected[item.Index()]; found {
1878
+ t.deselectItem(item)
1879
+ return true
1880
+ }
1881
+ return false
1882
+ }
1883
+
1884
+ func (t *Terminal) toggleItem(item *Item) bool {
1885
+ if _, found := t.selected[item.Index()]; !found {
1886
+ return t.selectItem(item)
1887
+ }
1888
+ t.deselectItem(item)
1889
+ return true
1890
+ }
1891
+
1892
+ func (t *Terminal) killPreview(code int) {
1893
+ select {
1894
+ case t.killChan <- code:
1895
+ default:
1896
+ if code != exitCancel {
1897
+ t.eventBox.Set(EvtQuit, code)
1898
+ }
1899
+ }
1900
+ }
1901
+
1902
+ func (t *Terminal) cancelPreview() {
1903
+ t.killPreview(exitCancel)
1904
+ }
1905
+
1906
+ // Loop is called to start Terminal I/O
1907
+ func (t *Terminal) Loop() {
1908
+ // prof := profile.Start(profile.ProfilePath("/tmp/"))
1909
+ <-t.startChan
1910
+ { // Late initialization
1911
+ intChan := make(chan os.Signal, 1)
1912
+ signal.Notify(intChan, os.Interrupt, syscall.SIGTERM)
1913
+ go func() {
1914
+ for s := range intChan {
1915
+ // Don't quit by SIGINT while executing because it should be for the executing command and not for fzf itself
1916
+ if !(s == os.Interrupt && t.executing.Get()) {
1917
+ t.reqBox.Set(reqQuit, nil)
1918
+ }
1919
+ }
1920
+ }()
1921
+
1922
+ contChan := make(chan os.Signal, 1)
1923
+ notifyOnCont(contChan)
1924
+ go func() {
1925
+ for {
1926
+ <-contChan
1927
+ t.reqBox.Set(reqReinit, nil)
1928
+ }
1929
+ }()
1930
+
1931
+ resizeChan := make(chan os.Signal, 1)
1932
+ notifyOnResize(resizeChan) // Non-portable
1933
+ go func() {
1934
+ for {
1935
+ <-resizeChan
1936
+ t.reqBox.Set(reqRedraw, nil)
1937
+ }
1938
+ }()
1939
+
1940
+ t.mutex.Lock()
1941
+ t.initFunc()
1942
+ t.resizeWindows()
1943
+ t.printPrompt()
1944
+ t.printInfo()
1945
+ t.printHeader()
1946
+ t.refresh()
1947
+ t.mutex.Unlock()
1948
+ go func() {
1949
+ timer := time.NewTimer(t.initDelay)
1950
+ <-timer.C
1951
+ t.reqBox.Set(reqRefresh, nil)
1952
+ }()
1953
+
1954
+ // Keep the spinner spinning
1955
+ go func() {
1956
+ for {
1957
+ t.mutex.Lock()
1958
+ reading := t.reading
1959
+ t.mutex.Unlock()
1960
+ time.Sleep(spinnerDuration)
1961
+ if reading {
1962
+ t.reqBox.Set(reqInfo, nil)
1963
+ }
1964
+ }
1965
+ }()
1966
+ }
1967
+
1968
+ if t.hasPreviewer() {
1969
+ go func() {
1970
+ var version int64
1971
+ for {
1972
+ var items []*Item
1973
+ var commandTemplate string
1974
+ var pwindow tui.Window
1975
+ t.previewBox.Wait(func(events *util.Events) {
1976
+ for req, value := range *events {
1977
+ switch req {
1978
+ case reqPreviewEnqueue:
1979
+ request := value.(previewRequest)
1980
+ commandTemplate = request.template
1981
+ items = request.list
1982
+ pwindow = request.pwindow
1983
+ }
1984
+ }
1985
+ events.Clear()
1986
+ })
1987
+ version++
1988
+ // We don't display preview window if no match
1989
+ if items[0] != nil {
1990
+ _, query := t.Input()
1991
+ command := t.replacePlaceholder(commandTemplate, false, string(query), items)
1992
+ initialOffset := 0
1993
+ cmd := util.ExecCommand(command, true)
1994
+ if pwindow != nil {
1995
+ height := pwindow.Height()
1996
+ initialOffset = util.Max(0, t.evaluateScrollOffset(items, util.Max(0, height-t.previewOpts.headerLines)))
1997
+ env := os.Environ()
1998
+ lines := fmt.Sprintf("LINES=%d", height)
1999
+ columns := fmt.Sprintf("COLUMNS=%d", pwindow.Width())
2000
+ env = append(env, lines)
2001
+ env = append(env, "FZF_PREVIEW_"+lines)
2002
+ env = append(env, columns)
2003
+ env = append(env, "FZF_PREVIEW_"+columns)
2004
+ cmd.Env = env
2005
+ }
2006
+
2007
+ out, _ := cmd.StdoutPipe()
2008
+ cmd.Stderr = cmd.Stdout
2009
+ reader := bufio.NewReader(out)
2010
+ eofChan := make(chan bool)
2011
+ finishChan := make(chan bool, 1)
2012
+ err := cmd.Start()
2013
+ if err == nil {
2014
+ reapChan := make(chan bool)
2015
+ lineChan := make(chan eachLine)
2016
+ // Goroutine 1 reads process output
2017
+ go func() {
2018
+ for {
2019
+ line, err := reader.ReadString('\n')
2020
+ lineChan <- eachLine{line, err}
2021
+ if err != nil {
2022
+ break
2023
+ }
2024
+ }
2025
+ eofChan <- true
2026
+ }()
2027
+
2028
+ // Goroutine 2 periodically requests rendering
2029
+ rendered := util.NewAtomicBool(false)
2030
+ go func(version int64) {
2031
+ lines := []string{}
2032
+ spinner := makeSpinner(t.unicode)
2033
+ spinnerIndex := -1 // Delay initial rendering by an extra tick
2034
+ ticker := time.NewTicker(previewChunkDelay)
2035
+ offset := initialOffset
2036
+ Loop:
2037
+ for {
2038
+ select {
2039
+ case <-ticker.C:
2040
+ if len(lines) > 0 && len(lines) >= initialOffset {
2041
+ if spinnerIndex >= 0 {
2042
+ spin := spinner[spinnerIndex%len(spinner)]
2043
+ t.reqBox.Set(reqPreviewDisplay, previewResult{version, lines, offset, spin})
2044
+ rendered.Set(true)
2045
+ offset = -1
2046
+ }
2047
+ spinnerIndex++
2048
+ }
2049
+ case eachLine := <-lineChan:
2050
+ line := eachLine.line
2051
+ err := eachLine.err
2052
+ if len(line) > 0 {
2053
+ clearIndex := strings.Index(line, clearCode)
2054
+ if clearIndex >= 0 {
2055
+ lines = []string{}
2056
+ line = line[clearIndex+len(clearCode):]
2057
+ version--
2058
+ offset = 0
2059
+ }
2060
+ lines = append(lines, line)
2061
+ }
2062
+ if err != nil {
2063
+ t.reqBox.Set(reqPreviewDisplay, previewResult{version, lines, offset, ""})
2064
+ rendered.Set(true)
2065
+ break Loop
2066
+ }
2067
+ }
2068
+ }
2069
+ ticker.Stop()
2070
+ reapChan <- true
2071
+ }(version)
2072
+
2073
+ // Goroutine 3 is responsible for cancelling running preview command
2074
+ go func(version int64) {
2075
+ timer := time.NewTimer(previewDelayed)
2076
+ Loop:
2077
+ for {
2078
+ select {
2079
+ case <-timer.C:
2080
+ t.reqBox.Set(reqPreviewDelayed, version)
2081
+ case code := <-t.killChan:
2082
+ if code != exitCancel {
2083
+ util.KillCommand(cmd)
2084
+ t.eventBox.Set(EvtQuit, code)
2085
+ } else {
2086
+ // We can immediately kill a long-running preview program
2087
+ // once we started rendering its partial output
2088
+ delay := previewCancelWait
2089
+ if rendered.Get() {
2090
+ delay = 0
2091
+ }
2092
+ timer := time.NewTimer(delay)
2093
+ select {
2094
+ case <-timer.C:
2095
+ util.KillCommand(cmd)
2096
+ case <-finishChan:
2097
+ }
2098
+ timer.Stop()
2099
+ }
2100
+ break Loop
2101
+ case <-finishChan:
2102
+ break Loop
2103
+ }
2104
+ }
2105
+ timer.Stop()
2106
+ reapChan <- true
2107
+ }(version)
2108
+
2109
+ <-eofChan // Goroutine 1 finished
2110
+ cmd.Wait() // NOTE: We should not call Wait before EOF
2111
+ finishChan <- true // Tell Goroutine 3 to stop
2112
+ <-reapChan // Goroutine 2 and 3 finished
2113
+ <-reapChan
2114
+ } else {
2115
+ // Failed to start the command. Report the error immediately.
2116
+ t.reqBox.Set(reqPreviewDisplay, previewResult{version, []string{err.Error()}, 0, ""})
2117
+ }
2118
+
2119
+ cleanTemporaryFiles()
2120
+ } else {
2121
+ t.reqBox.Set(reqPreviewDisplay, previewResult{version, nil, 0, ""})
2122
+ }
2123
+ }
2124
+ }()
2125
+ }
2126
+
2127
+ refreshPreview := func(command string) {
2128
+ if len(command) > 0 && t.isPreviewEnabled() {
2129
+ _, list := t.buildPlusList(command, false)
2130
+ t.cancelPreview()
2131
+ t.previewBox.Set(reqPreviewEnqueue, previewRequest{command, t.pwindow, list})
2132
+ }
2133
+ }
2134
+
2135
+ go func() {
2136
+ var focusedIndex int32 = minItem.Index()
2137
+ var version int64 = -1
2138
+ running := true
2139
+ code := exitError
2140
+ exit := func(getCode func() int) {
2141
+ t.tui.Close()
2142
+ code = getCode()
2143
+ if code <= exitNoMatch && t.history != nil {
2144
+ t.history.append(string(t.input))
2145
+ }
2146
+ running = false
2147
+ t.mutex.Unlock()
2148
+ }
2149
+
2150
+ for running {
2151
+ t.reqBox.Wait(func(events *util.Events) {
2152
+ defer events.Clear()
2153
+ t.mutex.Lock()
2154
+ for req, value := range *events {
2155
+ switch req {
2156
+ case reqPrompt:
2157
+ t.printPrompt()
2158
+ if t.noInfoLine() {
2159
+ t.printInfo()
2160
+ }
2161
+ case reqInfo:
2162
+ t.printInfo()
2163
+ case reqList:
2164
+ t.printList()
2165
+ var currentIndex int32 = minItem.Index()
2166
+ currentItem := t.currentItem()
2167
+ if currentItem != nil {
2168
+ currentIndex = currentItem.Index()
2169
+ }
2170
+ if focusedIndex != currentIndex || version != t.version {
2171
+ version = t.version
2172
+ focusedIndex = currentIndex
2173
+ refreshPreview(t.previewOpts.command)
2174
+ }
2175
+ case reqJump:
2176
+ if t.merger.Length() == 0 {
2177
+ t.jumping = jumpDisabled
2178
+ }
2179
+ t.printList()
2180
+ case reqHeader:
2181
+ t.printHeader()
2182
+ case reqRefresh:
2183
+ t.suppress = false
2184
+ case reqReinit:
2185
+ t.tui.Resume(t.fullscreen, t.sigstop)
2186
+ t.redraw()
2187
+ case reqRedraw:
2188
+ t.redraw()
2189
+ case reqClose:
2190
+ exit(func() int {
2191
+ if t.output() {
2192
+ return exitOk
2193
+ }
2194
+ return exitNoMatch
2195
+ })
2196
+ return
2197
+ case reqPreviewDisplay:
2198
+ result := value.(previewResult)
2199
+ if t.previewer.version != result.version {
2200
+ t.previewer.version = result.version
2201
+ t.previewer.following = t.previewOpts.follow
2202
+ }
2203
+ t.previewer.lines = result.lines
2204
+ t.previewer.spinner = result.spinner
2205
+ if t.previewer.following {
2206
+ t.previewer.offset = len(t.previewer.lines) - t.pwindow.Height()
2207
+ } else if result.offset >= 0 {
2208
+ t.previewer.offset = util.Constrain(result.offset, t.previewOpts.headerLines, len(t.previewer.lines)-1)
2209
+ }
2210
+ t.printPreview()
2211
+ case reqPreviewRefresh:
2212
+ t.printPreview()
2213
+ case reqPreviewDelayed:
2214
+ t.previewer.version = value.(int64)
2215
+ t.printPreviewDelayed()
2216
+ case reqPrintQuery:
2217
+ exit(func() int {
2218
+ t.printer(string(t.input))
2219
+ return exitOk
2220
+ })
2221
+ return
2222
+ case reqQuit:
2223
+ exit(func() int { return exitInterrupt })
2224
+ return
2225
+ }
2226
+ }
2227
+ t.refresh()
2228
+ t.mutex.Unlock()
2229
+ })
2230
+ }
2231
+ // prof.Stop()
2232
+ t.killPreview(code)
2233
+ }()
2234
+
2235
+ looping := true
2236
+ for looping {
2237
+ var newCommand *string
2238
+ changed := false
2239
+ beof := false
2240
+ queryChanged := false
2241
+
2242
+ event := t.tui.GetChar()
2243
+
2244
+ t.mutex.Lock()
2245
+ previousInput := t.input
2246
+ previousCx := t.cx
2247
+ events := []util.EventType{}
2248
+ req := func(evts ...util.EventType) {
2249
+ for _, event := range evts {
2250
+ events = append(events, event)
2251
+ if event == reqClose || event == reqQuit {
2252
+ looping = false
2253
+ }
2254
+ }
2255
+ }
2256
+ togglePreview := func(enabled bool) {
2257
+ if t.previewer.enabled != enabled {
2258
+ t.previewer.enabled = enabled
2259
+ t.tui.Clear()
2260
+ t.resizeWindows()
2261
+ req(reqPrompt, reqList, reqInfo, reqHeader)
2262
+ }
2263
+ }
2264
+ toggle := func() bool {
2265
+ current := t.currentItem()
2266
+ if current != nil && t.toggleItem(current) {
2267
+ req(reqInfo)
2268
+ return true
2269
+ }
2270
+ return false
2271
+ }
2272
+ scrollPreviewTo := func(newOffset int) {
2273
+ if !t.previewer.scrollable {
2274
+ return
2275
+ }
2276
+ t.previewer.following = false
2277
+ numLines := len(t.previewer.lines)
2278
+ if t.previewOpts.cycle {
2279
+ newOffset = (newOffset + numLines) % numLines
2280
+ }
2281
+ newOffset = util.Constrain(newOffset, t.previewOpts.headerLines, numLines-1)
2282
+ if t.previewer.offset != newOffset {
2283
+ t.previewer.offset = newOffset
2284
+ req(reqPreviewRefresh)
2285
+ }
2286
+ }
2287
+ scrollPreviewBy := func(amount int) {
2288
+ scrollPreviewTo(t.previewer.offset + amount)
2289
+ }
2290
+ for key, ret := range t.expect {
2291
+ if keyMatch(key, event) {
2292
+ t.pressed = ret
2293
+ t.reqBox.Set(reqClose, nil)
2294
+ t.mutex.Unlock()
2295
+ return
2296
+ }
2297
+ }
2298
+
2299
+ actionsFor := func(eventType tui.EventType) []action {
2300
+ return t.keymap[eventType.AsEvent()]
2301
+ }
2302
+
2303
+ var doAction func(action) bool
2304
+ doActions := func(actions []action) bool {
2305
+ for _, action := range actions {
2306
+ if !doAction(action) {
2307
+ return false
2308
+ }
2309
+ }
2310
+ return true
2311
+ }
2312
+ doAction = func(a action) bool {
2313
+ switch a.t {
2314
+ case actIgnore:
2315
+ case actExecute, actExecuteSilent:
2316
+ t.executeCommand(a.a, false, a.t == actExecuteSilent)
2317
+ case actExecuteMulti:
2318
+ t.executeCommand(a.a, true, false)
2319
+ case actInvalid:
2320
+ t.mutex.Unlock()
2321
+ return false
2322
+ case actTogglePreview:
2323
+ if t.hasPreviewer() {
2324
+ togglePreview(!t.previewer.enabled)
2325
+ if t.previewer.enabled {
2326
+ valid, list := t.buildPlusList(t.previewOpts.command, false)
2327
+ if valid {
2328
+ t.cancelPreview()
2329
+ t.previewBox.Set(reqPreviewEnqueue,
2330
+ previewRequest{t.previewOpts.command, t.pwindow, list})
2331
+ }
2332
+ }
2333
+ }
2334
+ case actTogglePreviewWrap:
2335
+ if t.hasPreviewWindow() {
2336
+ t.previewOpts.wrap = !t.previewOpts.wrap
2337
+ // Reset preview version so that full redraw occurs
2338
+ t.previewed.version = 0
2339
+ req(reqPreviewRefresh)
2340
+ }
2341
+ case actToggleSort:
2342
+ t.sort = !t.sort
2343
+ changed = true
2344
+ case actPreviewTop:
2345
+ if t.hasPreviewWindow() {
2346
+ scrollPreviewTo(0)
2347
+ }
2348
+ case actPreviewBottom:
2349
+ if t.hasPreviewWindow() {
2350
+ scrollPreviewTo(len(t.previewer.lines) - t.pwindow.Height())
2351
+ }
2352
+ case actPreviewUp:
2353
+ if t.hasPreviewWindow() {
2354
+ scrollPreviewBy(-1)
2355
+ }
2356
+ case actPreviewDown:
2357
+ if t.hasPreviewWindow() {
2358
+ scrollPreviewBy(1)
2359
+ }
2360
+ case actPreviewPageUp:
2361
+ if t.hasPreviewWindow() {
2362
+ scrollPreviewBy(-t.pwindow.Height())
2363
+ }
2364
+ case actPreviewPageDown:
2365
+ if t.hasPreviewWindow() {
2366
+ scrollPreviewBy(t.pwindow.Height())
2367
+ }
2368
+ case actPreviewHalfPageUp:
2369
+ if t.hasPreviewWindow() {
2370
+ scrollPreviewBy(-t.pwindow.Height() / 2)
2371
+ }
2372
+ case actPreviewHalfPageDown:
2373
+ if t.hasPreviewWindow() {
2374
+ scrollPreviewBy(t.pwindow.Height() / 2)
2375
+ }
2376
+ case actBeginningOfLine:
2377
+ t.cx = 0
2378
+ case actBackwardChar:
2379
+ if t.cx > 0 {
2380
+ t.cx--
2381
+ }
2382
+ case actPrintQuery:
2383
+ req(reqPrintQuery)
2384
+ case actChangePrompt:
2385
+ t.prompt, t.promptLen = t.parsePrompt(a.a)
2386
+ req(reqPrompt)
2387
+ case actPreview:
2388
+ togglePreview(true)
2389
+ refreshPreview(a.a)
2390
+ case actRefreshPreview:
2391
+ refreshPreview(t.previewOpts.command)
2392
+ case actReplaceQuery:
2393
+ current := t.currentItem()
2394
+ if current != nil {
2395
+ t.input = current.text.ToRunes()
2396
+ t.cx = len(t.input)
2397
+ }
2398
+ case actAbort:
2399
+ req(reqQuit)
2400
+ case actDeleteChar:
2401
+ t.delChar()
2402
+ case actDeleteCharEOF:
2403
+ if !t.delChar() && t.cx == 0 {
2404
+ req(reqQuit)
2405
+ }
2406
+ case actEndOfLine:
2407
+ t.cx = len(t.input)
2408
+ case actCancel:
2409
+ if len(t.input) == 0 {
2410
+ req(reqQuit)
2411
+ } else {
2412
+ t.yanked = t.input
2413
+ t.input = []rune{}
2414
+ t.cx = 0
2415
+ }
2416
+ case actBackwardDeleteCharEOF:
2417
+ if len(t.input) == 0 {
2418
+ req(reqQuit)
2419
+ } else if t.cx > 0 {
2420
+ t.input = append(t.input[:t.cx-1], t.input[t.cx:]...)
2421
+ t.cx--
2422
+ }
2423
+ case actForwardChar:
2424
+ if t.cx < len(t.input) {
2425
+ t.cx++
2426
+ }
2427
+ case actBackwardDeleteChar:
2428
+ beof = len(t.input) == 0
2429
+ if t.cx > 0 {
2430
+ t.input = append(t.input[:t.cx-1], t.input[t.cx:]...)
2431
+ t.cx--
2432
+ }
2433
+ case actSelectAll:
2434
+ if t.multi > 0 {
2435
+ for i := 0; i < t.merger.Length(); i++ {
2436
+ if !t.selectItem(t.merger.Get(i).item) {
2437
+ break
2438
+ }
2439
+ }
2440
+ req(reqList, reqInfo)
2441
+ }
2442
+ case actDeselectAll:
2443
+ if t.multi > 0 {
2444
+ for i := 0; i < t.merger.Length() && len(t.selected) > 0; i++ {
2445
+ t.deselectItem(t.merger.Get(i).item)
2446
+ }
2447
+ req(reqList, reqInfo)
2448
+ }
2449
+ case actClose:
2450
+ if t.isPreviewEnabled() {
2451
+ togglePreview(false)
2452
+ } else {
2453
+ req(reqQuit)
2454
+ }
2455
+ case actSelect:
2456
+ current := t.currentItem()
2457
+ if t.multi > 0 && current != nil && t.selectItemChanged(current) {
2458
+ req(reqList, reqInfo)
2459
+ }
2460
+ case actDeselect:
2461
+ current := t.currentItem()
2462
+ if t.multi > 0 && current != nil && t.deselectItemChanged(current) {
2463
+ req(reqList, reqInfo)
2464
+ }
2465
+ case actToggle:
2466
+ if t.multi > 0 && t.merger.Length() > 0 && toggle() {
2467
+ req(reqList)
2468
+ }
2469
+ case actToggleAll:
2470
+ if t.multi > 0 {
2471
+ prevIndexes := make(map[int]struct{})
2472
+ for i := 0; i < t.merger.Length() && len(t.selected) > 0; i++ {
2473
+ item := t.merger.Get(i).item
2474
+ if _, found := t.selected[item.Index()]; found {
2475
+ prevIndexes[i] = struct{}{}
2476
+ t.deselectItem(item)
2477
+ }
2478
+ }
2479
+
2480
+ for i := 0; i < t.merger.Length(); i++ {
2481
+ if _, found := prevIndexes[i]; !found {
2482
+ item := t.merger.Get(i).item
2483
+ if !t.selectItem(item) {
2484
+ break
2485
+ }
2486
+ }
2487
+ }
2488
+ req(reqList, reqInfo)
2489
+ }
2490
+ case actToggleIn:
2491
+ if t.layout != layoutDefault {
2492
+ return doAction(action{t: actToggleUp})
2493
+ }
2494
+ return doAction(action{t: actToggleDown})
2495
+ case actToggleOut:
2496
+ if t.layout != layoutDefault {
2497
+ return doAction(action{t: actToggleDown})
2498
+ }
2499
+ return doAction(action{t: actToggleUp})
2500
+ case actToggleDown:
2501
+ if t.multi > 0 && t.merger.Length() > 0 && toggle() {
2502
+ t.vmove(-1, true)
2503
+ req(reqList)
2504
+ }
2505
+ case actToggleUp:
2506
+ if t.multi > 0 && t.merger.Length() > 0 && toggle() {
2507
+ t.vmove(1, true)
2508
+ req(reqList)
2509
+ }
2510
+ case actDown:
2511
+ t.vmove(-1, true)
2512
+ req(reqList)
2513
+ case actUp:
2514
+ t.vmove(1, true)
2515
+ req(reqList)
2516
+ case actAccept:
2517
+ req(reqClose)
2518
+ case actAcceptNonEmpty:
2519
+ if len(t.selected) > 0 || t.merger.Length() > 0 || !t.reading && t.count == 0 {
2520
+ req(reqClose)
2521
+ }
2522
+ case actClearScreen:
2523
+ req(reqRedraw)
2524
+ case actClearQuery:
2525
+ t.input = []rune{}
2526
+ t.cx = 0
2527
+ case actClearSelection:
2528
+ if t.multi > 0 {
2529
+ t.selected = make(map[int32]selectedItem)
2530
+ t.version++
2531
+ req(reqList, reqInfo)
2532
+ }
2533
+ case actFirst:
2534
+ t.vset(0)
2535
+ req(reqList)
2536
+ case actLast:
2537
+ t.vset(t.merger.Length() - 1)
2538
+ req(reqList)
2539
+ case actUnixLineDiscard:
2540
+ beof = len(t.input) == 0
2541
+ if t.cx > 0 {
2542
+ t.yanked = copySlice(t.input[:t.cx])
2543
+ t.input = t.input[t.cx:]
2544
+ t.cx = 0
2545
+ }
2546
+ case actUnixWordRubout:
2547
+ beof = len(t.input) == 0
2548
+ if t.cx > 0 {
2549
+ t.rubout("\\s\\S")
2550
+ }
2551
+ case actBackwardKillWord:
2552
+ beof = len(t.input) == 0
2553
+ if t.cx > 0 {
2554
+ t.rubout(t.wordRubout)
2555
+ }
2556
+ case actYank:
2557
+ suffix := copySlice(t.input[t.cx:])
2558
+ t.input = append(append(t.input[:t.cx], t.yanked...), suffix...)
2559
+ t.cx += len(t.yanked)
2560
+ case actPageUp:
2561
+ t.vmove(t.maxItems()-1, false)
2562
+ req(reqList)
2563
+ case actPageDown:
2564
+ t.vmove(-(t.maxItems() - 1), false)
2565
+ req(reqList)
2566
+ case actHalfPageUp:
2567
+ t.vmove(t.maxItems()/2, false)
2568
+ req(reqList)
2569
+ case actHalfPageDown:
2570
+ t.vmove(-(t.maxItems() / 2), false)
2571
+ req(reqList)
2572
+ case actJump:
2573
+ t.jumping = jumpEnabled
2574
+ req(reqJump)
2575
+ case actJumpAccept:
2576
+ t.jumping = jumpAcceptEnabled
2577
+ req(reqJump)
2578
+ case actBackwardWord:
2579
+ t.cx = findLastMatch(t.wordRubout, string(t.input[:t.cx])) + 1
2580
+ case actForwardWord:
2581
+ t.cx += findFirstMatch(t.wordNext, string(t.input[t.cx:])) + 1
2582
+ case actKillWord:
2583
+ ncx := t.cx +
2584
+ findFirstMatch(t.wordNext, string(t.input[t.cx:])) + 1
2585
+ if ncx > t.cx {
2586
+ t.yanked = copySlice(t.input[t.cx:ncx])
2587
+ t.input = append(t.input[:t.cx], t.input[ncx:]...)
2588
+ }
2589
+ case actKillLine:
2590
+ if t.cx < len(t.input) {
2591
+ t.yanked = copySlice(t.input[t.cx:])
2592
+ t.input = t.input[:t.cx]
2593
+ }
2594
+ case actRune:
2595
+ prefix := copySlice(t.input[:t.cx])
2596
+ t.input = append(append(prefix, event.Char), t.input[t.cx:]...)
2597
+ t.cx++
2598
+ case actPreviousHistory:
2599
+ if t.history != nil {
2600
+ t.history.override(string(t.input))
2601
+ t.input = trimQuery(t.history.previous())
2602
+ t.cx = len(t.input)
2603
+ }
2604
+ case actNextHistory:
2605
+ if t.history != nil {
2606
+ t.history.override(string(t.input))
2607
+ t.input = trimQuery(t.history.next())
2608
+ t.cx = len(t.input)
2609
+ }
2610
+ case actToggleSearch:
2611
+ t.paused = !t.paused
2612
+ changed = !t.paused
2613
+ req(reqPrompt)
2614
+ case actEnableSearch:
2615
+ t.paused = false
2616
+ changed = true
2617
+ req(reqPrompt)
2618
+ case actDisableSearch:
2619
+ t.paused = true
2620
+ req(reqPrompt)
2621
+ case actSigStop:
2622
+ p, err := os.FindProcess(os.Getpid())
2623
+ if err == nil {
2624
+ t.sigstop = true
2625
+ t.tui.Clear()
2626
+ t.tui.Pause(t.fullscreen)
2627
+ notifyStop(p)
2628
+ t.mutex.Unlock()
2629
+ return false
2630
+ }
2631
+ case actMouse:
2632
+ me := event.MouseEvent
2633
+ mx, my := me.X, me.Y
2634
+ if me.S != 0 {
2635
+ // Scroll
2636
+ if t.window.Enclose(my, mx) && t.merger.Length() > 0 {
2637
+ if t.multi > 0 && me.Mod {
2638
+ toggle()
2639
+ }
2640
+ t.vmove(me.S, true)
2641
+ req(reqList)
2642
+ } else if t.hasPreviewWindow() && t.pwindow.Enclose(my, mx) {
2643
+ scrollPreviewBy(-me.S)
2644
+ }
2645
+ } else if t.window.Enclose(my, mx) {
2646
+ mx -= t.window.Left()
2647
+ my -= t.window.Top()
2648
+ mx = util.Constrain(mx-t.promptLen, 0, len(t.input))
2649
+ min := 2 + len(t.header)
2650
+ if t.noInfoLine() {
2651
+ min--
2652
+ }
2653
+ h := t.window.Height()
2654
+ switch t.layout {
2655
+ case layoutDefault:
2656
+ my = h - my - 1
2657
+ case layoutReverseList:
2658
+ if my < h-min {
2659
+ my += min
2660
+ } else {
2661
+ my = h - my - 1
2662
+ }
2663
+ }
2664
+ if me.Double {
2665
+ // Double-click
2666
+ if my >= min {
2667
+ if t.vset(t.offset+my-min) && t.cy < t.merger.Length() {
2668
+ return doActions(actionsFor(tui.DoubleClick))
2669
+ }
2670
+ }
2671
+ } else if me.Down {
2672
+ if my == t.promptLine() && mx >= 0 {
2673
+ // Prompt
2674
+ t.cx = mx + t.xoffset
2675
+ } else if my >= min {
2676
+ // List
2677
+ if t.vset(t.offset+my-min) && t.multi > 0 && me.Mod {
2678
+ toggle()
2679
+ }
2680
+ req(reqList)
2681
+ if me.Left {
2682
+ return doActions(actionsFor(tui.LeftClick))
2683
+ }
2684
+ return doActions(actionsFor(tui.RightClick))
2685
+ }
2686
+ }
2687
+ }
2688
+ case actReload:
2689
+ t.failed = nil
2690
+
2691
+ valid, list := t.buildPlusList(a.a, false)
2692
+ if !valid {
2693
+ // We run the command even when there's no match
2694
+ // 1. If the template doesn't have any slots
2695
+ // 2. If the template has {q}
2696
+ slot, _, query := hasPreviewFlags(a.a)
2697
+ valid = !slot || query
2698
+ }
2699
+ if valid {
2700
+ command := t.replacePlaceholder(a.a, false, string(t.input), list)
2701
+ newCommand = &command
2702
+ t.reading = true
2703
+ t.version++
2704
+ }
2705
+ case actUnbind:
2706
+ keys := parseKeyChords(a.a, "PANIC")
2707
+ for key := range keys {
2708
+ delete(t.keymap, key)
2709
+ }
2710
+ }
2711
+ return true
2712
+ }
2713
+
2714
+ if t.jumping == jumpDisabled {
2715
+ actions := t.keymap[event.Comparable()]
2716
+ if len(actions) == 0 && event.Type == tui.Rune {
2717
+ doAction(action{t: actRune})
2718
+ } else if !doActions(actions) {
2719
+ continue
2720
+ }
2721
+ t.truncateQuery()
2722
+ queryChanged = string(previousInput) != string(t.input)
2723
+ changed = changed || queryChanged
2724
+ if onChanges, prs := t.keymap[tui.Change.AsEvent()]; queryChanged && prs {
2725
+ if !doActions(onChanges) {
2726
+ continue
2727
+ }
2728
+ }
2729
+ if onEOFs, prs := t.keymap[tui.BackwardEOF.AsEvent()]; beof && prs {
2730
+ if !doActions(onEOFs) {
2731
+ continue
2732
+ }
2733
+ }
2734
+ } else {
2735
+ if event.Type == tui.Rune {
2736
+ if idx := strings.IndexRune(t.jumpLabels, event.Char); idx >= 0 && idx < t.maxItems() && idx < t.merger.Length() {
2737
+ t.cy = idx + t.offset
2738
+ if t.jumping == jumpAcceptEnabled {
2739
+ req(reqClose)
2740
+ }
2741
+ }
2742
+ }
2743
+ t.jumping = jumpDisabled
2744
+ req(reqList)
2745
+ }
2746
+
2747
+ if queryChanged {
2748
+ if t.isPreviewEnabled() {
2749
+ _, _, q := hasPreviewFlags(t.previewOpts.command)
2750
+ if q {
2751
+ t.version++
2752
+ }
2753
+ }
2754
+ }
2755
+
2756
+ if queryChanged || t.cx != previousCx {
2757
+ req(reqPrompt)
2758
+ }
2759
+
2760
+ t.mutex.Unlock() // Must be unlocked before touching reqBox
2761
+
2762
+ if changed || newCommand != nil {
2763
+ t.eventBox.Set(EvtSearchNew, searchRequest{sort: t.sort, command: newCommand})
2764
+ }
2765
+ for _, event := range events {
2766
+ t.reqBox.Set(event, nil)
2767
+ }
2768
+ }
2769
+ }
2770
+
2771
+ func (t *Terminal) constrain() {
2772
+ // count of items to display allowed by filtering
2773
+ count := t.merger.Length()
2774
+ // count of lines can be displayed
2775
+ height := t.maxItems()
2776
+
2777
+ t.cy = util.Constrain(t.cy, 0, count-1)
2778
+
2779
+ minOffset := util.Max(t.cy-height+1, 0)
2780
+ maxOffset := util.Max(util.Min(count-height, t.cy), 0)
2781
+ t.offset = util.Constrain(t.offset, minOffset, maxOffset)
2782
+ if t.scrollOff == 0 {
2783
+ return
2784
+ }
2785
+
2786
+ scrollOff := util.Min(height/2, t.scrollOff)
2787
+ for {
2788
+ prevOffset := t.offset
2789
+ if t.cy-t.offset < scrollOff {
2790
+ t.offset = util.Max(minOffset, t.offset-1)
2791
+ }
2792
+ if t.cy-t.offset >= height-scrollOff {
2793
+ t.offset = util.Min(maxOffset, t.offset+1)
2794
+ }
2795
+ if t.offset == prevOffset {
2796
+ break
2797
+ }
2798
+ }
2799
+ }
2800
+
2801
+ func (t *Terminal) vmove(o int, allowCycle bool) {
2802
+ if t.layout != layoutDefault {
2803
+ o *= -1
2804
+ }
2805
+ dest := t.cy + o
2806
+ if t.cycle && allowCycle {
2807
+ max := t.merger.Length() - 1
2808
+ if dest > max {
2809
+ if t.cy == max {
2810
+ dest = 0
2811
+ }
2812
+ } else if dest < 0 {
2813
+ if t.cy == 0 {
2814
+ dest = max
2815
+ }
2816
+ }
2817
+ }
2818
+ t.vset(dest)
2819
+ }
2820
+
2821
+ func (t *Terminal) vset(o int) bool {
2822
+ t.cy = util.Constrain(o, 0, t.merger.Length()-1)
2823
+ return t.cy == o
2824
+ }
2825
+
2826
+ func (t *Terminal) maxItems() int {
2827
+ max := t.window.Height() - 2 - len(t.header)
2828
+ if t.noInfoLine() {
2829
+ max++
2830
+ }
2831
+ return util.Max(max, 0)
2832
+ }