bunmicro 0.9.10 → 0.9.19
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/CHANGELOG.md +16 -0
- package/package.json +1 -1
- package/runtime/jsplugins/example/example.js +3 -1
- package/src/config/clean.js +172 -0
- package/src/config/config.js +3 -1
- package/src/config/defaults.js +1 -1
- package/src/index.js +510 -150
- package/src/platform/clipboard.js +125 -7
- package/src/plugins/js-bridge.js +10 -8
- package/todo.txt +7 -3
|
@@ -6,14 +6,31 @@ const internalRegisters = new Map();
|
|
|
6
6
|
export class ClipboardManager {
|
|
7
7
|
constructor() {
|
|
8
8
|
this.backend = detectClipboardBackend();
|
|
9
|
+
this._writeBackend = null;
|
|
10
|
+
this._altBackend = null;
|
|
11
|
+
this._readFromInternal = false;
|
|
9
12
|
}
|
|
10
13
|
|
|
11
14
|
methodName() {
|
|
15
|
+
return (this._writeBackend ?? this.backend).name;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
readMethodName(register = "clipboard") {
|
|
19
|
+
if (register !== "clipboard" && register !== "primary") return "internal";
|
|
20
|
+
if (register === "clipboard" && this._readFromInternal) return "internal";
|
|
21
|
+
if (register === "primary" && !this.backend.supportsPrimary) return "internal";
|
|
12
22
|
return this.backend.name;
|
|
13
23
|
}
|
|
14
24
|
|
|
25
|
+
altMethodName() {
|
|
26
|
+
return this._altBackend?.name ?? null;
|
|
27
|
+
}
|
|
28
|
+
|
|
15
29
|
fallbackToInternal() {
|
|
16
30
|
this.backend = internalClipboard();
|
|
31
|
+
this._writeBackend = null;
|
|
32
|
+
this._altBackend = null;
|
|
33
|
+
this._readFromInternal = false;
|
|
17
34
|
return this.backend;
|
|
18
35
|
}
|
|
19
36
|
|
|
@@ -21,6 +38,13 @@ export class ClipboardManager {
|
|
|
21
38
|
if (register !== "clipboard" && register !== "primary") {
|
|
22
39
|
return internalRegisters.get(register) ?? "";
|
|
23
40
|
}
|
|
41
|
+
if (register === "primary" && !this.backend.supportsPrimary) {
|
|
42
|
+
return internalRegisters.get(register) ?? "";
|
|
43
|
+
}
|
|
44
|
+
// terminal mode: paste from internal to avoid OSC 52 read issues over SSH
|
|
45
|
+
if (register === "clipboard" && this._readFromInternal) {
|
|
46
|
+
return internalRegisters.get(register) ?? "";
|
|
47
|
+
}
|
|
24
48
|
try {
|
|
25
49
|
const text = this.backend.read?.(register);
|
|
26
50
|
if (text == null) return internalRegisters.get(register) ?? "";
|
|
@@ -33,15 +57,62 @@ export class ClipboardManager {
|
|
|
33
57
|
write(text, register = "clipboard") {
|
|
34
58
|
internalRegisters.set(register, text);
|
|
35
59
|
if (register !== "clipboard" && register !== "primary") return true;
|
|
60
|
+
const wb = this._writeBackend ?? this.backend;
|
|
61
|
+
if (register === "primary" && !wb.supportsPrimary) return true;
|
|
36
62
|
try {
|
|
37
|
-
const ok =
|
|
38
|
-
if (!ok)
|
|
63
|
+
const ok = wb.write?.(text, register) ?? true;
|
|
64
|
+
if (!ok) {
|
|
65
|
+
if (wb === this._writeBackend) this._writeBackend = null;
|
|
66
|
+
else this.fallbackToInternal();
|
|
67
|
+
}
|
|
39
68
|
return true;
|
|
40
69
|
} catch {
|
|
41
|
-
this.
|
|
70
|
+
if (wb === this._writeBackend) this._writeBackend = null;
|
|
71
|
+
else this.fallbackToInternal();
|
|
42
72
|
return true;
|
|
43
73
|
}
|
|
44
74
|
}
|
|
75
|
+
|
|
76
|
+
writeAlt(text, register = "clipboard") {
|
|
77
|
+
if (!this._altBackend) return false;
|
|
78
|
+
internalRegisters.set(register, text);
|
|
79
|
+
try {
|
|
80
|
+
return this._altBackend.write?.(text, register) ?? true;
|
|
81
|
+
} catch {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async initFromSetting(setting, ttyIn, ttyOut, timeoutMs = 150) {
|
|
87
|
+
this.backend = detectClipboardBackend();
|
|
88
|
+
this._writeBackend = null;
|
|
89
|
+
this._altBackend = null;
|
|
90
|
+
this._readFromInternal = false;
|
|
91
|
+
if (setting === "internal") {
|
|
92
|
+
this.fallbackToInternal();
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (setting === "terminal") {
|
|
96
|
+
// skip probe — directly enable OSC 52 write (handles write-only terminals)
|
|
97
|
+
if (ttyOut) {
|
|
98
|
+
this._altBackend = this.backend; // external as clickable alt
|
|
99
|
+
this._writeBackend = osc52Clipboard(ttyOut);
|
|
100
|
+
this._readFromInternal = true; // paste via internal (SSH-safe)
|
|
101
|
+
}
|
|
102
|
+
} else {
|
|
103
|
+
// "external" (default): probe OSC 52 as optional alt
|
|
104
|
+
if (ttyIn && ttyOut && process.stdout?.isTTY) {
|
|
105
|
+
const ok = await probeOSC52(ttyIn, ttyOut, timeoutMs);
|
|
106
|
+
if (ok) this._altBackend = osc52Clipboard(ttyOut);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// kept for backward compatibility (--version probe etc.)
|
|
112
|
+
async probeAndUpgradeOSC52(ttyIn, ttyOut, timeoutMs = 150) {
|
|
113
|
+
const ok = await probeOSC52(ttyIn, ttyOut, timeoutMs);
|
|
114
|
+
if (ok) this._writeBackend = osc52Clipboard(ttyOut);
|
|
115
|
+
}
|
|
45
116
|
}
|
|
46
117
|
|
|
47
118
|
function detectClipboardBackend() {
|
|
@@ -109,14 +180,24 @@ function termuxClipboard(set, get) {
|
|
|
109
180
|
function wlClipboard(wlCopy, wlPaste) {
|
|
110
181
|
return {
|
|
111
182
|
name: "wl-clipboard",
|
|
112
|
-
|
|
113
|
-
|
|
183
|
+
supportsPrimary: true,
|
|
184
|
+
read: (register) => {
|
|
185
|
+
const args = [wlPaste, "--no-newline"];
|
|
186
|
+
if (register === "primary") args.push("--primary");
|
|
187
|
+
return outputOrThrow(runSync(args, { timeout: CLIPBOARD_TIMEOUT_MS }));
|
|
188
|
+
},
|
|
189
|
+
write: (text, register) => {
|
|
190
|
+
const args = [wlCopy];
|
|
191
|
+
if (register === "primary") args.push("--primary");
|
|
192
|
+
return runSync(args, { stdin: text, stdout: "ignore", timeout: CLIPBOARD_TIMEOUT_MS }).ok;
|
|
193
|
+
},
|
|
114
194
|
};
|
|
115
195
|
}
|
|
116
196
|
|
|
117
197
|
function xclipClipboard(xclip) {
|
|
118
198
|
return {
|
|
119
199
|
name: "xclip",
|
|
200
|
+
supportsPrimary: true,
|
|
120
201
|
read: (register) => {
|
|
121
202
|
const selection = register === "primary" ? "primary" : "clipboard";
|
|
122
203
|
return outputOrThrow(runSync([xclip, "-selection", selection, "-o"], { timeout: CLIPBOARD_TIMEOUT_MS }));
|
|
@@ -131,6 +212,7 @@ function xclipClipboard(xclip) {
|
|
|
131
212
|
function xselClipboard(xsel) {
|
|
132
213
|
return {
|
|
133
214
|
name: "xsel",
|
|
215
|
+
supportsPrimary: true,
|
|
134
216
|
read: (register) => {
|
|
135
217
|
const selection = register === "primary" ? "--primary" : "--clipboard";
|
|
136
218
|
return outputOrThrow(runSync([xsel, selection, "--output"], { timeout: CLIPBOARD_TIMEOUT_MS }));
|
|
@@ -146,8 +228,11 @@ function powershellClipboard(shell) {
|
|
|
146
228
|
return {
|
|
147
229
|
name: "powershell",
|
|
148
230
|
// Get-Clipboard -Raw appends \r\n to stdout; strip exactly one trailing line ending.
|
|
149
|
-
read: () => outputOrThrow(runSync([shell, "-NoProfile", "-Command", "Get-Clipboard -Raw"], {
|
|
150
|
-
write: (text) =>
|
|
231
|
+
read: () => outputOrThrow(runSync([shell, "-NoProfile", "-Command", "Get-Clipboard -Raw"], {})).replace(/\r?\n$/, ""),
|
|
232
|
+
write: (text) => {
|
|
233
|
+
text=(text+'').replaceAll("'","''") ;
|
|
234
|
+
return runSync([shell, "-NoProfile", "-Command", `Set-Clipboard '${text}'`], { stdout: "ignore" }).ok ;
|
|
235
|
+
},
|
|
151
236
|
};
|
|
152
237
|
}
|
|
153
238
|
|
|
@@ -155,3 +240,36 @@ function outputOrThrow(result) {
|
|
|
155
240
|
if (!result.ok) throw new Error(result.stderr || result.stdout || "clipboard command failed");
|
|
156
241
|
return result.stdout;
|
|
157
242
|
}
|
|
243
|
+
|
|
244
|
+
export function osc52Clipboard(stdout) {
|
|
245
|
+
const inTmux = !!process.env.TMUX;
|
|
246
|
+
return {
|
|
247
|
+
name: "OSC 52",
|
|
248
|
+
write(text) {
|
|
249
|
+
const b64 = Buffer.from(text, "utf-8").toString("base64");
|
|
250
|
+
stdout.write(inTmux
|
|
251
|
+
? `\x1bPtmux;\x1b\x1b]52;c;${b64}\x07\x1b\\`
|
|
252
|
+
: `\x1b]52;c;${b64}\x07`);
|
|
253
|
+
return true;
|
|
254
|
+
},
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export async function probeOSC52(ttyIn, ttyOut, timeoutMs) {
|
|
259
|
+
if (process.env.TMUX) return true;
|
|
260
|
+
return new Promise((resolve) => {
|
|
261
|
+
let done = false;
|
|
262
|
+
const timer = setTimeout(() => {
|
|
263
|
+
if (!done) { done = true; ttyIn.removeListener("data", onData); resolve(false); }
|
|
264
|
+
}, timeoutMs);
|
|
265
|
+
function onData(chunk) {
|
|
266
|
+
if (done) return;
|
|
267
|
+
const s = Buffer.isBuffer(chunk) ? chunk.toString("latin1") : String(chunk);
|
|
268
|
+
if (s.includes("\x1b]52;")) {
|
|
269
|
+
done = true; clearTimeout(timer); ttyIn.removeListener("data", onData); resolve(true);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
ttyIn.on("data", onData);
|
|
273
|
+
ttyOut.write("\x1b]52;c;?\x07");
|
|
274
|
+
});
|
|
275
|
+
}
|
package/src/plugins/js-bridge.js
CHANGED
|
@@ -51,8 +51,8 @@ function registerBuiltinActions() {
|
|
|
51
51
|
reg("CursorEnd", (app) => { app.pane && (app.pane.selection = null); app.buffer?.moveEndOfBuffer(); app.scrollCursorToBoundary?.(app.pane, "end"); });
|
|
52
52
|
reg("ParagraphPrevious", (app) => { app.pane && (app.pane.selection = null); app.buffer?.paragraphPrevious(); });
|
|
53
53
|
reg("ParagraphNext", (app) => { app.pane && (app.pane.selection = null); app.buffer?.paragraphNext(); });
|
|
54
|
-
reg("PageUp", (app) => { app.pane && (app.pane.selection = null); app.
|
|
55
|
-
reg("PageDown", (app) => { app.pane && (app.pane.selection = null); app.
|
|
54
|
+
reg("PageUp", (app) => { app.pane && (app.pane.selection = null); app.pageScroll?.(app.pane, -1); });
|
|
55
|
+
reg("PageDown", (app) => { app.pane && (app.pane.selection = null); app.pageScroll?.(app.pane, 1); });
|
|
56
56
|
|
|
57
57
|
// Selection — extend
|
|
58
58
|
reg("SelectUp", (app) => _actExtendSel(app, (buf) => buf._moveUpVisual?.() ?? buf.moveUp?.()));
|
|
@@ -67,8 +67,8 @@ function registerBuiltinActions() {
|
|
|
67
67
|
reg("SelectToEndOfLine", (app) => _actExtendSel(app, (buf) => buf.moveEnd?.()));
|
|
68
68
|
reg("SelectToStart", (app) => _actExtendSel(app, (buf) => buf.moveStartOfBuffer?.()));
|
|
69
69
|
reg("SelectToEnd", (app) => _actExtendSel(app, (buf) => buf.moveEndOfBuffer?.()));
|
|
70
|
-
reg("SelectPageUp", (app) =>
|
|
71
|
-
reg("SelectPageDown", (app) =>
|
|
70
|
+
reg("SelectPageUp", (app) => app.cursorPage?.(app.pane, -1, { select: true }));
|
|
71
|
+
reg("SelectPageDown", (app) => app.cursorPage?.(app.pane, 1, { select: true }));
|
|
72
72
|
reg("SelectToParagraphPrevious", (app) => _actExtendSel(app, (buf) => buf.paragraphPrevious?.()));
|
|
73
73
|
reg("SelectToParagraphNext", (app) => _actExtendSel(app, (buf) => buf.paragraphNext?.()));
|
|
74
74
|
|
|
@@ -298,10 +298,10 @@ function registerBuiltinActions() {
|
|
|
298
298
|
reg("End", (app) => { app.pane && (app.pane.selection = null); app.buffer?._lastVisX != null && (app.buffer._lastVisX = null); app.buffer?.moveEndOfBuffer(); app.scrollCursorToBoundary?.(app.pane, "end"); });
|
|
299
299
|
|
|
300
300
|
// Page aliases
|
|
301
|
-
reg("CursorPageUp", (app) =>
|
|
302
|
-
reg("CursorPageDown", (app) =>
|
|
303
|
-
reg("HalfPageUp", (app) =>
|
|
304
|
-
reg("HalfPageDown", (app) =>
|
|
301
|
+
reg("CursorPageUp", (app) => app.cursorPage?.(app.pane, -1));
|
|
302
|
+
reg("CursorPageDown", (app) => app.cursorPage?.(app.pane, 1));
|
|
303
|
+
reg("HalfPageUp", (app) => app.cursorPage?.(app.pane, -1, { amount: Math.max(1, Math.floor((app.pane?.h ?? 24) / 2)) }));
|
|
304
|
+
reg("HalfPageDown", (app) => app.cursorPage?.(app.pane, 1, { amount: Math.max(1, Math.floor((app.pane?.h ?? 24) / 2)) }));
|
|
305
305
|
|
|
306
306
|
// Cursor-to-view-boundary
|
|
307
307
|
reg("CursorToViewTop", (app) => {
|
|
@@ -857,6 +857,8 @@ function _makePaneAPI(buffer, app) {
|
|
|
857
857
|
return {
|
|
858
858
|
get Buf() { return _makeBufAPI(buffer); },
|
|
859
859
|
get Cursor() { return _makeCursorAPI(buffer); },
|
|
860
|
+
CursorLocation: () => app?.formatCursorLocation?.(buffer) ?? "+1.0:1",
|
|
861
|
+
AbsoluteCursorLocation: () => app?.formatAbsoluteCursorLocation?.(buffer) ?? "+1:1",
|
|
860
862
|
|
|
861
863
|
Save: async () => app?.save?.(),
|
|
862
864
|
Quit: async () => app?.quit?.(),
|
package/todo.txt
CHANGED
|
@@ -283,11 +283,15 @@ Clipboard / platform integration
|
|
|
283
283
|
Remaining: real platform verification for wl-copy/wl-paste, xclip, xsel.
|
|
284
284
|
[ ] Verify Android behavior only when process.platform reports android; Linux must not use Termux clipboard commands.
|
|
285
285
|
[ ] Verify macOS pbcopy/pbpaste and Windows PowerShell Set-Clipboard/Get-Clipboard.
|
|
286
|
-
[
|
|
287
|
-
|
|
286
|
+
[~] Implement terminal OSC 52 clipboard method.
|
|
287
|
+
Done: osc52Clipboard backend + probeOSC52 exported from clipboard.js; App.start() probes on startup (150ms, before main data handler), upgrades _writeBackend if supported; tmux $TMUX env skips probe and uses DCS passthrough format; --version probes last and prints Clipboard: result after other fields.
|
|
288
|
+
Remaining: OSC 52 read (async response handling for Ctrl+V from system clipboard via escape sequence).
|
|
289
|
+
[~] Implement primary selection behavior for Linux where applicable.
|
|
290
|
+
Done: wlClipboard read/write now pass --primary flag when register==="primary"; _syncPrimarySelection() called at end of every _dispatchInput event loop (keyboard+shift+arrows+alt-s+ctrl-a+mouse drag/double-click/gutter); middle mouse button click moves cursor to click position and pastes primary register.
|
|
291
|
+
Remaining: real platform testing; primary selection for macOS (no direct equivalent); wl-clipboard backend primary verification.
|
|
288
292
|
[~] Implement internal multi-register clipboard and multi-cursor clipboard parity.
|
|
289
293
|
Done: internal register map exists and is used as fallback; Ctrl-C/Ctrl-X/Ctrl-V/Ctrl-Y handle current selection and line copy/cut/paste.
|
|
290
|
-
Remaining: Go micro multi-register semantics,
|
|
294
|
+
Remaining: Go micro multi-register semantics, multi-cursor clipboard behavior.
|
|
291
295
|
|
|
292
296
|
Shell / jobs / terminal pane
|
|
293
297
|
----------------------------
|