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,987 @@
1
+ package tui
2
+
3
+ import (
4
+ "bytes"
5
+ "fmt"
6
+ "os"
7
+ "regexp"
8
+ "strconv"
9
+ "strings"
10
+ "time"
11
+ "unicode/utf8"
12
+
13
+ "github.com/mattn/go-runewidth"
14
+ "github.com/rivo/uniseg"
15
+
16
+ "golang.org/x/term"
17
+ )
18
+
19
+ const (
20
+ defaultWidth = 80
21
+ defaultHeight = 24
22
+
23
+ defaultEscDelay = 100
24
+ escPollInterval = 5
25
+ offsetPollTries = 10
26
+ maxInputBuffer = 10 * 1024
27
+ )
28
+
29
+ const consoleDevice string = "/dev/tty"
30
+
31
+ var offsetRegexp *regexp.Regexp = regexp.MustCompile("(.*)\x1b\\[([0-9]+);([0-9]+)R")
32
+ var offsetRegexpBegin *regexp.Regexp = regexp.MustCompile("^\x1b\\[[0-9]+;[0-9]+R")
33
+
34
+ func (r *LightRenderer) stderr(str string) {
35
+ r.stderrInternal(str, true)
36
+ }
37
+
38
+ // FIXME: Need better handling of non-displayable characters
39
+ func (r *LightRenderer) stderrInternal(str string, allowNLCR bool) {
40
+ bytes := []byte(str)
41
+ runes := []rune{}
42
+ for len(bytes) > 0 {
43
+ r, sz := utf8.DecodeRune(bytes)
44
+ nlcr := r == '\n' || r == '\r'
45
+ if r >= 32 || r == '\x1b' || nlcr {
46
+ if r == utf8.RuneError || nlcr && !allowNLCR {
47
+ runes = append(runes, ' ')
48
+ } else {
49
+ runes = append(runes, r)
50
+ }
51
+ }
52
+ bytes = bytes[sz:]
53
+ }
54
+ r.queued.WriteString(string(runes))
55
+ }
56
+
57
+ func (r *LightRenderer) csi(code string) {
58
+ r.stderr("\x1b[" + code)
59
+ }
60
+
61
+ func (r *LightRenderer) flush() {
62
+ if r.queued.Len() > 0 {
63
+ fmt.Fprint(os.Stderr, r.queued.String())
64
+ r.queued.Reset()
65
+ }
66
+ }
67
+
68
+ // Light renderer
69
+ type LightRenderer struct {
70
+ theme *ColorTheme
71
+ mouse bool
72
+ forceBlack bool
73
+ clearOnExit bool
74
+ prevDownTime time.Time
75
+ clickY []int
76
+ ttyin *os.File
77
+ buffer []byte
78
+ origState *term.State
79
+ width int
80
+ height int
81
+ yoffset int
82
+ tabstop int
83
+ escDelay int
84
+ fullscreen bool
85
+ upOneLine bool
86
+ queued strings.Builder
87
+ y int
88
+ x int
89
+ maxHeightFunc func(int) int
90
+
91
+ // Windows only
92
+ ttyinChannel chan byte
93
+ inHandle uintptr
94
+ outHandle uintptr
95
+ origStateInput uint32
96
+ origStateOutput uint32
97
+ }
98
+
99
+ type LightWindow struct {
100
+ renderer *LightRenderer
101
+ colored bool
102
+ preview bool
103
+ border BorderStyle
104
+ top int
105
+ left int
106
+ width int
107
+ height int
108
+ posx int
109
+ posy int
110
+ tabstop int
111
+ fg Color
112
+ bg Color
113
+ }
114
+
115
+ func NewLightRenderer(theme *ColorTheme, forceBlack bool, mouse bool, tabstop int, clearOnExit bool, fullscreen bool, maxHeightFunc func(int) int) Renderer {
116
+ r := LightRenderer{
117
+ theme: theme,
118
+ forceBlack: forceBlack,
119
+ mouse: mouse,
120
+ clearOnExit: clearOnExit,
121
+ ttyin: openTtyIn(),
122
+ yoffset: 0,
123
+ tabstop: tabstop,
124
+ fullscreen: fullscreen,
125
+ upOneLine: false,
126
+ maxHeightFunc: maxHeightFunc}
127
+ return &r
128
+ }
129
+
130
+ func repeat(r rune, times int) string {
131
+ if times > 0 {
132
+ return strings.Repeat(string(r), times)
133
+ }
134
+ return ""
135
+ }
136
+
137
+ func atoi(s string, defaultValue int) int {
138
+ value, err := strconv.Atoi(s)
139
+ if err != nil {
140
+ return defaultValue
141
+ }
142
+ return value
143
+ }
144
+
145
+ func (r *LightRenderer) Init() {
146
+ r.escDelay = atoi(os.Getenv("ESCDELAY"), defaultEscDelay)
147
+
148
+ if err := r.initPlatform(); err != nil {
149
+ errorExit(err.Error())
150
+ }
151
+ r.updateTerminalSize()
152
+ initTheme(r.theme, r.defaultTheme(), r.forceBlack)
153
+
154
+ if r.fullscreen {
155
+ r.smcup()
156
+ } else {
157
+ // We assume that --no-clear is used for repetitive relaunching of fzf.
158
+ // So we do not clear the lower bottom of the screen.
159
+ if r.clearOnExit {
160
+ r.csi("J")
161
+ }
162
+ y, x := r.findOffset()
163
+ r.mouse = r.mouse && y >= 0
164
+ // When --no-clear is used for repetitive relaunching, there is a small
165
+ // time frame between fzf processes where the user keystrokes are not
166
+ // captured by either of fzf process which can cause x offset to be
167
+ // increased and we're left with unwanted extra new line.
168
+ if x > 0 && r.clearOnExit {
169
+ r.upOneLine = true
170
+ r.makeSpace()
171
+ }
172
+ for i := 1; i < r.MaxY(); i++ {
173
+ r.makeSpace()
174
+ }
175
+ }
176
+
177
+ if r.mouse {
178
+ r.csi("?1000h")
179
+ }
180
+ r.csi(fmt.Sprintf("%dA", r.MaxY()-1))
181
+ r.csi("G")
182
+ r.csi("K")
183
+ if !r.clearOnExit && !r.fullscreen {
184
+ r.csi("s")
185
+ }
186
+ if !r.fullscreen && r.mouse {
187
+ r.yoffset, _ = r.findOffset()
188
+ }
189
+ }
190
+
191
+ func (r *LightRenderer) makeSpace() {
192
+ r.stderr("\n")
193
+ r.csi("G")
194
+ }
195
+
196
+ func (r *LightRenderer) move(y int, x int) {
197
+ // w.csi("u")
198
+ if r.y < y {
199
+ r.csi(fmt.Sprintf("%dB", y-r.y))
200
+ } else if r.y > y {
201
+ r.csi(fmt.Sprintf("%dA", r.y-y))
202
+ }
203
+ r.stderr("\r")
204
+ if x > 0 {
205
+ r.csi(fmt.Sprintf("%dC", x))
206
+ }
207
+ r.y = y
208
+ r.x = x
209
+ }
210
+
211
+ func (r *LightRenderer) origin() {
212
+ r.move(0, 0)
213
+ }
214
+
215
+ func getEnv(name string, defaultValue int) int {
216
+ env := os.Getenv(name)
217
+ if len(env) == 0 {
218
+ return defaultValue
219
+ }
220
+ return atoi(env, defaultValue)
221
+ }
222
+
223
+ func (r *LightRenderer) getBytes() []byte {
224
+ return r.getBytesInternal(r.buffer, false)
225
+ }
226
+
227
+ func (r *LightRenderer) getBytesInternal(buffer []byte, nonblock bool) []byte {
228
+ c, ok := r.getch(nonblock)
229
+ if !nonblock && !ok {
230
+ r.Close()
231
+ errorExit("Failed to read " + consoleDevice)
232
+ }
233
+
234
+ retries := 0
235
+ if c == ESC.Int() || nonblock {
236
+ retries = r.escDelay / escPollInterval
237
+ }
238
+ buffer = append(buffer, byte(c))
239
+
240
+ pc := c
241
+ for {
242
+ c, ok = r.getch(true)
243
+ if !ok {
244
+ if retries > 0 {
245
+ retries--
246
+ time.Sleep(escPollInterval * time.Millisecond)
247
+ continue
248
+ }
249
+ break
250
+ } else if c == ESC.Int() && pc != c {
251
+ retries = r.escDelay / escPollInterval
252
+ } else {
253
+ retries = 0
254
+ }
255
+ buffer = append(buffer, byte(c))
256
+ pc = c
257
+
258
+ // This should never happen under normal conditions,
259
+ // so terminate fzf immediately.
260
+ if len(buffer) > maxInputBuffer {
261
+ r.Close()
262
+ panic(fmt.Sprintf("Input buffer overflow (%d): %v", len(buffer), buffer))
263
+ }
264
+ }
265
+
266
+ return buffer
267
+ }
268
+
269
+ func (r *LightRenderer) GetChar() Event {
270
+ if len(r.buffer) == 0 {
271
+ r.buffer = r.getBytes()
272
+ }
273
+ if len(r.buffer) == 0 {
274
+ panic("Empty buffer")
275
+ }
276
+
277
+ sz := 1
278
+ defer func() {
279
+ r.buffer = r.buffer[sz:]
280
+ }()
281
+
282
+ switch r.buffer[0] {
283
+ case CtrlC.Byte():
284
+ return Event{CtrlC, 0, nil}
285
+ case CtrlG.Byte():
286
+ return Event{CtrlG, 0, nil}
287
+ case CtrlQ.Byte():
288
+ return Event{CtrlQ, 0, nil}
289
+ case 127:
290
+ return Event{BSpace, 0, nil}
291
+ case 0:
292
+ return Event{CtrlSpace, 0, nil}
293
+ case 28:
294
+ return Event{CtrlBackSlash, 0, nil}
295
+ case 29:
296
+ return Event{CtrlRightBracket, 0, nil}
297
+ case 30:
298
+ return Event{CtrlCaret, 0, nil}
299
+ case 31:
300
+ return Event{CtrlSlash, 0, nil}
301
+ case ESC.Byte():
302
+ ev := r.escSequence(&sz)
303
+ // Second chance
304
+ if ev.Type == Invalid {
305
+ r.buffer = r.getBytes()
306
+ ev = r.escSequence(&sz)
307
+ }
308
+ return ev
309
+ }
310
+
311
+ // CTRL-A ~ CTRL-Z
312
+ if r.buffer[0] <= CtrlZ.Byte() {
313
+ return Event{EventType(r.buffer[0]), 0, nil}
314
+ }
315
+ char, rsz := utf8.DecodeRune(r.buffer)
316
+ if char == utf8.RuneError {
317
+ return Event{ESC, 0, nil}
318
+ }
319
+ sz = rsz
320
+ return Event{Rune, char, nil}
321
+ }
322
+
323
+ func (r *LightRenderer) escSequence(sz *int) Event {
324
+ if len(r.buffer) < 2 {
325
+ return Event{ESC, 0, nil}
326
+ }
327
+
328
+ loc := offsetRegexpBegin.FindIndex(r.buffer)
329
+ if loc != nil && loc[0] == 0 {
330
+ *sz = loc[1]
331
+ return Event{Invalid, 0, nil}
332
+ }
333
+
334
+ *sz = 2
335
+ if r.buffer[1] >= 1 && r.buffer[1] <= 'z'-'a'+1 {
336
+ return CtrlAltKey(rune(r.buffer[1] + 'a' - 1))
337
+ }
338
+ alt := false
339
+ if len(r.buffer) > 2 && r.buffer[1] == ESC.Byte() {
340
+ r.buffer = r.buffer[1:]
341
+ alt = true
342
+ }
343
+ switch r.buffer[1] {
344
+ case ESC.Byte():
345
+ return Event{ESC, 0, nil}
346
+ case 127:
347
+ return Event{AltBS, 0, nil}
348
+ case '[', 'O':
349
+ if len(r.buffer) < 3 {
350
+ return Event{Invalid, 0, nil}
351
+ }
352
+ *sz = 3
353
+ switch r.buffer[2] {
354
+ case 'D':
355
+ if alt {
356
+ return Event{AltLeft, 0, nil}
357
+ }
358
+ return Event{Left, 0, nil}
359
+ case 'C':
360
+ if alt {
361
+ // Ugh..
362
+ return Event{AltRight, 0, nil}
363
+ }
364
+ return Event{Right, 0, nil}
365
+ case 'B':
366
+ if alt {
367
+ return Event{AltDown, 0, nil}
368
+ }
369
+ return Event{Down, 0, nil}
370
+ case 'A':
371
+ if alt {
372
+ return Event{AltUp, 0, nil}
373
+ }
374
+ return Event{Up, 0, nil}
375
+ case 'Z':
376
+ return Event{BTab, 0, nil}
377
+ case 'H':
378
+ return Event{Home, 0, nil}
379
+ case 'F':
380
+ return Event{End, 0, nil}
381
+ case 'M':
382
+ return r.mouseSequence(sz)
383
+ case 'P':
384
+ return Event{F1, 0, nil}
385
+ case 'Q':
386
+ return Event{F2, 0, nil}
387
+ case 'R':
388
+ return Event{F3, 0, nil}
389
+ case 'S':
390
+ return Event{F4, 0, nil}
391
+ case '1', '2', '3', '4', '5', '6':
392
+ if len(r.buffer) < 4 {
393
+ return Event{Invalid, 0, nil}
394
+ }
395
+ *sz = 4
396
+ switch r.buffer[2] {
397
+ case '2':
398
+ if r.buffer[3] == '~' {
399
+ return Event{Insert, 0, nil}
400
+ }
401
+ if len(r.buffer) > 4 && r.buffer[4] == '~' {
402
+ *sz = 5
403
+ switch r.buffer[3] {
404
+ case '0':
405
+ return Event{F9, 0, nil}
406
+ case '1':
407
+ return Event{F10, 0, nil}
408
+ case '3':
409
+ return Event{F11, 0, nil}
410
+ case '4':
411
+ return Event{F12, 0, nil}
412
+ }
413
+ }
414
+ // Bracketed paste mode: \e[200~ ... \e[201~
415
+ if len(r.buffer) > 5 && r.buffer[3] == '0' && (r.buffer[4] == '0' || r.buffer[4] == '1') && r.buffer[5] == '~' {
416
+ // Immediately discard the sequence from the buffer and reread input
417
+ r.buffer = r.buffer[6:]
418
+ *sz = 0
419
+ return r.GetChar()
420
+ }
421
+ return Event{Invalid, 0, nil} // INS
422
+ case '3':
423
+ return Event{Del, 0, nil}
424
+ case '4':
425
+ return Event{End, 0, nil}
426
+ case '5':
427
+ return Event{PgUp, 0, nil}
428
+ case '6':
429
+ return Event{PgDn, 0, nil}
430
+ case '1':
431
+ switch r.buffer[3] {
432
+ case '~':
433
+ return Event{Home, 0, nil}
434
+ case '1', '2', '3', '4', '5', '7', '8', '9':
435
+ if len(r.buffer) == 5 && r.buffer[4] == '~' {
436
+ *sz = 5
437
+ switch r.buffer[3] {
438
+ case '1':
439
+ return Event{F1, 0, nil}
440
+ case '2':
441
+ return Event{F2, 0, nil}
442
+ case '3':
443
+ return Event{F3, 0, nil}
444
+ case '4':
445
+ return Event{F4, 0, nil}
446
+ case '5':
447
+ return Event{F5, 0, nil}
448
+ case '7':
449
+ return Event{F6, 0, nil}
450
+ case '8':
451
+ return Event{F7, 0, nil}
452
+ case '9':
453
+ return Event{F8, 0, nil}
454
+ }
455
+ }
456
+ return Event{Invalid, 0, nil}
457
+ case ';':
458
+ if len(r.buffer) < 6 {
459
+ return Event{Invalid, 0, nil}
460
+ }
461
+ *sz = 6
462
+ switch r.buffer[4] {
463
+ case '1', '2', '3', '5':
464
+ alt := r.buffer[4] == '3'
465
+ altShift := r.buffer[4] == '1' && r.buffer[5] == '0'
466
+ char := r.buffer[5]
467
+ if altShift {
468
+ if len(r.buffer) < 7 {
469
+ return Event{Invalid, 0, nil}
470
+ }
471
+ *sz = 7
472
+ char = r.buffer[6]
473
+ }
474
+ switch char {
475
+ case 'A':
476
+ if alt {
477
+ return Event{AltUp, 0, nil}
478
+ }
479
+ if altShift {
480
+ return Event{AltSUp, 0, nil}
481
+ }
482
+ return Event{SUp, 0, nil}
483
+ case 'B':
484
+ if alt {
485
+ return Event{AltDown, 0, nil}
486
+ }
487
+ if altShift {
488
+ return Event{AltSDown, 0, nil}
489
+ }
490
+ return Event{SDown, 0, nil}
491
+ case 'C':
492
+ if alt {
493
+ return Event{AltRight, 0, nil}
494
+ }
495
+ if altShift {
496
+ return Event{AltSRight, 0, nil}
497
+ }
498
+ return Event{SRight, 0, nil}
499
+ case 'D':
500
+ if alt {
501
+ return Event{AltLeft, 0, nil}
502
+ }
503
+ if altShift {
504
+ return Event{AltSLeft, 0, nil}
505
+ }
506
+ return Event{SLeft, 0, nil}
507
+ }
508
+ } // r.buffer[4]
509
+ } // r.buffer[3]
510
+ } // r.buffer[2]
511
+ } // r.buffer[2]
512
+ } // r.buffer[1]
513
+ rest := bytes.NewBuffer(r.buffer[1:])
514
+ c, size, err := rest.ReadRune()
515
+ if err == nil {
516
+ *sz = 1 + size
517
+ return AltKey(c)
518
+ }
519
+ return Event{Invalid, 0, nil}
520
+ }
521
+
522
+ func (r *LightRenderer) mouseSequence(sz *int) Event {
523
+ if len(r.buffer) < 6 || !r.mouse {
524
+ return Event{Invalid, 0, nil}
525
+ }
526
+ *sz = 6
527
+ switch r.buffer[3] {
528
+ case 32, 34, 36, 40, 48, // mouse-down / shift / cmd / ctrl
529
+ 35, 39, 43, 51: // mouse-up / shift / cmd / ctrl
530
+ mod := r.buffer[3] >= 36
531
+ left := r.buffer[3] == 32
532
+ down := r.buffer[3]%2 == 0
533
+ x := int(r.buffer[4] - 33)
534
+ y := int(r.buffer[5]-33) - r.yoffset
535
+ double := false
536
+ if down {
537
+ now := time.Now()
538
+ if !left { // Right double click is not allowed
539
+ r.clickY = []int{}
540
+ } else if now.Sub(r.prevDownTime) < doubleClickDuration {
541
+ r.clickY = append(r.clickY, y)
542
+ } else {
543
+ r.clickY = []int{y}
544
+ }
545
+ r.prevDownTime = now
546
+ } else {
547
+ if len(r.clickY) > 1 && r.clickY[0] == r.clickY[1] &&
548
+ time.Since(r.prevDownTime) < doubleClickDuration {
549
+ double = true
550
+ }
551
+ }
552
+
553
+ return Event{Mouse, 0, &MouseEvent{y, x, 0, left, down, double, mod}}
554
+ case 96, 100, 104, 112, // scroll-up / shift / cmd / ctrl
555
+ 97, 101, 105, 113: // scroll-down / shift / cmd / ctrl
556
+ mod := r.buffer[3] >= 100
557
+ s := 1 - int(r.buffer[3]%2)*2
558
+ x := int(r.buffer[4] - 33)
559
+ y := int(r.buffer[5]-33) - r.yoffset
560
+ return Event{Mouse, 0, &MouseEvent{y, x, s, false, false, false, mod}}
561
+ }
562
+ return Event{Invalid, 0, nil}
563
+ }
564
+
565
+ func (r *LightRenderer) smcup() {
566
+ r.csi("?1049h")
567
+ }
568
+
569
+ func (r *LightRenderer) rmcup() {
570
+ r.csi("?1049l")
571
+ }
572
+
573
+ func (r *LightRenderer) Pause(clear bool) {
574
+ r.restoreTerminal()
575
+ if clear {
576
+ if r.fullscreen {
577
+ r.rmcup()
578
+ } else {
579
+ r.smcup()
580
+ r.csi("H")
581
+ }
582
+ r.flush()
583
+ }
584
+ }
585
+
586
+ func (r *LightRenderer) Resume(clear bool, sigcont bool) {
587
+ r.setupTerminal()
588
+ if clear {
589
+ if r.fullscreen {
590
+ r.smcup()
591
+ } else {
592
+ r.rmcup()
593
+ }
594
+ r.flush()
595
+ } else if sigcont && !r.fullscreen && r.mouse {
596
+ // NOTE: SIGCONT (Coming back from CTRL-Z):
597
+ // It's highly likely that the offset we obtained at the beginning is
598
+ // no longer correct, so we simply disable mouse input.
599
+ r.csi("?1000l")
600
+ r.mouse = false
601
+ }
602
+ }
603
+
604
+ func (r *LightRenderer) Clear() {
605
+ if r.fullscreen {
606
+ r.csi("H")
607
+ }
608
+ // r.csi("u")
609
+ r.origin()
610
+ r.csi("J")
611
+ r.flush()
612
+ }
613
+
614
+ func (r *LightRenderer) RefreshWindows(windows []Window) {
615
+ r.flush()
616
+ }
617
+
618
+ func (r *LightRenderer) Refresh() {
619
+ r.updateTerminalSize()
620
+ }
621
+
622
+ func (r *LightRenderer) Close() {
623
+ // r.csi("u")
624
+ if r.clearOnExit {
625
+ if r.fullscreen {
626
+ r.rmcup()
627
+ } else {
628
+ r.origin()
629
+ if r.upOneLine {
630
+ r.csi("A")
631
+ }
632
+ r.csi("J")
633
+ }
634
+ } else if !r.fullscreen {
635
+ r.csi("u")
636
+ }
637
+ if r.mouse {
638
+ r.csi("?1000l")
639
+ }
640
+ r.flush()
641
+ r.closePlatform()
642
+ r.restoreTerminal()
643
+ }
644
+
645
+ func (r *LightRenderer) MaxX() int {
646
+ return r.width
647
+ }
648
+
649
+ func (r *LightRenderer) MaxY() int {
650
+ return r.height
651
+ }
652
+
653
+ func (r *LightRenderer) NewWindow(top int, left int, width int, height int, preview bool, borderStyle BorderStyle) Window {
654
+ w := &LightWindow{
655
+ renderer: r,
656
+ colored: r.theme.Colored,
657
+ preview: preview,
658
+ border: borderStyle,
659
+ top: top,
660
+ left: left,
661
+ width: width,
662
+ height: height,
663
+ tabstop: r.tabstop,
664
+ fg: colDefault,
665
+ bg: colDefault}
666
+ if preview {
667
+ w.fg = r.theme.PreviewFg.Color
668
+ w.bg = r.theme.PreviewBg.Color
669
+ } else {
670
+ w.fg = r.theme.Fg.Color
671
+ w.bg = r.theme.Bg.Color
672
+ }
673
+ w.drawBorder()
674
+ return w
675
+ }
676
+
677
+ func (w *LightWindow) drawBorder() {
678
+ switch w.border.shape {
679
+ case BorderRounded, BorderSharp:
680
+ w.drawBorderAround()
681
+ case BorderHorizontal:
682
+ w.drawBorderHorizontal(true, true)
683
+ case BorderVertical:
684
+ w.drawBorderVertical(true, true)
685
+ case BorderTop:
686
+ w.drawBorderHorizontal(true, false)
687
+ case BorderBottom:
688
+ w.drawBorderHorizontal(false, true)
689
+ case BorderLeft:
690
+ w.drawBorderVertical(true, false)
691
+ case BorderRight:
692
+ w.drawBorderVertical(false, true)
693
+ }
694
+ }
695
+
696
+ func (w *LightWindow) drawBorderHorizontal(top, bottom bool) {
697
+ color := ColBorder
698
+ if w.preview {
699
+ color = ColPreviewBorder
700
+ }
701
+ if top {
702
+ w.Move(0, 0)
703
+ w.CPrint(color, repeat(w.border.horizontal, w.width))
704
+ }
705
+ if bottom {
706
+ w.Move(w.height-1, 0)
707
+ w.CPrint(color, repeat(w.border.horizontal, w.width))
708
+ }
709
+ }
710
+
711
+ func (w *LightWindow) drawBorderVertical(left, right bool) {
712
+ width := w.width - 2
713
+ if !left || !right {
714
+ width++
715
+ }
716
+ color := ColBorder
717
+ if w.preview {
718
+ color = ColPreviewBorder
719
+ }
720
+ for y := 0; y < w.height; y++ {
721
+ w.Move(y, 0)
722
+ if left {
723
+ w.CPrint(color, string(w.border.vertical))
724
+ }
725
+ w.CPrint(color, repeat(' ', width))
726
+ if right {
727
+ w.CPrint(color, string(w.border.vertical))
728
+ }
729
+ }
730
+ }
731
+
732
+ func (w *LightWindow) drawBorderAround() {
733
+ w.Move(0, 0)
734
+ color := ColBorder
735
+ if w.preview {
736
+ color = ColPreviewBorder
737
+ }
738
+ w.CPrint(color, string(w.border.topLeft)+repeat(w.border.horizontal, w.width-2)+string(w.border.topRight))
739
+ for y := 1; y < w.height-1; y++ {
740
+ w.Move(y, 0)
741
+ w.CPrint(color, string(w.border.vertical))
742
+ w.CPrint(color, repeat(' ', w.width-2))
743
+ w.CPrint(color, string(w.border.vertical))
744
+ }
745
+ w.Move(w.height-1, 0)
746
+ w.CPrint(color, string(w.border.bottomLeft)+repeat(w.border.horizontal, w.width-2)+string(w.border.bottomRight))
747
+ }
748
+
749
+ func (w *LightWindow) csi(code string) {
750
+ w.renderer.csi(code)
751
+ }
752
+
753
+ func (w *LightWindow) stderrInternal(str string, allowNLCR bool) {
754
+ w.renderer.stderrInternal(str, allowNLCR)
755
+ }
756
+
757
+ func (w *LightWindow) Top() int {
758
+ return w.top
759
+ }
760
+
761
+ func (w *LightWindow) Left() int {
762
+ return w.left
763
+ }
764
+
765
+ func (w *LightWindow) Width() int {
766
+ return w.width
767
+ }
768
+
769
+ func (w *LightWindow) Height() int {
770
+ return w.height
771
+ }
772
+
773
+ func (w *LightWindow) Refresh() {
774
+ }
775
+
776
+ func (w *LightWindow) Close() {
777
+ }
778
+
779
+ func (w *LightWindow) X() int {
780
+ return w.posx
781
+ }
782
+
783
+ func (w *LightWindow) Y() int {
784
+ return w.posy
785
+ }
786
+
787
+ func (w *LightWindow) Enclose(y int, x int) bool {
788
+ return x >= w.left && x < (w.left+w.width) &&
789
+ y >= w.top && y < (w.top+w.height)
790
+ }
791
+
792
+ func (w *LightWindow) Move(y int, x int) {
793
+ w.posx = x
794
+ w.posy = y
795
+
796
+ w.renderer.move(w.Top()+y, w.Left()+x)
797
+ }
798
+
799
+ func (w *LightWindow) MoveAndClear(y int, x int) {
800
+ w.Move(y, x)
801
+ // We should not delete preview window on the right
802
+ // csi("K")
803
+ w.Print(repeat(' ', w.width-x))
804
+ w.Move(y, x)
805
+ }
806
+
807
+ func attrCodes(attr Attr) []string {
808
+ codes := []string{}
809
+ if (attr & AttrClear) > 0 {
810
+ return codes
811
+ }
812
+ if (attr & Bold) > 0 {
813
+ codes = append(codes, "1")
814
+ }
815
+ if (attr & Dim) > 0 {
816
+ codes = append(codes, "2")
817
+ }
818
+ if (attr & Italic) > 0 {
819
+ codes = append(codes, "3")
820
+ }
821
+ if (attr & Underline) > 0 {
822
+ codes = append(codes, "4")
823
+ }
824
+ if (attr & Blink) > 0 {
825
+ codes = append(codes, "5")
826
+ }
827
+ if (attr & Reverse) > 0 {
828
+ codes = append(codes, "7")
829
+ }
830
+ return codes
831
+ }
832
+
833
+ func colorCodes(fg Color, bg Color) []string {
834
+ codes := []string{}
835
+ appendCode := func(c Color, offset int) {
836
+ if c == colDefault {
837
+ return
838
+ }
839
+ if c.is24() {
840
+ r := (c >> 16) & 0xff
841
+ g := (c >> 8) & 0xff
842
+ b := (c) & 0xff
843
+ codes = append(codes, fmt.Sprintf("%d;2;%d;%d;%d", 38+offset, r, g, b))
844
+ } else if c >= colBlack && c <= colWhite {
845
+ codes = append(codes, fmt.Sprintf("%d", int(c)+30+offset))
846
+ } else if c > colWhite && c < 16 {
847
+ codes = append(codes, fmt.Sprintf("%d", int(c)+90+offset-8))
848
+ } else if c >= 16 && c < 256 {
849
+ codes = append(codes, fmt.Sprintf("%d;5;%d", 38+offset, c))
850
+ }
851
+ }
852
+ appendCode(fg, 0)
853
+ appendCode(bg, 10)
854
+ return codes
855
+ }
856
+
857
+ func (w *LightWindow) csiColor(fg Color, bg Color, attr Attr) bool {
858
+ codes := append(attrCodes(attr), colorCodes(fg, bg)...)
859
+ w.csi(";" + strings.Join(codes, ";") + "m")
860
+ return len(codes) > 0
861
+ }
862
+
863
+ func (w *LightWindow) Print(text string) {
864
+ w.cprint2(colDefault, w.bg, AttrRegular, text)
865
+ }
866
+
867
+ func cleanse(str string) string {
868
+ return strings.Replace(str, "\x1b", "", -1)
869
+ }
870
+
871
+ func (w *LightWindow) CPrint(pair ColorPair, text string) {
872
+ w.csiColor(pair.Fg(), pair.Bg(), pair.Attr())
873
+ w.stderrInternal(cleanse(text), false)
874
+ w.csi("m")
875
+ }
876
+
877
+ func (w *LightWindow) cprint2(fg Color, bg Color, attr Attr, text string) {
878
+ if w.csiColor(fg, bg, attr) {
879
+ defer w.csi("m")
880
+ }
881
+ w.stderrInternal(cleanse(text), false)
882
+ }
883
+
884
+ type wrappedLine struct {
885
+ text string
886
+ displayWidth int
887
+ }
888
+
889
+ func wrapLine(input string, prefixLength int, max int, tabstop int) []wrappedLine {
890
+ lines := []wrappedLine{}
891
+ width := 0
892
+ line := ""
893
+ gr := uniseg.NewGraphemes(input)
894
+ for gr.Next() {
895
+ rs := gr.Runes()
896
+ str := string(rs)
897
+ var w int
898
+ if len(rs) == 1 && rs[0] == '\t' {
899
+ w = tabstop - (prefixLength+width)%tabstop
900
+ str = repeat(' ', w)
901
+ } else {
902
+ w = runewidth.StringWidth(str)
903
+ }
904
+ width += w
905
+
906
+ if prefixLength+width <= max {
907
+ line += str
908
+ } else {
909
+ lines = append(lines, wrappedLine{string(line), width - w})
910
+ line = str
911
+ prefixLength = 0
912
+ width = w
913
+ }
914
+ }
915
+ lines = append(lines, wrappedLine{string(line), width})
916
+ return lines
917
+ }
918
+
919
+ func (w *LightWindow) fill(str string, onMove func()) FillReturn {
920
+ allLines := strings.Split(str, "\n")
921
+ for i, line := range allLines {
922
+ lines := wrapLine(line, w.posx, w.width, w.tabstop)
923
+ for j, wl := range lines {
924
+ w.stderrInternal(wl.text, false)
925
+ w.posx += wl.displayWidth
926
+
927
+ // Wrap line
928
+ if j < len(lines)-1 || i < len(allLines)-1 {
929
+ if w.posy+1 >= w.height {
930
+ return FillSuspend
931
+ }
932
+ w.MoveAndClear(w.posy, w.posx)
933
+ w.Move(w.posy+1, 0)
934
+ onMove()
935
+ }
936
+ }
937
+ }
938
+ if w.posx+1 >= w.Width() {
939
+ if w.posy+1 >= w.height {
940
+ return FillSuspend
941
+ }
942
+ w.Move(w.posy+1, 0)
943
+ onMove()
944
+ return FillNextLine
945
+ }
946
+ return FillContinue
947
+ }
948
+
949
+ func (w *LightWindow) setBg() {
950
+ if w.bg != colDefault {
951
+ w.csiColor(colDefault, w.bg, AttrRegular)
952
+ }
953
+ }
954
+
955
+ func (w *LightWindow) Fill(text string) FillReturn {
956
+ w.Move(w.posy, w.posx)
957
+ w.setBg()
958
+ return w.fill(text, w.setBg)
959
+ }
960
+
961
+ func (w *LightWindow) CFill(fg Color, bg Color, attr Attr, text string) FillReturn {
962
+ w.Move(w.posy, w.posx)
963
+ if fg == colDefault {
964
+ fg = w.fg
965
+ }
966
+ if bg == colDefault {
967
+ bg = w.bg
968
+ }
969
+ if w.csiColor(fg, bg, attr) {
970
+ defer w.csi("m")
971
+ return w.fill(text, func() { w.csiColor(fg, bg, attr) })
972
+ }
973
+ return w.fill(text, w.setBg)
974
+ }
975
+
976
+ func (w *LightWindow) FinishFill() {
977
+ w.MoveAndClear(w.posy, w.posx)
978
+ for y := w.posy + 1; y < w.height; y++ {
979
+ w.MoveAndClear(y, 0)
980
+ }
981
+ }
982
+
983
+ func (w *LightWindow) Erase() {
984
+ w.drawBorder()
985
+ // We don't erase the window here to avoid flickering during scroll
986
+ w.Move(0, 0)
987
+ }