@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
package/README.md
CHANGED
|
@@ -1,116 +1,126 @@
|
|
|
1
1
|
# @termwright/probe-tview
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Capability-driven semantic probing for
|
|
4
|
+
[tview](https://github.com/rivo/tview), without editing or copying upstream
|
|
5
|
+
source files.
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
Termwright controls the Go build and uses the official `-toolexec` hook to add
|
|
8
|
+
owned compilation units to the tview package and, on Windows, tcell. The
|
|
9
|
+
compiler checks every private-field assumption. A new upstream version is
|
|
10
|
+
accepted by compilation plus behavioral conformance, not by matching a source
|
|
11
|
+
digest.
|
|
10
12
|
|
|
11
|
-
## Install
|
|
13
|
+
## Install and use
|
|
12
14
|
|
|
13
15
|
```sh
|
|
14
16
|
npm install --save-dev @termwright/probe-tview
|
|
15
17
|
```
|
|
16
18
|
|
|
17
|
-
|
|
19
|
+
The application opts in with one line immediately before `Run`:
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
One call prepares the build; the launcher owns everything else.
|
|
22
|
-
|
|
23
|
-
```ts
|
|
24
|
-
import {prepareInstrumentedBuild} from '@termwright/probe-tview';
|
|
25
|
-
|
|
26
|
-
const build = await prepareInstrumentedBuild({moduleDir: 'path/to/app'});
|
|
27
|
-
|
|
28
|
-
// build.env carries GOWORK; the project's own files are untouched.
|
|
29
|
-
await execFile('go', ['build', '-o', 'app-binary', '.'], {cwd: 'path/to/app', env: build.env});
|
|
21
|
+
```go
|
|
22
|
+
import "github.com/gorce-ai/termwright/clients/go/tviewprobe"
|
|
30
23
|
|
|
31
|
-
|
|
24
|
+
app.SetRoot(root, true)
|
|
25
|
+
defer tviewprobe.Attach(app, root)()
|
|
26
|
+
if err := app.Run(); err != nil {
|
|
27
|
+
panic(err)
|
|
28
|
+
}
|
|
32
29
|
```
|
|
33
30
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
## What it gives you
|
|
38
|
-
|
|
39
|
-
Being inside the package is the point. A `tview.Grid` exposes no accessor for
|
|
40
|
-
its children at all, so an out-of-package adapter has to be handed a callback;
|
|
41
|
-
here it is a field read that also carries whether the item was drawn. A widget
|
|
42
|
-
on a `Pages` page that is not shown reports as **hidden** rather than going
|
|
43
|
-
missing, so a test can tell "not on screen" from "not there".
|
|
31
|
+
Prepare the controlled build and pass the returned Go arguments to the build
|
|
32
|
+
or test command:
|
|
44
33
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
34
|
+
```ts
|
|
35
|
+
import { execFile } from 'node:child_process';
|
|
36
|
+
import { prepareInstrumentedBuild } from '@termwright/probe-tview';
|
|
37
|
+
|
|
38
|
+
const build = await prepareInstrumentedBuild({ moduleDir: 'path/to/app' });
|
|
39
|
+
await execFile('go', ['build', ...build.goArgs, '-o', 'app-binary', '.'], {
|
|
40
|
+
cwd: build.moduleDir,
|
|
41
|
+
env: build.env,
|
|
42
|
+
});
|
|
43
|
+
```
|
|
52
44
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
45
|
+
The mechanism works with ordinary module-cache dependencies, local
|
|
46
|
+
replacements, workspaces and vendored applications. It does not modify the
|
|
47
|
+
application's `go.mod`, `go.sum`, `go.work`, vendor tree, or upstream module
|
|
48
|
+
bytes.
|
|
49
|
+
|
|
50
|
+
## Intervention tiers
|
|
51
|
+
|
|
52
|
+
- T0 public tview/tcell APIs provide most widget hierarchy, state, geometry
|
|
53
|
+
and the `Screen.Show` output boundary.
|
|
54
|
+
- A T1 add-only tview unit exposes sealed root, Grid, Modal, DropDown and other
|
|
55
|
+
rendered state. Private-field drift is a compile error.
|
|
56
|
+
- On Windows a second T1 tcell unit writes the authenticated frame marker
|
|
57
|
+
through the console handle used by `Show`. Unix uses the public `Tty()`
|
|
58
|
+
writer.
|
|
59
|
+
|
|
60
|
+
The application screen is decorated before `Run`, and its existing public
|
|
61
|
+
before/after-draw hooks are chained. Those hooks arm exactly tview's final
|
|
62
|
+
`Show`; intermediate `Show` calls made by custom primitives or application
|
|
63
|
+
hooks still flush normally but cannot publish a partial semantic frame. After
|
|
64
|
+
the armed underlying `Show` completes, the probe reads the current
|
|
65
|
+
`Application.root`, admits one complete snapshot to the bounded publication
|
|
66
|
+
queue and writes its marker through the same screen sink. This also covers
|
|
67
|
+
tview's before-draw short-circuit and roots changed through `SetRoot`. It never
|
|
68
|
+
calls `Show` itself, never holds a process-global render mutex and never
|
|
69
|
+
performs socket I/O on the render goroutine. Runtime displacement of either
|
|
70
|
+
composed hook fails semantics closed.
|
|
71
|
+
|
|
72
|
+
A queue refusal, re-entrant `Show`, missing writer, partial marker, worker
|
|
73
|
+
failure or missing injected unit closes semantic publication with a typed
|
|
74
|
+
diagnostic. It is never hidden by a timeout increase, retry, quiet window or
|
|
75
|
+
stale-tree fallback.
|
|
76
|
+
|
|
77
|
+
## Optional semantics
|
|
78
|
+
|
|
79
|
+
The automatic tree does not require wrappers or per-widget annotations. An
|
|
80
|
+
application may add intent with the framework-neutral Go SDK:
|
|
57
81
|
|
|
58
82
|
```go
|
|
59
|
-
import (
|
|
60
|
-
"github.com/gorce-ai/termwright/clients/go/annotate"
|
|
61
|
-
"github.com/gorce-ai/termwright/clients/go/protocol"
|
|
62
|
-
)
|
|
63
|
-
|
|
64
|
-
annotate.Tag(label, annotate.Semantics{Key: "unread-label"})
|
|
65
83
|
annotate.Tag(unreadBadge, annotate.Semantics{
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
84
|
+
Key: "unread-badge",
|
|
85
|
+
Role: "status",
|
|
86
|
+
Name: "Unread messages",
|
|
87
|
+
TestID: "unread-badge",
|
|
88
|
+
Actions: []protocol.Action{protocol.ActionFocus},
|
|
69
89
|
})
|
|
70
90
|
```
|
|
71
91
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
`
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
Without `TERMWRIGHT_ENDPOINT` and `TERMWRIGHT_TOKEN` the instrumented copy opens
|
|
94
|
-
no socket, writes no marker and renders exactly what upstream renders. That is
|
|
95
|
-
measured, not asserted: the test suite builds the same application twice, once
|
|
96
|
-
against untouched tview and once against the copy, and requires the two screens
|
|
97
|
-
to be byte-identical.
|
|
98
|
-
|
|
99
|
-
## When it refuses
|
|
100
|
-
|
|
101
|
-
- `-mod=vendor` in `GOFLAGS` is reported by name rather than overridden;
|
|
102
|
-
workspace mode is incompatible with it, and overriding would change what
|
|
103
|
-
compiles.
|
|
104
|
-
- A framework version with no patch set is named as such — "this is not
|
|
105
|
-
tview v0.42.0" — instead of failing somewhere inside a diff.
|
|
92
|
+
Annotations can describe roles, names, ids, relationships and closed actions.
|
|
93
|
+
They cannot override geometry, focus, visibility, value, rendered text or
|
|
94
|
+
framework state. They are held in a side table and do not require replacing
|
|
95
|
+
tview constructors or fluent widget types.
|
|
96
|
+
|
|
97
|
+
## Dormant and unsupported modes
|
|
98
|
+
|
|
99
|
+
Without both `TERMWRIGHT_ENDPOINT` and `TERMWRIGHT_TOKEN`, `Attach` returns
|
|
100
|
+
before creating a client, channel, goroutine, socket or framework hook. The
|
|
101
|
+
instrumented and ordinary builds are required to render byte-identical output
|
|
102
|
+
in this mode.
|
|
103
|
+
|
|
104
|
+
A prebuilt binary cannot receive T1 units and therefore runs as explicitly
|
|
105
|
+
reported raw PTY. A controlled build whose capability unit fails to compile is
|
|
106
|
+
rejected loudly; Termwright does not create an exact-version patch profile or
|
|
107
|
+
silently downgrade its semantic tree.
|
|
108
|
+
|
|
109
|
+
Known declared limitations are clipped geometry for some tview containers and
|
|
110
|
+
enumeration of application-defined custom container children. Such nodes stay
|
|
111
|
+
visible as generic/opaque nodes and the reduced capability is present in run
|
|
112
|
+
metadata.
|
|
106
113
|
|
|
107
114
|
## Development
|
|
108
115
|
|
|
109
116
|
```sh
|
|
110
|
-
pnpm build
|
|
117
|
+
pnpm build
|
|
118
|
+
pnpm typecheck
|
|
119
|
+
pnpm test
|
|
120
|
+
cd ../../clients/go && go test -race -count=1 ./...
|
|
111
121
|
```
|
|
112
122
|
|
|
113
|
-
The
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
123
|
+
The native Linux and Windows certification rows require the relevant
|
|
124
|
+
toolchain and real PTY/ConPTY rather than treating their absence as a green
|
|
125
|
+
adapter result. Implementation invariants are recorded in
|
|
126
|
+
[`NOTES.md`](NOTES.md).
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
//go:build windows
|
|
2
|
+
|
|
3
|
+
package tcell
|
|
4
|
+
|
|
5
|
+
// This add-only companion joins the tcell package only for a Windows-targeted
|
|
6
|
+
// Termwright build. Windows' cScreen has no Tty(), but it owns the console
|
|
7
|
+
// handle that Show uses synchronously. Exposing one narrow marker operation
|
|
8
|
+
// preserves the same-handle commit boundary without modifying tcell's renderer.
|
|
9
|
+
|
|
10
|
+
import (
|
|
11
|
+
"errors"
|
|
12
|
+
"io"
|
|
13
|
+
"syscall"
|
|
14
|
+
"unicode/utf16"
|
|
15
|
+
"unsafe"
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
var (
|
|
19
|
+
termwrightKernel32 = syscall.NewLazyDLL("kernel32.dll")
|
|
20
|
+
termwrightGetConsoleMode = termwrightKernel32.NewProc("GetConsoleMode")
|
|
21
|
+
termwrightSetConsoleMode = termwrightKernel32.NewProc("SetConsoleMode")
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
const termwrightMarkerOutputMode = uint32(0x0001 | 0x0004) // processed output + VT processing
|
|
25
|
+
|
|
26
|
+
// TermwrightWriteMarker is defined on baseScreen because NewConsoleScreen
|
|
27
|
+
// returns *baseScreen, whose embedded screenImpl interface otherwise hides
|
|
28
|
+
// methods implemented only by the concrete *cScreen.
|
|
29
|
+
func (b *baseScreen) TermwrightWriteMarker(marker string) (resultErr error) {
|
|
30
|
+
s, ok := b.screenImpl.(*cScreen)
|
|
31
|
+
if !ok {
|
|
32
|
+
return errors.New("tcell: screen is not the Windows console implementation")
|
|
33
|
+
}
|
|
34
|
+
s.Lock()
|
|
35
|
+
defer s.Unlock()
|
|
36
|
+
if s.fini {
|
|
37
|
+
return errors.New("tcell: screen is finalized")
|
|
38
|
+
}
|
|
39
|
+
encoded := utf16.Encode([]rune(marker))
|
|
40
|
+
if len(encoded) == 0 {
|
|
41
|
+
return nil
|
|
42
|
+
}
|
|
43
|
+
var originalMode uint32
|
|
44
|
+
if ok, _, err := termwrightGetConsoleMode.Call(uintptr(s.out), uintptr(unsafe.Pointer(&originalMode))); ok == 0 {
|
|
45
|
+
return err
|
|
46
|
+
}
|
|
47
|
+
// Inspect the real writer capability instead of tcell's historical `vten`
|
|
48
|
+
// implementation field. tcell <= 2.11 used that field to select a legacy
|
|
49
|
+
// renderer, while >= 2.12 requires VT during screen initialization and no
|
|
50
|
+
// longer declares it. Vendored ordered-passthrough ConPTY serializes both
|
|
51
|
+
// legacy Console API output and VT on this handle, so a legacy frame can be
|
|
52
|
+
// followed by a temporarily VT-enabled marker without weakening ordering.
|
|
53
|
+
if originalMode&termwrightMarkerOutputMode != termwrightMarkerOutputMode {
|
|
54
|
+
if ok, _, err := termwrightSetConsoleMode.Call(uintptr(s.out), uintptr(originalMode|termwrightMarkerOutputMode)); ok == 0 {
|
|
55
|
+
return err
|
|
56
|
+
}
|
|
57
|
+
defer func() {
|
|
58
|
+
restored, _, restoreErr := termwrightSetConsoleMode.Call(uintptr(s.out), uintptr(originalMode))
|
|
59
|
+
if restored == 0 && resultErr == nil {
|
|
60
|
+
resultErr = restoreErr
|
|
61
|
+
}
|
|
62
|
+
}()
|
|
63
|
+
var activeMode uint32
|
|
64
|
+
if ok, _, err := termwrightGetConsoleMode.Call(uintptr(s.out), uintptr(unsafe.Pointer(&activeMode))); ok == 0 {
|
|
65
|
+
return err
|
|
66
|
+
}
|
|
67
|
+
if activeMode&termwrightMarkerOutputMode != termwrightMarkerOutputMode {
|
|
68
|
+
return errors.New("tcell: active Windows console output could not enable VT processing")
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
var written uint32
|
|
72
|
+
writeErr := syscall.WriteConsole(s.out, &encoded[0], uint32(len(encoded)), &written, nil)
|
|
73
|
+
if writeErr != nil {
|
|
74
|
+
return writeErr
|
|
75
|
+
}
|
|
76
|
+
if written != uint32(len(encoded)) {
|
|
77
|
+
return io.ErrShortWrite
|
|
78
|
+
}
|
|
79
|
+
return nil
|
|
80
|
+
}
|