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,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
+ }