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.
@@ -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
+ }
@@ -40,15 +40,19 @@ export class SyntaxDefinition {
40
40
 
41
41
  export async function loadSyntaxDefinitions(runtime) {
42
42
  const headers = new Map();
43
- for (const file of runtime.list(4)) {
44
- headers.set(file.name, parseHeaderFile(await file.text()));
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 definitions = [];
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
- const header = headers.get(activeFile.name) ?? (source ? parseHeaderYaml(source) : parseHeaderTextFallback(text, activeFile.name));
74
- definitions.push(new SyntaxDefinition(header, source ?? { rules: [] }, text));
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("-") && arg.length > 1 && i + 1 < argv.length) {
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 (runSync(["sh", "-c", "command -v termux-tts-speak"], { stdout: "ignore", stderr: "ignore" }).ok)
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 (runSync(["where.exe", shell], { stdout: "ignore", stderr: "ignore" }).ok) {
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 (runSync(["sh", "-c", `command -v ${bin}`], { stdout: "ignore", stderr: "ignore" }).ok) {
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 stdinText = Buffer.concat(chunks).toString("utf8");
6563
- const stdinBuf = new BufferModel({ text: stdinText, type: process.stdout.isTTY ? "default" : "stdout", command });
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
- const jsDirs = [
6693
- { dir: join(REPO_ROOT, "runtime", "jsplugins"), builtin: true },
6694
- { dir: join(config.configDir, "jsplug"), builtin: false },
6695
- ];
6696
- await jsPlugins.loadFrom(jsDirs);
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
- if (DEFAULT_SETTINGS.savecursor) {
6700
- context.cursorStates = await loadCursorStates(config.configDir);
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
- if (!process.stdout.isTTY) {
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
- if (config.getGlobalOption("savehistory") !== false) {
6714
- await loadHistory(config.configDir);
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