@rotorsoft/gent 1.17.0 → 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/README.md +169 -163
- package/dist/index.js +330 -183
- 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",
|
|
@@ -2786,13 +2786,13 @@ function getAvailableActions(state) {
|
|
|
2786
2786
|
actions.push({ id: "commit", label: "commit", shortcut: "c" });
|
|
2787
2787
|
}
|
|
2788
2788
|
if (state.hasUnpushedCommits && state.commits.length > 0) {
|
|
2789
|
-
actions.push({ id: "push", label: "push", shortcut: "
|
|
2789
|
+
actions.push({ id: "push", label: "push", shortcut: "s" });
|
|
2790
2790
|
}
|
|
2791
2791
|
if (!state.pr && state.commits.length > 0) {
|
|
2792
|
-
actions.push({ id: "pr", label: "pr", shortcut: "
|
|
2792
|
+
actions.push({ id: "pr", label: "pr", shortcut: "p" });
|
|
2793
2793
|
}
|
|
2794
2794
|
if (state.issue && state.pr?.state !== "merged") {
|
|
2795
|
-
actions.push({ id: "run", label: "run", shortcut: "
|
|
2795
|
+
actions.push({ id: "run", label: "run", shortcut: "r" });
|
|
2796
2796
|
}
|
|
2797
2797
|
}
|
|
2798
2798
|
actions.push({ id: "list", label: "list", shortcut: "l" });
|
|
@@ -2907,14 +2907,14 @@ var shortcutColors = [
|
|
|
2907
2907
|
chalk3.red.bold
|
|
2908
2908
|
];
|
|
2909
2909
|
function formatAction(a, color) {
|
|
2910
|
-
const
|
|
2911
|
-
const
|
|
2910
|
+
const idx = a.label.indexOf(a.shortcut);
|
|
2911
|
+
const styledKey = color(chalk3.underline(a.shortcut));
|
|
2912
2912
|
if (idx >= 0) {
|
|
2913
2913
|
const before = a.label.slice(0, idx);
|
|
2914
|
-
const after = a.label.slice(idx +
|
|
2915
|
-
return chalk3.dim(before) +
|
|
2914
|
+
const after = a.label.slice(idx + a.shortcut.length);
|
|
2915
|
+
return chalk3.dim(before) + styledKey + chalk3.dim(after);
|
|
2916
2916
|
}
|
|
2917
|
-
return
|
|
2917
|
+
return styledKey + " " + chalk3.dim(a.label);
|
|
2918
2918
|
}
|
|
2919
2919
|
function formatCommandBar(actions, w) {
|
|
2920
2920
|
const parts = actions.map((a, i) => {
|
|
@@ -2971,14 +2971,22 @@ function buildDashboardLines(state, actions, hint, refreshing, versionCheck) {
|
|
|
2971
2971
|
out(topRow(titleLabel, w));
|
|
2972
2972
|
renderSettingsTo(state, w, out, versionCheck);
|
|
2973
2973
|
if (!state.isGitRepo) {
|
|
2974
|
-
out(row(chalk3.
|
|
2975
|
-
out(row(chalk3.dim("
|
|
2974
|
+
out(row(chalk3.yellow("Not a git repository"), w));
|
|
2975
|
+
out(row(chalk3.dim("Navigate to a git repository to get started"), w));
|
|
2976
|
+
out(divRow(w));
|
|
2977
|
+
for (const line of formatCommandBar(actions, w)) {
|
|
2978
|
+
out(row(line, w));
|
|
2979
|
+
}
|
|
2976
2980
|
out(botRow(w));
|
|
2977
2981
|
return lines;
|
|
2978
2982
|
}
|
|
2979
2983
|
if (!state.isGhAuthenticated) {
|
|
2980
2984
|
out(row(chalk3.red("GitHub CLI not authenticated"), w));
|
|
2981
2985
|
out(row(chalk3.dim("Run: gh auth login"), w));
|
|
2986
|
+
out(divRow(w));
|
|
2987
|
+
for (const line of formatCommandBar(actions, w)) {
|
|
2988
|
+
out(row(line, w));
|
|
2989
|
+
}
|
|
2982
2990
|
out(botRow(w));
|
|
2983
2991
|
return lines;
|
|
2984
2992
|
}
|
|
@@ -3101,142 +3109,9 @@ function clearScreen() {
|
|
|
3101
3109
|
}
|
|
3102
3110
|
|
|
3103
3111
|
// src/tui/modal.ts
|
|
3104
|
-
import
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
const fill = w - 2 - label.length;
|
|
3108
|
-
return chalk4.bold("\u250C") + chalk4.bold.cyan(label) + chalk4.bold("\u2500".repeat(Math.max(0, fill)) + "\u2510");
|
|
3109
|
-
}
|
|
3110
|
-
function modalDivRow(w) {
|
|
3111
|
-
return chalk4.bold("\u251C" + "\u2500".repeat(w - 2) + "\u2524");
|
|
3112
|
-
}
|
|
3113
|
-
function modalBotRow(w) {
|
|
3114
|
-
return chalk4.bold("\u2514" + "\u2500".repeat(w - 2) + "\u2518");
|
|
3115
|
-
}
|
|
3116
|
-
function modalRow(text, w) {
|
|
3117
|
-
const inner = w - 4;
|
|
3118
|
-
const fitted = truncateAnsi(text, inner);
|
|
3119
|
-
const pad = Math.max(0, inner - visibleLen(fitted));
|
|
3120
|
-
return chalk4.bold("\u2502") + " " + fitted + " ".repeat(pad) + " " + chalk4.bold("\u2502");
|
|
3121
|
-
}
|
|
3122
|
-
function modalEmptyRow(w) {
|
|
3123
|
-
return modalRow("", w);
|
|
3124
|
-
}
|
|
3125
|
-
function buildModalFrame(title, contentLines, footerText, width) {
|
|
3126
|
-
const lines = [];
|
|
3127
|
-
lines.push(modalTopRow(title, width));
|
|
3128
|
-
lines.push(modalEmptyRow(width));
|
|
3129
|
-
for (const line of contentLines) {
|
|
3130
|
-
lines.push(modalRow(line, width));
|
|
3131
|
-
}
|
|
3132
|
-
lines.push(modalEmptyRow(width));
|
|
3133
|
-
lines.push(modalDivRow(width));
|
|
3134
|
-
lines.push(modalRow(chalk4.dim(footerText), width));
|
|
3135
|
-
lines.push(modalBotRow(width));
|
|
3136
|
-
return lines;
|
|
3137
|
-
}
|
|
3138
|
-
function isSeparator(entry) {
|
|
3139
|
-
return "separator" in entry;
|
|
3140
|
-
}
|
|
3141
|
-
function buildSelectContent(items, selectedIndex, maxWidth, currentIndex) {
|
|
3142
|
-
const lines = [];
|
|
3143
|
-
let selectableIdx = 0;
|
|
3144
|
-
for (const item of items) {
|
|
3145
|
-
if (isSeparator(item)) {
|
|
3146
|
-
lines.push(chalk4.dim(item.separator));
|
|
3147
|
-
} else {
|
|
3148
|
-
const isSelected = selectableIdx === selectedIndex;
|
|
3149
|
-
const isCurrent = currentIndex != null && selectableIdx === currentIndex;
|
|
3150
|
-
const prefix = isSelected ? chalk4.cyan.bold("> ") : " ";
|
|
3151
|
-
const bullet = chalk4.dim("\xB7 ");
|
|
3152
|
-
const label = truncateAnsi(item.name, maxWidth - 4);
|
|
3153
|
-
const styledLabel = isSelected ? chalk4.bold(label) : isCurrent ? chalk4.cyan(label) : label;
|
|
3154
|
-
lines.push(prefix + bullet + styledLabel);
|
|
3155
|
-
selectableIdx++;
|
|
3156
|
-
}
|
|
3157
|
-
}
|
|
3158
|
-
return lines;
|
|
3159
|
-
}
|
|
3160
|
-
function buildConfirmContent(message, selectedYes) {
|
|
3161
|
-
const yes = selectedYes ? chalk4.cyan.bold("> Yes") : chalk4.dim(" Yes");
|
|
3162
|
-
const no = !selectedYes ? chalk4.cyan.bold("> No") : chalk4.dim(" No");
|
|
3163
|
-
return [message, "", yes, no];
|
|
3164
|
-
}
|
|
3165
|
-
function buildInputContent(label, value, cursorVisible) {
|
|
3166
|
-
const cursor = cursorVisible ? chalk4.cyan("_") : " ";
|
|
3167
|
-
return [label, "", chalk4.cyan("> ") + value + cursor];
|
|
3168
|
-
}
|
|
3169
|
-
function buildMultilineInputContent(label, value, cursorVisible, maxWidth) {
|
|
3170
|
-
const cursor = cursorVisible ? chalk4.cyan("_") : " ";
|
|
3171
|
-
const lines = [label, ""];
|
|
3172
|
-
if (value === "") {
|
|
3173
|
-
lines.push(chalk4.cyan(" ") + cursor);
|
|
3174
|
-
} else {
|
|
3175
|
-
const inputLines = value.split("\n");
|
|
3176
|
-
const contentWidth = maxWidth - 2;
|
|
3177
|
-
for (let i = 0; i < inputLines.length; i++) {
|
|
3178
|
-
const raw = inputLines[i];
|
|
3179
|
-
const wrapped = wrapLine(raw, contentWidth);
|
|
3180
|
-
for (let j = 0; j < wrapped.length; j++) {
|
|
3181
|
-
const isLastLine = i === inputLines.length - 1 && j === wrapped.length - 1;
|
|
3182
|
-
const text = wrapped[j] + (isLastLine ? cursor : "");
|
|
3183
|
-
lines.push(chalk4.cyan(" ") + text);
|
|
3184
|
-
}
|
|
3185
|
-
}
|
|
3186
|
-
}
|
|
3187
|
-
return lines;
|
|
3188
|
-
}
|
|
3189
|
-
function wrapLine(text, width) {
|
|
3190
|
-
if (width <= 0) return [text];
|
|
3191
|
-
if (text.length <= width) return [text];
|
|
3192
|
-
const result = [];
|
|
3193
|
-
let remaining = text;
|
|
3194
|
-
while (remaining.length > width) {
|
|
3195
|
-
let breakAt = remaining.lastIndexOf(" ", width);
|
|
3196
|
-
if (breakAt <= 0) breakAt = width;
|
|
3197
|
-
result.push(remaining.slice(0, breakAt));
|
|
3198
|
-
remaining = remaining.slice(breakAt).replace(/^ /, "");
|
|
3199
|
-
}
|
|
3200
|
-
if (remaining.length > 0 || result.length === 0) {
|
|
3201
|
-
result.push(remaining);
|
|
3202
|
-
}
|
|
3203
|
-
return result;
|
|
3204
|
-
}
|
|
3205
|
-
function termSize() {
|
|
3206
|
-
return {
|
|
3207
|
-
cols: process.stdout.columns || 80,
|
|
3208
|
-
rows: process.stdout.rows || 24
|
|
3209
|
-
};
|
|
3210
|
-
}
|
|
3211
|
-
function moveTo(row2, col) {
|
|
3212
|
-
return `\x1B[${row2};${col}H`;
|
|
3213
|
-
}
|
|
3214
|
-
function hideCursor() {
|
|
3215
|
-
return "\x1B[?25l";
|
|
3216
|
-
}
|
|
3217
|
-
function showCursor() {
|
|
3218
|
-
return "\x1B[?25h";
|
|
3219
|
-
}
|
|
3220
|
-
function modalWidth() {
|
|
3221
|
-
const cols = process.stdout.columns || 80;
|
|
3222
|
-
return Math.min(60, cols - 4);
|
|
3223
|
-
}
|
|
3224
|
-
function renderOverlay(dashboardLines, modalLines, mWidth) {
|
|
3225
|
-
const { cols, rows } = termSize();
|
|
3226
|
-
process.stdout.write("\x1B[2J\x1B[0f");
|
|
3227
|
-
process.stdout.write(hideCursor());
|
|
3228
|
-
for (let i = 0; i < dashboardLines.length && i < rows; i++) {
|
|
3229
|
-
process.stdout.write(
|
|
3230
|
-
moveTo(i + 1, 1) + chalk4.dim(stripAnsi(dashboardLines[i]))
|
|
3231
|
-
);
|
|
3232
|
-
}
|
|
3233
|
-
const startRow = Math.max(1, Math.floor((rows - modalLines.length) / 2));
|
|
3234
|
-
const startCol = Math.max(1, Math.floor((cols - mWidth) / 2));
|
|
3235
|
-
for (let i = 0; i < modalLines.length; i++) {
|
|
3236
|
-
process.stdout.write(moveTo(startRow + i, startCol) + modalLines[i]);
|
|
3237
|
-
}
|
|
3238
|
-
process.stdout.write(moveTo(startRow + modalLines.length + 1, 1));
|
|
3239
|
-
}
|
|
3112
|
+
import chalk8 from "chalk";
|
|
3113
|
+
|
|
3114
|
+
// src/tui/key-reader.ts
|
|
3240
3115
|
function readKey() {
|
|
3241
3116
|
return new Promise((resolve) => {
|
|
3242
3117
|
const { stdin } = process;
|
|
@@ -3260,6 +3135,20 @@ function readKey() {
|
|
|
3260
3135
|
resolve({ name: "right", raw: data });
|
|
3261
3136
|
} else if (data === "\x1B[D") {
|
|
3262
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 });
|
|
3263
3152
|
} else if (data === "\r" || data === "\n") {
|
|
3264
3153
|
resolve({ name: "enter", raw: data });
|
|
3265
3154
|
} else if (data === "\x7F" || data === "\b") {
|
|
@@ -3279,39 +3168,30 @@ function readKey() {
|
|
|
3279
3168
|
stdin.on("data", onData);
|
|
3280
3169
|
});
|
|
3281
3170
|
}
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
return selectedYes;
|
|
3304
|
-
case "escape":
|
|
3305
|
-
process.stdout.write(showCursor());
|
|
3306
|
-
return false;
|
|
3307
|
-
case "y":
|
|
3308
|
-
process.stdout.write(showCursor());
|
|
3309
|
-
return true;
|
|
3310
|
-
case "n":
|
|
3311
|
-
process.stdout.write(showCursor());
|
|
3312
|
-
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++;
|
|
3313
3192
|
}
|
|
3314
3193
|
}
|
|
3194
|
+
return lines;
|
|
3315
3195
|
}
|
|
3316
3196
|
function selectableCount(items) {
|
|
3317
3197
|
return items.filter((i) => !isSeparator(i)).length;
|
|
@@ -3356,6 +3236,55 @@ async function showSelect(opts) {
|
|
|
3356
3236
|
}
|
|
3357
3237
|
}
|
|
3358
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
|
+
}
|
|
3359
3288
|
async function showInput(opts) {
|
|
3360
3289
|
const w = modalWidth();
|
|
3361
3290
|
let value = "";
|
|
@@ -3394,9 +3323,115 @@ async function showInput(opts) {
|
|
|
3394
3323
|
}
|
|
3395
3324
|
}
|
|
3396
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
|
+
}
|
|
3397
3431
|
async function showMultilineInput(opts) {
|
|
3398
3432
|
const w = modalWidth();
|
|
3399
3433
|
let value = "";
|
|
3434
|
+
let cursorPos = 0;
|
|
3400
3435
|
let cursorBlink = true;
|
|
3401
3436
|
const contentWidth = w - 6;
|
|
3402
3437
|
const render = () => {
|
|
@@ -3404,7 +3439,8 @@ async function showMultilineInput(opts) {
|
|
|
3404
3439
|
opts.label,
|
|
3405
3440
|
value,
|
|
3406
3441
|
cursorBlink,
|
|
3407
|
-
contentWidth
|
|
3442
|
+
contentWidth,
|
|
3443
|
+
cursorPos
|
|
3408
3444
|
);
|
|
3409
3445
|
const footer = "Enter Newline Ctrl+S Submit Esc Cancel";
|
|
3410
3446
|
const lines = buildModalFrame(opts.title, content, footer, w);
|
|
@@ -3418,7 +3454,8 @@ async function showMultilineInput(opts) {
|
|
|
3418
3454
|
process.stdout.write(showCursor());
|
|
3419
3455
|
return value.trim() || null;
|
|
3420
3456
|
case "enter":
|
|
3421
|
-
value
|
|
3457
|
+
value = value.slice(0, cursorPos) + "\n" + value.slice(cursorPos);
|
|
3458
|
+
cursorPos++;
|
|
3422
3459
|
cursorBlink = true;
|
|
3423
3460
|
render();
|
|
3424
3461
|
break;
|
|
@@ -3426,14 +3463,54 @@ async function showMultilineInput(opts) {
|
|
|
3426
3463
|
process.stdout.write(showCursor());
|
|
3427
3464
|
return null;
|
|
3428
3465
|
case "backspace":
|
|
3429
|
-
if (
|
|
3430
|
-
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);
|
|
3431
3475
|
}
|
|
3432
3476
|
render();
|
|
3433
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;
|
|
3434
3510
|
default:
|
|
3435
3511
|
if (key.raw.length === 1 && key.raw.charCodeAt(0) >= 32) {
|
|
3436
|
-
value
|
|
3512
|
+
value = value.slice(0, cursorPos) + key.raw + value.slice(cursorPos);
|
|
3513
|
+
cursorPos++;
|
|
3437
3514
|
cursorBlink = true;
|
|
3438
3515
|
render();
|
|
3439
3516
|
}
|
|
@@ -3441,6 +3518,76 @@ async function showMultilineInput(opts) {
|
|
|
3441
3518
|
}
|
|
3442
3519
|
}
|
|
3443
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
|
+
}
|
|
3444
3591
|
function showStatus(title, message, dashboardLines) {
|
|
3445
3592
|
const w = modalWidth();
|
|
3446
3593
|
const content = [message];
|