doing 2.0.20 → 2.0.21

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