aboutsummaryrefslogtreecommitdiff
path: root/vendor/github.com/nsf/termbox-go/termbox.go
diff options
context:
space:
mode:
authorMitja Felicijan <mitja.felicijan@gmail.com>2026-01-21 20:22:09 +0100
committerMitja Felicijan <mitja.felicijan@gmail.com>2026-01-21 20:22:09 +0100
commit5a8dbc6347b3541e84fe669b22c17ad3b715e258 (patch)
treeb148c450939688caaaeb4adac6f2faa1eaffe649 /vendor/github.com/nsf/termbox-go/termbox.go
downloadqwe-editor-5a8dbc6347b3541e84fe669b22c17ad3b715e258.tar.gz
Engage!
Diffstat (limited to 'vendor/github.com/nsf/termbox-go/termbox.go')
-rw-r--r--vendor/github.com/nsf/termbox-go/termbox.go602
1 files changed, 602 insertions, 0 deletions
diff --git a/vendor/github.com/nsf/termbox-go/termbox.go b/vendor/github.com/nsf/termbox-go/termbox.go
new file mode 100644
index 0000000..cb3d378
--- /dev/null
+++ b/vendor/github.com/nsf/termbox-go/termbox.go
@@ -0,0 +1,602 @@
1// +build !windows
2
3package termbox
4
5import "unicode/utf8"
6import "bytes"
7import "syscall"
8import "unsafe"
9import "strings"
10import "strconv"
11import "os"
12import "io"
13
14// private API
15
16const (
17 // for future contributors: after adding something here,
18 // you have to add the corresponding index in a terminfo
19 // file to `terminfo.go#ti_funcs`. The values can be taken
20 // from (ncurses) `term.h`. The builtin terminfo at terminfo_builtin.go
21 // also needs adjusting with the new values.
22 t_enter_ca = iota
23 t_exit_ca
24 t_show_cursor
25 t_hide_cursor
26 t_clear_screen
27 t_sgr0
28 t_underline
29 t_bold
30 t_hidden
31 t_blink
32 t_dim
33 t_cursive
34 t_reverse
35 t_enter_keypad
36 t_exit_keypad
37 t_enter_mouse
38 t_exit_mouse
39 t_max_funcs
40)
41
42const (
43 coord_invalid = -2
44 attr_invalid = Attribute(0xFFFF)
45)
46
47type input_event struct {
48 data []byte
49 err error
50}
51
52type extract_event_res int
53
54const (
55 event_not_extracted extract_event_res = iota
56 event_extracted
57 esc_wait
58)
59
60var (
61 // term specific sequences
62 keys []string
63 funcs []string
64
65 // termbox inner state
66 orig_tios syscall_Termios
67 back_buffer cellbuf
68 front_buffer cellbuf
69 termw int
70 termh int
71 input_mode = InputEsc
72 output_mode = OutputNormal
73 out *os.File
74 in int
75 lastfg = attr_invalid
76 lastbg = attr_invalid
77 lastx = coord_invalid
78 lasty = coord_invalid
79 cursor_x = cursor_hidden
80 cursor_y = cursor_hidden
81 foreground = ColorDefault
82 background = ColorDefault
83 inbuf = make([]byte, 0, 64)
84 outbuf bytes.Buffer
85 sigwinch = make(chan os.Signal, 1)
86 sigio = make(chan os.Signal, 1)
87 quit = make(chan int)
88 input_comm = make(chan input_event)
89 interrupt_comm = make(chan struct{})
90 intbuf = make([]byte, 0, 16)
91
92 // grayscale indexes
93 grayscale = []Attribute{
94 0, 17, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244,
95 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 232,
96 }
97)
98
99func write_cursor(x, y int) {
100 outbuf.WriteString("\033[")
101 outbuf.Write(strconv.AppendUint(intbuf, uint64(y+1), 10))
102 outbuf.WriteString(";")
103 outbuf.Write(strconv.AppendUint(intbuf, uint64(x+1), 10))
104 outbuf.WriteString("H")
105}
106
107func write_sgr_fg(a Attribute) {
108 switch output_mode {
109 case Output256, Output216, OutputGrayscale:
110 outbuf.WriteString("\033[38;5;")
111 outbuf.Write(strconv.AppendUint(intbuf, uint64(a-1), 10))
112 outbuf.WriteString("m")
113 case OutputRGB:
114 r, g, b := AttributeToRGB(a)
115 outbuf.WriteString(escapeRGB(true, r, g, b))
116 default:
117 if a < ColorDarkGray {
118 outbuf.WriteString("\033[3")
119 outbuf.Write(strconv.AppendUint(intbuf, uint64(a-ColorBlack), 10))
120 outbuf.WriteString("m")
121 } else {
122 outbuf.WriteString("\033[9")
123 outbuf.Write(strconv.AppendUint(intbuf, uint64(a-ColorDarkGray), 10))
124 outbuf.WriteString("m")
125 }
126 }
127}
128
129func write_sgr_bg(a Attribute) {
130 switch output_mode {
131 case Output256, Output216, OutputGrayscale:
132 outbuf.WriteString("\033[48;5;")
133 outbuf.Write(strconv.AppendUint(intbuf, uint64(a-1), 10))
134 outbuf.WriteString("m")
135 case OutputRGB:
136 r, g, b := AttributeToRGB(a)
137 outbuf.WriteString(escapeRGB(false, r, g, b))
138 default:
139 if a < ColorDarkGray {
140 outbuf.WriteString("\033[4")
141 outbuf.Write(strconv.AppendUint(intbuf, uint64(a-ColorBlack), 10))
142 outbuf.WriteString("m")
143 } else {
144 outbuf.WriteString("\033[10")
145 outbuf.Write(strconv.AppendUint(intbuf, uint64(a-ColorDarkGray), 10))
146 outbuf.WriteString("m")
147 }
148 }
149}
150
151func write_sgr(fg, bg Attribute) {
152 switch output_mode {
153 case Output256, Output216, OutputGrayscale:
154 outbuf.WriteString("\033[38;5;")
155 outbuf.Write(strconv.AppendUint(intbuf, uint64(fg-1), 10))
156 outbuf.WriteString("m")
157 outbuf.WriteString("\033[48;5;")
158 outbuf.Write(strconv.AppendUint(intbuf, uint64(bg-1), 10))
159 outbuf.WriteString("m")
160 case OutputRGB:
161 r, g, b := AttributeToRGB(fg)
162 outbuf.WriteString(escapeRGB(true, r, g, b))
163 r, g, b = AttributeToRGB(bg)
164 outbuf.WriteString(escapeRGB(false, r, g, b))
165 default:
166 if fg < ColorDarkGray {
167 outbuf.WriteString("\033[3")
168 outbuf.Write(strconv.AppendUint(intbuf, uint64(fg-ColorBlack), 10))
169 outbuf.WriteString(";")
170 } else {
171 outbuf.WriteString("\033[9")
172 outbuf.Write(strconv.AppendUint(intbuf, uint64(fg-ColorDarkGray), 10))
173 outbuf.WriteString(";")
174 }
175 if bg < ColorDarkGray {
176 outbuf.WriteString("4")
177 outbuf.Write(strconv.AppendUint(intbuf, uint64(bg-ColorBlack), 10))
178 outbuf.WriteString("m")
179 } else {
180 outbuf.WriteString("10")
181 outbuf.Write(strconv.AppendUint(intbuf, uint64(bg-ColorDarkGray), 10))
182 outbuf.WriteString("m")
183 }
184 }
185}
186
187func escapeRGB(fg bool, r uint8, g uint8, b uint8) string {
188 var escape string = "\033["
189 if fg {
190 escape += "38"
191 } else {
192 escape += "48"
193 }
194 escape += ";2;"
195 escape += strconv.FormatUint(uint64(r), 10) + ";"
196 escape += strconv.FormatUint(uint64(g), 10) + ";"
197 escape += strconv.FormatUint(uint64(b), 10) + "m"
198 return escape
199}
200
201type winsize struct {
202 rows uint16
203 cols uint16
204 xpixels uint16
205 ypixels uint16
206}
207
208func get_term_size(fd uintptr) (int, int) {
209 var sz winsize
210 _, _, _ = syscall.Syscall(syscall.SYS_IOCTL,
211 fd, uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(&sz)))
212 return int(sz.cols), int(sz.rows)
213}
214
215func send_attr(fg, bg Attribute) {
216 if fg == lastfg && bg == lastbg {
217 return
218 }
219
220 outbuf.WriteString(funcs[t_sgr0])
221
222 var fgcol, bgcol Attribute
223
224 switch output_mode {
225 case Output256:
226 fgcol = fg & 0x1FF
227 bgcol = bg & 0x1FF
228 case Output216:
229 fgcol = fg & 0xFF
230 bgcol = bg & 0xFF
231 if fgcol > 216 {
232 fgcol = ColorDefault
233 }
234 if bgcol > 216 {
235 bgcol = ColorDefault
236 }
237 if fgcol != ColorDefault {
238 fgcol += 0x10
239 }
240 if bgcol != ColorDefault {
241 bgcol += 0x10
242 }
243 case OutputGrayscale:
244 fgcol = fg & 0x1F
245 bgcol = bg & 0x1F
246 if fgcol > 26 {
247 fgcol = ColorDefault
248 }
249 if bgcol > 26 {
250 bgcol = ColorDefault
251 }
252 if fgcol != ColorDefault {
253 fgcol = grayscale[fgcol]
254 }
255 if bgcol != ColorDefault {
256 bgcol = grayscale[bgcol]
257 }
258 case OutputRGB:
259 fgcol = fg
260 bgcol = bg
261 default:
262 fgcol = fg & 0xFF
263 bgcol = bg & 0xFF
264 }
265
266 if fgcol != ColorDefault {
267 if bgcol != ColorDefault {
268 write_sgr(fgcol, bgcol)
269 } else {
270 write_sgr_fg(fgcol)
271 }
272 } else if bgcol != ColorDefault {
273 write_sgr_bg(bgcol)
274 }
275
276 if fg&AttrBold != 0 {
277 outbuf.WriteString(funcs[t_bold])
278 }
279 /*if bg&AttrBold != 0 {
280 outbuf.WriteString(funcs[t_blink])
281 }*/
282 if fg&AttrBlink != 0 {
283 outbuf.WriteString(funcs[t_blink])
284 }
285 if fg&AttrUnderline != 0 {
286 outbuf.WriteString(funcs[t_underline])
287 }
288 if fg&AttrCursive != 0 {
289 outbuf.WriteString(funcs[t_cursive])
290 }
291 if fg&AttrHidden != 0 {
292 outbuf.WriteString(funcs[t_hidden])
293 }
294 if fg&AttrDim != 0 {
295 outbuf.WriteString(funcs[t_dim])
296 }
297 if fg&AttrReverse|bg&AttrReverse != 0 {
298 outbuf.WriteString(funcs[t_reverse])
299 }
300
301 lastfg, lastbg = fg, bg
302}
303
304func send_char(x, y int, ch rune) {
305 var buf [8]byte
306 n := utf8.EncodeRune(buf[:], ch)
307 if x-1 != lastx || y != lasty {
308 write_cursor(x, y)
309 }
310 lastx, lasty = x, y
311 outbuf.Write(buf[:n])
312}
313
314func flush() error {
315 _, err := io.Copy(out, &outbuf)
316 outbuf.Reset()
317 return err
318}
319
320func send_clear() error {
321 send_attr(foreground, background)
322 outbuf.WriteString(funcs[t_clear_screen])
323 if !is_cursor_hidden(cursor_x, cursor_y) {
324 write_cursor(cursor_x, cursor_y)
325 }
326
327 // we need to invalidate cursor position too and these two vars are
328 // used only for simple cursor positioning optimization, cursor
329 // actually may be in the correct place, but we simply discard
330 // optimization once and it gives us simple solution for the case when
331 // cursor moved
332 lastx = coord_invalid
333 lasty = coord_invalid
334
335 return flush()
336}
337
338func update_size_maybe() error {
339 w, h := get_term_size(out.Fd())
340 if w != termw || h != termh {
341 termw, termh = w, h
342 back_buffer.resize(termw, termh)
343 front_buffer.resize(termw, termh)
344 front_buffer.clear()
345 return send_clear()
346 }
347 return nil
348}
349
350func tcsetattr(fd uintptr, termios *syscall_Termios) error {
351 r, _, e := syscall.Syscall(syscall.SYS_IOCTL,
352 fd, uintptr(syscall_TCSETS), uintptr(unsafe.Pointer(termios)))
353 if r != 0 {
354 return os.NewSyscallError("SYS_IOCTL", e)
355 }
356 return nil
357}
358
359func tcgetattr(fd uintptr, termios *syscall_Termios) error {
360 r, _, e := syscall.Syscall(syscall.SYS_IOCTL,
361 fd, uintptr(syscall_TCGETS), uintptr(unsafe.Pointer(termios)))
362 if r != 0 {
363 return os.NewSyscallError("SYS_IOCTL", e)
364 }
365 return nil
366}
367
368func parse_mouse_event(event *Event, buf string) (int, bool) {
369 if strings.HasPrefix(buf, "\033[M") && len(buf) >= 6 {
370 // X10 mouse encoding, the simplest one
371 // \033 [ M Cb Cx Cy
372 b := buf[3] - 32
373 switch b & 3 {
374 case 0:
375 if b&64 != 0 {
376 event.Key = MouseWheelUp
377 } else {
378 event.Key = MouseLeft
379 }
380 case 1:
381 if b&64 != 0 {
382 event.Key = MouseWheelDown
383 } else {
384 event.Key = MouseMiddle
385 }
386 case 2:
387 event.Key = MouseRight
388 case 3:
389 event.Key = MouseRelease
390 default:
391 return 6, false
392 }
393 event.Type = EventMouse // KeyEvent by default
394 if b&32 != 0 {
395 event.Mod |= ModMotion
396 }
397
398 // the coord is 1,1 for upper left
399 event.MouseX = int(buf[4]) - 1 - 32
400 event.MouseY = int(buf[5]) - 1 - 32
401 return 6, true
402 } else if strings.HasPrefix(buf, "\033[<") || strings.HasPrefix(buf, "\033[") {
403 // xterm 1006 extended mode or urxvt 1015 extended mode
404 // xterm: \033 [ < Cb ; Cx ; Cy (M or m)
405 // urxvt: \033 [ Cb ; Cx ; Cy M
406
407 // find the first M or m, that's where we stop
408 mi := strings.IndexAny(buf, "Mm")
409 if mi == -1 {
410 return 0, false
411 }
412
413 // whether it's a capital M or not
414 isM := buf[mi] == 'M'
415
416 // whether it's urxvt or not
417 isU := false
418
419 // buf[2] is safe here, because having M or m found means we have at
420 // least 3 bytes in a string
421 if buf[2] == '<' {
422 buf = buf[3:mi]
423 } else {
424 isU = true
425 buf = buf[2:mi]
426 }
427
428 s1 := strings.Index(buf, ";")
429 s2 := strings.LastIndex(buf, ";")
430 // not found or only one ';'
431 if s1 == -1 || s2 == -1 || s1 == s2 {
432 return 0, false
433 }
434
435 n1, err := strconv.ParseInt(buf[0:s1], 10, 64)
436 if err != nil {
437 return 0, false
438 }
439 n2, err := strconv.ParseInt(buf[s1+1:s2], 10, 64)
440 if err != nil {
441 return 0, false
442 }
443 n3, err := strconv.ParseInt(buf[s2+1:], 10, 64)
444 if err != nil {
445 return 0, false
446 }
447
448 // on urxvt, first number is encoded exactly as in X10, but we need to
449 // make it zero-based, on xterm it is zero-based already
450 if isU {
451 n1 -= 32
452 }
453 switch n1 & 3 {
454 case 0:
455 if n1&64 != 0 {
456 event.Key = MouseWheelUp
457 } else {
458 event.Key = MouseLeft
459 }
460 case 1:
461 if n1&64 != 0 {
462 event.Key = MouseWheelDown
463 } else {
464 event.Key = MouseMiddle
465 }
466 case 2:
467 event.Key = MouseRight
468 case 3:
469 event.Key = MouseRelease
470 default:
471 return mi + 1, false
472 }
473 if !isM {
474 // on xterm mouse release is signaled by lowercase m
475 event.Key = MouseRelease
476 }
477
478 event.Type = EventMouse // KeyEvent by default
479 if n1&32 != 0 {
480 event.Mod |= ModMotion
481 }
482
483 event.MouseX = int(n2) - 1
484 event.MouseY = int(n3) - 1
485 return mi + 1, true
486 }
487
488 return 0, false
489}
490
491func parse_escape_sequence(event *Event, buf []byte) (int, bool) {
492 bufstr := string(buf)
493 for i, key := range keys {
494 if strings.HasPrefix(bufstr, key) {
495 event.Ch = 0
496 event.Key = Key(0xFFFF - i)
497 return len(key), true
498 }
499 }
500
501 // if none of the keys match, let's try mouse sequences
502 return parse_mouse_event(event, bufstr)
503}
504
505func extract_raw_event(data []byte, event *Event) bool {
506 if len(inbuf) == 0 {
507 return false
508 }
509
510 n := len(data)
511 if n == 0 {
512 return false
513 }
514
515 n = copy(data, inbuf)
516 copy(inbuf, inbuf[n:])
517 inbuf = inbuf[:len(inbuf)-n]
518
519 event.N = n
520 event.Type = EventRaw
521 return true
522}
523
524func extract_event(inbuf []byte, event *Event, allow_esc_wait bool) extract_event_res {
525 if len(inbuf) == 0 {
526 event.N = 0
527 return event_not_extracted
528 }
529
530 if inbuf[0] == '\033' {
531 // possible escape sequence
532 if n, ok := parse_escape_sequence(event, inbuf); n != 0 {
533 event.N = n
534 if ok {
535 return event_extracted
536 } else {
537 return event_not_extracted
538 }
539 }
540
541 // possible partially read escape sequence; trigger a wait if appropriate
542 if enable_wait_for_escape_sequence() && allow_esc_wait {
543 event.N = 0
544 return esc_wait
545 }
546
547 // it's not escape sequence, then it's Alt or Esc, check input_mode
548 switch {
549 case input_mode&InputEsc != 0:
550 // if we're in escape mode, fill Esc event, pop buffer, return success
551 event.Ch = 0
552 event.Key = KeyEsc
553 event.Mod = 0
554 event.N = 1
555 return event_extracted
556 case input_mode&InputAlt != 0:
557 // if we're in alt mode, set Alt modifier to event and redo parsing
558 event.Mod = ModAlt
559 status := extract_event(inbuf[1:], event, false)
560 if status == event_extracted {
561 event.N++
562 } else {
563 event.N = 0
564 }
565 return status
566 default:
567 panic("unreachable")
568 }
569 }
570
571 // if we're here, this is not an escape sequence and not an alt sequence
572 // so, it's a FUNCTIONAL KEY or a UNICODE character
573
574 // first of all check if it's a functional key
575 if Key(inbuf[0]) <= KeySpace || Key(inbuf[0]) == KeyBackspace2 {
576 // fill event, pop buffer, return success
577 event.Ch = 0
578 event.Key = Key(inbuf[0])
579 event.N = 1
580 return event_extracted
581 }
582
583 // the only possible option is utf8 rune
584 if r, n := utf8.DecodeRune(inbuf); r != utf8.RuneError {
585 event.Ch = r
586 event.Key = 0
587 event.N = n
588 return event_extracted
589 }
590
591 return event_not_extracted
592}
593
594func fcntl(fd int, cmd int, arg int) (val int, err error) {
595 r, _, e := syscall.Syscall(syscall.SYS_FCNTL, uintptr(fd), uintptr(cmd),
596 uintptr(arg))
597 val = int(r)
598 if e != 0 {
599 err = e
600 }
601 return
602}