@rotorsoft/gent 1.17.1 → 1.18.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/dist/index.js +312 -173
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2221,7 +2221,7 @@ import { homedir } from "os";
|
|
|
2221
2221
|
// package.json
|
|
2222
2222
|
var package_default = {
|
|
2223
2223
|
name: "@rotorsoft/gent",
|
|
2224
|
-
version: "1.
|
|
2224
|
+
version: "1.18.0",
|
|
2225
2225
|
description: "AI-powered GitHub workflow CLI - leverage AI (Claude, Gemini, or Codex) to create tickets, implement features, and manage PRs",
|
|
2226
2226
|
keywords: [
|
|
2227
2227
|
"cli",
|
|
@@ -3109,142 +3109,9 @@ function clearScreen() {
|
|
|
3109
3109
|
}
|
|
3110
3110
|
|
|
3111
3111
|
// src/tui/modal.ts
|
|
3112
|
-
import
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
const fill = w - 2 - label.length;
|
|
3116
|
-
return chalk4.bold("\u250C") + chalk4.bold.cyan(label) + chalk4.bold("\u2500".repeat(Math.max(0, fill)) + "\u2510");
|
|
3117
|
-
}
|
|
3118
|
-
function modalDivRow(w) {
|
|
3119
|
-
return chalk4.bold("\u251C" + "\u2500".repeat(w - 2) + "\u2524");
|
|
3120
|
-
}
|
|
3121
|
-
function modalBotRow(w) {
|
|
3122
|
-
return chalk4.bold("\u2514" + "\u2500".repeat(w - 2) + "\u2518");
|
|
3123
|
-
}
|
|
3124
|
-
function modalRow(text, w) {
|
|
3125
|
-
const inner = w - 4;
|
|
3126
|
-
const fitted = truncateAnsi(text, inner);
|
|
3127
|
-
const pad = Math.max(0, inner - visibleLen(fitted));
|
|
3128
|
-
return chalk4.bold("\u2502") + " " + fitted + " ".repeat(pad) + " " + chalk4.bold("\u2502");
|
|
3129
|
-
}
|
|
3130
|
-
function modalEmptyRow(w) {
|
|
3131
|
-
return modalRow("", w);
|
|
3132
|
-
}
|
|
3133
|
-
function buildModalFrame(title, contentLines, footerText, width) {
|
|
3134
|
-
const lines = [];
|
|
3135
|
-
lines.push(modalTopRow(title, width));
|
|
3136
|
-
lines.push(modalEmptyRow(width));
|
|
3137
|
-
for (const line of contentLines) {
|
|
3138
|
-
lines.push(modalRow(line, width));
|
|
3139
|
-
}
|
|
3140
|
-
lines.push(modalEmptyRow(width));
|
|
3141
|
-
lines.push(modalDivRow(width));
|
|
3142
|
-
lines.push(modalRow(chalk4.dim(footerText), width));
|
|
3143
|
-
lines.push(modalBotRow(width));
|
|
3144
|
-
return lines;
|
|
3145
|
-
}
|
|
3146
|
-
function isSeparator(entry) {
|
|
3147
|
-
return "separator" in entry;
|
|
3148
|
-
}
|
|
3149
|
-
function buildSelectContent(items, selectedIndex, maxWidth, currentIndex) {
|
|
3150
|
-
const lines = [];
|
|
3151
|
-
let selectableIdx = 0;
|
|
3152
|
-
for (const item of items) {
|
|
3153
|
-
if (isSeparator(item)) {
|
|
3154
|
-
lines.push(chalk4.dim(item.separator));
|
|
3155
|
-
} else {
|
|
3156
|
-
const isSelected = selectableIdx === selectedIndex;
|
|
3157
|
-
const isCurrent = currentIndex != null && selectableIdx === currentIndex;
|
|
3158
|
-
const prefix = isSelected ? chalk4.cyan.bold("> ") : " ";
|
|
3159
|
-
const bullet = chalk4.dim("\xB7 ");
|
|
3160
|
-
const label = truncateAnsi(item.name, maxWidth - 4);
|
|
3161
|
-
const styledLabel = isSelected ? chalk4.bold(label) : isCurrent ? chalk4.cyan(label) : label;
|
|
3162
|
-
lines.push(prefix + bullet + styledLabel);
|
|
3163
|
-
selectableIdx++;
|
|
3164
|
-
}
|
|
3165
|
-
}
|
|
3166
|
-
return lines;
|
|
3167
|
-
}
|
|
3168
|
-
function buildConfirmContent(message, selectedYes) {
|
|
3169
|
-
const yes = selectedYes ? chalk4.cyan.bold("> Yes") : chalk4.dim(" Yes");
|
|
3170
|
-
const no = !selectedYes ? chalk4.cyan.bold("> No") : chalk4.dim(" No");
|
|
3171
|
-
return [message, "", yes, no];
|
|
3172
|
-
}
|
|
3173
|
-
function buildInputContent(label, value, cursorVisible) {
|
|
3174
|
-
const cursor = cursorVisible ? chalk4.cyan("_") : " ";
|
|
3175
|
-
return [label, "", chalk4.cyan("> ") + value + cursor];
|
|
3176
|
-
}
|
|
3177
|
-
function buildMultilineInputContent(label, value, cursorVisible, maxWidth) {
|
|
3178
|
-
const cursor = cursorVisible ? chalk4.cyan("_") : " ";
|
|
3179
|
-
const lines = [label, ""];
|
|
3180
|
-
if (value === "") {
|
|
3181
|
-
lines.push(chalk4.cyan(" ") + cursor);
|
|
3182
|
-
} else {
|
|
3183
|
-
const inputLines = value.split("\n");
|
|
3184
|
-
const contentWidth = maxWidth - 2;
|
|
3185
|
-
for (let i = 0; i < inputLines.length; i++) {
|
|
3186
|
-
const raw = inputLines[i];
|
|
3187
|
-
const wrapped = wrapLine(raw, contentWidth);
|
|
3188
|
-
for (let j = 0; j < wrapped.length; j++) {
|
|
3189
|
-
const isLastLine = i === inputLines.length - 1 && j === wrapped.length - 1;
|
|
3190
|
-
const text = wrapped[j] + (isLastLine ? cursor : "");
|
|
3191
|
-
lines.push(chalk4.cyan(" ") + text);
|
|
3192
|
-
}
|
|
3193
|
-
}
|
|
3194
|
-
}
|
|
3195
|
-
return lines;
|
|
3196
|
-
}
|
|
3197
|
-
function wrapLine(text, width) {
|
|
3198
|
-
if (width <= 0) return [text];
|
|
3199
|
-
if (text.length <= width) return [text];
|
|
3200
|
-
const result = [];
|
|
3201
|
-
let remaining = text;
|
|
3202
|
-
while (remaining.length > width) {
|
|
3203
|
-
let breakAt = remaining.lastIndexOf(" ", width);
|
|
3204
|
-
if (breakAt <= 0) breakAt = width;
|
|
3205
|
-
result.push(remaining.slice(0, breakAt));
|
|
3206
|
-
remaining = remaining.slice(breakAt).replace(/^ /, "");
|
|
3207
|
-
}
|
|
3208
|
-
if (remaining.length > 0 || result.length === 0) {
|
|
3209
|
-
result.push(remaining);
|
|
3210
|
-
}
|
|
3211
|
-
return result;
|
|
3212
|
-
}
|
|
3213
|
-
function termSize() {
|
|
3214
|
-
return {
|
|
3215
|
-
cols: process.stdout.columns || 80,
|
|
3216
|
-
rows: process.stdout.rows || 24
|
|
3217
|
-
};
|
|
3218
|
-
}
|
|
3219
|
-
function moveTo(row2, col) {
|
|
3220
|
-
return `\x1B[${row2};${col}H`;
|
|
3221
|
-
}
|
|
3222
|
-
function hideCursor() {
|
|
3223
|
-
return "\x1B[?25l";
|
|
3224
|
-
}
|
|
3225
|
-
function showCursor() {
|
|
3226
|
-
return "\x1B[?25h";
|
|
3227
|
-
}
|
|
3228
|
-
function modalWidth() {
|
|
3229
|
-
const cols = process.stdout.columns || 80;
|
|
3230
|
-
return Math.min(60, cols - 4);
|
|
3231
|
-
}
|
|
3232
|
-
function renderOverlay(dashboardLines, modalLines, mWidth) {
|
|
3233
|
-
const { cols, rows } = termSize();
|
|
3234
|
-
process.stdout.write("\x1B[2J\x1B[0f");
|
|
3235
|
-
process.stdout.write(hideCursor());
|
|
3236
|
-
for (let i = 0; i < dashboardLines.length && i < rows; i++) {
|
|
3237
|
-
process.stdout.write(
|
|
3238
|
-
moveTo(i + 1, 1) + chalk4.dim(stripAnsi(dashboardLines[i]))
|
|
3239
|
-
);
|
|
3240
|
-
}
|
|
3241
|
-
const startRow = Math.max(1, Math.floor((rows - modalLines.length) / 2));
|
|
3242
|
-
const startCol = Math.max(1, Math.floor((cols - mWidth) / 2));
|
|
3243
|
-
for (let i = 0; i < modalLines.length; i++) {
|
|
3244
|
-
process.stdout.write(moveTo(startRow + i, startCol) + modalLines[i]);
|
|
3245
|
-
}
|
|
3246
|
-
process.stdout.write(moveTo(startRow + modalLines.length + 1, 1));
|
|
3247
|
-
}
|
|
3112
|
+
import chalk8 from "chalk";
|
|
3113
|
+
|
|
3114
|
+
// src/tui/key-reader.ts
|
|
3248
3115
|
function readKey() {
|
|
3249
3116
|
return new Promise((resolve) => {
|
|
3250
3117
|
const { stdin } = process;
|
|
@@ -3268,6 +3135,20 @@ function readKey() {
|
|
|
3268
3135
|
resolve({ name: "right", raw: data });
|
|
3269
3136
|
} else if (data === "\x1B[D") {
|
|
3270
3137
|
resolve({ name: "left", raw: data });
|
|
3138
|
+
} else if (data === "\x1B[1;5C" || data === "\x1B[5C") {
|
|
3139
|
+
resolve({ name: "ctrl-right", raw: data });
|
|
3140
|
+
} else if (data === "\x1B[1;5D" || data === "\x1B[5D") {
|
|
3141
|
+
resolve({ name: "ctrl-left", raw: data });
|
|
3142
|
+
} else if (data === "\x1B[3~") {
|
|
3143
|
+
resolve({ name: "delete", raw: data });
|
|
3144
|
+
} else if (data === "\x1B[H" || data === "\x1B[1~") {
|
|
3145
|
+
resolve({ name: "home", raw: data });
|
|
3146
|
+
} else if (data === "\x1B[F" || data === "\x1B[4~") {
|
|
3147
|
+
resolve({ name: "end", raw: data });
|
|
3148
|
+
} else if (data === "") {
|
|
3149
|
+
resolve({ name: "home", raw: data });
|
|
3150
|
+
} else if (data === "") {
|
|
3151
|
+
resolve({ name: "end", raw: data });
|
|
3271
3152
|
} else if (data === "\r" || data === "\n") {
|
|
3272
3153
|
resolve({ name: "enter", raw: data });
|
|
3273
3154
|
} else if (data === "\x7F" || data === "\b") {
|
|
@@ -3287,39 +3168,30 @@ function readKey() {
|
|
|
3287
3168
|
stdin.on("data", onData);
|
|
3288
3169
|
});
|
|
3289
3170
|
}
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
return selectedYes;
|
|
3312
|
-
case "escape":
|
|
3313
|
-
process.stdout.write(showCursor());
|
|
3314
|
-
return false;
|
|
3315
|
-
case "y":
|
|
3316
|
-
process.stdout.write(showCursor());
|
|
3317
|
-
return true;
|
|
3318
|
-
case "n":
|
|
3319
|
-
process.stdout.write(showCursor());
|
|
3320
|
-
return false;
|
|
3171
|
+
|
|
3172
|
+
// src/tui/select-dialog.ts
|
|
3173
|
+
import chalk4 from "chalk";
|
|
3174
|
+
function isSeparator(entry) {
|
|
3175
|
+
return "separator" in entry;
|
|
3176
|
+
}
|
|
3177
|
+
function buildSelectContent(items, selectedIndex, maxWidth, currentIndex) {
|
|
3178
|
+
const lines = [];
|
|
3179
|
+
let selectableIdx = 0;
|
|
3180
|
+
for (const item of items) {
|
|
3181
|
+
if (isSeparator(item)) {
|
|
3182
|
+
lines.push(chalk4.dim(item.separator));
|
|
3183
|
+
} else {
|
|
3184
|
+
const isSelected = selectableIdx === selectedIndex;
|
|
3185
|
+
const isCurrent = currentIndex != null && selectableIdx === currentIndex;
|
|
3186
|
+
const prefix = isSelected ? chalk4.cyan.bold("> ") : " ";
|
|
3187
|
+
const bullet = chalk4.dim("\xB7 ");
|
|
3188
|
+
const label = truncateAnsi(item.name, maxWidth - 4);
|
|
3189
|
+
const styledLabel = isSelected ? chalk4.bold(label) : isCurrent ? chalk4.cyan(label) : label;
|
|
3190
|
+
lines.push(prefix + bullet + styledLabel);
|
|
3191
|
+
selectableIdx++;
|
|
3321
3192
|
}
|
|
3322
3193
|
}
|
|
3194
|
+
return lines;
|
|
3323
3195
|
}
|
|
3324
3196
|
function selectableCount(items) {
|
|
3325
3197
|
return items.filter((i) => !isSeparator(i)).length;
|
|
@@ -3364,6 +3236,55 @@ async function showSelect(opts) {
|
|
|
3364
3236
|
}
|
|
3365
3237
|
}
|
|
3366
3238
|
}
|
|
3239
|
+
|
|
3240
|
+
// src/tui/confirm-dialog.ts
|
|
3241
|
+
import chalk5 from "chalk";
|
|
3242
|
+
function buildConfirmContent(message, selectedYes) {
|
|
3243
|
+
const yes = selectedYes ? chalk5.cyan.bold("> Yes") : chalk5.dim(" Yes");
|
|
3244
|
+
const no = !selectedYes ? chalk5.cyan.bold("> No") : chalk5.dim(" No");
|
|
3245
|
+
return [message, "", yes, no];
|
|
3246
|
+
}
|
|
3247
|
+
async function showConfirm(opts) {
|
|
3248
|
+
const w = modalWidth();
|
|
3249
|
+
let selectedYes = true;
|
|
3250
|
+
const render = () => {
|
|
3251
|
+
const content = buildConfirmContent(opts.message, selectedYes);
|
|
3252
|
+
const footer = "\u2191\u2193 Select Enter Confirm Esc Cancel";
|
|
3253
|
+
const lines = buildModalFrame(opts.title, content, footer, w);
|
|
3254
|
+
renderOverlay(opts.dashboardLines, lines, w);
|
|
3255
|
+
};
|
|
3256
|
+
render();
|
|
3257
|
+
while (true) {
|
|
3258
|
+
const key = await readKey();
|
|
3259
|
+
switch (key.name) {
|
|
3260
|
+
case "up":
|
|
3261
|
+
case "down":
|
|
3262
|
+
case "tab":
|
|
3263
|
+
selectedYes = !selectedYes;
|
|
3264
|
+
render();
|
|
3265
|
+
break;
|
|
3266
|
+
case "enter":
|
|
3267
|
+
process.stdout.write(showCursor());
|
|
3268
|
+
return selectedYes;
|
|
3269
|
+
case "escape":
|
|
3270
|
+
process.stdout.write(showCursor());
|
|
3271
|
+
return false;
|
|
3272
|
+
case "y":
|
|
3273
|
+
process.stdout.write(showCursor());
|
|
3274
|
+
return true;
|
|
3275
|
+
case "n":
|
|
3276
|
+
process.stdout.write(showCursor());
|
|
3277
|
+
return false;
|
|
3278
|
+
}
|
|
3279
|
+
}
|
|
3280
|
+
}
|
|
3281
|
+
|
|
3282
|
+
// src/tui/input-dialog.ts
|
|
3283
|
+
import chalk6 from "chalk";
|
|
3284
|
+
function buildInputContent(label, value, cursorVisible) {
|
|
3285
|
+
const cursorChar = cursorVisible ? chalk6.inverse(" ") : "";
|
|
3286
|
+
return [label, "", chalk6.cyan("> ") + value + cursorChar];
|
|
3287
|
+
}
|
|
3367
3288
|
async function showInput(opts) {
|
|
3368
3289
|
const w = modalWidth();
|
|
3369
3290
|
let value = "";
|
|
@@ -3402,9 +3323,115 @@ async function showInput(opts) {
|
|
|
3402
3323
|
}
|
|
3403
3324
|
}
|
|
3404
3325
|
}
|
|
3326
|
+
|
|
3327
|
+
// src/tui/multiline-input.ts
|
|
3328
|
+
import chalk7 from "chalk";
|
|
3329
|
+
function wrapLineWithMap(text, width) {
|
|
3330
|
+
if (width <= 0) return [{ text, offset: 0 }];
|
|
3331
|
+
if (text.length <= width) return [{ text, offset: 0 }];
|
|
3332
|
+
const result = [];
|
|
3333
|
+
let pos = 0;
|
|
3334
|
+
let remaining = text;
|
|
3335
|
+
while (remaining.length > width) {
|
|
3336
|
+
let breakAt = remaining.lastIndexOf(" ", width);
|
|
3337
|
+
if (breakAt <= 0) breakAt = width;
|
|
3338
|
+
result.push({ text: remaining.slice(0, breakAt), offset: pos });
|
|
3339
|
+
pos += breakAt;
|
|
3340
|
+
remaining = remaining.slice(breakAt);
|
|
3341
|
+
if (remaining.startsWith(" ")) {
|
|
3342
|
+
remaining = remaining.slice(1);
|
|
3343
|
+
pos += 1;
|
|
3344
|
+
}
|
|
3345
|
+
}
|
|
3346
|
+
if (remaining.length > 0 || result.length === 0) {
|
|
3347
|
+
result.push({ text: remaining, offset: pos });
|
|
3348
|
+
}
|
|
3349
|
+
return result;
|
|
3350
|
+
}
|
|
3351
|
+
function getVisualLines(value, contentWidth) {
|
|
3352
|
+
const inputLines = value.split("\n");
|
|
3353
|
+
const result = [];
|
|
3354
|
+
let globalPos = 0;
|
|
3355
|
+
for (let i = 0; i < inputLines.length; i++) {
|
|
3356
|
+
const raw = inputLines[i];
|
|
3357
|
+
const wrapped = wrapLineWithMap(raw, contentWidth);
|
|
3358
|
+
for (const seg of wrapped) {
|
|
3359
|
+
result.push({
|
|
3360
|
+
text: seg.text,
|
|
3361
|
+
globalOffset: globalPos + seg.offset,
|
|
3362
|
+
length: seg.text.length
|
|
3363
|
+
});
|
|
3364
|
+
}
|
|
3365
|
+
globalPos += raw.length + 1;
|
|
3366
|
+
}
|
|
3367
|
+
return result;
|
|
3368
|
+
}
|
|
3369
|
+
function findCursorVisualPos(visualLines, cursorPos) {
|
|
3370
|
+
for (let i = 0; i < visualLines.length; i++) {
|
|
3371
|
+
const vl = visualLines[i];
|
|
3372
|
+
if (cursorPos >= vl.globalOffset && cursorPos <= vl.globalOffset + vl.length) {
|
|
3373
|
+
return { row: i, col: cursorPos - vl.globalOffset };
|
|
3374
|
+
}
|
|
3375
|
+
}
|
|
3376
|
+
const last = visualLines[visualLines.length - 1];
|
|
3377
|
+
return { row: visualLines.length - 1, col: last.length };
|
|
3378
|
+
}
|
|
3379
|
+
function moveCursorVertical(value, cursorPos, contentWidth, direction) {
|
|
3380
|
+
const visualLines = getVisualLines(value, contentWidth);
|
|
3381
|
+
const { row: row2, col } = findCursorVisualPos(visualLines, cursorPos);
|
|
3382
|
+
const newRow = row2 + direction;
|
|
3383
|
+
if (newRow < 0 || newRow >= visualLines.length) return cursorPos;
|
|
3384
|
+
const targetLine = visualLines[newRow];
|
|
3385
|
+
const newCol = Math.min(col, targetLine.length);
|
|
3386
|
+
return targetLine.globalOffset + newCol;
|
|
3387
|
+
}
|
|
3388
|
+
function moveCursorHome(value, cursorPos, contentWidth) {
|
|
3389
|
+
const visualLines = getVisualLines(value, contentWidth);
|
|
3390
|
+
const { row: row2 } = findCursorVisualPos(visualLines, cursorPos);
|
|
3391
|
+
return visualLines[row2].globalOffset;
|
|
3392
|
+
}
|
|
3393
|
+
function moveCursorEnd(value, cursorPos, contentWidth) {
|
|
3394
|
+
const visualLines = getVisualLines(value, contentWidth);
|
|
3395
|
+
const { row: row2 } = findCursorVisualPos(visualLines, cursorPos);
|
|
3396
|
+
return visualLines[row2].globalOffset + visualLines[row2].length;
|
|
3397
|
+
}
|
|
3398
|
+
function moveCursorWordLeft(value, cursorPos) {
|
|
3399
|
+
if (cursorPos <= 0) return 0;
|
|
3400
|
+
let pos = cursorPos - 1;
|
|
3401
|
+
while (pos > 0 && !/\w/.test(value[pos])) pos--;
|
|
3402
|
+
while (pos > 0 && /\w/.test(value[pos - 1])) pos--;
|
|
3403
|
+
return pos;
|
|
3404
|
+
}
|
|
3405
|
+
function moveCursorWordRight(value, cursorPos) {
|
|
3406
|
+
if (cursorPos >= value.length) return value.length;
|
|
3407
|
+
let pos = cursorPos;
|
|
3408
|
+
while (pos < value.length && /\w/.test(value[pos])) pos++;
|
|
3409
|
+
while (pos < value.length && !/\w/.test(value[pos])) pos++;
|
|
3410
|
+
return pos;
|
|
3411
|
+
}
|
|
3412
|
+
function buildMultilineInputContent(label, value, cursorVisible, maxWidth, cursorPos) {
|
|
3413
|
+
const cp = cursorPos ?? value.length;
|
|
3414
|
+
const lines = [label, ""];
|
|
3415
|
+
const contentWidth = maxWidth - 2;
|
|
3416
|
+
const visualLines = getVisualLines(value, contentWidth);
|
|
3417
|
+
const { row: cursorRow, col: cursorCol } = findCursorVisualPos(visualLines, cp);
|
|
3418
|
+
for (let i = 0; i < visualLines.length; i++) {
|
|
3419
|
+
const text = visualLines[i].text;
|
|
3420
|
+
if (i === cursorRow && cursorVisible) {
|
|
3421
|
+
const charUnderCursor = cursorCol < text.length ? text[cursorCol] : " ";
|
|
3422
|
+
lines.push(
|
|
3423
|
+
chalk7.cyan(" ") + text.slice(0, cursorCol) + chalk7.inverse(charUnderCursor) + text.slice(cursorCol + 1)
|
|
3424
|
+
);
|
|
3425
|
+
} else {
|
|
3426
|
+
lines.push(chalk7.cyan(" ") + text);
|
|
3427
|
+
}
|
|
3428
|
+
}
|
|
3429
|
+
return lines;
|
|
3430
|
+
}
|
|
3405
3431
|
async function showMultilineInput(opts) {
|
|
3406
3432
|
const w = modalWidth();
|
|
3407
3433
|
let value = "";
|
|
3434
|
+
let cursorPos = 0;
|
|
3408
3435
|
let cursorBlink = true;
|
|
3409
3436
|
const contentWidth = w - 6;
|
|
3410
3437
|
const render = () => {
|
|
@@ -3412,7 +3439,8 @@ async function showMultilineInput(opts) {
|
|
|
3412
3439
|
opts.label,
|
|
3413
3440
|
value,
|
|
3414
3441
|
cursorBlink,
|
|
3415
|
-
contentWidth
|
|
3442
|
+
contentWidth,
|
|
3443
|
+
cursorPos
|
|
3416
3444
|
);
|
|
3417
3445
|
const footer = "Enter Newline Ctrl+S Submit Esc Cancel";
|
|
3418
3446
|
const lines = buildModalFrame(opts.title, content, footer, w);
|
|
@@ -3426,7 +3454,8 @@ async function showMultilineInput(opts) {
|
|
|
3426
3454
|
process.stdout.write(showCursor());
|
|
3427
3455
|
return value.trim() || null;
|
|
3428
3456
|
case "enter":
|
|
3429
|
-
value
|
|
3457
|
+
value = value.slice(0, cursorPos) + "\n" + value.slice(cursorPos);
|
|
3458
|
+
cursorPos++;
|
|
3430
3459
|
cursorBlink = true;
|
|
3431
3460
|
render();
|
|
3432
3461
|
break;
|
|
@@ -3434,14 +3463,54 @@ async function showMultilineInput(opts) {
|
|
|
3434
3463
|
process.stdout.write(showCursor());
|
|
3435
3464
|
return null;
|
|
3436
3465
|
case "backspace":
|
|
3437
|
-
if (
|
|
3438
|
-
value = value.slice(0, -1);
|
|
3466
|
+
if (cursorPos > 0) {
|
|
3467
|
+
value = value.slice(0, cursorPos - 1) + value.slice(cursorPos);
|
|
3468
|
+
cursorPos--;
|
|
3469
|
+
}
|
|
3470
|
+
render();
|
|
3471
|
+
break;
|
|
3472
|
+
case "delete":
|
|
3473
|
+
if (cursorPos < value.length) {
|
|
3474
|
+
value = value.slice(0, cursorPos) + value.slice(cursorPos + 1);
|
|
3439
3475
|
}
|
|
3440
3476
|
render();
|
|
3441
3477
|
break;
|
|
3478
|
+
case "left":
|
|
3479
|
+
if (cursorPos > 0) cursorPos--;
|
|
3480
|
+
render();
|
|
3481
|
+
break;
|
|
3482
|
+
case "right":
|
|
3483
|
+
if (cursorPos < value.length) cursorPos++;
|
|
3484
|
+
render();
|
|
3485
|
+
break;
|
|
3486
|
+
case "up":
|
|
3487
|
+
cursorPos = moveCursorVertical(value, cursorPos, contentWidth - 2, -1);
|
|
3488
|
+
render();
|
|
3489
|
+
break;
|
|
3490
|
+
case "down":
|
|
3491
|
+
cursorPos = moveCursorVertical(value, cursorPos, contentWidth - 2, 1);
|
|
3492
|
+
render();
|
|
3493
|
+
break;
|
|
3494
|
+
case "home":
|
|
3495
|
+
cursorPos = moveCursorHome(value, cursorPos, contentWidth - 2);
|
|
3496
|
+
render();
|
|
3497
|
+
break;
|
|
3498
|
+
case "end":
|
|
3499
|
+
cursorPos = moveCursorEnd(value, cursorPos, contentWidth - 2);
|
|
3500
|
+
render();
|
|
3501
|
+
break;
|
|
3502
|
+
case "ctrl-left":
|
|
3503
|
+
cursorPos = moveCursorWordLeft(value, cursorPos);
|
|
3504
|
+
render();
|
|
3505
|
+
break;
|
|
3506
|
+
case "ctrl-right":
|
|
3507
|
+
cursorPos = moveCursorWordRight(value, cursorPos);
|
|
3508
|
+
render();
|
|
3509
|
+
break;
|
|
3442
3510
|
default:
|
|
3443
3511
|
if (key.raw.length === 1 && key.raw.charCodeAt(0) >= 32) {
|
|
3444
|
-
value
|
|
3512
|
+
value = value.slice(0, cursorPos) + key.raw + value.slice(cursorPos);
|
|
3513
|
+
cursorPos++;
|
|
3445
3514
|
cursorBlink = true;
|
|
3446
3515
|
render();
|
|
3447
3516
|
}
|
|
@@ -3449,6 +3518,76 @@ async function showMultilineInput(opts) {
|
|
|
3449
3518
|
}
|
|
3450
3519
|
}
|
|
3451
3520
|
}
|
|
3521
|
+
|
|
3522
|
+
// src/tui/modal.ts
|
|
3523
|
+
function modalTopRow(title, w) {
|
|
3524
|
+
const label = ` ${title} `;
|
|
3525
|
+
const fill = w - 2 - label.length;
|
|
3526
|
+
return chalk8.bold("\u250C") + chalk8.bold.cyan(label) + chalk8.bold("\u2500".repeat(Math.max(0, fill)) + "\u2510");
|
|
3527
|
+
}
|
|
3528
|
+
function modalDivRow(w) {
|
|
3529
|
+
return chalk8.bold("\u251C" + "\u2500".repeat(w - 2) + "\u2524");
|
|
3530
|
+
}
|
|
3531
|
+
function modalBotRow(w) {
|
|
3532
|
+
return chalk8.bold("\u2514" + "\u2500".repeat(w - 2) + "\u2518");
|
|
3533
|
+
}
|
|
3534
|
+
function modalRow(text, w) {
|
|
3535
|
+
const inner = w - 4;
|
|
3536
|
+
const fitted = truncateAnsi(text, inner);
|
|
3537
|
+
const pad = Math.max(0, inner - visibleLen(fitted));
|
|
3538
|
+
return chalk8.bold("\u2502") + " " + fitted + " ".repeat(pad) + " " + chalk8.bold("\u2502");
|
|
3539
|
+
}
|
|
3540
|
+
function modalEmptyRow(w) {
|
|
3541
|
+
return modalRow("", w);
|
|
3542
|
+
}
|
|
3543
|
+
function buildModalFrame(title, contentLines, footerText, width) {
|
|
3544
|
+
const lines = [];
|
|
3545
|
+
lines.push(modalTopRow(title, width));
|
|
3546
|
+
lines.push(modalEmptyRow(width));
|
|
3547
|
+
for (const line of contentLines) {
|
|
3548
|
+
lines.push(modalRow(line, width));
|
|
3549
|
+
}
|
|
3550
|
+
lines.push(modalEmptyRow(width));
|
|
3551
|
+
lines.push(modalDivRow(width));
|
|
3552
|
+
lines.push(modalRow(chalk8.dim(footerText), width));
|
|
3553
|
+
lines.push(modalBotRow(width));
|
|
3554
|
+
return lines;
|
|
3555
|
+
}
|
|
3556
|
+
function termSize() {
|
|
3557
|
+
return {
|
|
3558
|
+
cols: process.stdout.columns || 80,
|
|
3559
|
+
rows: process.stdout.rows || 24
|
|
3560
|
+
};
|
|
3561
|
+
}
|
|
3562
|
+
function moveTo(row2, col) {
|
|
3563
|
+
return `\x1B[${row2};${col}H`;
|
|
3564
|
+
}
|
|
3565
|
+
function hideCursor() {
|
|
3566
|
+
return "\x1B[?25l";
|
|
3567
|
+
}
|
|
3568
|
+
function showCursor() {
|
|
3569
|
+
return "\x1B[?25h";
|
|
3570
|
+
}
|
|
3571
|
+
function modalWidth() {
|
|
3572
|
+
const cols = process.stdout.columns || 80;
|
|
3573
|
+
return Math.min(60, cols - 4);
|
|
3574
|
+
}
|
|
3575
|
+
function renderOverlay(dashboardLines, modalLines, mWidth) {
|
|
3576
|
+
const { cols, rows } = termSize();
|
|
3577
|
+
process.stdout.write("\x1B[2J\x1B[0f");
|
|
3578
|
+
process.stdout.write(hideCursor());
|
|
3579
|
+
for (let i = 0; i < dashboardLines.length && i < rows; i++) {
|
|
3580
|
+
process.stdout.write(
|
|
3581
|
+
moveTo(i + 1, 1) + chalk8.dim(stripAnsi(dashboardLines[i]))
|
|
3582
|
+
);
|
|
3583
|
+
}
|
|
3584
|
+
const startRow = Math.max(1, Math.floor((rows - modalLines.length) / 2));
|
|
3585
|
+
const startCol = Math.max(1, Math.floor((cols - mWidth) / 2));
|
|
3586
|
+
for (let i = 0; i < modalLines.length; i++) {
|
|
3587
|
+
process.stdout.write(moveTo(startRow + i, startCol) + modalLines[i]);
|
|
3588
|
+
}
|
|
3589
|
+
process.stdout.write(moveTo(startRow + modalLines.length + 1, 1));
|
|
3590
|
+
}
|
|
3452
3591
|
function showStatus(title, message, dashboardLines) {
|
|
3453
3592
|
const w = modalWidth();
|
|
3454
3593
|
const content = [message];
|