bunmicro 0.9.19 → 0.9.21
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 +10 -0
- package/README.md +38 -2
- package/package.json +1 -1
- package/runtime/help/options.md +8 -0
- package/runtime/jsplugins/example/example.js +6 -6
- package/src/config/defaults.js +7 -0
- package/src/index.js +142 -6
- package/src/screen/screen.js +16 -4
- package/todo.txt +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.9.21] - 2026-06-09
|
|
4
|
+
- Prompt mouse click repositions cursor (command and shell prompt)
|
|
5
|
+
- Clicking > or $ label toggles between command/shell prompt, preserving input
|
|
6
|
+
- Prompt mouse double click: left=key-down, middle=key-up, right=key-enter (command and shell prompt)
|
|
7
|
+
- js and eval js/py/sh commands
|
|
8
|
+
|
|
9
|
+
## [0.9.20] - 2026-06-07
|
|
10
|
+
- Added cursor shape option
|
|
11
|
+
- Fixed selection hide cursor
|
|
12
|
+
|
|
3
13
|
## [0.9.19] - 2026-06-06
|
|
4
14
|
- Fixed Windows clipboard
|
|
5
15
|
- Fixed cli encoding help readme
|
package/README.md
CHANGED
|
@@ -24,12 +24,13 @@
|
|
|
24
24
|
- Alt-s to enter selection mode
|
|
25
25
|
- Useful without a mouse on Android
|
|
26
26
|
- Also available: Ctrl-E act SelectRight
|
|
27
|
-
## js plugin
|
|
27
|
+
## js plugin / command
|
|
28
28
|
- Instead of writing Lua, use your familiar JavaScript to extend functionalities
|
|
29
29
|
- runtime/jsplugins/`name`/`name`.js
|
|
30
30
|
- a full documentation in example.js
|
|
31
31
|
- an example plugin named chapter for turning to the next/prev page by number.
|
|
32
32
|
- It registers 2 commands: next/prevchapter
|
|
33
|
+
- js and eval js command explained at the bottom
|
|
33
34
|
## Output highlighted text to terminal
|
|
34
35
|
- Works like bat ccat glow
|
|
35
36
|
- bunmicro -bat file
|
|
@@ -179,16 +180,51 @@ bun x bunmicro
|
|
|
179
180
|
- Encoding: Reopen with a specific encoding.
|
|
180
181
|
* Show supported encodings by bunmicro --version
|
|
181
182
|
- Alt-G: Show nano-like key bindings menu
|
|
183
|
+
- Command/Shell Prompt row:
|
|
184
|
+
* See the next section
|
|
182
185
|
|
|
183
186
|
# Command/Shell Prompts
|
|
184
187
|
## Command
|
|
185
188
|
- Internal commands for automating / tuning bunmicro
|
|
186
189
|
- Press Tab for available commands, arrow keys for selection
|
|
187
190
|
- In this Bun version, I added more commands like
|
|
188
|
-
* js to eval JavaScript
|
|
189
191
|
* act/action to do automation actions.
|
|
190
192
|
* Press tab after act to get a list of them
|
|
191
193
|
* or use help actions to show the list
|
|
194
|
+
|
|
195
|
+
## eval — run code in py/js/sh
|
|
196
|
+
- Executed in a separate processs
|
|
197
|
+
- `eval js` — run selected text as JavaScript (via Bun)
|
|
198
|
+
- `eval py` — run selected text as Python (python3 / python on Windows)
|
|
199
|
+
- `eval sh` — run selected text as shell script (/bin/sh, or Bun shell on Windows)
|
|
200
|
+
- If no text is selected, append code inline:
|
|
201
|
+
* `eval js console.log("hi")`
|
|
202
|
+
* `eval py print("hi")`
|
|
203
|
+
* `eval sh echo hi`
|
|
204
|
+
- Code after the language name is taken literally (no shell quoting)
|
|
205
|
+
- Output is shown in the terminal (same as Ctrl-B shell mode)
|
|
206
|
+
- Runs via a temp file (`bunmicro-tmpXXXXXXXX.js/py/sh`), deleted after execution
|
|
207
|
+
|
|
208
|
+
## js — eval JavaScript inline
|
|
209
|
+
- Executed inside bunmicro's jsplugin context,
|
|
210
|
+
- `js <expression or code>`
|
|
211
|
+
- Code is passed raw to await eval (bypass shell quoting), result shown via alert
|
|
212
|
+
- Examples:
|
|
213
|
+
- `js new Date().toISOString()`
|
|
214
|
+
- `js let ln=micro.getLine(); ln`
|
|
215
|
+
- Implemented in `runtime/jsplugins/example/example.js` as a demo of `micro.MakeCommand`
|
|
192
216
|
## Shell
|
|
193
217
|
- Executes a given shell command like sh -c
|
|
194
218
|
- Outputs the result to the original terminal before entering bunmicro
|
|
219
|
+
## Prompt mouse gestures
|
|
220
|
+
- Click anywhere: move cursor to that position
|
|
221
|
+
- Click `>` or `$` label: toggle between Command and Shell mode (input is preserved)
|
|
222
|
+
- Double click left ⅓ of row: newer history (same as ↓ key)
|
|
223
|
+
- Double click middle ⅓ of row: older history (same as ↑ key)
|
|
224
|
+
- Double click right ⅓ of row: execute the current input (same as Enter)
|
|
225
|
+
## Prompt keyboard shortcuts
|
|
226
|
+
- `Ctrl-U`: delete everything before cursor
|
|
227
|
+
- `Ctrl-K`: delete everything after cursor
|
|
228
|
+
- `Ctrl-A` / `Home`: move cursor to start
|
|
229
|
+
- `Ctrl-E` / `End`: move cursor to end
|
|
230
|
+
- `↑` / `↓`: navigate command history
|
package/package.json
CHANGED
package/runtime/help/options.md
CHANGED
|
@@ -94,6 +94,13 @@ Here are the available options:
|
|
|
94
94
|
|
|
95
95
|
default value: `true`
|
|
96
96
|
|
|
97
|
+
* `cursorshape`: sets the editor cursor shape using the terminal's DECSCUSR
|
|
98
|
+
support. This setting is `global only`. Supported values are `default`,
|
|
99
|
+
`block`, `underline`, `bar`, and their `blinking-` variants. The plain
|
|
100
|
+
shape names use a steady cursor.
|
|
101
|
+
|
|
102
|
+
default value: `block`
|
|
103
|
+
|
|
97
104
|
* `detectlimit`: if this is not set to 0, it will limit the amount of first
|
|
98
105
|
lines in a file that are matched to determine the filetype.
|
|
99
106
|
A higher limit means better accuracy of guessing the filetype, but also
|
|
@@ -566,6 +573,7 @@ so that you can see what the formatting should look like.
|
|
|
566
573
|
"colorscheme": "default",
|
|
567
574
|
"comment": true,
|
|
568
575
|
"cursorline": true,
|
|
576
|
+
"cursorshape": "block",
|
|
569
577
|
"detectlimit": 100,
|
|
570
578
|
"diff": true,
|
|
571
579
|
"diffgutter": false,
|
|
@@ -41,6 +41,8 @@ Flat buffer helpers (all 1-based line numbers, omit → cursor line):
|
|
|
41
41
|
Other micro APIs:
|
|
42
42
|
micro.CurPane() — returns pane adapter for active pane
|
|
43
43
|
micro.MakeCommand(name, fn) — register Ctrl+E command; fn(bp, args[])
|
|
44
|
+
args.raw = full original input string (bypass shellSplit)
|
|
45
|
+
e.g. for command "js 1+1": args.raw = "js 1+1", args.raw.slice(3) = "1+1"
|
|
44
46
|
micro.RegisterAction(name, fn) — register bindable action
|
|
45
47
|
micro.TermMessage(msg) — show msg in editor status row
|
|
46
48
|
micro.alert(msg) — suspend editor, print msg, wait for Enter
|
|
@@ -70,12 +72,10 @@ micro.on("init", () => {
|
|
|
70
72
|
|
|
71
73
|
micro.MakeCommand("js", async (bp, args) =>
|
|
72
74
|
{
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
)
|
|
78
|
-
|
|
75
|
+
// args.raw is the full original input string, e.g. "js console.log('hi')"
|
|
76
|
+
// slice(3) skips the "js " prefix (2-char name + 1 space)
|
|
77
|
+
const scriptText = args.raw?.slice(3) ?? args.join(" ");
|
|
78
|
+
await micro.alert(await eval(scriptText));
|
|
79
79
|
});
|
|
80
80
|
|
|
81
81
|
|
package/src/config/defaults.js
CHANGED
|
@@ -57,6 +57,7 @@ export const DEFAULT_GLOBAL_ONLY_SETTINGS = {
|
|
|
57
57
|
autosave: 0,
|
|
58
58
|
clipboard: "external",
|
|
59
59
|
colorscheme: "default",
|
|
60
|
+
cursorshape: "block",
|
|
60
61
|
savehistory: true,
|
|
61
62
|
divchars: "|-",
|
|
62
63
|
divreverse: true,
|
|
@@ -81,6 +82,12 @@ export const DEFAULT_GLOBAL_ONLY_SETTINGS = {
|
|
|
81
82
|
|
|
82
83
|
export const OPTION_CHOICES = {
|
|
83
84
|
clipboard: ["internal", "external", "terminal"],
|
|
85
|
+
cursorshape: [
|
|
86
|
+
"default",
|
|
87
|
+
"block", "blinking-block",
|
|
88
|
+
"underline", "blinking-underline",
|
|
89
|
+
"bar", "blinking-bar",
|
|
90
|
+
],
|
|
84
91
|
fileformat: ["unix", "dos"],
|
|
85
92
|
helpsplit: ["hsplit", "vsplit"],
|
|
86
93
|
matchbracestyle: ["underline", "highlight"],
|
package/src/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
3
|
import child_process from "node:child_process"
|
|
4
|
-
import { accessSync, constants, existsSync, readdirSync, statSync } from "node:fs";
|
|
4
|
+
import { accessSync, constants, existsSync, readdirSync, statSync, unlinkSync } from "node:fs";
|
|
5
5
|
import { mkdir } from "node:fs/promises";
|
|
6
6
|
import { dirname, basename, join, resolve, sep } from "node:path";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
@@ -81,6 +81,7 @@ const DEFAULT_SETTINGS = {
|
|
|
81
81
|
tabsize: 4,
|
|
82
82
|
tabstospaces: false,
|
|
83
83
|
autosave: 0,
|
|
84
|
+
cursorshape: "block",
|
|
84
85
|
cursorline: true,
|
|
85
86
|
diffgutter: false,
|
|
86
87
|
eofnewline: true,
|
|
@@ -1249,6 +1250,9 @@ class BufferModel {
|
|
|
1249
1250
|
SetOption(option, value) {
|
|
1250
1251
|
const oldValue = this.Settings[option];
|
|
1251
1252
|
const parsed = parseOptionValue(value);
|
|
1253
|
+
if (option === "cursorshape" && !OPTION_CHOICES.cursorshape.includes(String(parsed))) {
|
|
1254
|
+
throw new Error(`Invalid value for cursorshape: ${parsed}`);
|
|
1255
|
+
}
|
|
1252
1256
|
if (option === "fileformat" && !OPTION_CHOICES.fileformat.includes(String(parsed))) {
|
|
1253
1257
|
throw new Error(`Invalid value for fileformat: ${parsed}`);
|
|
1254
1258
|
}
|
|
@@ -1263,6 +1267,9 @@ class BufferModel {
|
|
|
1263
1267
|
|
|
1264
1268
|
DoSetOptionNative(option, value) {
|
|
1265
1269
|
const oldValue = this.Settings[option];
|
|
1270
|
+
if (option === "cursorshape" && !OPTION_CHOICES.cursorshape.includes(String(value))) {
|
|
1271
|
+
throw new Error(`Invalid value for cursorshape: ${value}`);
|
|
1272
|
+
}
|
|
1266
1273
|
if (option === "fileformat" && !OPTION_CHOICES.fileformat.includes(String(value))) {
|
|
1267
1274
|
throw new Error(`Invalid value for fileformat: ${value}`);
|
|
1268
1275
|
}
|
|
@@ -1928,7 +1935,11 @@ class App {
|
|
|
1928
1935
|
const totalText = this.prompt.label + this.prompt.value;
|
|
1929
1936
|
const labelW = displayWidth(this.prompt.label);
|
|
1930
1937
|
const cursorInTotal = labelW + displayWidth(this.prompt.value.slice(0, this.prompt.cursor));
|
|
1931
|
-
|
|
1938
|
+
let scrollX = this._promptScrollX ?? 0;
|
|
1939
|
+
if (cursorInTotal > scrollX + this.cols - 1) scrollX = cursorInTotal - (this.cols - 1);
|
|
1940
|
+
if (cursorInTotal < scrollX) scrollX = cursorInTotal;
|
|
1941
|
+
scrollX = Math.max(0, scrollX);
|
|
1942
|
+
this._promptScrollX = scrollX;
|
|
1932
1943
|
const startIdx = scrollX > 0 ? visualColToCharIdx(totalText, 0, scrollX) : 0;
|
|
1933
1944
|
putText(this.screen, 0, promptRow, totalText.slice(startIdx), promptStyle, this.cols);
|
|
1934
1945
|
this.screen.setCursor(cursorInTotal - scrollX, promptRow, true, "bar");
|
|
@@ -1966,8 +1977,19 @@ class App {
|
|
|
1966
1977
|
cursorCol = p.x + gutterW + displayWidth(line.slice(buf.scroll.x, cursorX));
|
|
1967
1978
|
}
|
|
1968
1979
|
|
|
1969
|
-
|
|
1970
|
-
|
|
1980
|
+
// Go micro hides the terminal cursor while a non-empty selection is
|
|
1981
|
+
// active. Otherwise its block cursor makes the exclusive selection end
|
|
1982
|
+
// look selected even though copy/cut correctly omit that character.
|
|
1983
|
+
const hasSelection = p.selection && !sameLoc(p.selection.start, p.selection.end);
|
|
1984
|
+
const cursorVisible = !hasSelection &&
|
|
1985
|
+
cursorRow >= p.y && cursorRow < p.y + p.h &&
|
|
1986
|
+
cursorCol >= p.x && cursorCol < p.x + p.w;
|
|
1987
|
+
this.screen.setCursor(
|
|
1988
|
+
clamp(cursorCol, 0, this.cols - 1),
|
|
1989
|
+
clamp(cursorRow, 0, this.rows - 1),
|
|
1990
|
+
cursorVisible,
|
|
1991
|
+
DEFAULT_SETTINGS.cursorshape,
|
|
1992
|
+
);
|
|
1971
1993
|
} else if (!this.prompt && activePaneObj?.type !== "term") {
|
|
1972
1994
|
this.screen.setCursor(0, 0, false);
|
|
1973
1995
|
}
|
|
@@ -3173,6 +3195,61 @@ class App {
|
|
|
3173
3195
|
this._suppressMouseUntilUp = false;
|
|
3174
3196
|
}
|
|
3175
3197
|
|
|
3198
|
+
// Prompt row click: reposition cursor, toggle shell/command mode, or zone double-click
|
|
3199
|
+
if (this.prompt && event.y === this.rows - 1) {
|
|
3200
|
+
if ((event.action === "down" || event.action === "drag") && event.button === "left") {
|
|
3201
|
+
const totalText = this.prompt.label + this.prompt.value;
|
|
3202
|
+
const scrollX = this._promptScrollX ?? 0;
|
|
3203
|
+
const startIdx = scrollX > 0 ? visualColToCharIdx(totalText, 0, scrollX) : 0;
|
|
3204
|
+
const clickedCharIdx = visualColToCharIdx(totalText, startIdx, event.x);
|
|
3205
|
+
|
|
3206
|
+
if (event.action === "down") {
|
|
3207
|
+
// Double-click zone: divide prompt row into thirds
|
|
3208
|
+
const third = Math.floor(this.cols / 3);
|
|
3209
|
+
const zone = event.x < third ? "left" : event.x < third * 2 ? "middle" : "right";
|
|
3210
|
+
const now = Date.now();
|
|
3211
|
+
const isDoubleClick = this._lastPromptClickZone === zone &&
|
|
3212
|
+
now - (this._lastPromptClickTime ?? 0) < 400;
|
|
3213
|
+
this._lastPromptClickTime = now;
|
|
3214
|
+
this._lastPromptClickZone = zone;
|
|
3215
|
+
|
|
3216
|
+
if (isDoubleClick && !this.prompt.yn) {
|
|
3217
|
+
if (zone === "left") {
|
|
3218
|
+
this.prompt.historyDown();
|
|
3219
|
+
} else if (zone === "middle") {
|
|
3220
|
+
this.prompt.historyUp();
|
|
3221
|
+
} else {
|
|
3222
|
+
const prompt = this.prompt;
|
|
3223
|
+
this.prompt = null;
|
|
3224
|
+
prompt.commit();
|
|
3225
|
+
await prompt.callback(prompt.value);
|
|
3226
|
+
}
|
|
3227
|
+
this.render();
|
|
3228
|
+
return;
|
|
3229
|
+
}
|
|
3230
|
+
|
|
3231
|
+
// Single click on label (> or $): toggle Command ↔ Shell
|
|
3232
|
+
if (clickedCharIdx < this.prompt.label.length &&
|
|
3233
|
+
(this.prompt.type === "Command" || this.prompt.type === "Shell")) {
|
|
3234
|
+
const val = this.prompt.value;
|
|
3235
|
+
if (this.prompt.type === "Command") {
|
|
3236
|
+
this.openShellMode();
|
|
3237
|
+
this.prompt.value = val;
|
|
3238
|
+
this.prompt.cursor = val.length;
|
|
3239
|
+
} else {
|
|
3240
|
+
this.openCommandMode(val);
|
|
3241
|
+
}
|
|
3242
|
+
this.render();
|
|
3243
|
+
return;
|
|
3244
|
+
}
|
|
3245
|
+
}
|
|
3246
|
+
|
|
3247
|
+
this.prompt.cursor = Math.max(0, Math.min(clickedCharIdx - this.prompt.label.length, this.prompt.value.length));
|
|
3248
|
+
this.render();
|
|
3249
|
+
}
|
|
3250
|
+
return;
|
|
3251
|
+
}
|
|
3252
|
+
|
|
3176
3253
|
if (this.handleSuggestionMouse(event)) {
|
|
3177
3254
|
this.render();
|
|
3178
3255
|
return;
|
|
@@ -3593,6 +3670,19 @@ class App {
|
|
|
3593
3670
|
this.prompt.cursor = 0;
|
|
3594
3671
|
} else if (key === "end" || key === "ctrl-e") {
|
|
3595
3672
|
this.prompt.cursor = this.prompt.value.length;
|
|
3673
|
+
} else if (key === "ctrl-u") {
|
|
3674
|
+
const prompt = this.prompt;
|
|
3675
|
+
prompt.value = prompt.value.slice(prompt.cursor);
|
|
3676
|
+
prompt.cursor = 0;
|
|
3677
|
+
prompt.resetCompletion();
|
|
3678
|
+
this.message = "";
|
|
3679
|
+
prompt.onDelta?.(prompt.value);
|
|
3680
|
+
} else if (key === "ctrl-k") {
|
|
3681
|
+
const prompt = this.prompt;
|
|
3682
|
+
prompt.value = prompt.value.slice(0, prompt.cursor);
|
|
3683
|
+
prompt.resetCompletion();
|
|
3684
|
+
this.message = "";
|
|
3685
|
+
prompt.onDelta?.(prompt.value);
|
|
3596
3686
|
} else if (key === "backspace") {
|
|
3597
3687
|
const prompt = this.prompt;
|
|
3598
3688
|
if (prompt.cursor > 0) {
|
|
@@ -3656,6 +3746,7 @@ class App {
|
|
|
3656
3746
|
|
|
3657
3747
|
openPrompt(label, callback, options = {}) {
|
|
3658
3748
|
this.prompt = new Prompt(label, callback, options);
|
|
3749
|
+
this._promptScrollX = 0;
|
|
3659
3750
|
}
|
|
3660
3751
|
|
|
3661
3752
|
openYNPrompt(label, callback, { onCancel = null } = {}) {
|
|
@@ -4536,9 +4627,53 @@ class App {
|
|
|
4536
4627
|
this.message = toSpaces ? "Retabbed to spaces" : "Retabbed to tabs";
|
|
4537
4628
|
break;
|
|
4538
4629
|
}
|
|
4539
|
-
case "eval":
|
|
4540
|
-
|
|
4630
|
+
case "eval": {
|
|
4631
|
+
const lang = cmdArgs[0];
|
|
4632
|
+
if (!lang) { this.message = "Usage: eval js|py|sh [code]"; break; }
|
|
4633
|
+
if (lang !== "js" && lang !== "py" && lang !== "sh") {
|
|
4634
|
+
this.message = `eval: unknown language '${lang}' — use js, py, or sh`;
|
|
4635
|
+
break;
|
|
4636
|
+
}
|
|
4637
|
+
// Code source: inline (raw, bypass shell quoting) or selection
|
|
4638
|
+
let evalCode;
|
|
4639
|
+
const inlineMatch = /^\s*eval\s+(?:js|py|sh)\s+(.+)$/s.exec(input);
|
|
4640
|
+
if (inlineMatch) {
|
|
4641
|
+
evalCode = inlineMatch[1];
|
|
4642
|
+
} else {
|
|
4643
|
+
const sel = this.pane?.selection;
|
|
4644
|
+
evalCode = (sel && !sameLoc(sel.start, sel.end)) ? getSelectionText(buf, sel) : null;
|
|
4645
|
+
if (!evalCode) { this.message = `eval ${lang}: select text, or use: eval ${lang} <code>`; break; }
|
|
4646
|
+
}
|
|
4647
|
+
// Build temp file
|
|
4648
|
+
const { tmpdir } = await import("node:os");
|
|
4649
|
+
const suffix = crypto.randomUUID().replace(/-/g, "").slice(0, 8);
|
|
4650
|
+
let ext, execArgs, fileContent = evalCode;
|
|
4651
|
+
if (lang === "js") {
|
|
4652
|
+
ext = "js";
|
|
4653
|
+
execArgs = [Bun.which("bun") ?? "bun"];
|
|
4654
|
+
} else if (lang === "py") {
|
|
4655
|
+
ext = "py";
|
|
4656
|
+
const pyBin = process.platform === "win32" ? "python" : "python3";
|
|
4657
|
+
execArgs = [Bun.which(pyBin) ?? pyBin];
|
|
4658
|
+
} else { // sh
|
|
4659
|
+
if (existsSync("/bin/sh")) {
|
|
4660
|
+
ext = "sh";
|
|
4661
|
+
execArgs = ["/bin/sh"];
|
|
4662
|
+
} else {
|
|
4663
|
+
ext = "js";
|
|
4664
|
+
fileContent = `import { $ } from "bun";\nawait $\`\n${evalCode}\n\`;\n`;
|
|
4665
|
+
execArgs = [Bun.which("bun") ?? "bun"];
|
|
4666
|
+
}
|
|
4667
|
+
}
|
|
4668
|
+
const evalTmpFile = join(tmpdir(), `bunmicro-tmp${suffix}.${ext}`);
|
|
4669
|
+
await Bun.write(evalTmpFile, fileContent);
|
|
4670
|
+
try {
|
|
4671
|
+
await this.runInteractiveShell([...execArgs, evalTmpFile]);
|
|
4672
|
+
} finally {
|
|
4673
|
+
try { unlinkSync(evalTmpFile); } catch {}
|
|
4674
|
+
}
|
|
4541
4675
|
break;
|
|
4676
|
+
}
|
|
4542
4677
|
case "bind":
|
|
4543
4678
|
case "unbind":
|
|
4544
4679
|
this.message = `${cmd}: keybinding system not yet implemented`;
|
|
@@ -4642,6 +4777,7 @@ class App {
|
|
|
4642
4777
|
const pluginCmd = this.context.plugins?.commands?.get(cmd);
|
|
4643
4778
|
if (pluginCmd) {
|
|
4644
4779
|
try {
|
|
4780
|
+
cmdArgs.raw = input;
|
|
4645
4781
|
await pluginCmd(makePaneAdapter(this.buffer, this), cmdArgs);
|
|
4646
4782
|
} catch (e) {
|
|
4647
4783
|
this.message = String(e.message ?? e);
|
package/src/screen/screen.js
CHANGED
|
@@ -3,6 +3,20 @@ import { styleToAnsi } from "../display/ansi-style.js";
|
|
|
3
3
|
import { CellBuffer } from "./cell-buffer.js";
|
|
4
4
|
import { DISABLE_MOUSE, DISABLE_PASTE, ENABLE_MOUSE, ENABLE_PASTE, ResizeEvent } from "./events.js";
|
|
5
5
|
|
|
6
|
+
const CURSOR_SHAPE_SEQUENCE = {
|
|
7
|
+
default: "\x1b[0 q",
|
|
8
|
+
"blinking-block": "\x1b[1 q",
|
|
9
|
+
block: "\x1b[2 q",
|
|
10
|
+
"blinking-underline": "\x1b[3 q",
|
|
11
|
+
underline: "\x1b[4 q",
|
|
12
|
+
"blinking-bar": "\x1b[5 q",
|
|
13
|
+
bar: "\x1b[6 q",
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function cursorShapeSequence(shape) {
|
|
17
|
+
return CURSOR_SHAPE_SEQUENCE[shape] ?? CURSOR_SHAPE_SEQUENCE.block;
|
|
18
|
+
}
|
|
19
|
+
|
|
6
20
|
export class Screen {
|
|
7
21
|
constructor({ mouse = true } = {}) {
|
|
8
22
|
this.mouse = mouse;
|
|
@@ -23,7 +37,7 @@ export class Screen {
|
|
|
23
37
|
fini() {
|
|
24
38
|
if (this.mouse) this.write(DISABLE_MOUSE);
|
|
25
39
|
this.write(DISABLE_PASTE);
|
|
26
|
-
this.write("\x1b[?25h\x1b[?1049l\x1b[0m");
|
|
40
|
+
this.write("\x1b[0 q\x1b[?25h\x1b[?1049l\x1b[0m");
|
|
27
41
|
}
|
|
28
42
|
|
|
29
43
|
SetContent(x, y, ch, combining = [], style = null) {
|
|
@@ -81,9 +95,7 @@ export class Screen {
|
|
|
81
95
|
}
|
|
82
96
|
out += "\x1b[0m";
|
|
83
97
|
if (this.cursor && this.cursorVisible) {
|
|
84
|
-
|
|
85
|
-
const shape = this.cursor.shape === "bar" ? "\x1b[5 q" : this.cursor.shape === "steady-block" ? "\x1b[2 q" : "\x1b[1 q";
|
|
86
|
-
out += shape + this.move(this.cursor.y + 1, this.cursor.x + 1) + "\x1b[?25h";
|
|
98
|
+
out += cursorShapeSequence(this.cursor.shape) + this.move(this.cursor.y + 1, this.cursor.x + 1) + "\x1b[?25h";
|
|
87
99
|
} else out += "\x1b[?25l";
|
|
88
100
|
this.write(out);
|
|
89
101
|
this.previous = this.cells.clone();
|
package/todo.txt
CHANGED
|
@@ -15,6 +15,7 @@ Current handoff notes
|
|
|
15
15
|
- Always emits ANSI codes regardless of TTY state.
|
|
16
16
|
- -profile removed from usage (flag parsed but never implemented in Go either; -debug kept for future log.txt work).
|
|
17
17
|
[x] Recent editor UX parity implemented:
|
|
18
|
+
- Global cursorshape option supports common DECSCUSR cursor shapes: default, steady/blinking block, underline, and bar, with command completion.
|
|
18
19
|
- Tab autocomplete candidate row cycles highlight correctly and only highlights the selected candidate text.
|
|
19
20
|
- Tab autocomplete single-match case fixed: was silently no-op due to acHas=false; now inserts suffix directly.
|
|
20
21
|
- Mouse click on autocomplete candidates no longer moves the editor cursor to the click location.
|