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,1691 @@
1
+ package fzf
2
+
3
+ import (
4
+ "fmt"
5
+ "os"
6
+ "regexp"
7
+ "strconv"
8
+ "strings"
9
+ "unicode"
10
+
11
+ "github.com/junegunn/fzf/src/algo"
12
+ "github.com/junegunn/fzf/src/tui"
13
+
14
+ "github.com/mattn/go-runewidth"
15
+ "github.com/mattn/go-shellwords"
16
+ )
17
+
18
+ const usage = `usage: fzf [options]
19
+
20
+ Search
21
+ -x, --extended Extended-search mode
22
+ (enabled by default; +x or --no-extended to disable)
23
+ -e, --exact Enable Exact-match
24
+ --algo=TYPE Fuzzy matching algorithm: [v1|v2] (default: v2)
25
+ -i Case-insensitive match (default: smart-case match)
26
+ +i Case-sensitive match
27
+ --literal Do not normalize latin script letters before matching
28
+ -n, --nth=N[,..] Comma-separated list of field index expressions
29
+ for limiting search scope. Each can be a non-zero
30
+ integer or a range expression ([BEGIN]..[END]).
31
+ --with-nth=N[,..] Transform the presentation of each line using
32
+ field index expressions
33
+ -d, --delimiter=STR Field delimiter regex (default: AWK-style)
34
+ +s, --no-sort Do not sort the result
35
+ --tac Reverse the order of the input
36
+ --disabled Do not perform search
37
+ --tiebreak=CRI[,..] Comma-separated list of sort criteria to apply
38
+ when the scores are tied [length|begin|end|index]
39
+ (default: length)
40
+
41
+ Interface
42
+ -m, --multi[=MAX] Enable multi-select with tab/shift-tab
43
+ --no-mouse Disable mouse
44
+ --bind=KEYBINDS Custom key bindings. Refer to the man page.
45
+ --cycle Enable cyclic scroll
46
+ --keep-right Keep the right end of the line visible on overflow
47
+ --scroll-off=LINES Number of screen lines to keep above or below when
48
+ scrolling to the top or to the bottom (default: 0)
49
+ --no-hscroll Disable horizontal scroll
50
+ --hscroll-off=COLS Number of screen columns to keep to the right of the
51
+ highlighted substring (default: 10)
52
+ --filepath-word Make word-wise movements respect path separators
53
+ --jump-labels=CHARS Label characters for jump and jump-accept
54
+
55
+ Layout
56
+ --height=HEIGHT[%] Display fzf window below the cursor with the given
57
+ height instead of using fullscreen
58
+ --min-height=HEIGHT Minimum height when --height is given in percent
59
+ (default: 10)
60
+ --layout=LAYOUT Choose layout: [default|reverse|reverse-list]
61
+ --border[=STYLE] Draw border around the finder
62
+ [rounded|sharp|horizontal|vertical|
63
+ top|bottom|left|right|none] (default: rounded)
64
+ --margin=MARGIN Screen margin (TRBL | TB,RL | T,RL,B | T,R,B,L)
65
+ --padding=PADDING Padding inside border (TRBL | TB,RL | T,RL,B | T,R,B,L)
66
+ --info=STYLE Finder info style [default|inline|hidden]
67
+ --prompt=STR Input prompt (default: '> ')
68
+ --pointer=STR Pointer to the current line (default: '>')
69
+ --marker=STR Multi-select marker (default: '>')
70
+ --header=STR String to print as header
71
+ --header-lines=N The first N lines of the input are treated as header
72
+ --header-first Print header before the prompt line
73
+
74
+ Display
75
+ --ansi Enable processing of ANSI color codes
76
+ --tabstop=SPACES Number of spaces for a tab character (default: 8)
77
+ --color=COLSPEC Base scheme (dark|light|16|bw) and/or custom colors
78
+ --no-bold Do not use bold text
79
+
80
+ History
81
+ --history=FILE History file
82
+ --history-size=N Maximum number of history entries (default: 1000)
83
+
84
+ Preview
85
+ --preview=COMMAND Command to preview highlighted line ({})
86
+ --preview-window=OPT Preview window layout (default: right:50%)
87
+ [up|down|left|right][,SIZE[%]]
88
+ [,[no]wrap][,[no]cycle][,[no]follow][,[no]hidden]
89
+ [,border-BORDER_OPT]
90
+ [,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES]
91
+ [,default]
92
+
93
+ Scripting
94
+ -q, --query=STR Start the finder with the given query
95
+ -1, --select-1 Automatically select the only match
96
+ -0, --exit-0 Exit immediately when there's no match
97
+ -f, --filter=STR Filter mode. Do not start interactive finder.
98
+ --print-query Print query as the first line
99
+ --expect=KEYS Comma-separated list of keys to complete fzf
100
+ --read0 Read input delimited by ASCII NUL characters
101
+ --print0 Print output delimited by ASCII NUL characters
102
+ --sync Synchronous search for multi-staged filtering
103
+ --version Display version information and exit
104
+
105
+ Environment variables
106
+ FZF_DEFAULT_COMMAND Default command to use when input is tty
107
+ FZF_DEFAULT_OPTS Default options
108
+ (e.g. '--layout=reverse --inline-info')
109
+
110
+ `
111
+
112
+ // Case denotes case-sensitivity of search
113
+ type Case int
114
+
115
+ // Case-sensitivities
116
+ const (
117
+ CaseSmart Case = iota
118
+ CaseIgnore
119
+ CaseRespect
120
+ )
121
+
122
+ // Sort criteria
123
+ type criterion int
124
+
125
+ const (
126
+ byScore criterion = iota
127
+ byLength
128
+ byBegin
129
+ byEnd
130
+ )
131
+
132
+ type sizeSpec struct {
133
+ size float64
134
+ percent bool
135
+ }
136
+
137
+ func defaultMargin() [4]sizeSpec {
138
+ return [4]sizeSpec{}
139
+ }
140
+
141
+ type windowPosition int
142
+
143
+ const (
144
+ posUp windowPosition = iota
145
+ posDown
146
+ posLeft
147
+ posRight
148
+ )
149
+
150
+ type layoutType int
151
+
152
+ const (
153
+ layoutDefault layoutType = iota
154
+ layoutReverse
155
+ layoutReverseList
156
+ )
157
+
158
+ type infoStyle int
159
+
160
+ const (
161
+ infoDefault infoStyle = iota
162
+ infoInline
163
+ infoHidden
164
+ )
165
+
166
+ type previewOpts struct {
167
+ command string
168
+ position windowPosition
169
+ size sizeSpec
170
+ scroll string
171
+ hidden bool
172
+ wrap bool
173
+ cycle bool
174
+ follow bool
175
+ border tui.BorderShape
176
+ headerLines int
177
+ }
178
+
179
+ // Options stores the values of command-line options
180
+ type Options struct {
181
+ Fuzzy bool
182
+ FuzzyAlgo algo.Algo
183
+ Extended bool
184
+ Phony bool
185
+ Case Case
186
+ Normalize bool
187
+ Nth []Range
188
+ WithNth []Range
189
+ Delimiter Delimiter
190
+ Sort int
191
+ Tac bool
192
+ Criteria []criterion
193
+ Multi int
194
+ Ansi bool
195
+ Mouse bool
196
+ Theme *tui.ColorTheme
197
+ Black bool
198
+ Bold bool
199
+ Height sizeSpec
200
+ MinHeight int
201
+ Layout layoutType
202
+ Cycle bool
203
+ KeepRight bool
204
+ Hscroll bool
205
+ HscrollOff int
206
+ ScrollOff int
207
+ FileWord bool
208
+ InfoStyle infoStyle
209
+ JumpLabels string
210
+ Prompt string
211
+ Pointer string
212
+ Marker string
213
+ Query string
214
+ Select1 bool
215
+ Exit0 bool
216
+ Filter *string
217
+ ToggleSort bool
218
+ Expect map[tui.Event]string
219
+ Keymap map[tui.Event][]action
220
+ Preview previewOpts
221
+ PrintQuery bool
222
+ ReadZero bool
223
+ Printer func(string)
224
+ PrintSep string
225
+ Sync bool
226
+ History *History
227
+ Header []string
228
+ HeaderLines int
229
+ HeaderFirst bool
230
+ Margin [4]sizeSpec
231
+ Padding [4]sizeSpec
232
+ BorderShape tui.BorderShape
233
+ Unicode bool
234
+ Tabstop int
235
+ ClearOnExit bool
236
+ Version bool
237
+ }
238
+
239
+ func defaultPreviewOpts(command string) previewOpts {
240
+ return previewOpts{command, posRight, sizeSpec{50, true}, "", false, false, false, false, tui.BorderRounded, 0}
241
+ }
242
+
243
+ func defaultOptions() *Options {
244
+ return &Options{
245
+ Fuzzy: true,
246
+ FuzzyAlgo: algo.FuzzyMatchV2,
247
+ Extended: true,
248
+ Phony: false,
249
+ Case: CaseSmart,
250
+ Normalize: true,
251
+ Nth: make([]Range, 0),
252
+ WithNth: make([]Range, 0),
253
+ Delimiter: Delimiter{},
254
+ Sort: 1000,
255
+ Tac: false,
256
+ Criteria: []criterion{byScore, byLength},
257
+ Multi: 0,
258
+ Ansi: false,
259
+ Mouse: true,
260
+ Theme: tui.EmptyTheme(),
261
+ Black: false,
262
+ Bold: true,
263
+ MinHeight: 10,
264
+ Layout: layoutDefault,
265
+ Cycle: false,
266
+ KeepRight: false,
267
+ Hscroll: true,
268
+ HscrollOff: 10,
269
+ ScrollOff: 0,
270
+ FileWord: false,
271
+ InfoStyle: infoDefault,
272
+ JumpLabels: defaultJumpLabels,
273
+ Prompt: "> ",
274
+ Pointer: ">",
275
+ Marker: ">",
276
+ Query: "",
277
+ Select1: false,
278
+ Exit0: false,
279
+ Filter: nil,
280
+ ToggleSort: false,
281
+ Expect: make(map[tui.Event]string),
282
+ Keymap: make(map[tui.Event][]action),
283
+ Preview: defaultPreviewOpts(""),
284
+ PrintQuery: false,
285
+ ReadZero: false,
286
+ Printer: func(str string) { fmt.Println(str) },
287
+ PrintSep: "\n",
288
+ Sync: false,
289
+ History: nil,
290
+ Header: make([]string, 0),
291
+ HeaderLines: 0,
292
+ HeaderFirst: false,
293
+ Margin: defaultMargin(),
294
+ Padding: defaultMargin(),
295
+ Unicode: true,
296
+ Tabstop: 8,
297
+ ClearOnExit: true,
298
+ Version: false}
299
+ }
300
+
301
+ func help(code int) {
302
+ os.Stdout.WriteString(usage)
303
+ os.Exit(code)
304
+ }
305
+
306
+ func errorExit(msg string) {
307
+ os.Stderr.WriteString(msg + "\n")
308
+ os.Exit(exitError)
309
+ }
310
+
311
+ func optString(arg string, prefixes ...string) (bool, string) {
312
+ for _, prefix := range prefixes {
313
+ if strings.HasPrefix(arg, prefix) {
314
+ return true, arg[len(prefix):]
315
+ }
316
+ }
317
+ return false, ""
318
+ }
319
+
320
+ func nextString(args []string, i *int, message string) string {
321
+ if len(args) > *i+1 {
322
+ *i++
323
+ } else {
324
+ errorExit(message)
325
+ }
326
+ return args[*i]
327
+ }
328
+
329
+ func optionalNextString(args []string, i *int) (bool, string) {
330
+ if len(args) > *i+1 && !strings.HasPrefix(args[*i+1], "-") && !strings.HasPrefix(args[*i+1], "+") {
331
+ *i++
332
+ return true, args[*i]
333
+ }
334
+ return false, ""
335
+ }
336
+
337
+ func atoi(str string) int {
338
+ num, err := strconv.Atoi(str)
339
+ if err != nil {
340
+ errorExit("not a valid integer: " + str)
341
+ }
342
+ return num
343
+ }
344
+
345
+ func atof(str string) float64 {
346
+ num, err := strconv.ParseFloat(str, 64)
347
+ if err != nil {
348
+ errorExit("not a valid number: " + str)
349
+ }
350
+ return num
351
+ }
352
+
353
+ func nextInt(args []string, i *int, message string) int {
354
+ if len(args) > *i+1 {
355
+ *i++
356
+ } else {
357
+ errorExit(message)
358
+ }
359
+ return atoi(args[*i])
360
+ }
361
+
362
+ func optionalNumeric(args []string, i *int, defaultValue int) int {
363
+ if len(args) > *i+1 {
364
+ if strings.IndexAny(args[*i+1], "0123456789") == 0 {
365
+ *i++
366
+ return atoi(args[*i])
367
+ }
368
+ }
369
+ return defaultValue
370
+ }
371
+
372
+ func splitNth(str string) []Range {
373
+ if match, _ := regexp.MatchString("^[0-9,-.]+$", str); !match {
374
+ errorExit("invalid format: " + str)
375
+ }
376
+
377
+ tokens := strings.Split(str, ",")
378
+ ranges := make([]Range, len(tokens))
379
+ for idx, s := range tokens {
380
+ r, ok := ParseRange(&s)
381
+ if !ok {
382
+ errorExit("invalid format: " + str)
383
+ }
384
+ ranges[idx] = r
385
+ }
386
+ return ranges
387
+ }
388
+
389
+ func delimiterRegexp(str string) Delimiter {
390
+ // Special handling of \t
391
+ str = strings.Replace(str, "\\t", "\t", -1)
392
+
393
+ // 1. Pattern does not contain any special character
394
+ if regexp.QuoteMeta(str) == str {
395
+ return Delimiter{str: &str}
396
+ }
397
+
398
+ rx, e := regexp.Compile(str)
399
+ // 2. Pattern is not a valid regular expression
400
+ if e != nil {
401
+ return Delimiter{str: &str}
402
+ }
403
+
404
+ // 3. Pattern as regular expression. Slow.
405
+ return Delimiter{regex: rx}
406
+ }
407
+
408
+ func isAlphabet(char uint8) bool {
409
+ return char >= 'a' && char <= 'z'
410
+ }
411
+
412
+ func isNumeric(char uint8) bool {
413
+ return char >= '0' && char <= '9'
414
+ }
415
+
416
+ func parseAlgo(str string) algo.Algo {
417
+ switch str {
418
+ case "v1":
419
+ return algo.FuzzyMatchV1
420
+ case "v2":
421
+ return algo.FuzzyMatchV2
422
+ default:
423
+ errorExit("invalid algorithm (expected: v1 or v2)")
424
+ }
425
+ return algo.FuzzyMatchV2
426
+ }
427
+
428
+ func parseBorder(str string, optional bool) tui.BorderShape {
429
+ switch str {
430
+ case "rounded":
431
+ return tui.BorderRounded
432
+ case "sharp":
433
+ return tui.BorderSharp
434
+ case "horizontal":
435
+ return tui.BorderHorizontal
436
+ case "vertical":
437
+ return tui.BorderVertical
438
+ case "top":
439
+ return tui.BorderTop
440
+ case "bottom":
441
+ return tui.BorderBottom
442
+ case "left":
443
+ return tui.BorderLeft
444
+ case "right":
445
+ return tui.BorderRight
446
+ case "none":
447
+ return tui.BorderNone
448
+ default:
449
+ if optional && str == "" {
450
+ return tui.BorderRounded
451
+ }
452
+ errorExit("invalid border style (expected: rounded|sharp|horizontal|vertical|top|bottom|left|right|none)")
453
+ }
454
+ return tui.BorderNone
455
+ }
456
+
457
+ func parseKeyChords(str string, message string) map[tui.Event]string {
458
+ if len(str) == 0 {
459
+ errorExit(message)
460
+ }
461
+
462
+ str = regexp.MustCompile("(?i)(alt-),").ReplaceAllString(str, "$1"+string([]rune{escapedComma}))
463
+ tokens := strings.Split(str, ",")
464
+ if str == "," || strings.HasPrefix(str, ",,") || strings.HasSuffix(str, ",,") || strings.Contains(str, ",,,") {
465
+ tokens = append(tokens, ",")
466
+ }
467
+
468
+ chords := make(map[tui.Event]string)
469
+ for _, key := range tokens {
470
+ if len(key) == 0 {
471
+ continue // ignore
472
+ }
473
+ key = strings.ReplaceAll(key, string([]rune{escapedComma}), ",")
474
+ lkey := strings.ToLower(key)
475
+ add := func(e tui.EventType) {
476
+ chords[e.AsEvent()] = key
477
+ }
478
+ switch lkey {
479
+ case "up":
480
+ add(tui.Up)
481
+ case "down":
482
+ add(tui.Down)
483
+ case "left":
484
+ add(tui.Left)
485
+ case "right":
486
+ add(tui.Right)
487
+ case "enter", "return":
488
+ add(tui.CtrlM)
489
+ case "space":
490
+ chords[tui.Key(' ')] = key
491
+ case "bspace", "bs":
492
+ add(tui.BSpace)
493
+ case "ctrl-space":
494
+ add(tui.CtrlSpace)
495
+ case "ctrl-^", "ctrl-6":
496
+ add(tui.CtrlCaret)
497
+ case "ctrl-/", "ctrl-_":
498
+ add(tui.CtrlSlash)
499
+ case "ctrl-\\":
500
+ add(tui.CtrlBackSlash)
501
+ case "ctrl-]":
502
+ add(tui.CtrlRightBracket)
503
+ case "change":
504
+ add(tui.Change)
505
+ case "backward-eof":
506
+ add(tui.BackwardEOF)
507
+ case "alt-enter", "alt-return":
508
+ chords[tui.CtrlAltKey('m')] = key
509
+ case "alt-space":
510
+ chords[tui.AltKey(' ')] = key
511
+ case "alt-bs", "alt-bspace":
512
+ add(tui.AltBS)
513
+ case "alt-up":
514
+ add(tui.AltUp)
515
+ case "alt-down":
516
+ add(tui.AltDown)
517
+ case "alt-left":
518
+ add(tui.AltLeft)
519
+ case "alt-right":
520
+ add(tui.AltRight)
521
+ case "tab":
522
+ add(tui.Tab)
523
+ case "btab", "shift-tab":
524
+ add(tui.BTab)
525
+ case "esc":
526
+ add(tui.ESC)
527
+ case "del":
528
+ add(tui.Del)
529
+ case "home":
530
+ add(tui.Home)
531
+ case "end":
532
+ add(tui.End)
533
+ case "insert":
534
+ add(tui.Insert)
535
+ case "pgup", "page-up":
536
+ add(tui.PgUp)
537
+ case "pgdn", "page-down":
538
+ add(tui.PgDn)
539
+ case "alt-shift-up", "shift-alt-up":
540
+ add(tui.AltSUp)
541
+ case "alt-shift-down", "shift-alt-down":
542
+ add(tui.AltSDown)
543
+ case "alt-shift-left", "shift-alt-left":
544
+ add(tui.AltSLeft)
545
+ case "alt-shift-right", "shift-alt-right":
546
+ add(tui.AltSRight)
547
+ case "shift-up":
548
+ add(tui.SUp)
549
+ case "shift-down":
550
+ add(tui.SDown)
551
+ case "shift-left":
552
+ add(tui.SLeft)
553
+ case "shift-right":
554
+ add(tui.SRight)
555
+ case "left-click":
556
+ add(tui.LeftClick)
557
+ case "right-click":
558
+ add(tui.RightClick)
559
+ case "double-click":
560
+ add(tui.DoubleClick)
561
+ case "f10":
562
+ add(tui.F10)
563
+ case "f11":
564
+ add(tui.F11)
565
+ case "f12":
566
+ add(tui.F12)
567
+ default:
568
+ runes := []rune(key)
569
+ if len(key) == 10 && strings.HasPrefix(lkey, "ctrl-alt-") && isAlphabet(lkey[9]) {
570
+ chords[tui.CtrlAltKey(rune(key[9]))] = key
571
+ } else if len(key) == 6 && strings.HasPrefix(lkey, "ctrl-") && isAlphabet(lkey[5]) {
572
+ add(tui.EventType(tui.CtrlA.Int() + int(lkey[5]) - 'a'))
573
+ } else if len(runes) == 5 && strings.HasPrefix(lkey, "alt-") {
574
+ r := runes[4]
575
+ switch r {
576
+ case escapedColon:
577
+ r = ':'
578
+ case escapedComma:
579
+ r = ','
580
+ case escapedPlus:
581
+ r = '+'
582
+ }
583
+ chords[tui.AltKey(r)] = key
584
+ } else if len(key) == 2 && strings.HasPrefix(lkey, "f") && key[1] >= '1' && key[1] <= '9' {
585
+ add(tui.EventType(tui.F1.Int() + int(key[1]) - '1'))
586
+ } else if len(runes) == 1 {
587
+ chords[tui.Key(runes[0])] = key
588
+ } else {
589
+ errorExit("unsupported key: " + key)
590
+ }
591
+ }
592
+ }
593
+ return chords
594
+ }
595
+
596
+ func parseTiebreak(str string) []criterion {
597
+ criteria := []criterion{byScore}
598
+ hasIndex := false
599
+ hasLength := false
600
+ hasBegin := false
601
+ hasEnd := false
602
+ check := func(notExpected *bool, name string) {
603
+ if *notExpected {
604
+ errorExit("duplicate sort criteria: " + name)
605
+ }
606
+ if hasIndex {
607
+ errorExit("index should be the last criterion")
608
+ }
609
+ *notExpected = true
610
+ }
611
+ for _, str := range strings.Split(strings.ToLower(str), ",") {
612
+ switch str {
613
+ case "index":
614
+ check(&hasIndex, "index")
615
+ case "length":
616
+ check(&hasLength, "length")
617
+ criteria = append(criteria, byLength)
618
+ case "begin":
619
+ check(&hasBegin, "begin")
620
+ criteria = append(criteria, byBegin)
621
+ case "end":
622
+ check(&hasEnd, "end")
623
+ criteria = append(criteria, byEnd)
624
+ default:
625
+ errorExit("invalid sort criterion: " + str)
626
+ }
627
+ }
628
+ return criteria
629
+ }
630
+
631
+ func dupeTheme(theme *tui.ColorTheme) *tui.ColorTheme {
632
+ dupe := *theme
633
+ return &dupe
634
+ }
635
+
636
+ func parseTheme(defaultTheme *tui.ColorTheme, str string) *tui.ColorTheme {
637
+ theme := dupeTheme(defaultTheme)
638
+ rrggbb := regexp.MustCompile("^#[0-9a-fA-F]{6}$")
639
+ for _, str := range strings.Split(strings.ToLower(str), ",") {
640
+ switch str {
641
+ case "dark":
642
+ theme = dupeTheme(tui.Dark256)
643
+ case "light":
644
+ theme = dupeTheme(tui.Light256)
645
+ case "16":
646
+ theme = dupeTheme(tui.Default16)
647
+ case "bw", "no":
648
+ theme = tui.NoColorTheme()
649
+ default:
650
+ fail := func() {
651
+ errorExit("invalid color specification: " + str)
652
+ }
653
+ // Color is disabled
654
+ if theme == nil {
655
+ continue
656
+ }
657
+
658
+ components := strings.Split(str, ":")
659
+ if len(components) < 2 {
660
+ fail()
661
+ }
662
+
663
+ mergeAttr := func(cattr *tui.ColorAttr) {
664
+ for _, component := range components[1:] {
665
+ switch component {
666
+ case "regular":
667
+ cattr.Attr = tui.AttrRegular
668
+ case "bold", "strong":
669
+ cattr.Attr |= tui.Bold
670
+ case "dim":
671
+ cattr.Attr |= tui.Dim
672
+ case "italic":
673
+ cattr.Attr |= tui.Italic
674
+ case "underline":
675
+ cattr.Attr |= tui.Underline
676
+ case "blink":
677
+ cattr.Attr |= tui.Blink
678
+ case "reverse":
679
+ cattr.Attr |= tui.Reverse
680
+ case "black":
681
+ cattr.Color = tui.Color(0)
682
+ case "red":
683
+ cattr.Color = tui.Color(1)
684
+ case "green":
685
+ cattr.Color = tui.Color(2)
686
+ case "yellow":
687
+ cattr.Color = tui.Color(3)
688
+ case "blue":
689
+ cattr.Color = tui.Color(4)
690
+ case "magenta":
691
+ cattr.Color = tui.Color(5)
692
+ case "cyan":
693
+ cattr.Color = tui.Color(6)
694
+ case "white":
695
+ cattr.Color = tui.Color(7)
696
+ case "bright-black", "gray", "grey":
697
+ cattr.Color = tui.Color(8)
698
+ case "bright-red":
699
+ cattr.Color = tui.Color(9)
700
+ case "bright-green":
701
+ cattr.Color = tui.Color(10)
702
+ case "bright-yellow":
703
+ cattr.Color = tui.Color(11)
704
+ case "bright-blue":
705
+ cattr.Color = tui.Color(12)
706
+ case "bright-magenta":
707
+ cattr.Color = tui.Color(13)
708
+ case "bright-cyan":
709
+ cattr.Color = tui.Color(14)
710
+ case "bright-white":
711
+ cattr.Color = tui.Color(15)
712
+ case "":
713
+ default:
714
+ if rrggbb.MatchString(component) {
715
+ cattr.Color = tui.HexToColor(component)
716
+ } else {
717
+ ansi32, err := strconv.Atoi(component)
718
+ if err != nil || ansi32 < -1 || ansi32 > 255 {
719
+ fail()
720
+ }
721
+ cattr.Color = tui.Color(ansi32)
722
+ }
723
+ }
724
+ }
725
+ }
726
+ switch components[0] {
727
+ case "query", "input":
728
+ mergeAttr(&theme.Input)
729
+ case "disabled":
730
+ mergeAttr(&theme.Disabled)
731
+ case "fg":
732
+ mergeAttr(&theme.Fg)
733
+ case "bg":
734
+ mergeAttr(&theme.Bg)
735
+ case "preview-fg":
736
+ mergeAttr(&theme.PreviewFg)
737
+ case "preview-bg":
738
+ mergeAttr(&theme.PreviewBg)
739
+ case "fg+":
740
+ mergeAttr(&theme.Current)
741
+ case "bg+":
742
+ mergeAttr(&theme.DarkBg)
743
+ case "gutter":
744
+ mergeAttr(&theme.Gutter)
745
+ case "hl":
746
+ mergeAttr(&theme.Match)
747
+ case "hl+":
748
+ mergeAttr(&theme.CurrentMatch)
749
+ case "border":
750
+ mergeAttr(&theme.Border)
751
+ case "prompt":
752
+ mergeAttr(&theme.Prompt)
753
+ case "spinner":
754
+ mergeAttr(&theme.Spinner)
755
+ case "info":
756
+ mergeAttr(&theme.Info)
757
+ case "pointer":
758
+ mergeAttr(&theme.Cursor)
759
+ case "marker":
760
+ mergeAttr(&theme.Selected)
761
+ case "header":
762
+ mergeAttr(&theme.Header)
763
+ default:
764
+ fail()
765
+ }
766
+ }
767
+ }
768
+ return theme
769
+ }
770
+
771
+ var executeRegexp *regexp.Regexp
772
+
773
+ func firstKey(keymap map[tui.Event]string) tui.Event {
774
+ for k := range keymap {
775
+ return k
776
+ }
777
+ return tui.EventType(0).AsEvent()
778
+ }
779
+
780
+ const (
781
+ escapedColon = 0
782
+ escapedComma = 1
783
+ escapedPlus = 2
784
+ )
785
+
786
+ func init() {
787
+ // Backreferences are not supported.
788
+ // "~!@#$%^&*;/|".each_char.map { |c| Regexp.escape(c) }.map { |c| "#{c}[^#{c}]*#{c}" }.join('|')
789
+ executeRegexp = regexp.MustCompile(
790
+ `(?si)[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt|unbind):.+|[:+](execute(?:-multi|-silent)?|reload|preview|change-prompt|unbind)(\([^)]*\)|\[[^\]]*\]|~[^~]*~|![^!]*!|@[^@]*@|\#[^\#]*\#|\$[^\$]*\$|%[^%]*%|\^[^\^]*\^|&[^&]*&|\*[^\*]*\*|;[^;]*;|/[^/]*/|\|[^\|]*\|)`)
791
+ }
792
+
793
+ func parseKeymap(keymap map[tui.Event][]action, str string) {
794
+ masked := executeRegexp.ReplaceAllStringFunc(str, func(src string) string {
795
+ symbol := ":"
796
+ if strings.HasPrefix(src, "+") {
797
+ symbol = "+"
798
+ }
799
+ prefix := symbol + "execute"
800
+ if strings.HasPrefix(src[1:], "reload") {
801
+ prefix = symbol + "reload"
802
+ } else if strings.HasPrefix(src[1:], "preview") {
803
+ prefix = symbol + "preview"
804
+ } else if strings.HasPrefix(src[1:], "unbind") {
805
+ prefix = symbol + "unbind"
806
+ } else if strings.HasPrefix(src[1:], "change-prompt") {
807
+ prefix = symbol + "change-prompt"
808
+ } else if src[len(prefix)] == '-' {
809
+ c := src[len(prefix)+1]
810
+ if c == 's' || c == 'S' {
811
+ prefix += "-silent"
812
+ } else {
813
+ prefix += "-multi"
814
+ }
815
+ }
816
+ return prefix + "(" + strings.Repeat(" ", len(src)-len(prefix)-2) + ")"
817
+ })
818
+ masked = strings.Replace(masked, "::", string([]rune{escapedColon, ':'}), -1)
819
+ masked = strings.Replace(masked, ",:", string([]rune{escapedComma, ':'}), -1)
820
+ masked = strings.Replace(masked, "+:", string([]rune{escapedPlus, ':'}), -1)
821
+
822
+ idx := 0
823
+ for _, pairStr := range strings.Split(masked, ",") {
824
+ origPairStr := str[idx : idx+len(pairStr)]
825
+ idx += len(pairStr) + 1
826
+
827
+ pair := strings.SplitN(pairStr, ":", 2)
828
+ if len(pair) < 2 {
829
+ errorExit("bind action not specified: " + origPairStr)
830
+ }
831
+ var key tui.Event
832
+ if len(pair[0]) == 1 && pair[0][0] == escapedColon {
833
+ key = tui.Key(':')
834
+ } else if len(pair[0]) == 1 && pair[0][0] == escapedComma {
835
+ key = tui.Key(',')
836
+ } else if len(pair[0]) == 1 && pair[0][0] == escapedPlus {
837
+ key = tui.Key('+')
838
+ } else {
839
+ keys := parseKeyChords(pair[0], "key name required")
840
+ key = firstKey(keys)
841
+ }
842
+
843
+ idx2 := len(pair[0]) + 1
844
+ specs := strings.Split(pair[1], "+")
845
+ actions := make([]action, 0, len(specs))
846
+ appendAction := func(types ...actionType) {
847
+ actions = append(actions, toActions(types...)...)
848
+ }
849
+ prevSpec := ""
850
+ for specIndex, maskedSpec := range specs {
851
+ spec := origPairStr[idx2 : idx2+len(maskedSpec)]
852
+ idx2 += len(maskedSpec) + 1
853
+ spec = prevSpec + spec
854
+ specLower := strings.ToLower(spec)
855
+ switch specLower {
856
+ case "ignore":
857
+ appendAction(actIgnore)
858
+ case "beginning-of-line":
859
+ appendAction(actBeginningOfLine)
860
+ case "abort":
861
+ appendAction(actAbort)
862
+ case "accept":
863
+ appendAction(actAccept)
864
+ case "accept-non-empty":
865
+ appendAction(actAcceptNonEmpty)
866
+ case "print-query":
867
+ appendAction(actPrintQuery)
868
+ case "refresh-preview":
869
+ appendAction(actRefreshPreview)
870
+ case "replace-query":
871
+ appendAction(actReplaceQuery)
872
+ case "backward-char":
873
+ appendAction(actBackwardChar)
874
+ case "backward-delete-char":
875
+ appendAction(actBackwardDeleteChar)
876
+ case "backward-delete-char/eof":
877
+ appendAction(actBackwardDeleteCharEOF)
878
+ case "backward-word":
879
+ appendAction(actBackwardWord)
880
+ case "clear-screen":
881
+ appendAction(actClearScreen)
882
+ case "delete-char":
883
+ appendAction(actDeleteChar)
884
+ case "delete-char/eof":
885
+ appendAction(actDeleteCharEOF)
886
+ case "deselect":
887
+ appendAction(actDeselect)
888
+ case "end-of-line":
889
+ appendAction(actEndOfLine)
890
+ case "cancel":
891
+ appendAction(actCancel)
892
+ case "clear-query":
893
+ appendAction(actClearQuery)
894
+ case "clear-selection":
895
+ appendAction(actClearSelection)
896
+ case "forward-char":
897
+ appendAction(actForwardChar)
898
+ case "forward-word":
899
+ appendAction(actForwardWord)
900
+ case "jump":
901
+ appendAction(actJump)
902
+ case "jump-accept":
903
+ appendAction(actJumpAccept)
904
+ case "kill-line":
905
+ appendAction(actKillLine)
906
+ case "kill-word":
907
+ appendAction(actKillWord)
908
+ case "unix-line-discard", "line-discard":
909
+ appendAction(actUnixLineDiscard)
910
+ case "unix-word-rubout", "word-rubout":
911
+ appendAction(actUnixWordRubout)
912
+ case "yank":
913
+ appendAction(actYank)
914
+ case "backward-kill-word":
915
+ appendAction(actBackwardKillWord)
916
+ case "toggle-down":
917
+ appendAction(actToggle, actDown)
918
+ case "toggle-up":
919
+ appendAction(actToggle, actUp)
920
+ case "toggle-in":
921
+ appendAction(actToggleIn)
922
+ case "toggle-out":
923
+ appendAction(actToggleOut)
924
+ case "toggle-all":
925
+ appendAction(actToggleAll)
926
+ case "toggle-search":
927
+ appendAction(actToggleSearch)
928
+ case "select":
929
+ appendAction(actSelect)
930
+ case "select-all":
931
+ appendAction(actSelectAll)
932
+ case "deselect-all":
933
+ appendAction(actDeselectAll)
934
+ case "close":
935
+ appendAction(actClose)
936
+ case "toggle":
937
+ appendAction(actToggle)
938
+ case "down":
939
+ appendAction(actDown)
940
+ case "up":
941
+ appendAction(actUp)
942
+ case "first", "top":
943
+ appendAction(actFirst)
944
+ case "last":
945
+ appendAction(actLast)
946
+ case "page-up":
947
+ appendAction(actPageUp)
948
+ case "page-down":
949
+ appendAction(actPageDown)
950
+ case "half-page-up":
951
+ appendAction(actHalfPageUp)
952
+ case "half-page-down":
953
+ appendAction(actHalfPageDown)
954
+ case "previous-history":
955
+ appendAction(actPreviousHistory)
956
+ case "next-history":
957
+ appendAction(actNextHistory)
958
+ case "toggle-preview":
959
+ appendAction(actTogglePreview)
960
+ case "toggle-preview-wrap":
961
+ appendAction(actTogglePreviewWrap)
962
+ case "toggle-sort":
963
+ appendAction(actToggleSort)
964
+ case "preview-top":
965
+ appendAction(actPreviewTop)
966
+ case "preview-bottom":
967
+ appendAction(actPreviewBottom)
968
+ case "preview-up":
969
+ appendAction(actPreviewUp)
970
+ case "preview-down":
971
+ appendAction(actPreviewDown)
972
+ case "preview-page-up":
973
+ appendAction(actPreviewPageUp)
974
+ case "preview-page-down":
975
+ appendAction(actPreviewPageDown)
976
+ case "preview-half-page-up":
977
+ appendAction(actPreviewHalfPageUp)
978
+ case "preview-half-page-down":
979
+ appendAction(actPreviewHalfPageDown)
980
+ case "enable-search":
981
+ appendAction(actEnableSearch)
982
+ case "disable-search":
983
+ appendAction(actDisableSearch)
984
+ case "put":
985
+ if key.Type == tui.Rune && unicode.IsGraphic(key.Char) {
986
+ appendAction(actRune)
987
+ } else {
988
+ errorExit("unable to put non-printable character: " + pair[0])
989
+ }
990
+ default:
991
+ t := isExecuteAction(specLower)
992
+ if t == actIgnore {
993
+ if specIndex == 0 && specLower == "" {
994
+ actions = append(keymap[key], actions...)
995
+ } else {
996
+ errorExit("unknown action: " + spec)
997
+ }
998
+ } else {
999
+ var offset int
1000
+ switch t {
1001
+ case actReload:
1002
+ offset = len("reload")
1003
+ case actPreview:
1004
+ offset = len("preview")
1005
+ case actChangePrompt:
1006
+ offset = len("change-prompt")
1007
+ case actUnbind:
1008
+ offset = len("unbind")
1009
+ case actExecuteSilent:
1010
+ offset = len("execute-silent")
1011
+ case actExecuteMulti:
1012
+ offset = len("execute-multi")
1013
+ default:
1014
+ offset = len("execute")
1015
+ }
1016
+ var actionArg string
1017
+ if spec[offset] == ':' {
1018
+ if specIndex == len(specs)-1 {
1019
+ actionArg = spec[offset+1:]
1020
+ actions = append(actions, action{t: t, a: actionArg})
1021
+ } else {
1022
+ prevSpec = spec + "+"
1023
+ continue
1024
+ }
1025
+ } else {
1026
+ actionArg = spec[offset+1 : len(spec)-1]
1027
+ actions = append(actions, action{t: t, a: actionArg})
1028
+ }
1029
+ if t == actUnbind {
1030
+ parseKeyChords(actionArg, "unbind target required")
1031
+ }
1032
+ }
1033
+ }
1034
+ prevSpec = ""
1035
+ }
1036
+ keymap[key] = actions
1037
+ }
1038
+ }
1039
+
1040
+ func isExecuteAction(str string) actionType {
1041
+ matches := executeRegexp.FindAllStringSubmatch(":"+str, -1)
1042
+ if matches == nil || len(matches) != 1 {
1043
+ return actIgnore
1044
+ }
1045
+ prefix := matches[0][1]
1046
+ if len(prefix) == 0 {
1047
+ prefix = matches[0][2]
1048
+ }
1049
+ switch prefix {
1050
+ case "reload":
1051
+ return actReload
1052
+ case "unbind":
1053
+ return actUnbind
1054
+ case "preview":
1055
+ return actPreview
1056
+ case "change-prompt":
1057
+ return actChangePrompt
1058
+ case "execute":
1059
+ return actExecute
1060
+ case "execute-silent":
1061
+ return actExecuteSilent
1062
+ case "execute-multi":
1063
+ return actExecuteMulti
1064
+ }
1065
+ return actIgnore
1066
+ }
1067
+
1068
+ func parseToggleSort(keymap map[tui.Event][]action, str string) {
1069
+ keys := parseKeyChords(str, "key name required")
1070
+ if len(keys) != 1 {
1071
+ errorExit("multiple keys specified")
1072
+ }
1073
+ keymap[firstKey(keys)] = toActions(actToggleSort)
1074
+ }
1075
+
1076
+ func strLines(str string) []string {
1077
+ return strings.Split(strings.TrimSuffix(str, "\n"), "\n")
1078
+ }
1079
+
1080
+ func parseSize(str string, maxPercent float64, label string) sizeSpec {
1081
+ var val float64
1082
+ percent := strings.HasSuffix(str, "%")
1083
+ if percent {
1084
+ val = atof(str[:len(str)-1])
1085
+ if val < 0 {
1086
+ errorExit(label + " must be non-negative")
1087
+ }
1088
+ if val > maxPercent {
1089
+ errorExit(fmt.Sprintf("%s too large (max: %d%%)", label, int(maxPercent)))
1090
+ }
1091
+ } else {
1092
+ if strings.Contains(str, ".") {
1093
+ errorExit(label + " (without %) must be a non-negative integer")
1094
+ }
1095
+
1096
+ val = float64(atoi(str))
1097
+ if val < 0 {
1098
+ errorExit(label + " must be non-negative")
1099
+ }
1100
+ }
1101
+ return sizeSpec{val, percent}
1102
+ }
1103
+
1104
+ func parseHeight(str string) sizeSpec {
1105
+ size := parseSize(str, 100, "height")
1106
+ return size
1107
+ }
1108
+
1109
+ func parseLayout(str string) layoutType {
1110
+ switch str {
1111
+ case "default":
1112
+ return layoutDefault
1113
+ case "reverse":
1114
+ return layoutReverse
1115
+ case "reverse-list":
1116
+ return layoutReverseList
1117
+ default:
1118
+ errorExit("invalid layout (expected: default / reverse / reverse-list)")
1119
+ }
1120
+ return layoutDefault
1121
+ }
1122
+
1123
+ func parseInfoStyle(str string) infoStyle {
1124
+ switch str {
1125
+ case "default":
1126
+ return infoDefault
1127
+ case "inline":
1128
+ return infoInline
1129
+ case "hidden":
1130
+ return infoHidden
1131
+ default:
1132
+ errorExit("invalid info style (expected: default / inline / hidden)")
1133
+ }
1134
+ return infoDefault
1135
+ }
1136
+
1137
+ func parsePreviewWindow(opts *previewOpts, input string) {
1138
+ delimRegex := regexp.MustCompile("[:,]") // : for backward compatibility
1139
+ sizeRegex := regexp.MustCompile("^[0-9]+%?$")
1140
+ offsetRegex := regexp.MustCompile(`^(\+{-?[0-9]+})?([+-][0-9]+)*(-?/[1-9][0-9]*)?$`)
1141
+ headerRegex := regexp.MustCompile("^~(0|[1-9][0-9]*)$")
1142
+ tokens := delimRegex.Split(input, -1)
1143
+ for _, token := range tokens {
1144
+ switch token {
1145
+ case "":
1146
+ case "default":
1147
+ *opts = defaultPreviewOpts(opts.command)
1148
+ case "hidden":
1149
+ opts.hidden = true
1150
+ case "nohidden":
1151
+ opts.hidden = false
1152
+ case "wrap":
1153
+ opts.wrap = true
1154
+ case "nowrap":
1155
+ opts.wrap = false
1156
+ case "cycle":
1157
+ opts.cycle = true
1158
+ case "nocycle":
1159
+ opts.cycle = false
1160
+ case "up", "top":
1161
+ opts.position = posUp
1162
+ case "down", "bottom":
1163
+ opts.position = posDown
1164
+ case "left":
1165
+ opts.position = posLeft
1166
+ case "right":
1167
+ opts.position = posRight
1168
+ case "rounded", "border", "border-rounded":
1169
+ opts.border = tui.BorderRounded
1170
+ case "sharp", "border-sharp":
1171
+ opts.border = tui.BorderSharp
1172
+ case "noborder", "border-none":
1173
+ opts.border = tui.BorderNone
1174
+ case "border-horizontal":
1175
+ opts.border = tui.BorderHorizontal
1176
+ case "border-vertical":
1177
+ opts.border = tui.BorderVertical
1178
+ case "border-top":
1179
+ opts.border = tui.BorderTop
1180
+ case "border-bottom":
1181
+ opts.border = tui.BorderBottom
1182
+ case "border-left":
1183
+ opts.border = tui.BorderLeft
1184
+ case "border-right":
1185
+ opts.border = tui.BorderRight
1186
+ case "follow":
1187
+ opts.follow = true
1188
+ case "nofollow":
1189
+ opts.follow = false
1190
+ default:
1191
+ if headerRegex.MatchString(token) {
1192
+ opts.headerLines = atoi(token[1:])
1193
+ } else if sizeRegex.MatchString(token) {
1194
+ opts.size = parseSize(token, 99, "window size")
1195
+ } else if offsetRegex.MatchString(token) {
1196
+ opts.scroll = token
1197
+ } else {
1198
+ errorExit("invalid preview window option: " + token)
1199
+ }
1200
+ }
1201
+ }
1202
+ }
1203
+
1204
+ func parseMargin(opt string, margin string) [4]sizeSpec {
1205
+ margins := strings.Split(margin, ",")
1206
+ checked := func(str string) sizeSpec {
1207
+ return parseSize(str, 49, opt)
1208
+ }
1209
+ switch len(margins) {
1210
+ case 1:
1211
+ m := checked(margins[0])
1212
+ return [4]sizeSpec{m, m, m, m}
1213
+ case 2:
1214
+ tb := checked(margins[0])
1215
+ rl := checked(margins[1])
1216
+ return [4]sizeSpec{tb, rl, tb, rl}
1217
+ case 3:
1218
+ t := checked(margins[0])
1219
+ rl := checked(margins[1])
1220
+ b := checked(margins[2])
1221
+ return [4]sizeSpec{t, rl, b, rl}
1222
+ case 4:
1223
+ return [4]sizeSpec{
1224
+ checked(margins[0]), checked(margins[1]),
1225
+ checked(margins[2]), checked(margins[3])}
1226
+ default:
1227
+ errorExit("invalid " + opt + ": " + margin)
1228
+ }
1229
+ return defaultMargin()
1230
+ }
1231
+
1232
+ func parseOptions(opts *Options, allArgs []string) {
1233
+ var historyMax int
1234
+ if opts.History == nil {
1235
+ historyMax = defaultHistoryMax
1236
+ } else {
1237
+ historyMax = opts.History.maxSize
1238
+ }
1239
+ setHistory := func(path string) {
1240
+ h, e := NewHistory(path, historyMax)
1241
+ if e != nil {
1242
+ errorExit(e.Error())
1243
+ }
1244
+ opts.History = h
1245
+ }
1246
+ setHistoryMax := func(max int) {
1247
+ historyMax = max
1248
+ if historyMax < 1 {
1249
+ errorExit("history max must be a positive integer")
1250
+ }
1251
+ if opts.History != nil {
1252
+ opts.History.maxSize = historyMax
1253
+ }
1254
+ }
1255
+ validateJumpLabels := false
1256
+ validatePointer := false
1257
+ validateMarker := false
1258
+ for i := 0; i < len(allArgs); i++ {
1259
+ arg := allArgs[i]
1260
+ switch arg {
1261
+ case "-h", "--help":
1262
+ help(exitOk)
1263
+ case "-x", "--extended":
1264
+ opts.Extended = true
1265
+ case "-e", "--exact":
1266
+ opts.Fuzzy = false
1267
+ case "--extended-exact":
1268
+ // Note that we now don't have --no-extended-exact
1269
+ opts.Fuzzy = false
1270
+ opts.Extended = true
1271
+ case "+x", "--no-extended":
1272
+ opts.Extended = false
1273
+ case "+e", "--no-exact":
1274
+ opts.Fuzzy = true
1275
+ case "-q", "--query":
1276
+ opts.Query = nextString(allArgs, &i, "query string required")
1277
+ case "-f", "--filter":
1278
+ filter := nextString(allArgs, &i, "query string required")
1279
+ opts.Filter = &filter
1280
+ case "--literal":
1281
+ opts.Normalize = false
1282
+ case "--no-literal":
1283
+ opts.Normalize = true
1284
+ case "--algo":
1285
+ opts.FuzzyAlgo = parseAlgo(nextString(allArgs, &i, "algorithm required (v1|v2)"))
1286
+ case "--expect":
1287
+ for k, v := range parseKeyChords(nextString(allArgs, &i, "key names required"), "key names required") {
1288
+ opts.Expect[k] = v
1289
+ }
1290
+ case "--no-expect":
1291
+ opts.Expect = make(map[tui.Event]string)
1292
+ case "--enabled", "--no-phony":
1293
+ opts.Phony = false
1294
+ case "--disabled", "--phony":
1295
+ opts.Phony = true
1296
+ case "--tiebreak":
1297
+ opts.Criteria = parseTiebreak(nextString(allArgs, &i, "sort criterion required"))
1298
+ case "--bind":
1299
+ parseKeymap(opts.Keymap, nextString(allArgs, &i, "bind expression required"))
1300
+ case "--color":
1301
+ _, spec := optionalNextString(allArgs, &i)
1302
+ if len(spec) == 0 {
1303
+ opts.Theme = tui.EmptyTheme()
1304
+ } else {
1305
+ opts.Theme = parseTheme(opts.Theme, spec)
1306
+ }
1307
+ case "--toggle-sort":
1308
+ parseToggleSort(opts.Keymap, nextString(allArgs, &i, "key name required"))
1309
+ case "-d", "--delimiter":
1310
+ opts.Delimiter = delimiterRegexp(nextString(allArgs, &i, "delimiter required"))
1311
+ case "-n", "--nth":
1312
+ opts.Nth = splitNth(nextString(allArgs, &i, "nth expression required"))
1313
+ case "--with-nth":
1314
+ opts.WithNth = splitNth(nextString(allArgs, &i, "nth expression required"))
1315
+ case "-s", "--sort":
1316
+ opts.Sort = optionalNumeric(allArgs, &i, 1)
1317
+ case "+s", "--no-sort":
1318
+ opts.Sort = 0
1319
+ case "--tac":
1320
+ opts.Tac = true
1321
+ case "--no-tac":
1322
+ opts.Tac = false
1323
+ case "-i":
1324
+ opts.Case = CaseIgnore
1325
+ case "+i":
1326
+ opts.Case = CaseRespect
1327
+ case "-m", "--multi":
1328
+ opts.Multi = optionalNumeric(allArgs, &i, maxMulti)
1329
+ case "+m", "--no-multi":
1330
+ opts.Multi = 0
1331
+ case "--ansi":
1332
+ opts.Ansi = true
1333
+ case "--no-ansi":
1334
+ opts.Ansi = false
1335
+ case "--no-mouse":
1336
+ opts.Mouse = false
1337
+ case "+c", "--no-color":
1338
+ opts.Theme = tui.NoColorTheme()
1339
+ case "+2", "--no-256":
1340
+ opts.Theme = tui.Default16
1341
+ case "--black":
1342
+ opts.Black = true
1343
+ case "--no-black":
1344
+ opts.Black = false
1345
+ case "--bold":
1346
+ opts.Bold = true
1347
+ case "--no-bold":
1348
+ opts.Bold = false
1349
+ case "--layout":
1350
+ opts.Layout = parseLayout(
1351
+ nextString(allArgs, &i, "layout required (default / reverse / reverse-list)"))
1352
+ case "--reverse":
1353
+ opts.Layout = layoutReverse
1354
+ case "--no-reverse":
1355
+ opts.Layout = layoutDefault
1356
+ case "--cycle":
1357
+ opts.Cycle = true
1358
+ case "--no-cycle":
1359
+ opts.Cycle = false
1360
+ case "--keep-right":
1361
+ opts.KeepRight = true
1362
+ case "--no-keep-right":
1363
+ opts.KeepRight = false
1364
+ case "--hscroll":
1365
+ opts.Hscroll = true
1366
+ case "--no-hscroll":
1367
+ opts.Hscroll = false
1368
+ case "--hscroll-off":
1369
+ opts.HscrollOff = nextInt(allArgs, &i, "hscroll offset required")
1370
+ case "--scroll-off":
1371
+ opts.ScrollOff = nextInt(allArgs, &i, "scroll offset required")
1372
+ case "--filepath-word":
1373
+ opts.FileWord = true
1374
+ case "--no-filepath-word":
1375
+ opts.FileWord = false
1376
+ case "--info":
1377
+ opts.InfoStyle = parseInfoStyle(
1378
+ nextString(allArgs, &i, "info style required"))
1379
+ case "--no-info":
1380
+ opts.InfoStyle = infoHidden
1381
+ case "--inline-info":
1382
+ opts.InfoStyle = infoInline
1383
+ case "--no-inline-info":
1384
+ opts.InfoStyle = infoDefault
1385
+ case "--jump-labels":
1386
+ opts.JumpLabels = nextString(allArgs, &i, "label characters required")
1387
+ validateJumpLabels = true
1388
+ case "-1", "--select-1":
1389
+ opts.Select1 = true
1390
+ case "+1", "--no-select-1":
1391
+ opts.Select1 = false
1392
+ case "-0", "--exit-0":
1393
+ opts.Exit0 = true
1394
+ case "+0", "--no-exit-0":
1395
+ opts.Exit0 = false
1396
+ case "--read0":
1397
+ opts.ReadZero = true
1398
+ case "--no-read0":
1399
+ opts.ReadZero = false
1400
+ case "--print0":
1401
+ opts.Printer = func(str string) { fmt.Print(str, "\x00") }
1402
+ opts.PrintSep = "\x00"
1403
+ case "--no-print0":
1404
+ opts.Printer = func(str string) { fmt.Println(str) }
1405
+ opts.PrintSep = "\n"
1406
+ case "--print-query":
1407
+ opts.PrintQuery = true
1408
+ case "--no-print-query":
1409
+ opts.PrintQuery = false
1410
+ case "--prompt":
1411
+ opts.Prompt = nextString(allArgs, &i, "prompt string required")
1412
+ case "--pointer":
1413
+ opts.Pointer = nextString(allArgs, &i, "pointer sign string required")
1414
+ validatePointer = true
1415
+ case "--marker":
1416
+ opts.Marker = nextString(allArgs, &i, "selected sign string required")
1417
+ validateMarker = true
1418
+ case "--sync":
1419
+ opts.Sync = true
1420
+ case "--no-sync":
1421
+ opts.Sync = false
1422
+ case "--async":
1423
+ opts.Sync = false
1424
+ case "--no-history":
1425
+ opts.History = nil
1426
+ case "--history":
1427
+ setHistory(nextString(allArgs, &i, "history file path required"))
1428
+ case "--history-size":
1429
+ setHistoryMax(nextInt(allArgs, &i, "history max size required"))
1430
+ case "--no-header":
1431
+ opts.Header = []string{}
1432
+ case "--no-header-lines":
1433
+ opts.HeaderLines = 0
1434
+ case "--header":
1435
+ opts.Header = strLines(nextString(allArgs, &i, "header string required"))
1436
+ case "--header-lines":
1437
+ opts.HeaderLines = atoi(
1438
+ nextString(allArgs, &i, "number of header lines required"))
1439
+ case "--header-first":
1440
+ opts.HeaderFirst = true
1441
+ case "--no-header-first":
1442
+ opts.HeaderFirst = false
1443
+ case "--preview":
1444
+ opts.Preview.command = nextString(allArgs, &i, "preview command required")
1445
+ case "--no-preview":
1446
+ opts.Preview.command = ""
1447
+ case "--preview-window":
1448
+ parsePreviewWindow(&opts.Preview,
1449
+ nextString(allArgs, &i, "preview window layout required: [up|down|left|right][,SIZE[%]][,border-BORDER_OPT][,wrap][,cycle][,hidden][,+SCROLL[OFFSETS][/DENOM]][,~HEADER_LINES][,default]"))
1450
+ case "--height":
1451
+ opts.Height = parseHeight(nextString(allArgs, &i, "height required: HEIGHT[%]"))
1452
+ case "--min-height":
1453
+ opts.MinHeight = nextInt(allArgs, &i, "height required: HEIGHT")
1454
+ case "--no-height":
1455
+ opts.Height = sizeSpec{}
1456
+ case "--no-margin":
1457
+ opts.Margin = defaultMargin()
1458
+ case "--no-padding":
1459
+ opts.Padding = defaultMargin()
1460
+ case "--no-border":
1461
+ opts.BorderShape = tui.BorderNone
1462
+ case "--border":
1463
+ hasArg, arg := optionalNextString(allArgs, &i)
1464
+ opts.BorderShape = parseBorder(arg, !hasArg)
1465
+ case "--no-unicode":
1466
+ opts.Unicode = false
1467
+ case "--unicode":
1468
+ opts.Unicode = true
1469
+ case "--margin":
1470
+ opts.Margin = parseMargin(
1471
+ "margin",
1472
+ nextString(allArgs, &i, "margin required (TRBL / TB,RL / T,RL,B / T,R,B,L)"))
1473
+ case "--padding":
1474
+ opts.Padding = parseMargin(
1475
+ "padding",
1476
+ nextString(allArgs, &i, "padding required (TRBL / TB,RL / T,RL,B / T,R,B,L)"))
1477
+ case "--tabstop":
1478
+ opts.Tabstop = nextInt(allArgs, &i, "tab stop required")
1479
+ case "--clear":
1480
+ opts.ClearOnExit = true
1481
+ case "--no-clear":
1482
+ opts.ClearOnExit = false
1483
+ case "--version":
1484
+ opts.Version = true
1485
+ default:
1486
+ if match, value := optString(arg, "--algo="); match {
1487
+ opts.FuzzyAlgo = parseAlgo(value)
1488
+ } else if match, value := optString(arg, "-q", "--query="); match {
1489
+ opts.Query = value
1490
+ } else if match, value := optString(arg, "-f", "--filter="); match {
1491
+ opts.Filter = &value
1492
+ } else if match, value := optString(arg, "-d", "--delimiter="); match {
1493
+ opts.Delimiter = delimiterRegexp(value)
1494
+ } else if match, value := optString(arg, "--border="); match {
1495
+ opts.BorderShape = parseBorder(value, false)
1496
+ } else if match, value := optString(arg, "--prompt="); match {
1497
+ opts.Prompt = value
1498
+ } else if match, value := optString(arg, "--pointer="); match {
1499
+ opts.Pointer = value
1500
+ validatePointer = true
1501
+ } else if match, value := optString(arg, "--marker="); match {
1502
+ opts.Marker = value
1503
+ validateMarker = true
1504
+ } else if match, value := optString(arg, "-n", "--nth="); match {
1505
+ opts.Nth = splitNth(value)
1506
+ } else if match, value := optString(arg, "--with-nth="); match {
1507
+ opts.WithNth = splitNth(value)
1508
+ } else if match, _ := optString(arg, "-s", "--sort="); match {
1509
+ opts.Sort = 1 // Don't care
1510
+ } else if match, value := optString(arg, "-m", "--multi="); match {
1511
+ opts.Multi = atoi(value)
1512
+ } else if match, value := optString(arg, "--height="); match {
1513
+ opts.Height = parseHeight(value)
1514
+ } else if match, value := optString(arg, "--min-height="); match {
1515
+ opts.MinHeight = atoi(value)
1516
+ } else if match, value := optString(arg, "--layout="); match {
1517
+ opts.Layout = parseLayout(value)
1518
+ } else if match, value := optString(arg, "--info="); match {
1519
+ opts.InfoStyle = parseInfoStyle(value)
1520
+ } else if match, value := optString(arg, "--toggle-sort="); match {
1521
+ parseToggleSort(opts.Keymap, value)
1522
+ } else if match, value := optString(arg, "--expect="); match {
1523
+ for k, v := range parseKeyChords(value, "key names required") {
1524
+ opts.Expect[k] = v
1525
+ }
1526
+ } else if match, value := optString(arg, "--tiebreak="); match {
1527
+ opts.Criteria = parseTiebreak(value)
1528
+ } else if match, value := optString(arg, "--color="); match {
1529
+ opts.Theme = parseTheme(opts.Theme, value)
1530
+ } else if match, value := optString(arg, "--bind="); match {
1531
+ parseKeymap(opts.Keymap, value)
1532
+ } else if match, value := optString(arg, "--history="); match {
1533
+ setHistory(value)
1534
+ } else if match, value := optString(arg, "--history-size="); match {
1535
+ setHistoryMax(atoi(value))
1536
+ } else if match, value := optString(arg, "--header="); match {
1537
+ opts.Header = strLines(value)
1538
+ } else if match, value := optString(arg, "--header-lines="); match {
1539
+ opts.HeaderLines = atoi(value)
1540
+ } else if match, value := optString(arg, "--preview="); match {
1541
+ opts.Preview.command = value
1542
+ } else if match, value := optString(arg, "--preview-window="); match {
1543
+ parsePreviewWindow(&opts.Preview, value)
1544
+ } else if match, value := optString(arg, "--margin="); match {
1545
+ opts.Margin = parseMargin("margin", value)
1546
+ } else if match, value := optString(arg, "--padding="); match {
1547
+ opts.Padding = parseMargin("padding", value)
1548
+ } else if match, value := optString(arg, "--tabstop="); match {
1549
+ opts.Tabstop = atoi(value)
1550
+ } else if match, value := optString(arg, "--hscroll-off="); match {
1551
+ opts.HscrollOff = atoi(value)
1552
+ } else if match, value := optString(arg, "--scroll-off="); match {
1553
+ opts.ScrollOff = atoi(value)
1554
+ } else if match, value := optString(arg, "--jump-labels="); match {
1555
+ opts.JumpLabels = value
1556
+ validateJumpLabels = true
1557
+ } else {
1558
+ errorExit("unknown option: " + arg)
1559
+ }
1560
+ }
1561
+ }
1562
+
1563
+ if opts.HeaderLines < 0 {
1564
+ errorExit("header lines must be a non-negative integer")
1565
+ }
1566
+
1567
+ if opts.HscrollOff < 0 {
1568
+ errorExit("hscroll offset must be a non-negative integer")
1569
+ }
1570
+
1571
+ if opts.ScrollOff < 0 {
1572
+ errorExit("scroll offset must be a non-negative integer")
1573
+ }
1574
+
1575
+ if opts.Tabstop < 1 {
1576
+ errorExit("tab stop must be a positive integer")
1577
+ }
1578
+
1579
+ if len(opts.JumpLabels) == 0 {
1580
+ errorExit("empty jump labels")
1581
+ }
1582
+
1583
+ if validateJumpLabels {
1584
+ for _, r := range opts.JumpLabels {
1585
+ if r < 32 || r > 126 {
1586
+ errorExit("non-ascii jump labels are not allowed")
1587
+ }
1588
+ }
1589
+ }
1590
+
1591
+ if validatePointer {
1592
+ if err := validateSign(opts.Pointer, "pointer"); err != nil {
1593
+ errorExit(err.Error())
1594
+ }
1595
+ }
1596
+
1597
+ if validateMarker {
1598
+ if err := validateSign(opts.Marker, "marker"); err != nil {
1599
+ errorExit(err.Error())
1600
+ }
1601
+ }
1602
+ }
1603
+
1604
+ func validateSign(sign string, signOptName string) error {
1605
+ if sign == "" {
1606
+ return fmt.Errorf("%v cannot be empty", signOptName)
1607
+ }
1608
+ for _, r := range sign {
1609
+ if !unicode.IsGraphic(r) {
1610
+ return fmt.Errorf("invalid character in %v", signOptName)
1611
+ }
1612
+ }
1613
+ if runewidth.StringWidth(sign) > 2 {
1614
+ return fmt.Errorf("%v display width should be up to 2", signOptName)
1615
+ }
1616
+ return nil
1617
+ }
1618
+
1619
+ func postProcessOptions(opts *Options) {
1620
+ if !opts.Version && !tui.IsLightRendererSupported() && opts.Height.size > 0 {
1621
+ errorExit("--height option is currently not supported on this platform")
1622
+ }
1623
+ // Default actions for CTRL-N / CTRL-P when --history is set
1624
+ if opts.History != nil {
1625
+ if _, prs := opts.Keymap[tui.CtrlP.AsEvent()]; !prs {
1626
+ opts.Keymap[tui.CtrlP.AsEvent()] = toActions(actPreviousHistory)
1627
+ }
1628
+ if _, prs := opts.Keymap[tui.CtrlN.AsEvent()]; !prs {
1629
+ opts.Keymap[tui.CtrlN.AsEvent()] = toActions(actNextHistory)
1630
+ }
1631
+ }
1632
+
1633
+ // Extend the default key map
1634
+ keymap := defaultKeymap()
1635
+ for key, actions := range opts.Keymap {
1636
+ for _, act := range actions {
1637
+ if act.t == actToggleSort {
1638
+ opts.ToggleSort = true
1639
+ }
1640
+ }
1641
+ keymap[key] = actions
1642
+ }
1643
+ opts.Keymap = keymap
1644
+
1645
+ // If we're not using extended search mode, --nth option becomes irrelevant
1646
+ // if it contains the whole range
1647
+ if !opts.Extended || len(opts.Nth) == 1 {
1648
+ for _, r := range opts.Nth {
1649
+ if r.begin == rangeEllipsis && r.end == rangeEllipsis {
1650
+ opts.Nth = make([]Range, 0)
1651
+ return
1652
+ }
1653
+ }
1654
+ }
1655
+
1656
+ if opts.Bold {
1657
+ theme := opts.Theme
1658
+ boldify := func(c tui.ColorAttr) tui.ColorAttr {
1659
+ dup := c
1660
+ if !theme.Colored {
1661
+ dup.Attr |= tui.Bold
1662
+ } else if (c.Attr & tui.AttrRegular) == 0 {
1663
+ dup.Attr |= tui.Bold
1664
+ }
1665
+ return dup
1666
+ }
1667
+ theme.Current = boldify(theme.Current)
1668
+ theme.CurrentMatch = boldify(theme.CurrentMatch)
1669
+ theme.Prompt = boldify(theme.Prompt)
1670
+ theme.Input = boldify(theme.Input)
1671
+ theme.Cursor = boldify(theme.Cursor)
1672
+ theme.Spinner = boldify(theme.Spinner)
1673
+ }
1674
+ }
1675
+
1676
+ // ParseOptions parses command-line options
1677
+ func ParseOptions() *Options {
1678
+ opts := defaultOptions()
1679
+
1680
+ // Options from Env var
1681
+ words, _ := shellwords.Parse(os.Getenv("FZF_DEFAULT_OPTS"))
1682
+ if len(words) > 0 {
1683
+ parseOptions(opts, words)
1684
+ }
1685
+
1686
+ // Options from command-line arguments
1687
+ parseOptions(opts, os.Args[1:])
1688
+
1689
+ postProcessOptions(opts)
1690
+ return opts
1691
+ }