@termwright/probe-tview 0.2.0 → 0.3.0
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.
- package/README.md +99 -89
- package/assets/tcell_marker_windows.go.txt +80 -0
- package/assets/tview_probe.go.txt +1368 -0
- package/assets/tview_probe_test.go.txt +483 -0
- package/dist/index.d.ts +16 -32
- package/dist/index.js +118 -116
- package/dist/index.js.map +1 -1
- package/package.json +9 -8
- package/upstream-patches/tview/v0.42.0/add/termwright_probe.go +0 -876
- package/upstream-patches/tview/v0.42.0/add/termwright_probe_test.go +0 -428
- package/upstream-patches/tview/v0.42.0/manifest.json +0 -42
- package/upstream-patches/tview/v0.42.0/patches/application.go.patch +0 -14
- package/upstream-patches/tview/v0.42.0/patches/go.mod.patch +0 -15
|
@@ -1,428 +0,0 @@
|
|
|
1
|
-
package tview
|
|
2
|
-
|
|
3
|
-
// Tests for the injected probe. They ship with the patch set and run inside
|
|
4
|
-
// the instrumented copy, which is the only place the probe's internals exist.
|
|
5
|
-
//
|
|
6
|
-
// The one that matters is the stalled driver: publication happens under the
|
|
7
|
-
// application's write lock, so a driver that stops reading must cost frames
|
|
8
|
-
// and never the application's ability to draw.
|
|
9
|
-
|
|
10
|
-
import (
|
|
11
|
-
"encoding/binary"
|
|
12
|
-
"errors"
|
|
13
|
-
"io"
|
|
14
|
-
"net"
|
|
15
|
-
"os"
|
|
16
|
-
"path/filepath"
|
|
17
|
-
"runtime"
|
|
18
|
-
"strconv"
|
|
19
|
-
"strings"
|
|
20
|
-
"testing"
|
|
21
|
-
"time"
|
|
22
|
-
|
|
23
|
-
"github.com/gdamore/tcell/v2"
|
|
24
|
-
|
|
25
|
-
"github.com/gorce-ai/termwright/clients/go/annotate"
|
|
26
|
-
"github.com/gorce-ai/termwright/clients/go/protocol"
|
|
27
|
-
)
|
|
28
|
-
|
|
29
|
-
// stalledDriver answers the handshake and then reads nothing until released.
|
|
30
|
-
type stalledDriver struct {
|
|
31
|
-
listener net.Listener
|
|
32
|
-
resume chan struct{}
|
|
33
|
-
conn net.Conn
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
func startStalledDriver(t *testing.T, path string) *stalledDriver {
|
|
37
|
-
t.Helper()
|
|
38
|
-
listener, err := net.Listen("unix", path)
|
|
39
|
-
if err != nil {
|
|
40
|
-
t.Fatal(err)
|
|
41
|
-
}
|
|
42
|
-
driver := &stalledDriver{listener: listener, resume: make(chan struct{})}
|
|
43
|
-
|
|
44
|
-
go func() {
|
|
45
|
-
conn, err := listener.Accept()
|
|
46
|
-
if err != nil {
|
|
47
|
-
return
|
|
48
|
-
}
|
|
49
|
-
driver.conn = conn
|
|
50
|
-
// Keep this test independent of the host kernel's default socket
|
|
51
|
-
// capacity. Linux CI otherwise accepted every test frame, so the test
|
|
52
|
-
// reported a skip without exercising the write deadline at all.
|
|
53
|
-
if unix, ok := conn.(*net.UnixConn); ok {
|
|
54
|
-
if err := unix.SetReadBuffer(4 * 1024); err != nil {
|
|
55
|
-
return
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Read exactly the hello frame. A broad Read may also consume the first
|
|
60
|
-
// snapshot when the client writes it immediately after the handshake.
|
|
61
|
-
// That accidentally frees the receive buffer and makes this test depend
|
|
62
|
-
// on scheduler timing instead of exercising the stalled-writer path.
|
|
63
|
-
header := make([]byte, protocol.FrameHeaderBytes)
|
|
64
|
-
if _, err := io.ReadFull(conn, header); err != nil {
|
|
65
|
-
return
|
|
66
|
-
}
|
|
67
|
-
bodyLength := int(binary.BigEndian.Uint32(header))
|
|
68
|
-
if bodyLength <= 0 || bodyLength > protocol.DefaultLimits.MaxFrameBytes {
|
|
69
|
-
return
|
|
70
|
-
}
|
|
71
|
-
if _, err := io.ReadFull(conn, make([]byte, bodyLength)); err != nil {
|
|
72
|
-
return
|
|
73
|
-
}
|
|
74
|
-
ack, _ := protocol.EncodeFrame(map[string]any{
|
|
75
|
-
"type": "hello-ack", "protocol": protocol.ProtocolID, "sessionId": "s-1",
|
|
76
|
-
"limits": protocol.DefaultLimits, "subscribe": "diffs",
|
|
77
|
-
"marker": map[string]any{"enabled": true},
|
|
78
|
-
}, protocol.DefaultLimits.MaxFrameBytes)
|
|
79
|
-
_, _ = conn.Write(ack)
|
|
80
|
-
|
|
81
|
-
<-driver.resume
|
|
82
|
-
buffer := make([]byte, 64*1024)
|
|
83
|
-
for {
|
|
84
|
-
if _, err := conn.Read(buffer); err != nil {
|
|
85
|
-
return
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}()
|
|
89
|
-
|
|
90
|
-
t.Cleanup(func() { _ = listener.Close() })
|
|
91
|
-
return driver
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// probeAgainst builds a probe wired to `endpoint`, without touching the
|
|
95
|
-
// package-level one that a real run installs.
|
|
96
|
-
func probeAgainst(t *testing.T, endpoint string) *termwrightProbeState {
|
|
97
|
-
t.Helper()
|
|
98
|
-
t.Setenv("TERMWRIGHT_ENDPOINT", endpoint)
|
|
99
|
-
t.Setenv("TERMWRIGHT_TOKEN", "token")
|
|
100
|
-
|
|
101
|
-
probe := newTermwrightProbe()
|
|
102
|
-
if probe == nil {
|
|
103
|
-
t.Fatal("the probe stayed dormant with the handshake variables set")
|
|
104
|
-
}
|
|
105
|
-
t.Cleanup(func() { _ = probe.client.Close() })
|
|
106
|
-
|
|
107
|
-
deadline := time.Now().Add(2 * time.Second)
|
|
108
|
-
for !probe.client.Connected() && time.Now().Before(deadline) {
|
|
109
|
-
time.Sleep(5 * time.Millisecond)
|
|
110
|
-
}
|
|
111
|
-
if !probe.client.Connected() {
|
|
112
|
-
t.Fatal("the handshake did not complete")
|
|
113
|
-
}
|
|
114
|
-
return probe
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// sampleApplication builds a tree with enough in it to be worth publishing.
|
|
118
|
-
func sampleApplication(t *testing.T) (*Application, tcell.Screen, *List) {
|
|
119
|
-
t.Helper()
|
|
120
|
-
screen := tcell.NewSimulationScreen("UTF-8")
|
|
121
|
-
if err := screen.Init(); err != nil {
|
|
122
|
-
t.Fatal(err)
|
|
123
|
-
}
|
|
124
|
-
screen.SetSize(80, 24)
|
|
125
|
-
t.Cleanup(screen.Fini)
|
|
126
|
-
|
|
127
|
-
// Big enough that a handful of frames fills a socket buffer. A small tree
|
|
128
|
-
// makes the stalled-driver test skip itself, which looks like a pass and
|
|
129
|
-
// covers nothing.
|
|
130
|
-
label := strings.Repeat("a reasonably long list item label ", 4)
|
|
131
|
-
list := NewList().ShowSecondaryText(false)
|
|
132
|
-
for index := 0; index < 400; index++ {
|
|
133
|
-
list.AddItem(label+strconv.Itoa(index), "", 0, nil)
|
|
134
|
-
}
|
|
135
|
-
root := NewFlex().SetDirection(FlexRow).
|
|
136
|
-
AddItem(NewTextView().SetText("header"), 1, 0, false).
|
|
137
|
-
AddItem(list, 0, 1, true)
|
|
138
|
-
|
|
139
|
-
app := NewApplication()
|
|
140
|
-
app.root = root
|
|
141
|
-
root.SetRect(0, 0, 80, 24)
|
|
142
|
-
return app, screen, list
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// churn rewrites every label, so the frame really differs from the last one.
|
|
146
|
-
//
|
|
147
|
-
// Without it the driver subscribes to diffs and an unchanged tree produces a
|
|
148
|
-
// delta of almost nothing — which never fills a socket buffer, so the stalled
|
|
149
|
-
// tests skip themselves and cover nothing while looking green.
|
|
150
|
-
func churn(list *List, round int) {
|
|
151
|
-
suffix := strconv.Itoa(round)
|
|
152
|
-
for index := 0; index < list.GetItemCount(); index++ {
|
|
153
|
-
main, _ := list.GetItemText(index)
|
|
154
|
-
list.SetItemText(index, main[:len(main)-len(suffixOf(main))]+suffix, "")
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// suffixOf returns the trailing digits of a label.
|
|
159
|
-
func suffixOf(label string) string {
|
|
160
|
-
end := len(label)
|
|
161
|
-
for end > 0 && label[end-1] >= '0' && label[end-1] <= '9' {
|
|
162
|
-
end--
|
|
163
|
-
}
|
|
164
|
-
return label[end:]
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
func TestTheProbeIsDormantWithoutTheHandshakeVariables(t *testing.T) {
|
|
168
|
-
t.Setenv("TERMWRIGHT_ENDPOINT", "")
|
|
169
|
-
t.Setenv("TERMWRIGHT_TOKEN", "")
|
|
170
|
-
|
|
171
|
-
if probe := newTermwrightProbe(); probe != nil {
|
|
172
|
-
t.Fatal("an uninstrumented run built a probe")
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
func TestAStalledDriverCostsFramesAndNotTheApplication(t *testing.T) {
|
|
177
|
-
// The requirement in one test: no probe write may block the render loop
|
|
178
|
-
// indefinitely, and the application must survive termwright disappearing.
|
|
179
|
-
path := filepath.Join(shortDir(t), "s")
|
|
180
|
-
driver := startStalledDriver(t, path)
|
|
181
|
-
probe := probeAgainst(t, path)
|
|
182
|
-
app, screen, list := sampleApplication(t)
|
|
183
|
-
|
|
184
|
-
started := time.Now()
|
|
185
|
-
var deltasBefore int64
|
|
186
|
-
for attempt := 0; attempt < 400 && probe.timedOut.Load() == 0; attempt++ {
|
|
187
|
-
churn(list, attempt)
|
|
188
|
-
probe.afterFrame(app, screen)
|
|
189
|
-
if probe.frames.Load() > 0 && deltasBefore == 0 {
|
|
190
|
-
deltasBefore = probe.client.DeltasSent()
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
elapsed := time.Since(started)
|
|
194
|
-
|
|
195
|
-
if probe.timedOut.Load() == 0 {
|
|
196
|
-
t.Fatal("the bounded socket accepted every frame; the stalled-driver path was not exercised")
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// Bounded: 400 frames against a driver that never reads must not take
|
|
200
|
-
// anything like 400 × the deadline, because the session closes on the
|
|
201
|
-
// first timeout and every later publish returns at once.
|
|
202
|
-
if elapsed > 5*time.Second {
|
|
203
|
-
t.Fatalf("publishing against a stalled driver took %s, which is not bounded", elapsed)
|
|
204
|
-
}
|
|
205
|
-
if probe.dropped.Load() == 0 {
|
|
206
|
-
t.Fatal("frames were lost but nothing was counted")
|
|
207
|
-
}
|
|
208
|
-
// The application is still drawable: the hook returns, and it returns
|
|
209
|
-
// without having held anything open.
|
|
210
|
-
probe.afterFrame(app, screen)
|
|
211
|
-
|
|
212
|
-
// And the obligation is outstanding, so the driver cannot be handed a
|
|
213
|
-
// delta based on a revision it never received.
|
|
214
|
-
if !probe.client.FullSnapshotRequired() {
|
|
215
|
-
t.Fatal("frames were dropped without demanding a full snapshot next")
|
|
216
|
-
}
|
|
217
|
-
_ = driver
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
func TestAFailedPublishWritesNoMarker(t *testing.T) {
|
|
221
|
-
// A marker names a revision. Writing one for a tree that never arrived
|
|
222
|
-
// makes the driver wait for it and then blame the adapter's timing.
|
|
223
|
-
path := filepath.Join(shortDir(t), "s")
|
|
224
|
-
_ = startStalledDriver(t, path)
|
|
225
|
-
probe := probeAgainst(t, path)
|
|
226
|
-
app, screen, list := sampleApplication(t)
|
|
227
|
-
|
|
228
|
-
read, write, err := os.Pipe()
|
|
229
|
-
if err != nil {
|
|
230
|
-
t.Fatal(err)
|
|
231
|
-
}
|
|
232
|
-
original := os.Stdout
|
|
233
|
-
os.Stdout = write
|
|
234
|
-
t.Cleanup(func() { os.Stdout = original })
|
|
235
|
-
|
|
236
|
-
for attempt := 0; attempt < 400 && probe.timedOut.Load() == 0; attempt++ {
|
|
237
|
-
churn(list, attempt)
|
|
238
|
-
probe.afterFrame(app, screen)
|
|
239
|
-
}
|
|
240
|
-
_ = write.Close()
|
|
241
|
-
|
|
242
|
-
buffer := make([]byte, 64*1024)
|
|
243
|
-
n, _ := read.Read(buffer)
|
|
244
|
-
written := string(buffer[:n])
|
|
245
|
-
|
|
246
|
-
if probe.timedOut.Load() == 0 {
|
|
247
|
-
t.Fatal("the bounded socket accepted every frame; the failed-publish path was not exercised")
|
|
248
|
-
}
|
|
249
|
-
// Whatever markers the first few successful frames wrote, the dropped
|
|
250
|
-
// ones must not have added any: one marker per published revision.
|
|
251
|
-
if markers := countMarkers(written); uint64(markers) != probe.frames.Load() {
|
|
252
|
-
t.Fatalf("wrote %d markers for %d published frames", markers, probe.frames.Load())
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
func TestTheProbeRecognisesARejectedSnapshotSeparately(t *testing.T) {
|
|
257
|
-
// A snapshot refused by validation is not a driver that stopped reading,
|
|
258
|
-
// and the two must not share a diagnosis.
|
|
259
|
-
if errors.Is(protocol.ErrWriteTimeout, os.ErrDeadlineExceeded) {
|
|
260
|
-
return
|
|
261
|
-
}
|
|
262
|
-
if protocol.ValidationCode(protocol.ErrWriteTimeout) != "" {
|
|
263
|
-
t.Fatal("a write timeout was reported as a validation failure")
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
func TestMarkerCounterRecognisesOnlyTermwrightOSCMarkers(t *testing.T) {
|
|
268
|
-
text := "plain\x1b]0;window title\x07" +
|
|
269
|
-
"\x1b]8487;twm;1;first\x07between" +
|
|
270
|
-
"\x1b]8487;twm;2;second\x07"
|
|
271
|
-
if markers := countMarkers(text); markers != 2 {
|
|
272
|
-
t.Fatalf("counted %d Termwright markers, want 2", markers)
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
func TestAnnotationsResolveKeysAfterTheWholeRetainedTreeIsKnown(t *testing.T) {
|
|
277
|
-
annotate.Reset()
|
|
278
|
-
t.Cleanup(annotate.Reset)
|
|
279
|
-
|
|
280
|
-
control := NewButton("Save")
|
|
281
|
-
label := NewTextView().SetText("Release name")
|
|
282
|
-
help := NewTextView().SetText("Use a unique name")
|
|
283
|
-
annotate.Tag(control, annotate.Semantics{
|
|
284
|
-
Name: "Save release",
|
|
285
|
-
Actions: []protocol.Action{protocol.ActionActivate, protocol.ActionActivate, protocol.Action("invalid")},
|
|
286
|
-
LabelledBy: []annotate.SemanticKey{"release-label"},
|
|
287
|
-
DescribedBy: []annotate.SemanticKey{"release-help", "missing"},
|
|
288
|
-
})
|
|
289
|
-
annotate.Tag(label, annotate.Semantics{Key: "release-label"})
|
|
290
|
-
annotate.Tag(help, annotate.Semantics{Key: "release-help"})
|
|
291
|
-
|
|
292
|
-
// The control deliberately comes first. A one-pass resolver would miss
|
|
293
|
-
// both targets because neither has been walked yet.
|
|
294
|
-
root := NewFlex().SetDirection(FlexRow).
|
|
295
|
-
AddItem(control, 1, 0, false).
|
|
296
|
-
AddItem(label, 1, 0, false).
|
|
297
|
-
AddItem(help, 1, 0, false)
|
|
298
|
-
app := NewApplication()
|
|
299
|
-
app.root = root
|
|
300
|
-
root.SetRect(0, 0, 40, 3)
|
|
301
|
-
|
|
302
|
-
probe := &termwrightProbeState{ids: make(map[Primitive]string)}
|
|
303
|
-
snapshot := probe.snapshot(app, 40, 3, false)
|
|
304
|
-
controlID := probe.identity(control)
|
|
305
|
-
labelID := probe.identity(label)
|
|
306
|
-
helpID := probe.identity(help)
|
|
307
|
-
|
|
308
|
-
var node *protocol.Node
|
|
309
|
-
for index := range snapshot.Nodes {
|
|
310
|
-
if snapshot.Nodes[index].ID == controlID {
|
|
311
|
-
node = &snapshot.Nodes[index]
|
|
312
|
-
break
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
if node == nil {
|
|
316
|
-
t.Fatal("annotated control was not published")
|
|
317
|
-
}
|
|
318
|
-
if strings.Join(node.LabelledBy, ",") != labelID || strings.Join(node.DescribedBy, ",") != helpID {
|
|
319
|
-
t.Fatalf("relations were not resolved by key: labelledBy=%v describedBy=%v", node.LabelledBy, node.DescribedBy)
|
|
320
|
-
}
|
|
321
|
-
if len(node.Actions) != 1 || node.Actions[0] != protocol.ActionActivate {
|
|
322
|
-
t.Fatalf("actions were not closed and deduplicated: %v", node.Actions)
|
|
323
|
-
}
|
|
324
|
-
if node.P != protocol.ProvenanceFramework {
|
|
325
|
-
t.Fatalf("node-wide provenance = %q, want framework", node.P)
|
|
326
|
-
}
|
|
327
|
-
for _, field := range []string{"name", "actions", "labelledBy", "describedBy"} {
|
|
328
|
-
if node.PX[field] != protocol.ProvenanceAnnotation {
|
|
329
|
-
t.Fatalf("%s provenance = %q, want annotation (all px=%v)", field, node.PX[field], node.PX)
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
if node.Bounds == nil || node.Role != protocol.RoleButton || node.PX["role"] != protocol.ProvenanceRecognizer {
|
|
333
|
-
t.Fatalf("annotation replaced framework/recognizer facts: %+v", node)
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
func TestDuplicateSemanticKeysCannotBecomeAmbiguousRelations(t *testing.T) {
|
|
338
|
-
annotate.Reset()
|
|
339
|
-
t.Cleanup(annotate.Reset)
|
|
340
|
-
|
|
341
|
-
control := NewButton("Save")
|
|
342
|
-
first := NewTextView().SetText("First")
|
|
343
|
-
second := NewTextView().SetText("Second")
|
|
344
|
-
annotate.Tag(control, annotate.Semantics{LabelledBy: []annotate.SemanticKey{"duplicate"}})
|
|
345
|
-
annotate.Tag(first, annotate.Semantics{Key: "duplicate"})
|
|
346
|
-
annotate.Tag(second, annotate.Semantics{Key: "duplicate"})
|
|
347
|
-
|
|
348
|
-
root := NewFlex().
|
|
349
|
-
AddItem(control, 1, 0, false).
|
|
350
|
-
AddItem(first, 1, 0, false).
|
|
351
|
-
AddItem(second, 1, 0, false)
|
|
352
|
-
app := NewApplication()
|
|
353
|
-
app.root = root
|
|
354
|
-
root.SetRect(0, 0, 30, 1)
|
|
355
|
-
probe := &termwrightProbeState{ids: make(map[Primitive]string)}
|
|
356
|
-
snapshot := probe.snapshot(app, 30, 1, false)
|
|
357
|
-
controlID := probe.identity(control)
|
|
358
|
-
for _, node := range snapshot.Nodes {
|
|
359
|
-
if node.ID == controlID && len(node.LabelledBy) != 0 {
|
|
360
|
-
t.Fatalf("duplicate key resolved arbitrarily to %v", node.LabelledBy)
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
func TestQualifiedSnapshotReportsOnlyObservableTviewGeometry(t *testing.T) {
|
|
366
|
-
root := NewFlex()
|
|
367
|
-
button := NewButton("Approve")
|
|
368
|
-
hidden := NewButton("Hidden")
|
|
369
|
-
root.AddItem(button, 1, 0, false).AddItem(hidden, 1, 0, false)
|
|
370
|
-
root.SetRect(75, 23, 10, 2)
|
|
371
|
-
button.SetRect(75, 23, 10, 1)
|
|
372
|
-
hidden.SetRect(75, 24, 10, 1)
|
|
373
|
-
|
|
374
|
-
app := NewApplication()
|
|
375
|
-
app.root = root
|
|
376
|
-
probe := &termwrightProbeState{ids: make(map[Primitive]string)}
|
|
377
|
-
snapshot := probe.snapshot(app, 80, 24, true)
|
|
378
|
-
|
|
379
|
-
if snapshot.V != 2 || snapshot.CoordinateSpace == nil || snapshot.HitGrid == nil || snapshot.HitGrid.Status != "unsupported" {
|
|
380
|
-
t.Fatalf("snapshot is not honestly qualified: %+v", snapshot)
|
|
381
|
-
}
|
|
382
|
-
for index, node := range snapshot.Nodes {
|
|
383
|
-
if node.Geometry == nil {
|
|
384
|
-
t.Fatalf("node %d has no qualified geometry: %+v", index, node)
|
|
385
|
-
}
|
|
386
|
-
if node.Bounds != nil || node.Occlusion != "" {
|
|
387
|
-
t.Fatalf("v2 node retained legacy geometry fields: %+v", node)
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
geometry := snapshot.Nodes[1].Geometry
|
|
391
|
-
if geometry.Displayed.Status != "known" || geometry.Displayed.Value == nil || !*geometry.Displayed.Value {
|
|
392
|
-
t.Fatalf("displayed observation = %+v", geometry.Displayed)
|
|
393
|
-
}
|
|
394
|
-
if geometry.IntendedRect.Value == nil || *geometry.IntendedRect.Value != (protocol.Rect{Row: 23, Column: 75, Width: 10, Height: 1}) {
|
|
395
|
-
t.Fatalf("intended rect = %+v", geometry.IntendedRect)
|
|
396
|
-
}
|
|
397
|
-
if geometry.VisibleRect.Value == nil || *geometry.VisibleRect.Value != (protocol.Rect{Row: 23, Column: 75, Width: 5, Height: 1}) {
|
|
398
|
-
t.Fatalf("visible rect = %+v", geometry.VisibleRect)
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
func countMarkers(text string) int {
|
|
403
|
-
count := 0
|
|
404
|
-
const prefix = "\x1b]8487;twm;"
|
|
405
|
-
for index := 0; index+len(prefix) <= len(text); index++ {
|
|
406
|
-
if text[index:index+len(prefix)] == prefix {
|
|
407
|
-
count++
|
|
408
|
-
index += len(prefix) - 1
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
return count
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
// shortDir keeps a unix socket path under the platform limit, which the
|
|
415
|
-
// default temporary directory on macOS routinely exceeds.
|
|
416
|
-
func shortDir(t *testing.T) string {
|
|
417
|
-
t.Helper()
|
|
418
|
-
base := "/tmp"
|
|
419
|
-
if runtime.GOOS == "windows" {
|
|
420
|
-
base = ""
|
|
421
|
-
}
|
|
422
|
-
dir, err := os.MkdirTemp(base, "tw")
|
|
423
|
-
if err != nil {
|
|
424
|
-
t.Fatal(err)
|
|
425
|
-
}
|
|
426
|
-
t.Cleanup(func() { _ = os.RemoveAll(dir) })
|
|
427
|
-
return dir
|
|
428
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"framework": "github.com/rivo/tview",
|
|
3
|
-
"frameworkVersion": "v0.42.0",
|
|
4
|
-
"patchSetVersion": 11,
|
|
5
|
-
"note": "Anchored after screen.Show() in draw(); see docs/architecture/audit/tview.md §1. go.mod gains the protocol client, supplied from disk by the generated workspace.",
|
|
6
|
-
"requires": [
|
|
7
|
-
{
|
|
8
|
-
"module": "github.com/gorce-ai/termwright/clients/go",
|
|
9
|
-
"suppliedBy": "workspace"
|
|
10
|
-
},
|
|
11
|
-
{
|
|
12
|
-
"module": "github.com/gorce-ai/termwright/clients/go/annotate",
|
|
13
|
-
"suppliedBy": "workspace"
|
|
14
|
-
}
|
|
15
|
-
],
|
|
16
|
-
"patched": [
|
|
17
|
-
{
|
|
18
|
-
"path": "application.go",
|
|
19
|
-
"patch": "patches/application.go.patch",
|
|
20
|
-
"sha256Before": "sha256:acf51c64021e45132cd28848f5559b51567df26f1c823cc9249d55581bcd7e39",
|
|
21
|
-
"sha256After": "sha256:f79553c643934bcef1038d19be0473ccd6edbe1a24b2935bffbec4de562538cd"
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
"path": "go.mod",
|
|
25
|
-
"patch": "patches/go.mod.patch",
|
|
26
|
-
"sha256Before": "sha256:a2f3b131206042b58863c3c04474e7a9dd865c74c54f41d4179b77a34bafd9d9",
|
|
27
|
-
"sha256After": "sha256:068951344e45181175b58e0ccd002ef7222ca3dd8bf21017df601879d4a1966a"
|
|
28
|
-
}
|
|
29
|
-
],
|
|
30
|
-
"added": [
|
|
31
|
-
{
|
|
32
|
-
"path": "termwright_probe.go",
|
|
33
|
-
"source": "add/termwright_probe.go",
|
|
34
|
-
"sha256": "sha256:97566d3783f9996c07c087f0e243557f0266a50aaa89e9c2f676abe72c1f9185"
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
"path": "termwright_probe_test.go",
|
|
38
|
-
"source": "add/termwright_probe_test.go",
|
|
39
|
-
"sha256": "sha256:1c97fba8f84ad9b646219df06af80d76dc74cf30c408364f20cf2ecf3ea07aec"
|
|
40
|
-
}
|
|
41
|
-
]
|
|
42
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
--- a/application.go
|
|
2
|
-
+++ b/application.go
|
|
3
|
-
@@ -743,6 +743,11 @@
|
|
4
|
-
// Sync screen.
|
|
5
|
-
screen.Show()
|
|
6
|
-
|
|
7
|
-
+ // Injected by termwright: the frame's bytes have been flushed, so this is
|
|
8
|
-
+ // where an observation may be taken and a render-commit marker written.
|
|
9
|
-
+ // Still under a.Lock() — see termwright_probe.go for what that forbids.
|
|
10
|
-
+ termwrightAfterFrame(a, screen)
|
|
11
|
-
+
|
|
12
|
-
return a
|
|
13
|
-
}
|
|
14
|
-
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
--- a/go.mod
|
|
2
|
-
+++ b/go.mod
|
|
3
|
-
@@ -8,6 +8,12 @@
|
|
4
|
-
github.com/rivo/uniseg v0.4.7
|
|
5
|
-
)
|
|
6
|
-
|
|
7
|
-
+// Added by termwright: the injected probe speaks the protocol through the
|
|
8
|
-
+// published client rather than reimplementing framing and markers. The
|
|
9
|
-
+// generated workspace supplies this module from disk, so no network fetch and
|
|
10
|
-
+// no go.sum entry are involved.
|
|
11
|
-
+require github.com/gorce-ai/termwright/clients/go v0.0.0
|
|
12
|
-
+
|
|
13
|
-
require (
|
|
14
|
-
github.com/gdamore/encoding v1.0.1 // indirect
|
|
15
|
-
github.com/mattn/go-runewidth v0.0.16 // indirect
|