@oxecli/oxe 1.0.107 → 1.0.108
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/dist/oxe.js +10 -2
- package/dist/ui.js +100 -17
- package/package.json +1 -1
package/dist/oxe.js
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import { main } from "./cli.js";
|
|
2
|
+
import { dockTearDown, showCursor } from "./ui.js";
|
|
2
3
|
export { main };
|
|
3
4
|
// The TUI keeps the terminal cursor hidden except inside a prompt field. Always
|
|
4
|
-
// restore it on exit so the shell isn't
|
|
5
|
+
// restore it on exit and tear down any docked prompt box so the shell isn't
|
|
6
|
+
// left with a stale prompt box or an invisible cursor.
|
|
5
7
|
process.on("exit", () => {
|
|
6
8
|
try {
|
|
7
|
-
|
|
9
|
+
dockTearDown();
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
/* ignore */
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
showCursor();
|
|
8
16
|
}
|
|
9
17
|
catch {
|
|
10
18
|
/* ignore */
|
package/dist/ui.js
CHANGED
|
@@ -173,6 +173,81 @@ export function truncateStyled(text, maxVisible) {
|
|
|
173
173
|
}
|
|
174
174
|
return out;
|
|
175
175
|
}
|
|
176
|
+
/**
|
|
177
|
+
* Wrap a styled (ANSI) string into lines of at most `width` visible characters,
|
|
178
|
+
* wrapping whole words onto new lines and splitting any single word longer than
|
|
179
|
+
* `width`. ANSI escape sequences are preserved and re-attached to their glyphs,
|
|
180
|
+
* so no style is lost and no escape sequence is ever cut mid-way.
|
|
181
|
+
*/
|
|
182
|
+
function wrapStyledToWidth(line, width) {
|
|
183
|
+
if (width <= 0)
|
|
184
|
+
return [line];
|
|
185
|
+
if (plainLen(line) <= width)
|
|
186
|
+
return [line];
|
|
187
|
+
// Tokenize into glyph units, each carrying the ANSI prefix that precedes it.
|
|
188
|
+
const units = [];
|
|
189
|
+
const re = /(\x1b\[[0-9;]*[a-zA-Z]|\x1b\].*?\x07)|([\s\S])/g;
|
|
190
|
+
let pending = "";
|
|
191
|
+
let m;
|
|
192
|
+
re.lastIndex = 0;
|
|
193
|
+
while ((m = re.exec(line)) !== null) {
|
|
194
|
+
if (m[1] !== undefined)
|
|
195
|
+
pending += m[1];
|
|
196
|
+
else {
|
|
197
|
+
units.push({ ch: m[2], pre: pending });
|
|
198
|
+
pending = "";
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
const out = [];
|
|
202
|
+
let cur = "";
|
|
203
|
+
let curLen = 0;
|
|
204
|
+
let word = [];
|
|
205
|
+
const emitChunk = (chunk, prependSpace) => {
|
|
206
|
+
const add = (prependSpace && curLen > 0 ? 1 : 0) + chunk.length;
|
|
207
|
+
if (curLen + add > width)
|
|
208
|
+
return false;
|
|
209
|
+
if (prependSpace && curLen > 0) {
|
|
210
|
+
cur += " ";
|
|
211
|
+
curLen++;
|
|
212
|
+
}
|
|
213
|
+
for (const u of chunk)
|
|
214
|
+
cur += u.pre + u.ch;
|
|
215
|
+
curLen += chunk.length;
|
|
216
|
+
return true;
|
|
217
|
+
};
|
|
218
|
+
const flushWord = () => {
|
|
219
|
+
if (!word.length)
|
|
220
|
+
return;
|
|
221
|
+
const wlen = word.length;
|
|
222
|
+
// Split overlong words into <= width chunks so nothing is ever cut off.
|
|
223
|
+
const chunks = [];
|
|
224
|
+
if (wlen <= width)
|
|
225
|
+
chunks.push(word);
|
|
226
|
+
else
|
|
227
|
+
for (let i = 0; i < wlen; i += width)
|
|
228
|
+
chunks.push(word.slice(i, i + width));
|
|
229
|
+
for (let ci = 0; ci < chunks.length; ci++) {
|
|
230
|
+
const chunk = chunks[ci];
|
|
231
|
+
if (!emitChunk(chunk, ci === 0)) {
|
|
232
|
+
out.push(cur);
|
|
233
|
+
cur = "";
|
|
234
|
+
curLen = 0;
|
|
235
|
+
emitChunk(chunk, false);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
word = [];
|
|
239
|
+
};
|
|
240
|
+
for (const u of units) {
|
|
241
|
+
if (/\s/.test(u.ch))
|
|
242
|
+
flushWord();
|
|
243
|
+
else
|
|
244
|
+
word.push(u);
|
|
245
|
+
}
|
|
246
|
+
flushWord();
|
|
247
|
+
if (cur)
|
|
248
|
+
out.push(cur);
|
|
249
|
+
return out.length ? out : [line];
|
|
250
|
+
}
|
|
176
251
|
// ---------------------------------------------------------------------------
|
|
177
252
|
// Markup tag -> ANSI
|
|
178
253
|
// ---------------------------------------------------------------------------
|
|
@@ -412,22 +487,11 @@ export function markdownToAnsi(text) {
|
|
|
412
487
|
if (!line.includes("\x1b[36m•\x1b[0m") && !line.includes(".\x1b[0m ")) {
|
|
413
488
|
line = formatInlineMarkdown(line);
|
|
414
489
|
}
|
|
415
|
-
// Word wrap paragraph line if too long
|
|
490
|
+
// Word wrap paragraph line if too long. Whole words move to a new row; any
|
|
491
|
+
// single word longer than the width is split so nothing is ever cut off at
|
|
492
|
+
// the right edge.
|
|
416
493
|
if (plainLen(line) > width && !line.startsWith(" ")) {
|
|
417
|
-
|
|
418
|
-
let cur = "";
|
|
419
|
-
for (const w of words) {
|
|
420
|
-
if (plainLen(cur) + plainLen(w) + 1 > width) {
|
|
421
|
-
if (cur)
|
|
422
|
-
out.push(cur);
|
|
423
|
-
cur = w;
|
|
424
|
-
}
|
|
425
|
-
else {
|
|
426
|
-
cur = cur ? `${cur} ${w}` : w;
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
if (cur)
|
|
430
|
-
out.push(cur);
|
|
494
|
+
out.push(...wrapStyledToWidth(line, width));
|
|
431
495
|
}
|
|
432
496
|
else {
|
|
433
497
|
out.push(line);
|
|
@@ -1377,11 +1441,18 @@ export function renderBufferWithCursor(buffer, pasteSpans, prefix, label, cursor
|
|
|
1377
1441
|
const lines = [top, ...body, bottom];
|
|
1378
1442
|
let totalRows = body.length + 2;
|
|
1379
1443
|
if (hints && (hints.left || hints.right)) {
|
|
1380
|
-
|
|
1444
|
+
let left = hints.left ?? "";
|
|
1381
1445
|
const right = hints.right ?? "";
|
|
1382
1446
|
// Inset the hint row inside the box (box spans columns 0..boxW-1): a
|
|
1383
1447
|
// leading space gives left-edge padding, and the right hint ends one
|
|
1384
|
-
// column before the box's right border for the same padding.
|
|
1448
|
+
// column before the box's right border for the same padding. Truncate the
|
|
1449
|
+
// left hint so the row never exceeds the box width on narrow terminals
|
|
1450
|
+
// (otherwise it overflows past the right border instead of scaling down).
|
|
1451
|
+
const hintMax = boxW - 2;
|
|
1452
|
+
if (plainLen(left) + plainLen(right) + 1 > hintMax) {
|
|
1453
|
+
const room = Math.max(0, hintMax - plainLen(right) - 1);
|
|
1454
|
+
left = truncateStyled(left, room);
|
|
1455
|
+
}
|
|
1385
1456
|
const gap = Math.max(1, boxW - 2 - plainLen(left) - plainLen(right));
|
|
1386
1457
|
lines.push(" " + left + " ".repeat(gap) + right);
|
|
1387
1458
|
totalRows += 1;
|
|
@@ -1504,6 +1575,7 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = [], hin
|
|
|
1504
1575
|
dockEnterDocked();
|
|
1505
1576
|
}
|
|
1506
1577
|
process.stdin.removeListener("keypress", onKeypress);
|
|
1578
|
+
process.stdout.removeListener("resize", onResize);
|
|
1507
1579
|
process.stdin.pause();
|
|
1508
1580
|
showCursor();
|
|
1509
1581
|
if (isErr)
|
|
@@ -1945,6 +2017,17 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = [], hin
|
|
|
1945
2017
|
}
|
|
1946
2018
|
};
|
|
1947
2019
|
process.stdin.on("keypress", onKeypress);
|
|
2020
|
+
// Reflow the idle prompt box when the terminal resizes so it scales down
|
|
2021
|
+
// instead of wrapping/overflowing its borders on a narrow/scaled terminal.
|
|
2022
|
+
const onResize = () => {
|
|
2023
|
+
try {
|
|
2024
|
+
repaint(false);
|
|
2025
|
+
}
|
|
2026
|
+
catch {
|
|
2027
|
+
/* ignore */
|
|
2028
|
+
}
|
|
2029
|
+
};
|
|
2030
|
+
process.stdout.on("resize", onResize);
|
|
1948
2031
|
repaint(true);
|
|
1949
2032
|
});
|
|
1950
2033
|
}
|