doing 2.0.18 → 2.0.22

Sign up to get free protection for your applications and to get access to all the features.
Files changed (107) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -0
  3. data/Gemfile.lock +15 -5
  4. data/README.md +1 -1
  5. data/bin/doing +2 -18
  6. data/doing.gemspec +5 -4
  7. data/doing.rdoc +2 -2
  8. data/generate_completions.sh +3 -3
  9. data/lib/doing/cli_status.rb +6 -2
  10. data/lib/doing/completion/bash_completion.rb +185 -0
  11. data/lib/doing/completion/fish_completion.rb +175 -0
  12. data/lib/doing/completion/string.rb +17 -0
  13. data/lib/doing/completion/zsh_completion.rb +140 -0
  14. data/lib/doing/completion.rb +39 -0
  15. data/lib/doing/version.rb +1 -1
  16. data/lib/doing/wwid.rb +18 -6
  17. data/lib/doing.rb +1 -1
  18. data/lib/helpers/fzf/.goreleaser.yml +119 -0
  19. data/lib/helpers/fzf/.rubocop.yml +28 -0
  20. data/lib/helpers/fzf/ADVANCED.md +565 -0
  21. data/lib/helpers/fzf/BUILD.md +49 -0
  22. data/lib/helpers/fzf/CHANGELOG.md +1193 -0
  23. data/lib/helpers/fzf/Dockerfile +11 -0
  24. data/lib/helpers/fzf/LICENSE +21 -0
  25. data/lib/helpers/fzf/Makefile +166 -0
  26. data/lib/helpers/fzf/README-VIM.md +486 -0
  27. data/lib/helpers/fzf/README.md +712 -0
  28. data/lib/helpers/fzf/bin/fzf-tmux +233 -0
  29. data/lib/helpers/fzf/doc/fzf.txt +512 -0
  30. data/lib/helpers/fzf/go.mod +17 -0
  31. data/lib/helpers/fzf/go.sum +31 -0
  32. data/lib/helpers/fzf/install +382 -0
  33. data/lib/helpers/fzf/install.ps1 +65 -0
  34. data/lib/helpers/fzf/main.go +14 -0
  35. data/lib/helpers/fzf/man/man1/fzf-tmux.1 +68 -0
  36. data/lib/helpers/fzf/man/man1/fzf.1 +1001 -0
  37. data/lib/helpers/fzf/plugin/fzf.vim +1048 -0
  38. data/lib/helpers/fzf/shell/completion.bash +381 -0
  39. data/lib/helpers/fzf/shell/completion.zsh +329 -0
  40. data/lib/helpers/fzf/shell/key-bindings.bash +96 -0
  41. data/lib/helpers/fzf/shell/key-bindings.fish +172 -0
  42. data/lib/helpers/fzf/shell/key-bindings.zsh +114 -0
  43. data/lib/helpers/fzf/src/LICENSE +21 -0
  44. data/lib/helpers/fzf/src/algo/algo.go +884 -0
  45. data/lib/helpers/fzf/src/algo/algo_test.go +197 -0
  46. data/lib/helpers/fzf/src/algo/normalize.go +492 -0
  47. data/lib/helpers/fzf/src/ansi.go +409 -0
  48. data/lib/helpers/fzf/src/ansi_test.go +427 -0
  49. data/lib/helpers/fzf/src/cache.go +81 -0
  50. data/lib/helpers/fzf/src/cache_test.go +39 -0
  51. data/lib/helpers/fzf/src/chunklist.go +89 -0
  52. data/lib/helpers/fzf/src/chunklist_test.go +80 -0
  53. data/lib/helpers/fzf/src/constants.go +85 -0
  54. data/lib/helpers/fzf/src/core.go +351 -0
  55. data/lib/helpers/fzf/src/history.go +96 -0
  56. data/lib/helpers/fzf/src/history_test.go +68 -0
  57. data/lib/helpers/fzf/src/item.go +44 -0
  58. data/lib/helpers/fzf/src/item_test.go +23 -0
  59. data/lib/helpers/fzf/src/matcher.go +235 -0
  60. data/lib/helpers/fzf/src/merger.go +120 -0
  61. data/lib/helpers/fzf/src/merger_test.go +88 -0
  62. data/lib/helpers/fzf/src/options.go +1691 -0
  63. data/lib/helpers/fzf/src/options_test.go +457 -0
  64. data/lib/helpers/fzf/src/pattern.go +425 -0
  65. data/lib/helpers/fzf/src/pattern_test.go +209 -0
  66. data/lib/helpers/fzf/src/protector/protector.go +8 -0
  67. data/lib/helpers/fzf/src/protector/protector_openbsd.go +10 -0
  68. data/lib/helpers/fzf/src/reader.go +201 -0
  69. data/lib/helpers/fzf/src/reader_test.go +63 -0
  70. data/lib/helpers/fzf/src/result.go +243 -0
  71. data/lib/helpers/fzf/src/result_others.go +16 -0
  72. data/lib/helpers/fzf/src/result_test.go +159 -0
  73. data/lib/helpers/fzf/src/result_x86.go +16 -0
  74. data/lib/helpers/fzf/src/terminal.go +2832 -0
  75. data/lib/helpers/fzf/src/terminal_test.go +638 -0
  76. data/lib/helpers/fzf/src/terminal_unix.go +26 -0
  77. data/lib/helpers/fzf/src/terminal_windows.go +45 -0
  78. data/lib/helpers/fzf/src/tokenizer.go +253 -0
  79. data/lib/helpers/fzf/src/tokenizer_test.go +112 -0
  80. data/lib/helpers/fzf/src/tui/dummy.go +46 -0
  81. data/lib/helpers/fzf/src/tui/light.go +987 -0
  82. data/lib/helpers/fzf/src/tui/light_unix.go +110 -0
  83. data/lib/helpers/fzf/src/tui/light_windows.go +145 -0
  84. data/lib/helpers/fzf/src/tui/tcell.go +721 -0
  85. data/lib/helpers/fzf/src/tui/tcell_test.go +392 -0
  86. data/lib/helpers/fzf/src/tui/ttyname_unix.go +47 -0
  87. data/lib/helpers/fzf/src/tui/ttyname_windows.go +14 -0
  88. data/lib/helpers/fzf/src/tui/tui.go +625 -0
  89. data/lib/helpers/fzf/src/tui/tui_test.go +20 -0
  90. data/lib/helpers/fzf/src/util/atomicbool.go +34 -0
  91. data/lib/helpers/fzf/src/util/atomicbool_test.go +17 -0
  92. data/lib/helpers/fzf/src/util/chars.go +198 -0
  93. data/lib/helpers/fzf/src/util/chars_test.go +46 -0
  94. data/lib/helpers/fzf/src/util/eventbox.go +96 -0
  95. data/lib/helpers/fzf/src/util/eventbox_test.go +61 -0
  96. data/lib/helpers/fzf/src/util/slab.go +12 -0
  97. data/lib/helpers/fzf/src/util/util.go +138 -0
  98. data/lib/helpers/fzf/src/util/util_test.go +40 -0
  99. data/lib/helpers/fzf/src/util/util_unix.go +47 -0
  100. data/lib/helpers/fzf/src/util/util_windows.go +83 -0
  101. data/lib/helpers/fzf/test/fzf.vader +175 -0
  102. data/lib/helpers/fzf/test/test_go.rb +2626 -0
  103. data/lib/helpers/fzf/uninstall +117 -0
  104. data/scripts/generate_bash_completions.rb +6 -12
  105. data/scripts/generate_fish_completions.rb +7 -16
  106. data/scripts/generate_zsh_completions.rb +6 -15
  107. metadata +144 -9
@@ -0,0 +1,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
+ }