oc-bg-color-sync 0.0.1 → 1.0.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 +42 -9
- package/index.ts +67 -26
- package/package.json +8 -11
package/README.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
Synchronize the terminal's native background color with OpenTUI's renderer background color. Fixes mismatched border/gutter colors in terminals that draw padding outside the application viewport.
|
|
4
4
|
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
Requires OpenCode 2.x. Add the plugin to your OpenCode terminal client config (`~/.config/opencode/cli.json`):
|
|
8
|
+
|
|
9
|
+
```json
|
|
10
|
+
{
|
|
11
|
+
"plugins": ["oc-bg-color-sync"]
|
|
12
|
+
}
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### OR
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
opencode plugin add oc-bg-color-sync
|
|
19
|
+
```
|
|
20
|
+
|
|
5
21
|
## Why
|
|
6
22
|
|
|
7
23
|
OpenTUI renders its own background, but the terminal emulator may show its own default color in gutters, borders, or padding areas around the app. This plugin emits **OSC 11** whenever the renderer background changes so the terminal stays in sync.
|
|
@@ -40,9 +56,18 @@ This plugin restores the OSC 11 sync as a userland workaround until upstream fin
|
|
|
40
56
|
|
|
41
57
|
## How it works
|
|
42
58
|
|
|
43
|
-
-
|
|
44
|
-
-
|
|
45
|
-
-
|
|
59
|
+
- Registers via `Plugin.define` from `@opencode/plugin/tui` (the OpenCode 2 TUI plugin API)
|
|
60
|
+
- Wraps `context.renderer.setBackgroundColor()`
|
|
61
|
+
- After the native call, parses the color with `parseColor` from `@opentui/core` and writes `OSC 11;rgb:rr/gg/bb\x07` to stdout
|
|
62
|
+
- On plugin dispose, writes `OSC 111\x07` to restore the terminal's default background
|
|
63
|
+
|
|
64
|
+
### Timing fix
|
|
65
|
+
|
|
66
|
+
The TUI's theme provider calls `setBackgroundColor()` during its initial render **before** plugins load. If we only patched the method, the terminal would stay out of sync until the theme changed again. The plugin therefore reads `context.theme.background.base` after patching and calls the patched method once to force an immediate sync.
|
|
67
|
+
|
|
68
|
+
### Hot-reload safety
|
|
69
|
+
|
|
70
|
+
OpenCode 2 reloads plugins on config changes without restarting the TUI. The cleanup function restores the original `setBackgroundColor` (verified by identity, so it never stomps a newer plugin generation or another plugin's patch) before emitting `OSC 111`, so each reload unwinds cleanly instead of stacking wrappers.
|
|
46
71
|
|
|
47
72
|
### OSC sequence reference
|
|
48
73
|
|
|
@@ -53,18 +78,26 @@ This plugin restores the OSC 11 sync as a userland workaround until upstream fin
|
|
|
53
78
|
|
|
54
79
|
See [VT100.net OSC 11 docs](https://vtdn.dev/docs/osc/osc11) and [OSC 110-112](https://vtdn.dev/docs/osc/osc110-112) for more.
|
|
55
80
|
|
|
56
|
-
##
|
|
81
|
+
## Debugging
|
|
57
82
|
|
|
58
|
-
|
|
83
|
+
To verify the plugin is emitting OSC sequences, run OpenCode in a terminal that logs escape sequences, or sniff stdout with `script`:
|
|
59
84
|
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
85
|
+
```bash
|
|
86
|
+
# Start a typescript session; all escape sequences are recorded
|
|
87
|
+
script -q /tmp/opencode-osc.log
|
|
88
|
+
opencode
|
|
89
|
+
# quit opencode, then exit the script shell
|
|
90
|
+
exit
|
|
91
|
+
# Search for OSC 11/111 in the log
|
|
92
|
+
grep -o $'\x1b]11;[^\x07]*\x07' /tmp/opencode-osc.log
|
|
93
|
+
grep -o $'\x1b]111\x07' /tmp/opencode-osc.log
|
|
64
94
|
```
|
|
65
95
|
|
|
96
|
+
You should see an `OSC 11` on startup and an `OSC 111` on exit.
|
|
97
|
+
|
|
66
98
|
## Requirements
|
|
67
99
|
|
|
100
|
+
- OpenCode 2.x
|
|
68
101
|
- A terminal emulator that supports OSC 11/111 (most modern terminals do; Ghostty has quirks with theme detection after OSC 11/111 usage)
|
|
69
102
|
|
|
70
103
|
## License
|
package/index.ts
CHANGED
|
@@ -1,30 +1,71 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
original(color);
|
|
10
|
-
const rgba = typeof color === "string" ? hexToRgb(color) : color;
|
|
11
|
-
|
|
12
|
-
if (rgba.a > 0) {
|
|
13
|
-
const hex = (n: number) => n.toString(16).padStart(2, "0");
|
|
14
|
-
process.stdout.write(
|
|
15
|
-
`\x1b]11;rgb:${hex(rgba.r)}/${hex(rgba.g)}/${hex(rgba.b)}\x07`,
|
|
16
|
-
);
|
|
17
|
-
}
|
|
18
|
-
};
|
|
1
|
+
import { Plugin } from "@opencode/plugin/tui"
|
|
2
|
+
import { parseColor, type ColorInput } from "@opentui/core"
|
|
3
|
+
import { writeSync } from "fs"
|
|
4
|
+
|
|
5
|
+
const hexByte = (n: number) =>
|
|
6
|
+
Math.round(n * 255)
|
|
7
|
+
.toString(16)
|
|
8
|
+
.padStart(2, "0")
|
|
19
9
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
10
|
+
const writeOsc = (sequence: string) => {
|
|
11
|
+
// writeSync(1, …) bypasses Node.js stream buffering so the sequence is
|
|
12
|
+
// flushed to the kernel immediately, even while the process is shutting
|
|
13
|
+
// down. process.stdout.write can leave bytes in the stream buffer that
|
|
14
|
+
// never get drained once the event loop exits.
|
|
15
|
+
try {
|
|
16
|
+
writeSync(1, sequence)
|
|
17
|
+
} catch {
|
|
18
|
+
process.stdout.write(sequence)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
24
21
|
|
|
25
|
-
|
|
22
|
+
export default Plugin.define({
|
|
26
23
|
id: "bg-sync",
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
setup(context) {
|
|
25
|
+
const renderer = context.renderer
|
|
26
|
+
|
|
27
|
+
// Capture whatever is installed right now: normally the prototype
|
|
28
|
+
// method, but possibly another plugin's wrapper or an earlier
|
|
29
|
+
// hot-reload generation of this plugin.
|
|
30
|
+
const descriptor = Object.getOwnPropertyDescriptor(renderer, "setBackgroundColor")
|
|
31
|
+
const previous = renderer.setBackgroundColor.bind(renderer)
|
|
32
|
+
|
|
33
|
+
const patched = (color: ColorInput) => {
|
|
34
|
+
previous(color)
|
|
35
|
+
const rgba = parseColor(color)
|
|
36
|
+
// "transparent" parses to a = 0; leave the terminal's default alone
|
|
37
|
+
// instead of forcing an opaque background.
|
|
38
|
+
if (rgba.a > 0) {
|
|
39
|
+
writeOsc(`\x1b]11;rgb:${hexByte(rgba.r)}/${hexByte(rgba.g)}/${hexByte(rgba.b)}\x07`)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
renderer.setBackgroundColor = patched
|
|
29
43
|
|
|
30
|
-
|
|
44
|
+
// The theme provider calls setBackgroundColor during its initial render,
|
|
45
|
+
// which happens before plugins load. After patching, re-apply the
|
|
46
|
+
// current theme background once so the terminal matches on startup.
|
|
47
|
+
renderer.setBackgroundColor(context.theme.background.base)
|
|
48
|
+
|
|
49
|
+
return () => {
|
|
50
|
+
// Only unwind our own patch. The host tears the old generation down
|
|
51
|
+
// before setting up a new one, but if a newer generation (or another
|
|
52
|
+
// plugin) has already replaced the method, restoring here would
|
|
53
|
+
// silently break it — and resetting the terminal background would
|
|
54
|
+
// clobber the state that replacement just installed.
|
|
55
|
+
if (renderer.setBackgroundColor !== patched) return
|
|
56
|
+
|
|
57
|
+
if (descriptor) Object.defineProperty(renderer, "setBackgroundColor", descriptor)
|
|
58
|
+
else delete (renderer as { setBackgroundColor?: (color: ColorInput) => void }).setBackgroundColor
|
|
59
|
+
|
|
60
|
+
// OSC 111 restores the terminal's default background. Prefer writeSync
|
|
61
|
+
// so the bytes are flushed before the process exits; the renderer's
|
|
62
|
+
// resetTerminalBgColor() writes through a path that may not drain in
|
|
63
|
+
// time.
|
|
64
|
+
try {
|
|
65
|
+
writeSync(1, "\x1b]111\x07")
|
|
66
|
+
} catch {
|
|
67
|
+
renderer.resetTerminalBgColor()
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
})
|
package/package.json
CHANGED
|
@@ -1,24 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oc-bg-color-sync",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"main": "index.ts",
|
|
6
5
|
"exports": {
|
|
7
|
-
"./tui":
|
|
8
|
-
"import": "./index.ts"
|
|
9
|
-
}
|
|
6
|
+
"./tui": "./index.ts"
|
|
10
7
|
},
|
|
11
8
|
"files": [
|
|
12
9
|
"index.ts"
|
|
13
10
|
],
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@opencode/plugin": "latest"
|
|
13
|
+
},
|
|
14
14
|
"peerDependencies": {
|
|
15
|
-
"@
|
|
16
|
-
"@opentui/core": "*",
|
|
17
|
-
"@opentui/solid": "*",
|
|
18
|
-
"solid-js": "*"
|
|
15
|
+
"@opentui/core": ">=0.6.6"
|
|
19
16
|
},
|
|
20
17
|
"devDependencies": {
|
|
21
|
-
"
|
|
22
|
-
"
|
|
18
|
+
"@types/bun": "latest",
|
|
19
|
+
"typescript": "^5"
|
|
23
20
|
}
|
|
24
21
|
}
|