-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathparser_apc.go
More file actions
202 lines (182 loc) · 5.13 KB
/
parser_apc.go
File metadata and controls
202 lines (182 loc) · 5.13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
package xterm
// Ported from xterm.js src/common/parser/ApcParser.ts.
//
// ApcParser handles Application Program Command sequences. Unlike OSC which
// uses numeric identifiers, APC uses the first character as the identifier
// (e.g., 'G' for Kitty graphics protocol).
// ApcHandler is the interface for handlers that process APC sequences.
// Intentionally separate from OscHandler to mirror xterm.js type structure.
type ApcHandler interface { //nolint:iface
Start()
Put(data []uint32, start, end int)
End(success bool) bool
}
// ApcFallbackHandler is called when no registered handler matches the APC identifier.
type ApcFallbackHandler func(ident int, action string, payload ...interface{})
// ApcParser parses APC sequences and dispatches to registered handlers.
type ApcParser struct {
state ApcState
active []ApcHandler
id int
handlers map[int][]ApcHandler
handlerFb ApcFallbackHandler
}
// NewApcParser creates a new ApcParser.
func NewApcParser() *ApcParser {
return &ApcParser{
state: ApcStateStart,
id: -1,
handlers: make(map[int][]ApcHandler),
handlerFb: func(int, string, ...interface{}) {},
}
}
// RegisterHandler registers a handler for the given APC identifier (character code).
// Returns a Disposable that removes the handler when disposed.
func (p *ApcParser) RegisterHandler(ident int, handler ApcHandler) Disposable {
p.handlers[ident] = append(p.handlers[ident], handler)
handlerList := p.handlers[ident]
return toDisposable(func() {
for i, h := range handlerList {
if h == handler {
p.handlers[ident] = append(handlerList[:i], handlerList[i+1:]...)
return
}
}
})
}
// ClearHandler removes all handlers for the given identifier.
func (p *ApcParser) ClearHandler(ident int) {
delete(p.handlers, ident)
}
// SetHandlerFallback sets the fallback handler called when no handler matches.
func (p *ApcParser) SetHandlerFallback(handler ApcFallbackHandler) {
p.handlerFb = handler
}
// Dispose removes all handlers and resets state.
func (p *ApcParser) Dispose() {
p.handlers = make(map[int][]ApcHandler)
p.handlerFb = func(int, string, ...interface{}) {}
p.active = nil
}
// Reset forces cleanup of active handlers and resets parser state.
func (p *ApcParser) Reset() {
if p.state == ApcStatePayload {
for j := len(p.active) - 1; j >= 0; j-- {
p.active[j].End(false)
}
}
p.active = nil
p.id = -1
p.state = ApcStateStart
}
func (p *ApcParser) start() {
if handlers, ok := p.handlers[p.id]; ok && len(handlers) > 0 {
p.active = handlers
for j := len(p.active) - 1; j >= 0; j-- {
p.active[j].Start()
}
} else {
p.active = nil
p.handlerFb(p.id, "START")
}
}
func (p *ApcParser) put(data []uint32, start, end int) {
if len(p.active) == 0 {
p.handlerFb(p.id, "PUT", utf32ToString(data, start, end))
} else {
for j := len(p.active) - 1; j >= 0; j-- {
p.active[j].Put(data, start, end)
}
}
}
// Start begins a new APC sequence, resetting any leftover state.
func (p *ApcParser) Start() {
p.Reset()
p.state = ApcStateID
}
// Put feeds data to the current APC command. The first character is used as
// the identifier, and subsequent data is passed as payload.
func (p *ApcParser) Put(data []uint32, start, end int) {
if p.state == ApcStateAbort {
return
}
if p.state == ApcStateID {
if start < end {
p.id = int(data[start])
start++
p.state = ApcStatePayload
p.start()
}
}
if p.state == ApcStatePayload && end-start > 0 {
p.put(data, start, end)
}
}
// End signals the end of an APC sequence. success indicates whether the
// sequence terminated normally or was aborted.
func (p *ApcParser) End(success bool) {
if p.state == ApcStateStart {
return
}
if p.state != ApcStateAbort {
// Early end in ID state means empty APC — invalid, just reset.
if p.state == ApcStateID {
p.active = nil
p.id = -1
p.state = ApcStateStart
return
}
if len(p.active) == 0 {
p.handlerFb(p.id, "END", success)
} else {
for j := len(p.active) - 1; j >= 0; j-- {
if p.active[j].End(success) {
for k := j - 1; k >= 0; k-- {
p.active[k].End(false)
}
break
}
}
}
}
p.active = nil
p.id = -1
p.state = ApcStateStart
}
// ApcStringHandler is a convenience wrapper that collects APC payload as a
// string and calls a callback function on End.
type ApcStringHandler struct {
handler func(data string) bool
data string
hitLimit bool
}
// NewApcStringHandler creates an ApcStringHandler from a callback.
func NewApcStringHandler(handler func(data string) bool) *ApcStringHandler {
return &ApcStringHandler{handler: handler}
}
// Start resets the string accumulator.
func (h *ApcStringHandler) Start() {
h.data = ""
h.hitLimit = false
}
// Put appends payload data as a string.
func (h *ApcStringHandler) Put(data []uint32, start, end int) {
if h.hitLimit {
return
}
h.data += utf32ToString(data, start, end)
if len(h.data) > ParserPayloadLimit {
h.data = ""
h.hitLimit = true
}
}
// End calls the handler callback if the sequence ended successfully.
func (h *ApcStringHandler) End(success bool) bool {
ret := false
if !h.hitLimit && success {
ret = h.handler(h.data)
}
h.data = ""
h.hitLimit = false
return ret
}