jsmdcui 0.7.0 → 0.9.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/CHANGELOG.md +127 -0
- package/README.md +189 -19
- package/cdp-maze.js +141 -0
- package/demos/maze.md +149 -0
- package/demos/todo-zh.md +98 -0
- package/demos/todo.md +98 -0
- package/edit +9 -0
- package/llm-maze.txt +94 -0
- package/package.json +1 -1
- package/runmd.mjs +44 -2
- package/runtime/help/cdp.md +5 -0
- package/runtime/help/help.md +189 -19
- package/runtime/jsplugins/cdp/cdp-server.js +26 -2
- package/runtime/jsplugins/cdp/cdp.js +60 -51
- package/runtime/jsplugins/chapter/chapter.js +6 -3
- package/runtime/jsplugins/example/example.js +12 -7
- package/runtime/syntax/markdown.yaml +1 -0
- package/single-exe/README.md +223 -45
- package/single-exe/compiled.js +6 -3
- package/single-exe/entry.mjs +4 -3
- package/single-exe/packAssets.sh +1 -1
- package/src/cui/fence-events.mjs +106 -0
- package/src/cui/id-collision.mjs +10 -28
- package/src/cui/kitty-debug.mjs +1 -1
- package/src/cui/kitty-images.mjs +11 -1
- package/src/cui/rpc.mjs +6 -3
- package/src/cui/server.mjs +13 -2
- package/src/index.js +425 -72
- package/src/lua/engine.js +1 -4
- package/src/platform/terminal.js +5 -0
- package/src/plugins/js-bridge.js +157 -8
- package/tests/compiled-runtime.test.js +8 -0
- package/tests/demo.test.js +40 -0
- package/tests/fence-events.test.js +405 -0
- package/tests/id-collision.test.js +11 -0
- package/tests/js-plugin-prompts.test.js +46 -0
- package/tests/key-event.md +10 -0
- package/tests/kitty-demo.md +1 -1
- package/tests/kitty-images.test.js +22 -0
- package/tests/platform-terminal.test.js +8 -0
- package/tests/textarea.md +8 -0
- package/tests/wui.test.js +16 -1
- package/demo.resized.jpg +0 -0
- package/src/runtime/compiled.js +0 -25
package/runtime/help/help.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|

|
|
2
2
|
|
|
3
|
+
- Maze Game
|
|
4
|
+
* The TUI can be controlled through the Chrome DevTools Protocol (CDP). Codex successfully solved this maze using it.
|
|
5
|
+
|
|
6
|
+

|
|
7
|
+
|
|
3
8
|
# Demo App
|
|
4
9
|
## My app
|
|
5
10
|
|
|
@@ -36,7 +41,8 @@ npx jsmdcui@latest README.md
|
|
|
36
41
|
|
|
37
42
|
## Quick start
|
|
38
43
|
|
|
39
|
-
jsmdcui requires [Bun](https://bun.com).
|
|
44
|
+
jsmdcui requires [Bun ≥ 1.3.12](https://bun.com).
|
|
45
|
+
On Android, install it in Termux:
|
|
40
46
|
|
|
41
47
|
```sh
|
|
42
48
|
npm install -g bun
|
|
@@ -89,6 +95,18 @@ npx jsmdcui --demo-imgtool
|
|
|
89
95
|
npx jsmdcui --demo-imgtool-zh
|
|
90
96
|
```
|
|
91
97
|
|
|
98
|
+
```sh
|
|
99
|
+
# Maze game demo
|
|
100
|
+
npx jsmdcui --demo-maze
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
To watch jsmdcui start the maze with a local CDP server and solve it
|
|
104
|
+
automatically after three seconds, run:
|
|
105
|
+
|
|
106
|
+
```sh
|
|
107
|
+
npx jsmdcui --cdp-maze
|
|
108
|
+
```
|
|
109
|
+
|
|
92
110
|
```sh
|
|
93
111
|
# WUI(Web User Interface) Demo
|
|
94
112
|
npx jsmdcui --wui
|
|
@@ -134,7 +152,7 @@ bun src/index.js --wui testapp.md
|
|
|
134
152
|
| --- | --- |
|
|
135
153
|
| `bun src/index.js app.md` | Render `app.md` as a read-only terminal UI and write five generated files beside it. |
|
|
136
154
|
| `bun src/index.js --kitty app.md` | Display Markdown images with Kitty graphics and the jsgotty MIME extension. |
|
|
137
|
-
| `bun src/index.js --kitty-compat app.md` |
|
|
155
|
+
| `bun src/index.js --kitty-compat app.md` | Convert Markdown images to PNG with `Bun.Image` and display them using the standard Kitty graphics protocol without the non-standard MIME `U` field. |
|
|
138
156
|
| `bun src/index.js --kitty --allow-url URL.md` | Download trusted HTTP(S) Markdown and its HTTP(S) images, then display supported images with Kitty graphics. |
|
|
139
157
|
| `JSMDCUI_KITTY_DEBUG=1 bun src/index.js --kitty app.md` | Enable Kitty image placement logging to `kitty-placement.log`. |
|
|
140
158
|
| `bun src/index.js --check app.md` | Check heading and fenced-block IDs for collisions, print line-by-line details, and exit. |
|
|
@@ -142,11 +160,13 @@ bun src/index.js --wui testapp.md
|
|
|
142
160
|
| `bun src/index.js --cat app.md` | Render the terminal version to stdout, write five generated files beside it, and exit. |
|
|
143
161
|
| `bun src/index.js --testapp.md` | Write the bundled `testapp.md` source to stdout and exit. |
|
|
144
162
|
| `bun src/index.js --export-readme` | Write or overwrite `./README.md` with the bundled README source and exit. |
|
|
163
|
+
| `bun src/index.js --export-cdp-maze` | Write or overwrite `./cdp-maze.js` with the bundled CDP maze solver and exit. |
|
|
145
164
|
| `bun src/index.js --demo-list` | List `testapp.md` and every bundled `demos/*.md` example with its command-line option, then exit. |
|
|
146
165
|
| `bun src/index.js --demo` | Use local `testapp.md` when present, otherwise write the bundled demo; open it in the terminal UI and write five generated files beside it. |
|
|
147
166
|
| `bun src/index.js --demo-<filename>` | Load `demos/<filename>.md`; preserve an existing local copy or write the bundled copy, then open it and generate its five companion files. New files added under `demos/` work automatically. |
|
|
148
167
|
| `bun src/index.js --demo-imgtool` | Compatibility alias for `--demo-image-processor`. |
|
|
149
168
|
| `bun src/index.js --demo-imgtool-zh` | Compatibility alias for `--demo-image-processor.zh-TW`. |
|
|
169
|
+
| `bun src/index.js --cdp-maze` | Load the maze demo, start CDP on `127.0.0.1:9222`, and run the bundled solver after three seconds. |
|
|
150
170
|
| `bun src/index.js --allow-url URL.md` | Download HTTP(S) Markdown to the current directory and, with Kitty mode enabled, download its HTTP(S) images; write 5 generated files and allow embedded code to run. Only use trusted URLs. |
|
|
151
171
|
| `bun src/index.js --wui` | Use local `testapp.md` when present, otherwise write the bundled demo; write five generated files in the current directory, then print and serve a random URL. |
|
|
152
172
|
| `bun src/index.js --wui app.md` | Write five generated files beside `app.md`, then print and serve a random URL. |
|
|
@@ -239,12 +259,81 @@ native `<textarea>` with the declared ID and classes. Long text wraps
|
|
|
239
259
|
automatically, and the field height is recalculated when the user types, the
|
|
240
260
|
window is resized, or frontend code calls `.val(value)`.
|
|
241
261
|
|
|
262
|
+
In the TUI, `text` remains a single-line control while `textarea` supports
|
|
263
|
+
native multiline editing. Enter splits the current body row and grows the
|
|
264
|
+
frame, Backspace at the start of a later row joins it to the previous row, and
|
|
265
|
+
Delete at the end of a row joins the following row. When the expanded control
|
|
266
|
+
does not fit on screen, the document viewport scrolls to keep the cursor
|
|
267
|
+
visible. The closing border and following Markdown content move with the
|
|
268
|
+
resized control.
|
|
269
|
+
|
|
270
|
+
A named `text` or `textarea` block can run inline front-end code before it
|
|
271
|
+
handles a key by placing a quoted HTML-style `@keydown` attribute after its
|
|
272
|
+
identity:
|
|
273
|
+
|
|
274
|
+
````md
|
|
275
|
+
```text#command.field @keydown="handleCommand(event)"
|
|
276
|
+
Initial value
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
```js front
|
|
280
|
+
export function handleCommand(event) {
|
|
281
|
+
if (event.key !== 'Enter') return;
|
|
282
|
+
event.preventDefault();
|
|
283
|
+
alert(`Command: ${$('#command').val()}`);
|
|
284
|
+
}
|
|
285
|
+
```
|
|
286
|
+
````
|
|
287
|
+
|
|
288
|
+
The block must have a unique ID. In the WUI, jsmdcui writes the code directly
|
|
289
|
+
to the generated textarea's native `onkeydown` attribute. Mobile browsers may
|
|
290
|
+
report ordinary software-keyboard letters as `Unidentified`, so the WUI also
|
|
291
|
+
generates an `onbeforeinput` fallback. The unidentified event is hidden from
|
|
292
|
+
the handler; `beforeinput.data` becomes `event.key`, then the same keydown code
|
|
293
|
+
runs once. The fallback also preserves `ctrlKey`, `shiftKey`, `altKey`, and
|
|
294
|
+
`metaKey` from the unidentified keydown. A short zero-delay timer distinguishes
|
|
295
|
+
it from the `beforeinput` that normally follows an already identified keydown,
|
|
296
|
+
preventing duplicate calls.
|
|
297
|
+
|
|
298
|
+
On Android browsers, `Alt-E`, `Alt-N`, `Alt-U`, and `Alt-I` may be consumed or
|
|
299
|
+
transformed by the software keyboard and therefore cannot always be observed
|
|
300
|
+
reliably by a keydown handler. Avoid relying on these combinations for portable
|
|
301
|
+
WUI controls.
|
|
302
|
+
|
|
303
|
+
In the TUI, the full source info string is kept in an event table before Bun
|
|
304
|
+
renders it. The keydown statements run with a synthetic `event` before the
|
|
305
|
+
text control handles the key. Both interfaces expose `event.key`, modifier
|
|
306
|
+
flags, `event.target.id`, and `event.target.value`. Use `event`, the native
|
|
307
|
+
inline-handler variable, rather than Vue's `$event` alias. Double quotes
|
|
308
|
+
delimit the handler; use single-quoted JavaScript strings inside it or escape
|
|
309
|
+
an embedded double quote as `\"`.
|
|
310
|
+
|
|
311
|
+
Both interfaces add a non-enumerable `event.toJSON()` method with matching
|
|
312
|
+
keyboard, modifier, prevention, and target fields. `JSON.stringify(event)`
|
|
313
|
+
calls it automatically, so the resulting JSON is portable between the TUI and
|
|
314
|
+
WUI.
|
|
315
|
+
|
|
316
|
+
A keydown handler can call `event.preventDefault()` to stop text insertion or
|
|
317
|
+
cursor movement. The `.prevent` modifier applies this automatically:
|
|
318
|
+
|
|
319
|
+
````md
|
|
320
|
+
```text#command @keydown.prevent="handleCommand(event)"
|
|
321
|
+
```
|
|
322
|
+
````
|
|
323
|
+
|
|
324
|
+
`@keydown` is the only keyboard event exposed by jsmdcui. Traditional terminal
|
|
325
|
+
input does not report physical key releases reliably, so jsmdcui does not
|
|
326
|
+
provide or emulate `@keyup` in either interface.
|
|
327
|
+
|
|
242
328
|
In the terminal TUI, only content after the protected `│ ` or `| ` prefix can
|
|
243
|
-
be edited
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
329
|
+
be edited, and the frame prefix cannot be deleted. Multiline paste remains
|
|
330
|
+
blocked. For single-line `text` controls, activate the lower-left frame corner
|
|
331
|
+
to add a row. Activate the upper-left frame corner to remove the trailing row
|
|
332
|
+
only when it is empty; non-empty content is never removed.
|
|
333
|
+
|
|
334
|
+
TUI text-control changes made through `.val(value)` participate in the same
|
|
335
|
+
history as direct editing. `Ctrl-Z` undoes the replacement and `Ctrl-Y` redoes
|
|
336
|
+
it, including multiline frame resizing and its associated rendered metadata.
|
|
248
337
|
|
|
249
338
|
### Heading task lists and selector API
|
|
250
339
|
|
|
@@ -384,7 +473,7 @@ The available selector methods are:
|
|
|
384
473
|
| Method | TUI | WUI |
|
|
385
474
|
| --- | --- | --- |
|
|
386
475
|
| `.val()` | Read text blocks or heading task-list values. | Read textareas/controls or heading task-list values. |
|
|
387
|
-
| `.val(value)` | Replace text-block contents
|
|
476
|
+
| `.val(value)` | Replace text-block contents, resize multiline values, and record Undo/Redo history. | Set textarea/control values and resize textareas. |
|
|
388
477
|
| `.html()` | Return a selected heading's inner HTML. | Return any successfully selected DOM element's `innerHTML`. |
|
|
389
478
|
| `.line()` | Return a heading's current 1-based TUI row, or `0` if missing. | Not available. |
|
|
390
479
|
| `.push(...items)` | Append unchecked strings or `{ value, checked }` task items; return the new direct-item count. | Same. |
|
|
@@ -397,16 +486,16 @@ The available selector methods are:
|
|
|
397
486
|
TUI heading rows are recalculated after text-block rows are added, removed, or
|
|
398
487
|
replaced with multiline `.val(value)` content.
|
|
399
488
|
|
|
400
|
-
The
|
|
489
|
+
The 3 UI building blocks are:
|
|
401
490
|
|
|
402
|
-
- Regular Markdown provides headings, text, lists, task checkboxes, code, and
|
|
491
|
+
- 1. `Regular Markdown` provides headings, text, lists, task checkboxes, code, and
|
|
403
492
|
links.
|
|
404
|
-
-
|
|
493
|
+
- 2. `js front` block contains UI code. Exported functions can use
|
|
405
494
|
`alert`, `confirm`, `prompt`, and the generated `rpc` client.
|
|
406
|
-
|
|
407
|
-
The terminal UI awaits it before closing an `mdcui` buffer.
|
|
408
|
-
`mdcui` buffers close without a save prompt.
|
|
409
|
-
-
|
|
495
|
+
* A front module may export `async function onMdcuiExit({ reason, path, $ })`.
|
|
496
|
+
* The terminal UI awaits it before closing an `mdcui` buffer.
|
|
497
|
+
* Modified `mdcui` buffers close without a save prompt.
|
|
498
|
+
- 3. `js back` block exports trusted backend functions. In the browser WUI,
|
|
410
499
|
`rpc` publishes only exported functions whose exported names do not start
|
|
411
500
|
with `_`. Call a published function from the front end with
|
|
412
501
|
`await rpc.functionName(arg1, arg2)`.
|
|
@@ -463,6 +552,10 @@ selection, search, and copy remain available.
|
|
|
463
552
|
| `Alt-G` | Show or hide the shortcut bar. |
|
|
464
553
|
| `Ctrl-Q` or `Alt-Q` | Close the current UI. |
|
|
465
554
|
|
|
555
|
+
To preview another color theme, press `Ctrl-E` or click on `€`, type `theme `, then use `Tab`
|
|
556
|
+
and the arrow keys to browse the available themes. Press `Enter` to switch to
|
|
557
|
+
the selected theme, or `Esc` to cancel and restore the previous one.
|
|
558
|
+
|
|
466
559
|
The terminal automatically reflows the Markdown when its width changes.
|
|
467
560
|
Only `javascript:` links execute in the TUI; ordinary web links behave as
|
|
468
561
|
normal links in the browser.
|
|
@@ -480,6 +573,63 @@ supported HTTP(S) images with Kitty graphics, combine the options:
|
|
|
480
573
|
bun src/index.js --kitty --allow-url https://example.com/app.md
|
|
481
574
|
```
|
|
482
575
|
|
|
576
|
+
### CDP control for TUI automation
|
|
577
|
+
|
|
578
|
+
The terminal UI can expose a Chrome DevTools Protocol (CDP) server, so another
|
|
579
|
+
Bun process can inspect the terminal buffer, click buffer coordinates, and send
|
|
580
|
+
real keyboard events to the running TUI.
|
|
581
|
+
|
|
582
|
+
Start CDP from the command line when launching jsmdcui:
|
|
583
|
+
|
|
584
|
+
```sh
|
|
585
|
+
bun src/index.js --remote-debugging-port=9222 demos/maze.md
|
|
586
|
+
bun src/index.js --remote-debugging-port=9222 --remote-debugging-address=127.0.0.1 demos/maze.md
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
Or start it from inside the TUI editor command prompt(Ctrl-E or €):
|
|
590
|
+
|
|
591
|
+
```text
|
|
592
|
+
Ctrl-E cdp
|
|
593
|
+
Ctrl-E cdp 9000
|
|
594
|
+
Ctrl-E cdp --address=127.0.0.1
|
|
595
|
+
Ctrl-E cdp 9000 --public
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
The default bind address is `127.0.0.1` and the default port is `9222`.
|
|
599
|
+
Use `--public` or `--address=0.0.0.0` only on a trusted network.
|
|
600
|
+
|
|
601
|
+
Once CDP is running, control the TUI with `Bun.WebView`. For more info, enter
|
|
602
|
+
jsmdcui and use `Ctrl-E` or `€` → `help cdp`.
|
|
603
|
+
|
|
604
|
+
The cli flag `--cdp-maze` is a combination of
|
|
605
|
+
1. Start the demos/maze.md
|
|
606
|
+
2. Start a local CDP server
|
|
607
|
+
3. Wait three seconds
|
|
608
|
+
4. Run the solver cdp-maze.js automatically
|
|
609
|
+
|
|
610
|
+
The solver was
|
|
611
|
+
generated from the `llm-maze.txt` instructions; it focuses the maze controls,
|
|
612
|
+
resets the game, reads the maze from the TUI, solves it with breadth-first
|
|
613
|
+
search, and sends arrow-key input until the maze is escaped:
|
|
614
|
+
|
|
615
|
+
```sh
|
|
616
|
+
npx jsmdcui@latest --cdp-maze
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
From the source tree, run:
|
|
620
|
+
|
|
621
|
+
```sh
|
|
622
|
+
bun src/index.js --cdp-maze
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
Useful automation methods used by `cdp-maze.js`:
|
|
626
|
+
|
|
627
|
+
- `view.evaluate(js)`
|
|
628
|
+
- `view.evaluate("micro.getAllText()")`
|
|
629
|
+
- `view.evaluate("micro.getAllAnsiText()")`
|
|
630
|
+
- `view.click(column, line)`
|
|
631
|
+
- `view.press(key, options)`
|
|
632
|
+
|
|
483
633
|
## Browser interaction
|
|
484
634
|
|
|
485
635
|
The WUI uses normal browser mouse and keyboard behavior. Clicking a
|
|
@@ -489,15 +639,35 @@ its associated text. `alert`, `confirm`, and `prompt` use the browser's built-in
|
|
|
489
639
|
dialogs. Checkbox changes exist only in the current page: refreshing does not
|
|
490
640
|
preserve them and does not update the Markdown file.
|
|
491
641
|
|
|
492
|
-
The WUI
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
642
|
+
The WUI first tries port `3000` and accepts connections through the machine's
|
|
643
|
+
available network interfaces. If port `3000` is already in use, the operating
|
|
644
|
+
system selects an available port instead; the printed URL always contains the
|
|
645
|
+
actual port. Set `PORT` to request another fixed port. The printed `localhost`
|
|
646
|
+
URL is for the same machine. From another device on the same network, replace
|
|
647
|
+
`localhost` with the server machine's IP address and keep the printed port and
|
|
648
|
+
full path.
|
|
496
649
|
|
|
497
650
|
Each server start prints a new random path. The old URL stops working after the
|
|
498
651
|
server is stopped or restarted. Keep the process running while using the page,
|
|
499
652
|
and press `Ctrl-C` in its terminal to stop it.
|
|
500
653
|
|
|
654
|
+
## Editing Markdown source
|
|
655
|
+
|
|
656
|
+
From a cloned repository, use the `edit` launcher to open a Markdown file as
|
|
657
|
+
ordinary editable UTF-8 source instead of rendering it as a Markdown UI:
|
|
658
|
+
|
|
659
|
+
```sh
|
|
660
|
+
bun ./edit app.md
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
This is equivalent to:
|
|
664
|
+
|
|
665
|
+
```sh
|
|
666
|
+
bun src/index.js --edit app.md
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
Additional file and cursor arguments are forwarded unchanged.
|
|
670
|
+
|
|
501
671
|
## Generated files
|
|
502
672
|
|
|
503
673
|
Opening a local Markdown UI generates these files beside the source file:
|
|
@@ -554,8 +554,30 @@ async function dispatch(ctx, state, method, params, sessionId, emit) {
|
|
|
554
554
|
return {};
|
|
555
555
|
}
|
|
556
556
|
case "Input.dispatchKeyEvent": {
|
|
557
|
-
|
|
558
|
-
|
|
557
|
+
const target = getSessionTarget(state, sessionId);
|
|
558
|
+
const key = params.key ?? params.text ?? "";
|
|
559
|
+
const signature = `${params.code ?? key}:${params.modifiers ?? 0}`;
|
|
560
|
+
if (params.type === "rawKeyDown" || params.type === "keyDown") {
|
|
561
|
+
if (target.cdpKeyDown !== signature || params.autoRepeat) {
|
|
562
|
+
await ctx.press?.(key, {
|
|
563
|
+
modifiers: params.modifiers ?? 0,
|
|
564
|
+
text: params.text ?? "",
|
|
565
|
+
code: params.code ?? "",
|
|
566
|
+
repeat: !!params.autoRepeat,
|
|
567
|
+
});
|
|
568
|
+
target.cdpKeyDown = signature;
|
|
569
|
+
}
|
|
570
|
+
} else if (params.type === "char") {
|
|
571
|
+
if (!target.cdpKeyDown) {
|
|
572
|
+
await ctx.press?.(key, {
|
|
573
|
+
modifiers: params.modifiers ?? 0,
|
|
574
|
+
text: params.text ?? "",
|
|
575
|
+
code: params.code ?? "",
|
|
576
|
+
repeat: !!params.autoRepeat,
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
} else if (params.type === "keyUp") {
|
|
580
|
+
target.cdpKeyDown = null;
|
|
559
581
|
}
|
|
560
582
|
return {};
|
|
561
583
|
}
|
|
@@ -695,6 +717,8 @@ function navigateTarget(state, target, url, emit, options = {}) {
|
|
|
695
717
|
}
|
|
696
718
|
|
|
697
719
|
function getSessionTarget(state, sessionId) {
|
|
720
|
+
if (!sessionId) return getImplicitTarget(state);
|
|
721
|
+
|
|
698
722
|
const targetId = state.sessions.get(sessionId);
|
|
699
723
|
const target = state.targets.get(targetId);
|
|
700
724
|
if (!target) throw new Error(`No target for session: ${sessionId}`);
|
|
@@ -34,6 +34,8 @@ Flat buffer helpers (all 1-based line numbers, omit → cursor line):
|
|
|
34
34
|
micro.getLine(n?) micro.putLine(text, n?) micro.delLine(n?)
|
|
35
35
|
micro.getLines(from?, to?) micro.getLinesCount()
|
|
36
36
|
micro.getAllText() — entire buffer as one string (lines joined by "\n")
|
|
37
|
+
micro.getAllAnsiText() — rendered ANSI document, or plain text when unavailable
|
|
38
|
+
micro.clickBufferCell(x, y) — activate a 1-based MDCUI cell, or goto in other buffers
|
|
37
39
|
micro.putAllText(text) — replace entire buffer content; pushes undo
|
|
38
40
|
micro.getSelection() micro.putSelection(text)
|
|
39
41
|
|
|
@@ -44,7 +46,10 @@ Other micro APIs:
|
|
|
44
46
|
e.g. for command "js 1+1": args.raw = "js 1+1", args.raw.slice(3) = "1+1"
|
|
45
47
|
micro.RegisterAction(name, fn) — register bindable action
|
|
46
48
|
micro.TermMessage(msg) — show msg in editor status row
|
|
47
|
-
micro.alert(msg) — suspend editor, print msg, wait for Enter
|
|
49
|
+
micro.alert(msg) — synchronous; suspend editor, print msg, wait for Enter
|
|
50
|
+
e.g. micro.alert("Done") (do not await)
|
|
51
|
+
micro.confirm(msg) — synchronous boolean result
|
|
52
|
+
micro.prompt(msg, default?) — synchronous string or null result
|
|
48
53
|
micro.Log(...args) — console.log passthrough
|
|
49
54
|
micro.GetOption(name) micro.SetOption(name, value)
|
|
50
55
|
micro.cmd.save() — call any editor command via proxy
|
|
@@ -79,7 +84,7 @@ micro.on("init", () => {
|
|
|
79
84
|
},
|
|
80
85
|
async click(x,y,opt){
|
|
81
86
|
x=x||1 ; y=y||1 ;
|
|
82
|
-
await micro.
|
|
87
|
+
await micro.clickBufferCell(x, y);
|
|
83
88
|
},
|
|
84
89
|
async scroll(dx,dy){
|
|
85
90
|
const pane = micro.CurPane();
|
|
@@ -108,54 +113,8 @@ micro.on("init", () => {
|
|
|
108
113
|
bp.Insert(text);
|
|
109
114
|
},
|
|
110
115
|
async press(key, options){
|
|
111
|
-
const
|
|
112
|
-
if (
|
|
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
|
-
}
|
|
116
|
+
const raw = cdpKeyToTerminalInput(key, options);
|
|
117
|
+
if (raw) await micro._dispatchRawInput(raw);
|
|
159
118
|
},
|
|
160
119
|
}
|
|
161
120
|
|
|
@@ -169,7 +128,7 @@ micro.on("init", () => {
|
|
|
169
128
|
const addr = isPublic ? `0.0.0.0:${port}` : `127.0.0.1:${port}`;
|
|
170
129
|
micro.TermMessage(`CDP@${addr} server running 伺服器啟動了`)
|
|
171
130
|
|
|
172
|
-
//
|
|
131
|
+
//micro.alert(CdpServer)
|
|
173
132
|
} // server not running
|
|
174
133
|
else
|
|
175
134
|
{
|
|
@@ -190,3 +149,53 @@ function toInteger(value) {
|
|
|
190
149
|
const number = Number(value);
|
|
191
150
|
return Number.isFinite(number) ? Math.trunc(number) : 0;
|
|
192
151
|
}
|
|
152
|
+
|
|
153
|
+
function cdpKeyToTerminalInput(key, options = {}) {
|
|
154
|
+
const modifiers = Number(options.modifiers) || 0;
|
|
155
|
+
const alt = !!(modifiers & 1);
|
|
156
|
+
const ctrl = !!(modifiers & 2);
|
|
157
|
+
const meta = !!(modifiers & 4);
|
|
158
|
+
const shift = !!(modifiers & 8);
|
|
159
|
+
const modifierCode = 1 + (shift ? 1 : 0) + (alt || meta ? 2 : 0) + (ctrl ? 4 : 0);
|
|
160
|
+
const value = String(key ?? options.text ?? "");
|
|
161
|
+
|
|
162
|
+
const cursorFinal = {
|
|
163
|
+
ArrowUp: "A",
|
|
164
|
+
ArrowDown: "B",
|
|
165
|
+
ArrowRight: "C",
|
|
166
|
+
ArrowLeft: "D",
|
|
167
|
+
Home: "H",
|
|
168
|
+
End: "F",
|
|
169
|
+
}[value];
|
|
170
|
+
if (cursorFinal) {
|
|
171
|
+
return modifierCode === 1
|
|
172
|
+
? `\x1b[${cursorFinal}`
|
|
173
|
+
: `\x1b[1;${modifierCode}${cursorFinal}`;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (value === "PageUp" || value === "PageDown" || value === "Delete") {
|
|
177
|
+
const number = value === "PageUp" ? 5 : value === "PageDown" ? 6 : 3;
|
|
178
|
+
return modifierCode === 1
|
|
179
|
+
? `\x1b[${number}~`
|
|
180
|
+
: `\x1b[${number};${modifierCode}~`;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (value === "Tab") return shift ? "\x1b[Z" : "\t";
|
|
184
|
+
if (value === "Enter") return alt || meta ? "\x1b\r" : "\r";
|
|
185
|
+
if (value === "Backspace") return "\x7f";
|
|
186
|
+
if (value === "Escape") return "\x1b";
|
|
187
|
+
|
|
188
|
+
let text = typeof options.text === "string" && options.text
|
|
189
|
+
? options.text
|
|
190
|
+
: value === "Space" ? " " : value;
|
|
191
|
+
if ([...text].length !== 1) return "";
|
|
192
|
+
|
|
193
|
+
if (ctrl) {
|
|
194
|
+
const code = text.toUpperCase().charCodeAt(0);
|
|
195
|
+
if (code >= 64 && code <= 95) text = String.fromCharCode(code & 31);
|
|
196
|
+
} else if (shift && /^[a-z]$/.test(text)) {
|
|
197
|
+
text = text.toUpperCase();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return alt || meta ? `\x1b${text}` : text;
|
|
201
|
+
}
|
|
@@ -35,6 +35,8 @@ Flat buffer helpers (all 1-based line numbers, omit → cursor line):
|
|
|
35
35
|
micro.getLine(n?) micro.putLine(text, n?) micro.delLine(n?)
|
|
36
36
|
micro.getLines(from?, to?) micro.getLinesCount()
|
|
37
37
|
micro.getAllText() — entire buffer as one string (lines joined by "\n")
|
|
38
|
+
micro.getAllAnsiText() — rendered ANSI document, or plain text when unavailable
|
|
39
|
+
micro.clickBufferCell(x, y) — activate a 1-based MDCUI cell, or goto in other buffers
|
|
38
40
|
micro.putAllText(text) — replace entire buffer content; pushes undo
|
|
39
41
|
micro.getSelection() micro.putSelection(text)
|
|
40
42
|
|
|
@@ -43,7 +45,10 @@ Other micro APIs:
|
|
|
43
45
|
micro.MakeCommand(name, fn) — register Ctrl+E command; fn(bp, args[])
|
|
44
46
|
micro.RegisterAction(name, fn) — register bindable action
|
|
45
47
|
micro.TermMessage(msg) — show msg in editor status row
|
|
46
|
-
micro.alert(msg) — suspend editor, print msg, wait for Enter
|
|
48
|
+
micro.alert(msg) — synchronous; suspend editor, print msg, wait for Enter
|
|
49
|
+
e.g. micro.alert("Done") (do not await)
|
|
50
|
+
micro.confirm(msg) — synchronous boolean result
|
|
51
|
+
micro.prompt(msg, default?) — synchronous string or null result
|
|
47
52
|
micro.Log(...args) — console.log passthrough
|
|
48
53
|
micro.GetOption(name) micro.SetOption(name, value)
|
|
49
54
|
micro.cmd.save() — call any editor command via proxy
|
|
@@ -104,5 +109,3 @@ micro.on("init", () => {
|
|
|
104
109
|
});
|
|
105
110
|
|
|
106
111
|
});
|
|
107
|
-
|
|
108
|
-
|
|
@@ -35,6 +35,8 @@ Flat buffer helpers (all 1-based line numbers, omit → cursor line):
|
|
|
35
35
|
micro.getLine(n?) micro.putLine(text, n?) micro.delLine(n?)
|
|
36
36
|
micro.getLines(from?, to?) micro.getLinesCount()
|
|
37
37
|
micro.getAllText() — entire buffer as one string (lines joined by "\n")
|
|
38
|
+
micro.getAllAnsiText() — rendered ANSI document, or plain text when unavailable
|
|
39
|
+
micro.clickBufferCell(x, y) — activate a 1-based MDCUI cell, or goto in other buffers
|
|
38
40
|
micro.putAllText(text) — replace entire buffer content; pushes undo
|
|
39
41
|
micro.getSelection() micro.putSelection(text)
|
|
40
42
|
|
|
@@ -45,7 +47,10 @@ Other micro APIs:
|
|
|
45
47
|
e.g. for command "js 1+1": args.raw = "js 1+1", args.raw.slice(3) = "1+1"
|
|
46
48
|
micro.RegisterAction(name, fn) — register bindable action
|
|
47
49
|
micro.TermMessage(msg) — show msg in editor status row
|
|
48
|
-
micro.alert(msg) — suspend editor, print msg, wait for Enter
|
|
50
|
+
micro.alert(msg) — synchronous; suspend editor, print msg, wait for Enter
|
|
51
|
+
e.g. micro.alert("Done") (do not await)
|
|
52
|
+
micro.confirm(msg) — synchronous boolean result
|
|
53
|
+
micro.prompt(msg, default?) — synchronous string or null result
|
|
49
54
|
micro.Log(...args) — console.log passthrough
|
|
50
55
|
micro.GetOption(name) micro.SetOption(name, value)
|
|
51
56
|
micro.cmd.save() — call any editor command via proxy
|
|
@@ -58,14 +63,14 @@ Other micro APIs:
|
|
|
58
63
|
|
|
59
64
|
micro.on("init", () => {
|
|
60
65
|
// Register a custom Ctrl+E command
|
|
61
|
-
micro.MakeCommand("showpath",
|
|
66
|
+
micro.MakeCommand("showpath", (bp, args) =>
|
|
62
67
|
{
|
|
63
68
|
//micro.TermMessage("Hello from JS plugin! Args: " + args.join(", "));
|
|
64
69
|
|
|
65
|
-
//
|
|
70
|
+
//micro.alert(micro.getLine())
|
|
66
71
|
const path = bp?.Buf?.Path || "(no path)";
|
|
67
72
|
const loc = bp?.CursorLocation?.() || "+1.0:1";
|
|
68
|
-
|
|
73
|
+
micro.alert(`${path}\n${loc}`)
|
|
69
74
|
|
|
70
75
|
});
|
|
71
76
|
|
|
@@ -75,12 +80,12 @@ micro.on("init", () => {
|
|
|
75
80
|
// args.raw is the full original input string, e.g. "js console.log('hi')"
|
|
76
81
|
// slice(3) skips the "js " prefix (2-char name + 1 space)
|
|
77
82
|
const scriptText = args.raw?.slice(3) ?? args.join(" ");
|
|
78
|
-
|
|
83
|
+
micro.alert(await eval(scriptText));
|
|
79
84
|
});
|
|
80
85
|
|
|
81
86
|
|
|
82
87
|
// greet: shows a message via micro.alert (leaves editor, shows text, Enter returns)
|
|
83
|
-
micro.MakeCommand("greet",
|
|
88
|
+
micro.MakeCommand("greet", (bp, args) =>
|
|
84
89
|
{
|
|
85
90
|
const name = args.length ?
|
|
86
91
|
args.join(" ") : "world";
|
|
@@ -89,7 +94,7 @@ micro.on("init", () => {
|
|
|
89
94
|
'# Hello\n- '+name
|
|
90
95
|
);
|
|
91
96
|
|
|
92
|
-
|
|
97
|
+
micro.alert(s);
|
|
93
98
|
});
|
|
94
99
|
|
|
95
100
|
// Register a custom action (can be bound to a key in keybindings.json)
|