bunmicro 0.9.30 → 1.0.2
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 +26 -0
- package/README.md +13 -0
- package/hlw.md +1 -0
- package/package.json +1 -1
- package/runtime/help/cdp.md +119 -0
- package/runtime/jsplugins/cdp/cdp-server.js +1161 -0
- package/runtime/jsplugins/cdp/cdp.js +192 -0
- package/src/highlight/parser.js +20 -11
- package/src/index.js +267 -36
- package/src/platform/clipboard.js +3 -0
- package/src/platform/commands.js +1 -4
- package/src/plugins/js-bridge.js +5 -5
- package/tests/wv-client.js +96 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
// No imports needed: `micro` is available as a global.
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
|
|
5
|
+
# JS Plugin Documentation
|
|
6
|
+
|
|
7
|
+
Available hooks (register with micro.on(hookName, fn)):
|
|
8
|
+
|
|
9
|
+
Lifecycle (no args):
|
|
10
|
+
"preinit" — before plugins load
|
|
11
|
+
"init" — main plugin setup, register commands/actions here
|
|
12
|
+
"postinit" — after all plugins loaded
|
|
13
|
+
|
|
14
|
+
Buffer events:
|
|
15
|
+
"onBufferOpen" (buffer) — buffer opened (raw BufferModel, not pane adapter)
|
|
16
|
+
"onBufferClose" (buffer) — buffer closed (raw BufferModel)
|
|
17
|
+
"onSetActive" (bp) — pane became active (tab switch, close, etc.)
|
|
18
|
+
"onSave" (bp) — buffer saved
|
|
19
|
+
|
|
20
|
+
Input events:
|
|
21
|
+
"onRune" (bp, ch) — printable character inserted (ch is the string)
|
|
22
|
+
|
|
23
|
+
Cancellable hooks (return false to cancel the action):
|
|
24
|
+
"preBackspace" (bp) — before backspace; return false to block
|
|
25
|
+
"preInsertNewline" (bp) — before Enter/newline; return false to block
|
|
26
|
+
|
|
27
|
+
bp is a pane adapter with:
|
|
28
|
+
bp.Buf.Line(n) bp.Buf.LinesNum() bp.Buf.FileType()
|
|
29
|
+
bp.Buf.Insert(loc, text) bp.Buf.Replace(s, e, text)
|
|
30
|
+
bp.Cursor.X bp.Cursor.Y bp.Cursor.Loc bp.Cursor.HasSelection()
|
|
31
|
+
bp.Save() bp.Backspace() bp.CursorLeft/Right() bp.InsertNewline()
|
|
32
|
+
|
|
33
|
+
Flat buffer helpers (all 1-based line numbers, omit → cursor line):
|
|
34
|
+
micro.getLine(n?) micro.putLine(text, n?) micro.delLine(n?)
|
|
35
|
+
micro.getLines(from?, to?) micro.getLinesCount()
|
|
36
|
+
micro.getAllText() — entire buffer as one string (lines joined by "\n")
|
|
37
|
+
micro.putAllText(text) — replace entire buffer content; pushes undo
|
|
38
|
+
micro.getSelection() micro.putSelection(text)
|
|
39
|
+
|
|
40
|
+
Other micro APIs:
|
|
41
|
+
micro.CurPane() — returns pane adapter for active pane
|
|
42
|
+
micro.MakeCommand(name, fn) — register Ctrl+E command; fn(bp, args[])
|
|
43
|
+
args.raw = full original input string (bypass shellSplit)
|
|
44
|
+
e.g. for command "js 1+1": args.raw = "js 1+1", args.raw.slice(3) = "1+1"
|
|
45
|
+
micro.RegisterAction(name, fn) — register bindable action
|
|
46
|
+
micro.TermMessage(msg) — show msg in editor status row
|
|
47
|
+
micro.alert(msg) — suspend editor, print msg, wait for Enter
|
|
48
|
+
micro.Log(...args) — console.log passthrough
|
|
49
|
+
micro.GetOption(name) micro.SetOption(name, value)
|
|
50
|
+
micro.cmd.save() — call any editor command via proxy
|
|
51
|
+
micro.action.CursorUp() — run any registered action via proxy
|
|
52
|
+
micro.shell.CMD(...args) — run CMD interactively (same as Ctrl-B); async
|
|
53
|
+
e.g. await micro.shell.ls('-l')
|
|
54
|
+
await micro.shell.git('diff', '--stat')
|
|
55
|
+
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
micro.on("init", () => {
|
|
59
|
+
// Register a custom Ctrl+E command
|
|
60
|
+
micro.MakeCommand("cdp", async (bp, args) =>
|
|
61
|
+
{
|
|
62
|
+
const addrFlag = args.find(a => a.startsWith("--address="))?.slice("--address=".length);
|
|
63
|
+
const isPublic = args.includes("--public");
|
|
64
|
+
const port = parseInt(args.find(a => /^\d+$/.test(a))) || parseInt(Bun.env.CDP_PORT) || 9222;
|
|
65
|
+
const hostname = addrFlag ?? (isPublic ? "0.0.0.0" : "127.0.0.1");
|
|
66
|
+
const path = bp?.Buf?.Path || "(no path)";
|
|
67
|
+
|
|
68
|
+
if(!micro.cdpContext)
|
|
69
|
+
{
|
|
70
|
+
micro.cdpContext={
|
|
71
|
+
title(){
|
|
72
|
+
return path;
|
|
73
|
+
},
|
|
74
|
+
async evaluate(txt){
|
|
75
|
+
return await eval(txt);
|
|
76
|
+
},
|
|
77
|
+
async navigate(url){
|
|
78
|
+
await micro.cmd.open('-f', url);
|
|
79
|
+
},
|
|
80
|
+
async click(x,y,opt){
|
|
81
|
+
x=x||1 ; y=y||1 ;
|
|
82
|
+
await micro.cmd.goto(y+':'+x);
|
|
83
|
+
},
|
|
84
|
+
async scroll(dx,dy){
|
|
85
|
+
const pane = micro.CurPane();
|
|
86
|
+
if (!pane) return;
|
|
87
|
+
|
|
88
|
+
dx = toInteger(dx);
|
|
89
|
+
dy = toInteger(dy);
|
|
90
|
+
|
|
91
|
+
const line = Math.max(1, pane.Cursor.Y + dy + 1);
|
|
92
|
+
const column = Math.max(1, pane.Cursor.X + dx + 1);
|
|
93
|
+
await micro.cmd.goto(`${line}:${column}`);
|
|
94
|
+
},
|
|
95
|
+
async scrollTo(selector){
|
|
96
|
+
const pattern = selectorToSearchPattern(selector);
|
|
97
|
+
await micro.cmd.find(pattern);
|
|
98
|
+
},
|
|
99
|
+
goBack(){
|
|
100
|
+
micro.action.PrevTab();
|
|
101
|
+
},
|
|
102
|
+
goForward(){
|
|
103
|
+
micro.action.NextTab();
|
|
104
|
+
},
|
|
105
|
+
async type(text){
|
|
106
|
+
const bp = micro.CurPane();
|
|
107
|
+
if (!bp) return;
|
|
108
|
+
bp.Insert(text);
|
|
109
|
+
},
|
|
110
|
+
async press(key, options){
|
|
111
|
+
const bp = micro.CurPane();
|
|
112
|
+
if (!bp) return;
|
|
113
|
+
|
|
114
|
+
// modifiers bitmask: Alt=1, Ctrl=2, Meta=4, Shift=8
|
|
115
|
+
const mod = options?.modifiers ?? 0;
|
|
116
|
+
const ctrl = !!(mod & 2);
|
|
117
|
+
const shift = !!(mod & 8);
|
|
118
|
+
|
|
119
|
+
if (ctrl) {
|
|
120
|
+
const ctrlMap = {
|
|
121
|
+
a: () => micro.action.SelectAll(),
|
|
122
|
+
c: () => micro.action.Copy(),
|
|
123
|
+
x: () => micro.action.Cut(),
|
|
124
|
+
v: () => micro.action.Paste(),
|
|
125
|
+
z: () => micro.action.Undo(),
|
|
126
|
+
y: () => micro.action.Redo(),
|
|
127
|
+
s: () => micro.action.Save(),
|
|
128
|
+
};
|
|
129
|
+
const h = ctrlMap[key.toLowerCase()];
|
|
130
|
+
if (h) await h();
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const arrowAction = shift
|
|
135
|
+
? { ArrowUp: 'SelectUp', ArrowDown: 'SelectDown', ArrowLeft: 'SelectLeft', ArrowRight: 'SelectRight' }
|
|
136
|
+
: { ArrowUp: 'CursorUp', ArrowDown: 'CursorDown', ArrowLeft: 'CursorLeft', ArrowRight: 'CursorRight' };
|
|
137
|
+
|
|
138
|
+
const keyMap = {
|
|
139
|
+
...arrowAction,
|
|
140
|
+
Enter: () => micro.action.InsertNewline(),
|
|
141
|
+
Backspace: () => micro.action.Backspace(),
|
|
142
|
+
Delete: () => micro.action.Delete(),
|
|
143
|
+
Tab: () => micro.action.InsertTab(),
|
|
144
|
+
Escape: () => micro.action.Escape(),
|
|
145
|
+
Home: () => shift ? micro.action.SelectToStartOfLine() : micro.action.StartOfLine(),
|
|
146
|
+
End: () => shift ? micro.action.SelectToEndOfLine() : micro.action.EndOfLine(),
|
|
147
|
+
PageUp: () => shift ? micro.action.SelectPageUp() : micro.action.CursorPageUp(),
|
|
148
|
+
PageDown: () => shift ? micro.action.SelectPageDown() : micro.action.CursorPageDown(),
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const entry = keyMap[key];
|
|
152
|
+
if (typeof entry === 'string') {
|
|
153
|
+
await micro.action[entry]();
|
|
154
|
+
} else if (typeof entry === 'function') {
|
|
155
|
+
await entry();
|
|
156
|
+
} else if (key.length === 1) {
|
|
157
|
+
bp.Insert(key);
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
let {CdpServer}=await import('./cdp-server.js');
|
|
163
|
+
|
|
164
|
+
micro.cdpPort = port;
|
|
165
|
+
CdpServer
|
|
166
|
+
.create(micro.cdpContext)
|
|
167
|
+
.listen(port, hostname);
|
|
168
|
+
|
|
169
|
+
const addr = isPublic ? `0.0.0.0:${port}` : `127.0.0.1:${port}`;
|
|
170
|
+
micro.TermMessage(`CDP@${addr} server running 伺服器啟動了`)
|
|
171
|
+
|
|
172
|
+
//await micro.alert(CdpServer)
|
|
173
|
+
} // server not running
|
|
174
|
+
else
|
|
175
|
+
{
|
|
176
|
+
micro.TermMessage(`CDP@${micro.cdpPort} already running a server 已有伺服器啟動`)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
function selectorToSearchPattern(selector) {
|
|
185
|
+
const value = String(selector ?? "");
|
|
186
|
+
return value.startsWith("#") ? value.slice(1) : value;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function toInteger(value) {
|
|
190
|
+
const number = Number(value);
|
|
191
|
+
return Number.isFinite(number) ? Math.trunc(number) : 0;
|
|
192
|
+
}
|
package/src/highlight/parser.js
CHANGED
|
@@ -40,15 +40,19 @@ export class SyntaxDefinition {
|
|
|
40
40
|
|
|
41
41
|
export async function loadSyntaxDefinitions(runtime) {
|
|
42
42
|
const headers = new Map();
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
const headerPromises = runtime.list(4).map(async (file) => {
|
|
44
|
+
try {
|
|
45
|
+
const text = await file.text();
|
|
46
|
+
headers.set(file.name, parseHeaderFile(text));
|
|
47
|
+
} catch {}
|
|
48
|
+
});
|
|
49
|
+
await Promise.allSettled(headerPromises);
|
|
46
50
|
|
|
47
|
-
const
|
|
48
|
-
for (const file of runtime.list(1)) {
|
|
51
|
+
const defPromises = runtime.list(1).map(async (file) => {
|
|
49
52
|
let text = "";
|
|
50
53
|
let activeFile = file;
|
|
51
54
|
let source = null;
|
|
55
|
+
let usedFallback = false;
|
|
52
56
|
try {
|
|
53
57
|
text = await file.text();
|
|
54
58
|
source = Bun.YAML.parse(text);
|
|
@@ -59,21 +63,26 @@ export async function loadSyntaxDefinitions(runtime) {
|
|
|
59
63
|
text = await fallback.text();
|
|
60
64
|
source = Bun.YAML.parse(text);
|
|
61
65
|
activeFile = fallback;
|
|
66
|
+
usedFallback = true;
|
|
62
67
|
console.error("Failed to load user syntax yaml, using built-in fallback:", file.name);
|
|
63
68
|
} catch {}
|
|
64
69
|
}
|
|
65
70
|
}
|
|
66
|
-
|
|
71
|
+
const header = headers.get(activeFile.name) ?? (source ? parseHeaderYaml(source) : parseHeaderTextFallback(text, activeFile.name));
|
|
67
72
|
if (!source) {
|
|
68
73
|
console.error("Failed to load syntax yaml:", file.name);
|
|
69
74
|
console.error(" Will not highlight this kind of file");
|
|
70
75
|
console.error(" @ loadSyntaxDefinitions ");
|
|
76
|
+
} else if (usedFallback) {
|
|
77
|
+
// keep the fallback path visible in logs, but do not fail the load
|
|
71
78
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
return definitions
|
|
79
|
+
return new SyntaxDefinition(header, source ?? { rules: [] }, text);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const definitions = await Promise.allSettled(defPromises);
|
|
83
|
+
return definitions
|
|
84
|
+
.filter((result) => result.status === "fulfilled")
|
|
85
|
+
.map((result) => result.value);
|
|
77
86
|
}
|
|
78
87
|
|
|
79
88
|
export function detectSyntax(definitions, { path = "", firstLine = "", lines = [] } = {}) {
|
package/src/index.js
CHANGED
|
@@ -1,5 +1,94 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
|
+
const jsStart = globalThis.Bun ? Bun.nanoseconds() : Date.now() * 1e6;
|
|
4
|
+
const checkpoints = [
|
|
5
|
+
{ name: "Bun Engine Boot", time: 0 },
|
|
6
|
+
{ name: "JS Load & Module Imports", time: jsStart }
|
|
7
|
+
];
|
|
8
|
+
function addCheckpoint(name) {
|
|
9
|
+
checkpoints.push({ name, time: globalThis.Bun ? Bun.nanoseconds() : Date.now() * 1e6 });
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let parallelTimings = null;
|
|
13
|
+
|
|
14
|
+
function printProfileReport() {
|
|
15
|
+
console.log("\x1b[1m\x1b[36m=== Bunmicro Startup Performance Profile ===\x1b[0m\n");
|
|
16
|
+
|
|
17
|
+
let totalMs = 0;
|
|
18
|
+
const rows = [];
|
|
19
|
+
|
|
20
|
+
for (let i = 0; i < checkpoints.length - 1; i++) {
|
|
21
|
+
const current = checkpoints[i];
|
|
22
|
+
const next = checkpoints[i + 1];
|
|
23
|
+
const durationNs = next.time - current.time;
|
|
24
|
+
const durationMs = durationNs / 1e6;
|
|
25
|
+
totalMs += durationMs;
|
|
26
|
+
|
|
27
|
+
rows.push({
|
|
28
|
+
phase: current.name,
|
|
29
|
+
durationMs: durationMs,
|
|
30
|
+
cumulativeMs: totalMs
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const colWidths = { phase: 32, duration: 15, cumulative: 15 };
|
|
35
|
+
|
|
36
|
+
const header =
|
|
37
|
+
"Phase".padEnd(colWidths.phase) + " | " +
|
|
38
|
+
"Duration (ms)".padStart(colWidths.duration) + " | " +
|
|
39
|
+
"Cumulative (ms)".padStart(colWidths.cumulative);
|
|
40
|
+
|
|
41
|
+
const separator =
|
|
42
|
+
"-".repeat(colWidths.phase) + "-+-" +
|
|
43
|
+
"-".repeat(colWidths.duration) + "-+-" +
|
|
44
|
+
"-".repeat(colWidths.cumulative);
|
|
45
|
+
|
|
46
|
+
console.log(header);
|
|
47
|
+
console.log(separator);
|
|
48
|
+
|
|
49
|
+
for (const row of rows) {
|
|
50
|
+
const phaseStr = row.phase.padEnd(colWidths.phase);
|
|
51
|
+
const durStr = row.durationMs.toFixed(3).padStart(colWidths.duration);
|
|
52
|
+
const cumStr = row.cumulativeMs.toFixed(3).padStart(colWidths.cumulative);
|
|
53
|
+
|
|
54
|
+
let color = "";
|
|
55
|
+
if (row.durationMs > 50) {
|
|
56
|
+
color = "\x1b[31m"; // Red
|
|
57
|
+
} else if (row.durationMs > 10) {
|
|
58
|
+
color = "\x1b[33m"; // Yellow
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const reset = color ? "\x1b[0m" : "";
|
|
62
|
+
console.log(`${color}${phaseStr}${reset} | ${color}${durStr}${reset} | ${cumStr}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
console.log(separator);
|
|
66
|
+
|
|
67
|
+
if (parallelTimings) {
|
|
68
|
+
console.log("\x1b[1mParallel Tasks Breakdown:\x1b[0m");
|
|
69
|
+
console.log(` ├── Lua Plugins & Hooks : ${parallelTimings.lua.toFixed(3).padStart(8)} ms`);
|
|
70
|
+
console.log(` ├── JS Plugins Load : ${parallelTimings.js.toFixed(3).padStart(8)} ms`);
|
|
71
|
+
console.log(` └── Buffer & History : ${parallelTimings.buffers.toFixed(3).padStart(8)} ms`);
|
|
72
|
+
console.log(separator);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
console.log(`\x1b[1mTotal Startup Time: ${totalMs.toFixed(3)} ms\x1b[0m\n`);
|
|
76
|
+
|
|
77
|
+
const slowest = [...rows].sort((a, b) => b.durationMs - a.durationMs)[0];
|
|
78
|
+
if (slowest) {
|
|
79
|
+
console.log(`\x1b[1mSlowest Phase:\x1b[0m ${slowest.phase} (${slowest.durationMs.toFixed(3)} ms)`);
|
|
80
|
+
if (slowest.phase.includes("Clipboard")) {
|
|
81
|
+
console.log("\x1b[32mTip: Clipboard probing can be slow. Setting 'clipboard' option to 'terminal' or a specific tool can bypass auto-detection.\x1b[0m");
|
|
82
|
+
} else if (slowest.phase.includes("Plugin")) {
|
|
83
|
+
console.log("\x1b[32mTip: Disable unnecessary plugins to speed up startup.\x1b[0m");
|
|
84
|
+
} else if (slowest.phase.includes("Syntax")) {
|
|
85
|
+
console.log("\x1b[32mTip: Syntax loading parses many YAML/JSON files. You can pre-compile or bundle syntax definitions to speed this up.\x1b[0m");
|
|
86
|
+
} else if (slowest.phase.includes("JS Load")) {
|
|
87
|
+
console.log("\x1b[32mTip: JS load time includes loading packages like wasmoon, which might take time due to file system lookups.\x1b[0m");
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
3
92
|
import child_process from "node:child_process"
|
|
4
93
|
import { accessSync, constants, existsSync, readdirSync, statSync, unlinkSync } from "node:fs";
|
|
5
94
|
import { mkdir } from "node:fs/promises";
|
|
@@ -512,10 +601,13 @@ function parseArgs(argv) {
|
|
|
512
601
|
clean: false,
|
|
513
602
|
cat: false,
|
|
514
603
|
docs: false,
|
|
604
|
+
changelog: false,
|
|
515
605
|
configDir: "",
|
|
516
606
|
debug: false,
|
|
517
607
|
profile: false,
|
|
518
608
|
plugin: "",
|
|
609
|
+
cdpPort: 0,
|
|
610
|
+
cdpAddress: "",
|
|
519
611
|
settings: new Map(),
|
|
520
612
|
};
|
|
521
613
|
const files = [];
|
|
@@ -527,12 +619,28 @@ function parseArgs(argv) {
|
|
|
527
619
|
else if (arg === "-help" || arg === "--help" || arg === "-h") flags.help = true;
|
|
528
620
|
else if (arg === "-clean") flags.clean = true;
|
|
529
621
|
else if (arg === "--cat" || arg === "-cat" || arg === "--ccat" || arg === "-ccat" || arg === "--bat" || arg === "-bat" || arg === "--glow" || arg === "-glow") flags.cat = true;
|
|
622
|
+
else if (arg === "--xxd" || arg === "--hexdump") {
|
|
623
|
+
flags.cat = true;
|
|
624
|
+
flags.settings.set("encoding", "hex3");
|
|
625
|
+
}
|
|
626
|
+
else if (arg === "--hex3") {
|
|
627
|
+
flags.settings.set("encoding", "hex3");
|
|
628
|
+
}
|
|
530
629
|
else if (arg === "--docs" || arg === "--readme") flags.docs = true;
|
|
630
|
+
else if (arg === "--changelog") flags.changelog = true;
|
|
531
631
|
else if (arg === "-debug") flags.debug = true;
|
|
532
|
-
else if (arg === "-profile") flags.profile = true;
|
|
632
|
+
else if (arg === "-profile" || arg === "--profile") flags.profile = true;
|
|
533
633
|
else if (arg === "-config-dir") flags.configDir = argv[++i] ?? "";
|
|
534
634
|
else if (arg === "-plugin") flags.plugin = argv[++i] ?? "";
|
|
535
|
-
else if (arg.startsWith("-")
|
|
635
|
+
else if (arg.startsWith("--remote-debugging-port=")) {
|
|
636
|
+
flags.cdpPort = parseInt(arg.slice("--remote-debugging-port=".length)) || 9222;
|
|
637
|
+
} else if (arg === "--remote-debugging-port") {
|
|
638
|
+
flags.cdpPort = parseInt(argv[++i]) || 9222;
|
|
639
|
+
} else if (arg.startsWith("--remote-debugging-address=")) {
|
|
640
|
+
flags.cdpAddress = arg.slice("--remote-debugging-address=".length);
|
|
641
|
+
} else if (arg === "--remote-debugging-address") {
|
|
642
|
+
flags.cdpAddress = argv[++i] ?? "";
|
|
643
|
+
} else if (arg.startsWith("-") && arg.length > 1 && i + 1 < argv.length) {
|
|
536
644
|
flags.settings.set(arg.slice(1), argv[++i]);
|
|
537
645
|
} else {
|
|
538
646
|
files.push(arg);
|
|
@@ -568,15 +676,27 @@ function usage() {
|
|
|
568
676
|
" Set an option for this session",
|
|
569
677
|
"-options",
|
|
570
678
|
" Show option help and exit\n",
|
|
679
|
+
"-profile, --profile",
|
|
680
|
+
" Print startup performance profile and exit\n",
|
|
571
681
|
"--cat, --ccat, --bat, --glow",
|
|
572
682
|
" Syntax-highlight file(s) and write to stdout, then exit (.md uses Bun.markdown.ansi)\n",
|
|
683
|
+
"--xxd, --hexdump",
|
|
684
|
+
" Hex3 dump file(s) and write to stdout (same as --cat -encoding hex3)\n",
|
|
685
|
+
"--hex3",
|
|
686
|
+
" Set -encoding hex3 for this session\n",
|
|
573
687
|
"-help, -h, --help",
|
|
574
688
|
" Show this help & exit",
|
|
575
689
|
"-version, -V, --version",
|
|
576
690
|
" Show version+backend info & exit",
|
|
577
691
|
"--docs, --readme",
|
|
578
692
|
` Show ${pkg.name}'s README.md & exit`,
|
|
579
|
-
|
|
693
|
+
"--changelog",
|
|
694
|
+
" Show CHANGELOG.md & exit",
|
|
695
|
+
"",
|
|
696
|
+
"--remote-debugging-port=PORT",
|
|
697
|
+
" Start CDP (Chrome DevTools Protocol) server on PORT at launch",
|
|
698
|
+
"--remote-debugging-address=ADDRESS",
|
|
699
|
+
" Bind CDP server to ADDRESS (default: 127.0.0.1); use 0.0.0.0 for all interfaces",
|
|
580
700
|
|
|
581
701
|
].join("\n");
|
|
582
702
|
}
|
|
@@ -5811,7 +5931,7 @@ function detectTtsCmd() {
|
|
|
5811
5931
|
Bun.env.TTS_LANG = lang ;
|
|
5812
5932
|
|
|
5813
5933
|
if (platform === "android") {
|
|
5814
|
-
if (
|
|
5934
|
+
if (Bun.which("termux-tts-speak"))
|
|
5815
5935
|
return { cmd: ["termux-tts-speak", "-p", String(pitch), "-r", String(speed)], via: "arg" };
|
|
5816
5936
|
}
|
|
5817
5937
|
|
|
@@ -5832,7 +5952,7 @@ function detectTtsCmd() {
|
|
|
5832
5952
|
const pitchPct = Math.round((pitch - 1) * 100);
|
|
5833
5953
|
const pitchAttr = (pitchPct >= 0 ? "+" : "") + pitchPct + "%";
|
|
5834
5954
|
for (const shell of ["pwsh.exe", "powershell.exe"]) {
|
|
5835
|
-
if (
|
|
5955
|
+
if (Bun.which(shell)) {
|
|
5836
5956
|
const psCmd =
|
|
5837
5957
|
"Add-Type -AssemblyName System.Speech; " +
|
|
5838
5958
|
`$s = New-Object System.Speech.Synthesis.SpeechSynthesizer; $s.Rate = ${rate}; ` +
|
|
@@ -5847,7 +5967,7 @@ function detectTtsCmd() {
|
|
|
5847
5967
|
// Linux / Android fallback: espeak-ng / espeak
|
|
5848
5968
|
// Speed: -s <wpm> (175 = normal), Pitch: -p <n> (0-99, 50 = normal)
|
|
5849
5969
|
for (const bin of ["espeak-ng", "espeak"]) {
|
|
5850
|
-
if (
|
|
5970
|
+
if (Bun.which(bin)) {
|
|
5851
5971
|
const spd = Math.round(175 * speed);
|
|
5852
5972
|
const pit = Math.max(0, Math.min(99, Math.round(50 * pitch)));
|
|
5853
5973
|
return { cmd: [bin, '-s', spd, '-p', pit], via: "arg" };
|
|
@@ -6559,8 +6679,16 @@ async function loadBuffers(files, command) {
|
|
|
6559
6679
|
} else if (!process.stdin.isTTY) {
|
|
6560
6680
|
const chunks = [];
|
|
6561
6681
|
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
6562
|
-
const
|
|
6563
|
-
const
|
|
6682
|
+
const context = loadBuffers.context ?? {};
|
|
6683
|
+
const encoding = context.config?.globalSettings?.encoding ?? DEFAULT_SETTINGS.encoding;
|
|
6684
|
+
const decoded = decodeTextBytesWithEncoding(Buffer.concat(chunks), encoding);
|
|
6685
|
+
const stdinText = decoded.text;
|
|
6686
|
+
const stdinBuf = new BufferModel({
|
|
6687
|
+
text: stdinText,
|
|
6688
|
+
type: process.stdout.isTTY ? "default" : "stdout",
|
|
6689
|
+
command,
|
|
6690
|
+
encoding: decoded.encoding,
|
|
6691
|
+
});
|
|
6564
6692
|
if (loadBuffers.context) attachSyntax(stdinBuf, loadBuffers.context, "", stdinText);
|
|
6565
6693
|
buffers.push(stdinBuf);
|
|
6566
6694
|
} else {
|
|
@@ -6579,7 +6707,13 @@ async function printReadmeDocs() {
|
|
|
6579
6707
|
process.stdout.write(Bun.markdown.ansi(readme, { hyperlinks: true }));
|
|
6580
6708
|
}
|
|
6581
6709
|
|
|
6710
|
+
async function printChangelogDocs() {
|
|
6711
|
+
const changelog = await Bun.file(join(REPO_ROOT, "CHANGELOG.md")).text();
|
|
6712
|
+
process.stdout.write(Bun.markdown.ansi(changelog, { hyperlinks: true }));
|
|
6713
|
+
}
|
|
6714
|
+
|
|
6582
6715
|
async function main() {
|
|
6716
|
+
addCheckpoint("Argument Parsing");
|
|
6583
6717
|
const { flags, files: rawFiles } = parseArgs(process.argv.slice(2));
|
|
6584
6718
|
if (flags.help) {
|
|
6585
6719
|
console.log(usage());
|
|
@@ -6614,6 +6748,10 @@ async function main() {
|
|
|
6614
6748
|
await printReadmeDocs();
|
|
6615
6749
|
return;
|
|
6616
6750
|
}
|
|
6751
|
+
if (flags.changelog) {
|
|
6752
|
+
await printChangelogDocs();
|
|
6753
|
+
return;
|
|
6754
|
+
}
|
|
6617
6755
|
if (flags.options) {
|
|
6618
6756
|
for (const [key, value] of Object.entries(defaultAllSettings()).sort(([a], [b]) => a.localeCompare(b))) {
|
|
6619
6757
|
console.log(`-${key} value`);
|
|
@@ -6621,13 +6759,16 @@ async function main() {
|
|
|
6621
6759
|
}
|
|
6622
6760
|
return;
|
|
6623
6761
|
}
|
|
6762
|
+
addCheckpoint("Config Initialization");
|
|
6624
6763
|
const config = await new Config({ configDir: flags.configDir }).init();
|
|
6625
6764
|
config.applyCliSettings(flags.settings);
|
|
6626
6765
|
syncEditorSettings(config);
|
|
6627
6766
|
|
|
6767
|
+
addCheckpoint("Runtime Registry Init");
|
|
6628
6768
|
const runtime = new RuntimeRegistry({ repoRoot: REPO_ROOT, configDir: config.configDir });
|
|
6629
6769
|
await runtime.init({ user: true });
|
|
6630
6770
|
|
|
6771
|
+
addCheckpoint("Colorscheme & Syntax Load");
|
|
6631
6772
|
const colorscheme = await new Colorscheme(runtime).load(config.getGlobalOption("colorscheme") || "default");
|
|
6632
6773
|
const syntaxDefinitions = await loadSyntaxDefinitions(runtime);
|
|
6633
6774
|
|
|
@@ -6636,6 +6777,7 @@ async function main() {
|
|
|
6636
6777
|
return;
|
|
6637
6778
|
}
|
|
6638
6779
|
|
|
6780
|
+
addCheckpoint("Lua Plugin Manager Init");
|
|
6639
6781
|
const plugins = new PluginManager({ config, runtime, repoRoot: REPO_ROOT });
|
|
6640
6782
|
await plugins.init();
|
|
6641
6783
|
|
|
@@ -6674,50 +6816,114 @@ async function main() {
|
|
|
6674
6816
|
return;
|
|
6675
6817
|
}
|
|
6676
6818
|
|
|
6677
|
-
const pluginErr = await plugins.loadAll();
|
|
6678
|
-
if (pluginErr) console.error(`Plugin runtime disabled: ${pluginErr.message}`);
|
|
6679
|
-
if (!pluginErr) {
|
|
6680
|
-
await plugins.run("preinit");
|
|
6681
|
-
await plugins.run("init");
|
|
6682
|
-
await plugins.run("postinit");
|
|
6683
|
-
}
|
|
6684
|
-
|
|
6685
|
-
// ── JS plugin system ──────────────────────────────────────────────────────
|
|
6686
|
-
const jsPlugins = new JsPluginManager();
|
|
6687
6819
|
const { files, command } = parseInput(rawFiles);
|
|
6820
|
+
const jsPlugins = new JsPluginManager();
|
|
6688
6821
|
const context = { colorscheme, syntaxDefinitions, plugins, config, runtime, jsPlugins };
|
|
6689
6822
|
jsPlugins.setContext(context);
|
|
6690
6823
|
buildMicroGlobal(jsPlugins); // sets globalThis.micro
|
|
6691
6824
|
|
|
6692
|
-
|
|
6693
|
-
|
|
6694
|
-
|
|
6695
|
-
|
|
6696
|
-
|
|
6697
|
-
|
|
6825
|
+
addCheckpoint("Parallel Initialization Start");
|
|
6826
|
+
|
|
6827
|
+
const luaPromise = (async () => {
|
|
6828
|
+
// return 0;
|
|
6829
|
+
const start = Bun.nanoseconds();
|
|
6830
|
+
const pluginErr = await plugins.loadAll();
|
|
6831
|
+
if (pluginErr) console.error(`Plugin runtime disabled: ${pluginErr.message}`);
|
|
6832
|
+
if (!pluginErr) {
|
|
6833
|
+
await plugins.run("preinit");
|
|
6834
|
+
await plugins.run("init");
|
|
6835
|
+
await plugins.run("postinit");
|
|
6836
|
+
}
|
|
6837
|
+
const end = Bun.nanoseconds();
|
|
6838
|
+
return { pluginErr, duration: end - start };
|
|
6839
|
+
})();
|
|
6840
|
+
|
|
6841
|
+
const jsPromise = (async () => {
|
|
6842
|
+
const start = Bun.nanoseconds();
|
|
6843
|
+
const jsDirs = [
|
|
6844
|
+
{ dir: join(REPO_ROOT, "runtime", "jsplugins"), builtin: true },
|
|
6845
|
+
{ dir: join(config.configDir, "jsplug"), builtin: false },
|
|
6846
|
+
];
|
|
6847
|
+
await jsPlugins.loadFrom(jsDirs);
|
|
6848
|
+
const end = Bun.nanoseconds();
|
|
6849
|
+
return { duration: end - start };
|
|
6850
|
+
})();
|
|
6851
|
+
|
|
6852
|
+
const buffersPromise = (async () => {
|
|
6853
|
+
const start = Bun.nanoseconds();
|
|
6854
|
+
let cursorStates = {};
|
|
6855
|
+
if (DEFAULT_SETTINGS.savecursor) {
|
|
6856
|
+
cursorStates = await loadCursorStates(config.configDir);
|
|
6857
|
+
}
|
|
6858
|
+
// Mix in context properties needed for buffer loading:
|
|
6859
|
+
context.cursorStates = cursorStates;
|
|
6860
|
+
context._openBuffers = new Map();
|
|
6861
|
+
context._termPrompt = process.stdout.isTTY ? termPromptLine : null;
|
|
6862
|
+
|
|
6863
|
+
loadBuffers.context = context;
|
|
6864
|
+
const buffers = await loadBuffers(files.map((file) =>
|
|
6865
|
+
isHttpUrl(file) ? file : resolve(file)
|
|
6866
|
+
), command);
|
|
6867
|
+
|
|
6868
|
+
let historyPromise = Promise.resolve();
|
|
6869
|
+
if (config.getGlobalOption("savehistory") !== false) {
|
|
6870
|
+
historyPromise = loadHistory(config.configDir);
|
|
6871
|
+
}
|
|
6872
|
+
await historyPromise;
|
|
6873
|
+
const end = Bun.nanoseconds();
|
|
6874
|
+
return { buffers, duration: end - start };
|
|
6875
|
+
})();
|
|
6876
|
+
|
|
6877
|
+
const [luaSettled, jsSettled, buffersSettled] = await Promise.allSettled([
|
|
6878
|
+
luaPromise,
|
|
6879
|
+
jsPromise,
|
|
6880
|
+
buffersPromise
|
|
6881
|
+
]);
|
|
6882
|
+
|
|
6883
|
+
const luaResult = luaSettled.status === "fulfilled"
|
|
6884
|
+
? luaSettled.value
|
|
6885
|
+
: { pluginErr: luaSettled.reason, duration: 0 };
|
|
6886
|
+
if (luaSettled.status === "rejected") {
|
|
6887
|
+
console.error(`Lua plugin runtime disabled: ${luaSettled.reason?.message || luaSettled.reason}`);
|
|
6888
|
+
}
|
|
6889
|
+
|
|
6890
|
+
const jsResult = jsSettled.status === "fulfilled"
|
|
6891
|
+
? jsSettled.value
|
|
6892
|
+
: { duration: 0 };
|
|
6893
|
+
if (jsSettled.status === "rejected") {
|
|
6894
|
+
console.error(`JS plugin runtime disabled: ${jsSettled.reason?.message || jsSettled.reason}`);
|
|
6895
|
+
}
|
|
6698
6896
|
|
|
6699
|
-
|
|
6700
|
-
|
|
6897
|
+
const buffersResult = buffersSettled.status === "fulfilled"
|
|
6898
|
+
? buffersSettled.value
|
|
6899
|
+
: { buffers: [new BufferModel({ command })], duration: 0 };
|
|
6900
|
+
if (buffersSettled.status === "rejected") {
|
|
6901
|
+
console.error(`Buffer load failed: ${buffersSettled.reason?.message || buffersSettled.reason}`);
|
|
6701
6902
|
}
|
|
6702
|
-
// Backup prompt available before App starts (stdin still in cooked mode).
|
|
6703
|
-
context._termPrompt = process.stdout.isTTY ? termPromptLine : null;
|
|
6704
|
-
loadBuffers.context = context;
|
|
6705
|
-
const buffers = await loadBuffers(files.map((file) =>
|
|
6706
|
-
isHttpUrl(file) ? file : resolve(file)
|
|
6707
|
-
), command);
|
|
6708
6903
|
|
|
6709
|
-
|
|
6904
|
+
addCheckpoint("Parallel Initialization End");
|
|
6905
|
+
|
|
6906
|
+
parallelTimings = {
|
|
6907
|
+
lua: luaResult.duration / 1e6,
|
|
6908
|
+
js: jsResult.duration / 1e6,
|
|
6909
|
+
buffers: buffersResult.duration / 1e6
|
|
6910
|
+
};
|
|
6911
|
+
|
|
6912
|
+
const { pluginErr } = luaResult;
|
|
6913
|
+
const { buffers } = buffersResult;
|
|
6914
|
+
|
|
6915
|
+
if (!process.stdout.isTTY && !flags.profile) {
|
|
6710
6916
|
console.log(buffers[0].lines.join("\n"));
|
|
6711
6917
|
return;
|
|
6712
6918
|
}
|
|
6713
|
-
|
|
6714
|
-
|
|
6715
|
-
}
|
|
6919
|
+
|
|
6920
|
+
addCheckpoint("App Instantiation");
|
|
6716
6921
|
const app = new App(buffers, context);
|
|
6717
6922
|
jsPlugins.setApp(app);
|
|
6718
6923
|
if (plugins && !pluginErr && app.buffer) plugins.curPaneAdapter = makePaneAdapter(app.buffer, app);
|
|
6719
6924
|
// Dispatch all JS plugin lifecycle hooks after setApp so TermMessage,
|
|
6720
6925
|
// CurPane, cmd/action proxies, and buffer APIs all work correctly.
|
|
6926
|
+
addCheckpoint("JS Lifecycle Hooks");
|
|
6721
6927
|
await jsPlugins.run("preinit");
|
|
6722
6928
|
await jsPlugins.run("init");
|
|
6723
6929
|
await jsPlugins.run("postinit");
|
|
@@ -6725,6 +6931,31 @@ async function main() {
|
|
|
6725
6931
|
for (const buffer of buffers) await plugins.run("onBufferOpen", buffer);
|
|
6726
6932
|
}
|
|
6727
6933
|
for (const buffer of buffers) await jsPlugins.run("onBufferOpen", buffer);
|
|
6934
|
+
if (flags.cdpPort) {
|
|
6935
|
+
const cdpArgs = [flags.cdpPort];
|
|
6936
|
+
if (flags.cdpAddress) cdpArgs.push(`--address=${flags.cdpAddress}`);
|
|
6937
|
+
await app.handleCommand(`cdp ${cdpArgs.join(" ")}`);
|
|
6938
|
+
}
|
|
6939
|
+
|
|
6940
|
+
if (flags.profile) {
|
|
6941
|
+
addCheckpoint("Clipboard Probing");
|
|
6942
|
+
const clipSetting = config.getGlobalOption("clipboard") ?? "external";
|
|
6943
|
+
const clipboard = new ClipboardManager();
|
|
6944
|
+
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
6945
|
+
process.stdin.setRawMode?.(true);
|
|
6946
|
+
process.stdin.resume();
|
|
6947
|
+
await clipboard.initFromSetting(clipSetting, process.stdin, process.stdout, 150);
|
|
6948
|
+
process.stdin.setRawMode?.(false);
|
|
6949
|
+
process.stdin.pause();
|
|
6950
|
+
} else {
|
|
6951
|
+
await clipboard.initFromSetting(clipSetting, process.stdin, process.stdout, 150);
|
|
6952
|
+
}
|
|
6953
|
+
|
|
6954
|
+
addCheckpoint("Profile Done");
|
|
6955
|
+
printProfileReport();
|
|
6956
|
+
process.exit(0);
|
|
6957
|
+
}
|
|
6958
|
+
|
|
6728
6959
|
await app.start();
|
|
6729
6960
|
}
|
|
6730
6961
|
|