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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/Gemfile.lock +1 -1
- data/README.md +1 -1
- data/doing.rdoc +1 -1
- data/lib/doing/version.rb +1 -1
- data/lib/helpers/fzf/.goreleaser.yml +119 -0
- data/lib/helpers/fzf/.rubocop.yml +28 -0
- data/lib/helpers/fzf/ADVANCED.md +565 -0
- data/lib/helpers/fzf/BUILD.md +49 -0
- data/lib/helpers/fzf/CHANGELOG.md +1193 -0
- data/lib/helpers/fzf/Dockerfile +11 -0
- data/lib/helpers/fzf/LICENSE +21 -0
- data/lib/helpers/fzf/Makefile +166 -0
- data/lib/helpers/fzf/README-VIM.md +486 -0
- data/lib/helpers/fzf/README.md +712 -0
- data/lib/helpers/fzf/bin/fzf-tmux +233 -0
- data/lib/helpers/fzf/doc/fzf.txt +512 -0
- data/lib/helpers/fzf/go.mod +17 -0
- data/lib/helpers/fzf/go.sum +31 -0
- data/lib/helpers/fzf/install +382 -0
- data/lib/helpers/fzf/install.ps1 +65 -0
- data/lib/helpers/fzf/main.go +14 -0
- data/lib/helpers/fzf/man/man1/fzf-tmux.1 +68 -0
- data/lib/helpers/fzf/man/man1/fzf.1 +1001 -0
- data/lib/helpers/fzf/plugin/fzf.vim +1048 -0
- data/lib/helpers/fzf/shell/completion.bash +381 -0
- data/lib/helpers/fzf/shell/completion.zsh +329 -0
- data/lib/helpers/fzf/shell/key-bindings.bash +96 -0
- data/lib/helpers/fzf/shell/key-bindings.fish +172 -0
- data/lib/helpers/fzf/shell/key-bindings.zsh +114 -0
- data/lib/helpers/fzf/src/LICENSE +21 -0
- data/lib/helpers/fzf/src/algo/algo.go +884 -0
- data/lib/helpers/fzf/src/algo/algo_test.go +197 -0
- data/lib/helpers/fzf/src/algo/normalize.go +492 -0
- data/lib/helpers/fzf/src/ansi.go +409 -0
- data/lib/helpers/fzf/src/ansi_test.go +427 -0
- data/lib/helpers/fzf/src/cache.go +81 -0
- data/lib/helpers/fzf/src/cache_test.go +39 -0
- data/lib/helpers/fzf/src/chunklist.go +89 -0
- data/lib/helpers/fzf/src/chunklist_test.go +80 -0
- data/lib/helpers/fzf/src/constants.go +85 -0
- data/lib/helpers/fzf/src/core.go +351 -0
- data/lib/helpers/fzf/src/history.go +96 -0
- data/lib/helpers/fzf/src/history_test.go +68 -0
- data/lib/helpers/fzf/src/item.go +44 -0
- data/lib/helpers/fzf/src/item_test.go +23 -0
- data/lib/helpers/fzf/src/matcher.go +235 -0
- data/lib/helpers/fzf/src/merger.go +120 -0
- data/lib/helpers/fzf/src/merger_test.go +88 -0
- data/lib/helpers/fzf/src/options.go +1691 -0
- data/lib/helpers/fzf/src/options_test.go +457 -0
- data/lib/helpers/fzf/src/pattern.go +425 -0
- data/lib/helpers/fzf/src/pattern_test.go +209 -0
- data/lib/helpers/fzf/src/protector/protector.go +8 -0
- data/lib/helpers/fzf/src/protector/protector_openbsd.go +10 -0
- data/lib/helpers/fzf/src/reader.go +201 -0
- data/lib/helpers/fzf/src/reader_test.go +63 -0
- data/lib/helpers/fzf/src/result.go +243 -0
- data/lib/helpers/fzf/src/result_others.go +16 -0
- data/lib/helpers/fzf/src/result_test.go +159 -0
- data/lib/helpers/fzf/src/result_x86.go +16 -0
- data/lib/helpers/fzf/src/terminal.go +2832 -0
- data/lib/helpers/fzf/src/terminal_test.go +638 -0
- data/lib/helpers/fzf/src/terminal_unix.go +26 -0
- data/lib/helpers/fzf/src/terminal_windows.go +45 -0
- data/lib/helpers/fzf/src/tokenizer.go +253 -0
- data/lib/helpers/fzf/src/tokenizer_test.go +112 -0
- data/lib/helpers/fzf/src/tui/dummy.go +46 -0
- data/lib/helpers/fzf/src/tui/light.go +987 -0
- data/lib/helpers/fzf/src/tui/light_unix.go +110 -0
- data/lib/helpers/fzf/src/tui/light_windows.go +145 -0
- data/lib/helpers/fzf/src/tui/tcell.go +721 -0
- data/lib/helpers/fzf/src/tui/tcell_test.go +392 -0
- data/lib/helpers/fzf/src/tui/ttyname_unix.go +47 -0
- data/lib/helpers/fzf/src/tui/ttyname_windows.go +14 -0
- data/lib/helpers/fzf/src/tui/tui.go +625 -0
- data/lib/helpers/fzf/src/tui/tui_test.go +20 -0
- data/lib/helpers/fzf/src/util/atomicbool.go +34 -0
- data/lib/helpers/fzf/src/util/atomicbool_test.go +17 -0
- data/lib/helpers/fzf/src/util/chars.go +198 -0
- data/lib/helpers/fzf/src/util/chars_test.go +46 -0
- data/lib/helpers/fzf/src/util/eventbox.go +96 -0
- data/lib/helpers/fzf/src/util/eventbox_test.go +61 -0
- data/lib/helpers/fzf/src/util/slab.go +12 -0
- data/lib/helpers/fzf/src/util/util.go +138 -0
- data/lib/helpers/fzf/src/util/util_test.go +40 -0
- data/lib/helpers/fzf/src/util/util_unix.go +47 -0
- data/lib/helpers/fzf/src/util/util_windows.go +83 -0
- data/lib/helpers/fzf/test/fzf.vader +175 -0
- data/lib/helpers/fzf/test/test_go.rb +2626 -0
- data/lib/helpers/fzf/uninstall +117 -0
- 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
|
+
}
|