1package vt
2
3import (
4 "strconv"
5 "strings"
6)
7
8// Kitty keyboard progressive enhancement flags.
9// https://sw.kovidgoyal.net/kitty/keyboard-protocol/#progressive-enhancement
10const (
11 KittyKeyboardDisambiguate = 1 << 0
12 KittyKeyboardReportEvents = 1 << 1
13 KittyKeyboardAlternateKeys = 1 << 2
14 KittyKeyboardAllKeysAsEscape = 1 << 3
15 KittyKeyboardAssociatedText = 1 << 4
16
17 kittyKeyboardFlagMask = 1<<5 - 1
18 // maxKittyKeyboardStack bounds the per-screen flag stack. When full, a
19 // push evicts the oldest entry, as the protocol recommends.
20 maxKittyKeyboardStack = 16
21)
22
23// kittyKeyboardStack is one screen's stack of keyboard enhancement flags. The
24// current flags are the top entry, or zero when the stack is empty.
25type kittyKeyboardStack []int
26
27func (k kittyKeyboardStack) current() int {
28 if len(k) == 0 {
29 return 0
30 }
31 return k[len(k)-1]
32}
33
34func (k *kittyKeyboardStack) push(flags int) {
35 if len(*k) == maxKittyKeyboardStack {
36 copy(*k, (*k)[1:])
37 *k = (*k)[:len(*k)-1]
38 }
39 *k = append(*k, flags&kittyKeyboardFlagMask)
40}
41
42func (k *kittyKeyboardStack) pop(n int) {
43 *k = (*k)[:len(*k)-min(n, len(*k))]
44}
45
46// set applies CSI = flags ; mode u. Mode 1 replaces, 2 sets bits, 3 clears
47// bits. With an empty stack the result becomes the single entry.
48func (k *kittyKeyboardStack) set(flags, mode int) {
49 cur := k.current()
50 switch mode {
51 case 1:
52 cur = flags
53 case 2:
54 cur |= flags
55 case 3:
56 cur &^= flags
57 default:
58 return
59 }
60 cur &= kittyKeyboardFlagMask
61 if len(*k) == 0 {
62 *k = append(*k, cur)
63 return
64 }
65 (*k)[len(*k)-1] = cur
66}
67
68// KittyKeyboardFlags reports the kitty keyboard enhancement flags currently
69// requested by the application on the active (main or alternate) screen.
70func (s *Screen) KittyKeyboardFlags() int { return s.kittyKeyboard.current() }
71
72// applyKittyKeyboard handles CSI u sequences with a ?, >, <, or = prefix. It
73// reports whether params belonged to the kitty keyboard protocol.
74func (s *Screen) applyKittyKeyboard(params string) bool {
75 if params == "" {
76 return false
77 }
78 prefix, rest := params[0], params[1:]
79 switch prefix {
80 case '?':
81 if rest != "" {
82 return true
83 }
84 resp := make([]byte, 0, 8)
85 resp = append(resp, "\x1b[?"...)
86 resp = strconv.AppendInt(resp, int64(s.kittyKeyboard.current()), 10)
87 resp = append(resp, 'u')
88 s.respond(resp)
89 case '>':
90 flags, _ := kittyKeyboardParams(rest)
91 s.kittyKeyboard.push(flags)
92 case '<':
93 n, _ := kittyKeyboardParams(rest)
94 s.kittyKeyboard.pop(max(n, 1))
95 case '=':
96 flags, mode := kittyKeyboardParams(rest)
97 s.kittyKeyboard.set(flags, max(mode, 1))
98 default:
99 return false
100 }
101 return true
102}
103
104// kittyKeyboardParams parses up to two decimal parameters. Missing or
105// malformed values are zero.
106func kittyKeyboardParams(params string) (first, second int) {
107 a, b, _ := strings.Cut(params, ";")
108 return parseCSIInt(a), parseCSIInt(b)
109}