@tiens.nguyen/gonext-local-worker 1.0.323 → 1.0.326
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/gonext-repl.mjs +78 -21
- package/package.json +1 -1
package/gonext-repl.mjs
CHANGED
|
@@ -351,10 +351,64 @@ function slashRestoreHistory() {
|
|
|
351
351
|
// menu was near the bottom of the screen and scrolled).
|
|
352
352
|
let slashOverlayActive = false;
|
|
353
353
|
|
|
354
|
-
//
|
|
355
|
-
//
|
|
356
|
-
|
|
357
|
-
|
|
354
|
+
// Visible width of PROMPT (">> ") — used to put the caret back on the input line by
|
|
355
|
+
// COLUMN, since the string itself carries color escapes that don't occupy cells.
|
|
356
|
+
const PROMPT_COLS = 3;
|
|
357
|
+
|
|
358
|
+
// Truncate to `max` VISIBLE columns, keeping the ANSI color escapes (which occupy no
|
|
359
|
+
// cells) intact. Essential for the overlay: every row it draws must fit on ONE physical
|
|
360
|
+
// terminal row — see slashOverlaySeq for why a wrapped row corrupts the redraw.
|
|
361
|
+
function clipVisible(s, max) {
|
|
362
|
+
if (max <= 0) return "";
|
|
363
|
+
let out = "", seen = 0, sawEscape = false;
|
|
364
|
+
for (let i = 0; i < s.length; i++) {
|
|
365
|
+
if (s[i] === "\x1b") {
|
|
366
|
+
const m = /^\x1b\[[0-9;]*m/.exec(s.slice(i));
|
|
367
|
+
if (m) { out += m[0]; i += m[0].length - 1; sawEscape = true; continue; }
|
|
368
|
+
}
|
|
369
|
+
if (seen >= max) return out + (sawEscape ? "\x1b[0m" : "");
|
|
370
|
+
out += s[i];
|
|
371
|
+
seen++;
|
|
372
|
+
}
|
|
373
|
+
return out;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Build the exact bytes that draw the overlay: the input line (">> " + typed text) with
|
|
377
|
+
// the command menu below it, caret left on the input line.
|
|
378
|
+
//
|
|
379
|
+
// Bug #109 v3 — the ↑/↓ stacking. TWO things have to hold for an in-place redraw, and the
|
|
380
|
+
// old version got the first right and the second wrong:
|
|
381
|
+
//
|
|
382
|
+
// 1. NO ABSOLUTE ANCHOR. The old code re-anchored each redraw with DECSC/DECRC
|
|
383
|
+
// (ESC 7 / ESC 8), an absolute screen position, after pre-scrolling "reserved" rows
|
|
384
|
+
// into existence. There is one save slot per terminal and its meaning changes as soon
|
|
385
|
+
// as the screen scrolls, so it is fragile. Every move here is RELATIVE (ESC[nA) or
|
|
386
|
+
// column-absolute within the current row (ESC[nG), like drawList() (the /server
|
|
387
|
+
// picker), which has always redrawn correctly.
|
|
388
|
+
//
|
|
389
|
+
// 2. NO WRAPPED ROWS — this is what actually bit the user. `ESC[nA` moves up n PHYSICAL
|
|
390
|
+
// rows, but `n` here is the number of LOGICAL menu rows. On a narrow terminal a row
|
|
391
|
+
// like " /model — switch the coding model for this session" occupies TWO physical
|
|
392
|
+
// rows, so the walk back up lands short, the next redraw's "\r ESC[2K" clears the
|
|
393
|
+
// wrong line, and a SECOND ">> /" + menu block is painted below the first — exactly
|
|
394
|
+
// the reported "it prints the item on every ↑". Verified in a terminal model: at 40
|
|
395
|
+
// columns the uncorrected version produces three prompt rows. So every row is clipped
|
|
396
|
+
// to the viewport width, which makes logical rows and physical rows the same thing.
|
|
397
|
+
// (This is also why the /server picker never showed the bug: its rows are short.)
|
|
398
|
+
function slashOverlaySeq(line, cursor, menu, cols) {
|
|
399
|
+
const width = Math.max(20, cols || 80);
|
|
400
|
+
const n = menu.length;
|
|
401
|
+
let out = "\r\x1b[2K" + clipVisible(PROMPT + line, width - 1);
|
|
402
|
+
for (const r of menu) out += "\n\x1b[2K" + clipVisible(r, width - 1);
|
|
403
|
+
// Wipe anything left below (the filtered list can SHRINK as you type), then walk back
|
|
404
|
+
// up to the input line and park the caret at the typed-text offset.
|
|
405
|
+
out += "\x1b[J";
|
|
406
|
+
if (n > 0) out += `\x1b[${n}A`;
|
|
407
|
+
out += `\r\x1b[${Math.min(width, PROMPT_COLS + cursor + 1)}G`;
|
|
408
|
+
return out;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Redraw the input line AND the command menu below it, fully in place.
|
|
358
412
|
function renderSlashOverlay(sel) {
|
|
359
413
|
const line = rl.line;
|
|
360
414
|
const cmds = filteredCommands(line);
|
|
@@ -363,42 +417,45 @@ function renderSlashOverlay(sel) {
|
|
|
363
417
|
slashSel = sel;
|
|
364
418
|
slashPrefix = line;
|
|
365
419
|
const menu = hintRows(cmds, sel);
|
|
366
|
-
const n = menu.length;
|
|
367
|
-
|
|
368
420
|
if (!slashOverlayActive) {
|
|
369
421
|
slashOverlayActive = true;
|
|
370
422
|
slashNeutralizeHistory();
|
|
371
|
-
process.stdout.write("\n".repeat(n) + `\x1b[${n}A`); // reserve n rows, back to input
|
|
372
|
-
slashHintRows = n;
|
|
373
|
-
} else if (n !== slashHintRows) {
|
|
374
|
-
// The filtered list changed size: clear the old menu, re-reserve.
|
|
375
|
-
process.stdout.write("\x1b7\n\x1b[J\x1b8");
|
|
376
|
-
process.stdout.write("\n".repeat(n) + `\x1b[${n}A`);
|
|
377
|
-
slashHintRows = n;
|
|
378
423
|
}
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
for (const r of menu) out += "\n\x1b[2K" + r;
|
|
384
|
-
out += "\x1b8";
|
|
385
|
-
process.stdout.write(out);
|
|
424
|
+
slashHintRows = menu.length;
|
|
425
|
+
process.stdout.write(
|
|
426
|
+
slashOverlaySeq(line, rl.cursor ?? line.length, menu, process.stdout.columns)
|
|
427
|
+
);
|
|
386
428
|
}
|
|
387
429
|
|
|
388
430
|
// Close the overlay: erase the menu below, keep the typed input line, hand drawing back
|
|
389
431
|
// to readline. Callers that changed the line to a non-slash value should refresh after.
|
|
432
|
+
// Relative moves only, for the same reason as above.
|
|
390
433
|
function closeSlashOverlay() {
|
|
391
434
|
if (!slashOverlayActive) {
|
|
392
435
|
slashRestoreHistory();
|
|
393
436
|
return;
|
|
394
437
|
}
|
|
395
|
-
process.stdout.write(
|
|
438
|
+
process.stdout.write(
|
|
439
|
+
slashCloseSeq(rl.line, rl.cursor ?? rl.line.length, process.stdout.columns)
|
|
440
|
+
);
|
|
396
441
|
slashOverlayActive = false;
|
|
397
442
|
slashHintRows = 0;
|
|
398
443
|
slashSel = -1;
|
|
399
444
|
slashNames = [];
|
|
400
445
|
slashRestoreHistory();
|
|
401
446
|
}
|
|
447
|
+
|
|
448
|
+
// Bytes that tear the menu down: repaint the input line, erase everything below it, and
|
|
449
|
+
// leave the caret back on the input line at the typed-text offset.
|
|
450
|
+
function slashCloseSeq(line, cursor, cols) {
|
|
451
|
+
const width = Math.max(20, cols || 80);
|
|
452
|
+
return (
|
|
453
|
+
"\r\x1b[2K" + clipVisible(PROMPT + line, width - 1) + // clean, non-wrapping input row
|
|
454
|
+
"\n\x1b[J" + // step below, erase the menu (and anything under it)
|
|
455
|
+
"\x1b[1A" + // back up to the input row (relative)
|
|
456
|
+
`\r\x1b[${Math.min(width, PROMPT_COLS + cursor + 1)}G`
|
|
457
|
+
);
|
|
458
|
+
}
|
|
402
459
|
if (process.stdin.isTTY) {
|
|
403
460
|
process.stdin.on("keypress", (_ch, key) => {
|
|
404
461
|
// An arrow-key list picker (/server) is up → ↑/↓ move the highlight, Enter chooses.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tiens.nguyen/gonext-local-worker",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.326",
|
|
4
4
|
"description": "Polls GoNext cloud API for async local LLM jobs and runs them against Ollama/OpenAI-compatible servers on this Mac",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|