@qodalis/cli 2.0.0-beta.7 → 2.0.0-beta.8
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/package.json +2 -2
- package/public-api.d.mts +99 -78
- package/public-api.d.ts +99 -78
- package/public-api.js +2874 -707
- package/public-api.js.map +1 -1
- package/public-api.mjs +2874 -708
- package/public-api.mjs.map +1 -1
- package/wasm/qodalis_cli_wasm_bg.wasm +0 -0
package/public-api.mjs
CHANGED
|
@@ -410,7 +410,7 @@ var CliBackgroundServiceRegistry_TOKEN = "cli-background-service-registry";
|
|
|
410
410
|
var CliPackageManagerService_TOKEN = "cli-package-manager-service";
|
|
411
411
|
|
|
412
412
|
// src/lib/version.ts
|
|
413
|
-
var LIBRARY_VERSION = "2.0.0-beta.
|
|
413
|
+
var LIBRARY_VERSION = "2.0.0-beta.8";
|
|
414
414
|
var API_VERSION = 2;
|
|
415
415
|
var CliAliasCommandProcessor = class {
|
|
416
416
|
constructor() {
|
|
@@ -2550,10 +2550,8 @@ var CliUptimeCommandProcessor = class {
|
|
|
2550
2550
|
icon: "\u23F1\uFE0F",
|
|
2551
2551
|
module: "misc"
|
|
2552
2552
|
};
|
|
2553
|
-
this.sessionStartTimes = /* @__PURE__ */ new WeakMap();
|
|
2554
2553
|
}
|
|
2555
2554
|
async initialize(context2) {
|
|
2556
|
-
this.sessionStartTimes.set(context2.terminal, Date.now());
|
|
2557
2555
|
try {
|
|
2558
2556
|
context2.backgroundServices.register({
|
|
2559
2557
|
name: "uptime-tracker",
|
|
@@ -2561,6 +2559,10 @@ var CliUptimeCommandProcessor = class {
|
|
|
2561
2559
|
type: "daemon",
|
|
2562
2560
|
onStart: async (ctx) => {
|
|
2563
2561
|
ctx.log("Uptime tracking started");
|
|
2562
|
+
await new Promise((resolve) => {
|
|
2563
|
+
ctx.signal.addEventListener("abort", () => resolve(), { once: true });
|
|
2564
|
+
});
|
|
2565
|
+
ctx.log("Uptime tracking stopped");
|
|
2564
2566
|
}
|
|
2565
2567
|
});
|
|
2566
2568
|
await context2.backgroundServices.start("uptime-tracker");
|
|
@@ -2569,9 +2571,16 @@ var CliUptimeCommandProcessor = class {
|
|
|
2569
2571
|
}
|
|
2570
2572
|
async processCommand(_, context2) {
|
|
2571
2573
|
const svcInfo = context2.backgroundServices.getStatus("uptime-tracker");
|
|
2572
|
-
const startTime = svcInfo?.status === "running" && svcInfo.startedAt ? svcInfo.startedAt.getTime() : this.sessionStartTimes.get(context2.terminal) ?? Date.now();
|
|
2573
|
-
const elapsed = Date.now() - startTime;
|
|
2574
2574
|
const { writer } = context2;
|
|
2575
|
+
if (!svcInfo || svcInfo.status !== "running") {
|
|
2576
|
+
writer.writeError("Uptime tracker is not running.");
|
|
2577
|
+
writer.writeInfo(
|
|
2578
|
+
`Use ${writer.wrapInColor("services start uptime-tracker", CliForegroundColor.Cyan)} to restart.`
|
|
2579
|
+
);
|
|
2580
|
+
return;
|
|
2581
|
+
}
|
|
2582
|
+
const startTime = svcInfo.startedAt.getTime();
|
|
2583
|
+
const elapsed = Date.now() - startTime;
|
|
2575
2584
|
const seconds = Math.floor(elapsed / 1e3) % 60;
|
|
2576
2585
|
const minutes = Math.floor(elapsed / (1e3 * 60)) % 60;
|
|
2577
2586
|
const hours = Math.floor(elapsed / (1e3 * 60 * 60)) % 24;
|
|
@@ -6217,6 +6226,9 @@ var CliServerManager = class {
|
|
|
6217
6226
|
|
|
6218
6227
|
// src/lib/wasm/fallback.ts
|
|
6219
6228
|
var JsFallbackAccelerator = class {
|
|
6229
|
+
constructor() {
|
|
6230
|
+
this.ruleSets = /* @__PURE__ */ new Map();
|
|
6231
|
+
}
|
|
6220
6232
|
textSearch(text, needle, startRow, startCol, caseSensitive, wrap) {
|
|
6221
6233
|
if (!needle) return [-1, -1];
|
|
6222
6234
|
const lines = text.split("\n");
|
|
@@ -6276,6 +6288,60 @@ var JsFallbackAccelerator = class {
|
|
|
6276
6288
|
}
|
|
6277
6289
|
return matches.sort();
|
|
6278
6290
|
}
|
|
6291
|
+
registerRuleSet(ruleSetId, rules) {
|
|
6292
|
+
const parsed = [];
|
|
6293
|
+
for (const line of rules.split("\n")) {
|
|
6294
|
+
if (!line) continue;
|
|
6295
|
+
const parts = line.split(" ");
|
|
6296
|
+
if (parts.length < 2) continue;
|
|
6297
|
+
try {
|
|
6298
|
+
parsed.push({
|
|
6299
|
+
pattern: new RegExp(parts[0]),
|
|
6300
|
+
tokenType: parts[1],
|
|
6301
|
+
captureGroup: parts[2] ? parseInt(parts[2], 10) : 0
|
|
6302
|
+
});
|
|
6303
|
+
} catch {
|
|
6304
|
+
}
|
|
6305
|
+
}
|
|
6306
|
+
this.ruleSets.set(ruleSetId, parsed);
|
|
6307
|
+
}
|
|
6308
|
+
tokenizeLine(line, ruleSetId) {
|
|
6309
|
+
const rules = this.ruleSets.get(ruleSetId);
|
|
6310
|
+
if (!rules || !line) return "";
|
|
6311
|
+
const tokens = [];
|
|
6312
|
+
let pos = 0;
|
|
6313
|
+
while (pos < line.length) {
|
|
6314
|
+
let matched = false;
|
|
6315
|
+
for (const rule of rules) {
|
|
6316
|
+
rule.pattern.lastIndex = 0;
|
|
6317
|
+
const sliced = line.slice(pos);
|
|
6318
|
+
const m = rule.pattern.exec(sliced);
|
|
6319
|
+
if (m) {
|
|
6320
|
+
let start;
|
|
6321
|
+
let end;
|
|
6322
|
+
if (rule.captureGroup > 0 && m[rule.captureGroup] !== void 0) {
|
|
6323
|
+
const groupText = m[rule.captureGroup];
|
|
6324
|
+
const groupOffset = m[0].indexOf(groupText);
|
|
6325
|
+
start = pos + m.index + groupOffset;
|
|
6326
|
+
end = start + groupText.length;
|
|
6327
|
+
} else {
|
|
6328
|
+
start = pos + m.index;
|
|
6329
|
+
end = pos + m.index + m[0].length;
|
|
6330
|
+
}
|
|
6331
|
+
if (pos + m.index + m[0].length > pos) {
|
|
6332
|
+
tokens.push(`${start},${end},${rule.tokenType}`);
|
|
6333
|
+
pos = pos + m.index + m[0].length;
|
|
6334
|
+
matched = true;
|
|
6335
|
+
break;
|
|
6336
|
+
}
|
|
6337
|
+
}
|
|
6338
|
+
}
|
|
6339
|
+
if (!matched) {
|
|
6340
|
+
pos++;
|
|
6341
|
+
}
|
|
6342
|
+
}
|
|
6343
|
+
return tokens.join("\n");
|
|
6344
|
+
}
|
|
6279
6345
|
commonPrefix(strings) {
|
|
6280
6346
|
if (strings.length === 0) return "";
|
|
6281
6347
|
let prefix = strings[0];
|
|
@@ -6448,6 +6514,30 @@ function wasmCommonPrefix(strings) {
|
|
|
6448
6514
|
wasm.__wbindgen_export3(d0, d1, 1);
|
|
6449
6515
|
}
|
|
6450
6516
|
}
|
|
6517
|
+
function wasmRegisterRuleSet(ruleSetId, rules) {
|
|
6518
|
+
const ptr0 = passStringToWasm0(ruleSetId, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
6519
|
+
const len0 = WASM_VECTOR_LEN;
|
|
6520
|
+
const ptr1 = passStringToWasm0(rules, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
6521
|
+
const len1 = WASM_VECTOR_LEN;
|
|
6522
|
+
wasm.register_rule_set(ptr0, len0, ptr1, len1);
|
|
6523
|
+
}
|
|
6524
|
+
function wasmTokenizeLine(line, ruleSetId) {
|
|
6525
|
+
let d0, d1;
|
|
6526
|
+
try {
|
|
6527
|
+
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
6528
|
+
const ptr0 = passStringToWasm0(line, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
6529
|
+
const len0 = WASM_VECTOR_LEN;
|
|
6530
|
+
const ptr1 = passStringToWasm0(ruleSetId, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
6531
|
+
const len1 = WASM_VECTOR_LEN;
|
|
6532
|
+
wasm.tokenize_line(retptr, ptr0, len0, ptr1, len1);
|
|
6533
|
+
d0 = getDataViewMemory0().getInt32(retptr + 0, true);
|
|
6534
|
+
d1 = getDataViewMemory0().getInt32(retptr + 4, true);
|
|
6535
|
+
return getStringFromWasm0(d0, d1);
|
|
6536
|
+
} finally {
|
|
6537
|
+
wasm.__wbindgen_add_to_stack_pointer(16);
|
|
6538
|
+
wasm.__wbindgen_export3(d0, d1, 1);
|
|
6539
|
+
}
|
|
6540
|
+
}
|
|
6451
6541
|
async function instantiateWasm(wasmBytes) {
|
|
6452
6542
|
const imports = { __proto__: null, "./qodalis_cli_wasm_bg.js": { __proto__: null } };
|
|
6453
6543
|
const { instance } = await WebAssembly.instantiate(wasmBytes, imports);
|
|
@@ -6488,6 +6578,12 @@ function createWasmAccelerator() {
|
|
|
6488
6578
|
},
|
|
6489
6579
|
commonPrefix(strings) {
|
|
6490
6580
|
return wasmCommonPrefix(strings.join("\n"));
|
|
6581
|
+
},
|
|
6582
|
+
registerRuleSet(ruleSetId, rules) {
|
|
6583
|
+
wasmRegisterRuleSet(ruleSetId, rules);
|
|
6584
|
+
},
|
|
6585
|
+
tokenizeLine(line, ruleSetId) {
|
|
6586
|
+
return wasmTokenizeLine(line, ruleSetId);
|
|
6491
6587
|
}
|
|
6492
6588
|
};
|
|
6493
6589
|
}
|
|
@@ -7414,7 +7510,11 @@ var NanoEditorRenderer = class {
|
|
|
7414
7510
|
output2 += "\x1B[2K";
|
|
7415
7511
|
if (lineIdx < buffer.lines.length) {
|
|
7416
7512
|
const line = buffer.lines[lineIdx];
|
|
7417
|
-
|
|
7513
|
+
if (this.highlightEngine) {
|
|
7514
|
+
output2 += this.highlightEngine.renderLine(lineIdx, line, cols);
|
|
7515
|
+
} else {
|
|
7516
|
+
output2 += line.length > cols ? line.slice(0, cols) : line;
|
|
7517
|
+
}
|
|
7418
7518
|
}
|
|
7419
7519
|
}
|
|
7420
7520
|
output2 += `\x1B[${rows - 1};1H`;
|
|
@@ -7493,6 +7593,135 @@ var NanoEditorRenderer = class {
|
|
|
7493
7593
|
}
|
|
7494
7594
|
};
|
|
7495
7595
|
|
|
7596
|
+
// src/lib/editor/syntax/utils.ts
|
|
7597
|
+
function truncateAnsi(str, maxCols) {
|
|
7598
|
+
let visible = 0;
|
|
7599
|
+
let i = 0;
|
|
7600
|
+
let result = "";
|
|
7601
|
+
let hasActiveColor = false;
|
|
7602
|
+
while (i < str.length && visible < maxCols) {
|
|
7603
|
+
if (str[i] === "\x1B" && i + 1 < str.length && str[i + 1] === "[") {
|
|
7604
|
+
let j = i + 2;
|
|
7605
|
+
while (j < str.length && !(str.charCodeAt(j) >= 64 && str.charCodeAt(j) <= 126)) {
|
|
7606
|
+
j++;
|
|
7607
|
+
}
|
|
7608
|
+
if (j < str.length) {
|
|
7609
|
+
const seq = str.slice(i, j + 1);
|
|
7610
|
+
result += seq;
|
|
7611
|
+
if (seq === "\x1B[0m") {
|
|
7612
|
+
hasActiveColor = false;
|
|
7613
|
+
} else {
|
|
7614
|
+
hasActiveColor = true;
|
|
7615
|
+
}
|
|
7616
|
+
i = j + 1;
|
|
7617
|
+
} else {
|
|
7618
|
+
break;
|
|
7619
|
+
}
|
|
7620
|
+
} else {
|
|
7621
|
+
result += str[i];
|
|
7622
|
+
visible++;
|
|
7623
|
+
i++;
|
|
7624
|
+
}
|
|
7625
|
+
}
|
|
7626
|
+
if (hasActiveColor) {
|
|
7627
|
+
result += "\x1B[0m";
|
|
7628
|
+
}
|
|
7629
|
+
return result;
|
|
7630
|
+
}
|
|
7631
|
+
function parseTokens(raw) {
|
|
7632
|
+
if (!raw) return [];
|
|
7633
|
+
return raw.split("\n").map((entry) => {
|
|
7634
|
+
const [s, e, ...rest] = entry.split(",");
|
|
7635
|
+
return { start: parseInt(s, 10), end: parseInt(e, 10), tokenType: rest.join(",") };
|
|
7636
|
+
});
|
|
7637
|
+
}
|
|
7638
|
+
|
|
7639
|
+
// src/lib/editor/syntax/engine.ts
|
|
7640
|
+
var SyntaxHighlightEngine = class {
|
|
7641
|
+
constructor(highlighter, theme, accelerator2) {
|
|
7642
|
+
this.highlighter = highlighter;
|
|
7643
|
+
this.theme = theme;
|
|
7644
|
+
this.accelerator = accelerator2;
|
|
7645
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
7646
|
+
this.registeredRuleSets = /* @__PURE__ */ new Set();
|
|
7647
|
+
}
|
|
7648
|
+
invalidate(fromLine) {
|
|
7649
|
+
for (const key of this.cache.keys()) {
|
|
7650
|
+
if (key >= fromLine) {
|
|
7651
|
+
this.cache.delete(key);
|
|
7652
|
+
}
|
|
7653
|
+
}
|
|
7654
|
+
}
|
|
7655
|
+
renderLine(lineIdx, line, cols) {
|
|
7656
|
+
const entryState = lineIdx === 0 ? this.highlighter.initialState : this.cache.get(lineIdx - 1)?.exitState ?? this.highlighter.initialState;
|
|
7657
|
+
const cached = this.cache.get(lineIdx);
|
|
7658
|
+
if (cached && cached.lineContent === line && cached.entryState === entryState) {
|
|
7659
|
+
return truncateAnsi(cached.rendered, cols);
|
|
7660
|
+
}
|
|
7661
|
+
try {
|
|
7662
|
+
const ruleSetId = `${this.highlighter.id}:${entryState}`;
|
|
7663
|
+
if (!this.registeredRuleSets.has(ruleSetId)) {
|
|
7664
|
+
const rules = this.highlighter.getRules(entryState);
|
|
7665
|
+
const serialized = rules.map(
|
|
7666
|
+
(r) => `${r.pattern.source} ${r.tokenType} ${r.captureGroup ?? 0}`
|
|
7667
|
+
).join("\n");
|
|
7668
|
+
this.accelerator.registerRuleSet(ruleSetId, serialized);
|
|
7669
|
+
this.registeredRuleSets.add(ruleSetId);
|
|
7670
|
+
}
|
|
7671
|
+
const raw = this.accelerator.tokenizeLine(line, ruleSetId);
|
|
7672
|
+
const tokens = parseTokens(raw);
|
|
7673
|
+
const exitState = this.highlighter.getNextLineState(line, entryState);
|
|
7674
|
+
const rendered = this.applyTheme(line, tokens);
|
|
7675
|
+
this.cache.set(lineIdx, { lineContent: line, entryState, exitState, rendered });
|
|
7676
|
+
return truncateAnsi(rendered, cols);
|
|
7677
|
+
} catch {
|
|
7678
|
+
return line.length > cols ? line.slice(0, cols) : line;
|
|
7679
|
+
}
|
|
7680
|
+
}
|
|
7681
|
+
applyTheme(line, tokens) {
|
|
7682
|
+
if (tokens.length === 0) return line;
|
|
7683
|
+
let result = "";
|
|
7684
|
+
let pos = 0;
|
|
7685
|
+
for (const token of tokens) {
|
|
7686
|
+
if (token.start > pos) {
|
|
7687
|
+
result += line.slice(pos, token.start);
|
|
7688
|
+
}
|
|
7689
|
+
const color = this.theme.colors[token.tokenType];
|
|
7690
|
+
const text = line.slice(token.start, token.end);
|
|
7691
|
+
if (color) {
|
|
7692
|
+
result += color + text + "\x1B[0m";
|
|
7693
|
+
} else {
|
|
7694
|
+
result += text;
|
|
7695
|
+
}
|
|
7696
|
+
pos = token.end;
|
|
7697
|
+
}
|
|
7698
|
+
if (pos < line.length) {
|
|
7699
|
+
result += line.slice(pos);
|
|
7700
|
+
}
|
|
7701
|
+
return result;
|
|
7702
|
+
}
|
|
7703
|
+
};
|
|
7704
|
+
|
|
7705
|
+
// src/lib/editor/syntax/theme.ts
|
|
7706
|
+
var defaultSyntaxTheme = {
|
|
7707
|
+
name: "default",
|
|
7708
|
+
colors: {
|
|
7709
|
+
keyword: "\x1B[93m",
|
|
7710
|
+
string: "\x1B[32m",
|
|
7711
|
+
number: "\x1B[36m",
|
|
7712
|
+
comment: "\x1B[90m",
|
|
7713
|
+
tag: "\x1B[91m",
|
|
7714
|
+
attribute: "\x1B[33m",
|
|
7715
|
+
value: "\x1B[36m",
|
|
7716
|
+
punctuation: "\x1B[97m",
|
|
7717
|
+
heading: "\x1B[1;93m",
|
|
7718
|
+
link: "\x1B[4;36m",
|
|
7719
|
+
bold: "\x1B[1m",
|
|
7720
|
+
italic: "\x1B[3m",
|
|
7721
|
+
code: "\x1B[90;47m"
|
|
7722
|
+
}
|
|
7723
|
+
};
|
|
7724
|
+
|
|
7496
7725
|
// src/lib/processors/system/cli-nano-command-processor.ts
|
|
7497
7726
|
var FS_TOKEN = "cli-file-system-service";
|
|
7498
7727
|
var CliNanoCommandProcessor = class {
|
|
@@ -7560,6 +7789,7 @@ var CliNanoCommandProcessor = class {
|
|
|
7560
7789
|
} else {
|
|
7561
7790
|
this.filePath = null;
|
|
7562
7791
|
}
|
|
7792
|
+
this.setupHighlighting(this.filePath);
|
|
7563
7793
|
context2.enterFullScreenMode(this);
|
|
7564
7794
|
this.renderer.render(this.buffer, this.filePath || "New Buffer");
|
|
7565
7795
|
this.resizeDisposable = context2.terminal.onResize(() => {
|
|
@@ -7688,11 +7918,13 @@ var CliNanoCommandProcessor = class {
|
|
|
7688
7918
|
}
|
|
7689
7919
|
if (data === "\v") {
|
|
7690
7920
|
this.buffer.deleteLine();
|
|
7921
|
+
this.renderer.highlightEngine?.invalidate(this.buffer.cursorRow);
|
|
7691
7922
|
this.render();
|
|
7692
7923
|
return;
|
|
7693
7924
|
}
|
|
7694
7925
|
if (data === "") {
|
|
7695
7926
|
if (this.buffer.uncutLines()) {
|
|
7927
|
+
this.renderer.highlightEngine?.invalidate(this.buffer.cursorRow);
|
|
7696
7928
|
this.render();
|
|
7697
7929
|
} else {
|
|
7698
7930
|
this.showStatus("Clipboard is empty");
|
|
@@ -7753,11 +7985,16 @@ var CliNanoCommandProcessor = class {
|
|
|
7753
7985
|
}
|
|
7754
7986
|
if (data === "\r") {
|
|
7755
7987
|
this.buffer.insertNewline();
|
|
7988
|
+
this.renderer.highlightEngine?.invalidate(this.buffer.cursorRow - 1);
|
|
7756
7989
|
this.render();
|
|
7757
7990
|
return;
|
|
7758
7991
|
}
|
|
7759
7992
|
if (data === "\x7F") {
|
|
7993
|
+
const wasMerge = this.buffer.cursorCol === 0 && this.buffer.cursorRow > 0;
|
|
7760
7994
|
this.buffer.deleteCharBefore();
|
|
7995
|
+
if (wasMerge) {
|
|
7996
|
+
this.renderer.highlightEngine?.invalidate(this.buffer.cursorRow);
|
|
7997
|
+
}
|
|
7761
7998
|
this.render();
|
|
7762
7999
|
return;
|
|
7763
8000
|
}
|
|
@@ -7850,6 +8087,31 @@ var CliNanoCommandProcessor = class {
|
|
|
7850
8087
|
` ${writer.wrapInColor("^Y", CliForegroundColor.Yellow)} Page Up ${writer.wrapInColor("^V", CliForegroundColor.Yellow)} Page Down`
|
|
7851
8088
|
);
|
|
7852
8089
|
}
|
|
8090
|
+
// ── Syntax highlighting ──
|
|
8091
|
+
setupHighlighting(filePath) {
|
|
8092
|
+
if (!filePath) {
|
|
8093
|
+
this.renderer.highlightEngine = void 0;
|
|
8094
|
+
return;
|
|
8095
|
+
}
|
|
8096
|
+
try {
|
|
8097
|
+
const registry = this.context.services.get(
|
|
8098
|
+
"syntax-highlighter-registry"
|
|
8099
|
+
);
|
|
8100
|
+
const ext = "." + filePath.split(".").pop()?.toLowerCase();
|
|
8101
|
+
const highlighter = registry.getByExtension(ext);
|
|
8102
|
+
if (highlighter) {
|
|
8103
|
+
this.renderer.highlightEngine = new SyntaxHighlightEngine(
|
|
8104
|
+
highlighter,
|
|
8105
|
+
defaultSyntaxTheme,
|
|
8106
|
+
getAccelerator()
|
|
8107
|
+
);
|
|
8108
|
+
} else {
|
|
8109
|
+
this.renderer.highlightEngine = void 0;
|
|
8110
|
+
}
|
|
8111
|
+
} catch {
|
|
8112
|
+
this.renderer.highlightEngine = void 0;
|
|
8113
|
+
}
|
|
8114
|
+
}
|
|
7853
8115
|
// ── Rendering helpers ──
|
|
7854
8116
|
render() {
|
|
7855
8117
|
this.renderer.render(
|
|
@@ -8387,17 +8649,23 @@ var CliProcessRegistry = class {
|
|
|
8387
8649
|
get currentPid() {
|
|
8388
8650
|
return this._currentPid;
|
|
8389
8651
|
}
|
|
8390
|
-
register(command2) {
|
|
8652
|
+
register(command2, options) {
|
|
8391
8653
|
const pid = this.nextPid++;
|
|
8392
8654
|
const abortController = new AbortController();
|
|
8655
|
+
const type = options?.type ?? "command";
|
|
8393
8656
|
this.processes.set(pid, {
|
|
8394
8657
|
pid,
|
|
8658
|
+
name: options?.name ?? command2,
|
|
8659
|
+
type,
|
|
8395
8660
|
command: command2,
|
|
8396
8661
|
startTime: Date.now(),
|
|
8397
8662
|
status: "running",
|
|
8398
|
-
abortController
|
|
8663
|
+
abortController,
|
|
8664
|
+
onKill: options?.onKill
|
|
8399
8665
|
});
|
|
8400
|
-
|
|
8666
|
+
if (type === "command") {
|
|
8667
|
+
this._currentPid = pid;
|
|
8668
|
+
}
|
|
8401
8669
|
this.pruneHistory();
|
|
8402
8670
|
return { pid, abortController };
|
|
8403
8671
|
}
|
|
@@ -8424,6 +8692,10 @@ var CliProcessRegistry = class {
|
|
|
8424
8692
|
kill(pid) {
|
|
8425
8693
|
const entry = this.processes.get(pid);
|
|
8426
8694
|
if (!entry || entry.status !== "running") return false;
|
|
8695
|
+
if (entry.onKill) {
|
|
8696
|
+
Promise.resolve(entry.onKill()).catch(() => {
|
|
8697
|
+
});
|
|
8698
|
+
}
|
|
8427
8699
|
entry.abortController.abort();
|
|
8428
8700
|
entry.status = "killed";
|
|
8429
8701
|
entry.exitCode = -9;
|
|
@@ -8433,7 +8705,9 @@ var CliProcessRegistry = class {
|
|
|
8433
8705
|
return true;
|
|
8434
8706
|
}
|
|
8435
8707
|
list() {
|
|
8436
|
-
return Array.from(this.processes.values()).map(
|
|
8708
|
+
return Array.from(this.processes.values()).map(
|
|
8709
|
+
({ abortController, onKill, ...entry }) => entry
|
|
8710
|
+
);
|
|
8437
8711
|
}
|
|
8438
8712
|
pruneHistory() {
|
|
8439
8713
|
const entries = Array.from(this.processes.entries());
|
|
@@ -8470,11 +8744,13 @@ var CliPsCommandProcessor = class {
|
|
|
8470
8744
|
context2.writer.writeInfo("No processes");
|
|
8471
8745
|
return;
|
|
8472
8746
|
}
|
|
8473
|
-
const headers = ["PID", "STATUS", "EXIT", "TIME", "COMMAND"];
|
|
8747
|
+
const headers = ["PID", "NAME", "TYPE", "STATUS", "EXIT", "TIME", "COMMAND"];
|
|
8474
8748
|
const rows = processes.map((p) => {
|
|
8475
8749
|
const elapsed = `${Math.round((Date.now() - p.startTime) / 1e3)}s`;
|
|
8476
8750
|
return [
|
|
8477
8751
|
String(p.pid),
|
|
8752
|
+
p.name,
|
|
8753
|
+
p.type,
|
|
8478
8754
|
p.status,
|
|
8479
8755
|
p.exitCode !== void 0 ? String(p.exitCode) : "-",
|
|
8480
8756
|
elapsed,
|
|
@@ -8519,6 +8795,553 @@ var CliKillCommandProcessor = class {
|
|
|
8519
8795
|
}
|
|
8520
8796
|
}
|
|
8521
8797
|
};
|
|
8798
|
+
var ESC = "\x1B";
|
|
8799
|
+
var CSI = `${ESC}[`;
|
|
8800
|
+
var ansi = {
|
|
8801
|
+
clearScreen: `${CSI}2J`,
|
|
8802
|
+
cursorHome: `${CSI}H`,
|
|
8803
|
+
hideCursor: `${CSI}?25l`,
|
|
8804
|
+
cursorTo: (row, col) => `${CSI}${row};${col}H`,
|
|
8805
|
+
fg: {
|
|
8806
|
+
green: `${CSI}32m`,
|
|
8807
|
+
cyan: `${CSI}36m`,
|
|
8808
|
+
yellow: `${CSI}93m`,
|
|
8809
|
+
white: `${CSI}97m`,
|
|
8810
|
+
red: `${CSI}91m`,
|
|
8811
|
+
gray: `${CSI}90m`
|
|
8812
|
+
},
|
|
8813
|
+
bg: {
|
|
8814
|
+
blue: `${CSI}44m`
|
|
8815
|
+
},
|
|
8816
|
+
bold: `${CSI}1m`,
|
|
8817
|
+
dim: `${CSI}2m`,
|
|
8818
|
+
inverse: `${CSI}7m`,
|
|
8819
|
+
reset: `${CSI}0m`
|
|
8820
|
+
};
|
|
8821
|
+
var BOX = {
|
|
8822
|
+
topLeft: "\u250C",
|
|
8823
|
+
topRight: "\u2510",
|
|
8824
|
+
bottomLeft: "\u2514",
|
|
8825
|
+
bottomRight: "\u2518",
|
|
8826
|
+
horizontal: "\u2500",
|
|
8827
|
+
vertical: "\u2502"
|
|
8828
|
+
};
|
|
8829
|
+
function formatElapsed(ms) {
|
|
8830
|
+
const seconds = Math.floor(ms / 1e3) % 60;
|
|
8831
|
+
const minutes = Math.floor(ms / (1e3 * 60)) % 60;
|
|
8832
|
+
const hours = Math.floor(ms / (1e3 * 60 * 60)) % 24;
|
|
8833
|
+
const days = Math.floor(ms / (1e3 * 60 * 60 * 24));
|
|
8834
|
+
const parts = [];
|
|
8835
|
+
if (days > 0) parts.push(`${days}d`);
|
|
8836
|
+
if (hours > 0) parts.push(`${hours}h`);
|
|
8837
|
+
if (minutes > 0) parts.push(`${minutes}m`);
|
|
8838
|
+
parts.push(`${seconds}s`);
|
|
8839
|
+
return parts.join(" ");
|
|
8840
|
+
}
|
|
8841
|
+
function statusColor(status) {
|
|
8842
|
+
switch (status) {
|
|
8843
|
+
case "running":
|
|
8844
|
+
return ansi.fg.green;
|
|
8845
|
+
case "completed":
|
|
8846
|
+
case "done":
|
|
8847
|
+
return ansi.fg.cyan;
|
|
8848
|
+
case "pending":
|
|
8849
|
+
return ansi.fg.yellow;
|
|
8850
|
+
case "stopped":
|
|
8851
|
+
return ansi.fg.white;
|
|
8852
|
+
case "failed":
|
|
8853
|
+
case "killed":
|
|
8854
|
+
return ansi.fg.red;
|
|
8855
|
+
default:
|
|
8856
|
+
return ansi.fg.white;
|
|
8857
|
+
}
|
|
8858
|
+
}
|
|
8859
|
+
function truncate(str, maxLen) {
|
|
8860
|
+
if (str.length <= maxLen) return str;
|
|
8861
|
+
return str.substring(0, maxLen - 1) + "\u2026";
|
|
8862
|
+
}
|
|
8863
|
+
function pad(str, len) {
|
|
8864
|
+
return str.length >= len ? str.substring(0, len) : str + " ".repeat(len - str.length);
|
|
8865
|
+
}
|
|
8866
|
+
var CliHtopCommandProcessor = class {
|
|
8867
|
+
constructor() {
|
|
8868
|
+
this.command = "htop";
|
|
8869
|
+
this.description = "Interactive process and service monitor";
|
|
8870
|
+
this.author = DefaultLibraryAuthor;
|
|
8871
|
+
this.metadata = {
|
|
8872
|
+
icon: "\u{1F4CA}",
|
|
8873
|
+
sealed: true,
|
|
8874
|
+
module: "system"
|
|
8875
|
+
};
|
|
8876
|
+
this.context = null;
|
|
8877
|
+
this.registry = null;
|
|
8878
|
+
this.refreshTimer = null;
|
|
8879
|
+
this.rows = [];
|
|
8880
|
+
this.selectedIndex = 0;
|
|
8881
|
+
this.scrollOffset = 0;
|
|
8882
|
+
this.termCols = 80;
|
|
8883
|
+
this.termRows = 24;
|
|
8884
|
+
this.showingLogs = false;
|
|
8885
|
+
this.errorMessage = null;
|
|
8886
|
+
this.errorTimeout = null;
|
|
8887
|
+
}
|
|
8888
|
+
async processCommand(_command, context2) {
|
|
8889
|
+
try {
|
|
8890
|
+
this.registry = context2.services.get(
|
|
8891
|
+
CliProcessRegistry_TOKEN
|
|
8892
|
+
);
|
|
8893
|
+
} catch {
|
|
8894
|
+
context2.writer.writeError("Process registry not available");
|
|
8895
|
+
return;
|
|
8896
|
+
}
|
|
8897
|
+
this.context = context2;
|
|
8898
|
+
this.termCols = context2.terminal.cols || 80;
|
|
8899
|
+
this.termRows = context2.terminal.rows || 24;
|
|
8900
|
+
this.selectedIndex = 0;
|
|
8901
|
+
this.scrollOffset = 0;
|
|
8902
|
+
this.showingLogs = false;
|
|
8903
|
+
this.errorMessage = null;
|
|
8904
|
+
context2.enterFullScreenMode(this);
|
|
8905
|
+
this.refreshData();
|
|
8906
|
+
this.render();
|
|
8907
|
+
this.refreshTimer = context2.createInterval(() => {
|
|
8908
|
+
if (!this.showingLogs) {
|
|
8909
|
+
this.refreshData();
|
|
8910
|
+
this.render();
|
|
8911
|
+
}
|
|
8912
|
+
}, 1e3);
|
|
8913
|
+
}
|
|
8914
|
+
async onData(data, context2) {
|
|
8915
|
+
if (this.showingLogs) {
|
|
8916
|
+
this.showingLogs = false;
|
|
8917
|
+
this.render();
|
|
8918
|
+
return;
|
|
8919
|
+
}
|
|
8920
|
+
switch (data) {
|
|
8921
|
+
case `${ESC}[A`:
|
|
8922
|
+
this.moveSelection(-1);
|
|
8923
|
+
this.render();
|
|
8924
|
+
break;
|
|
8925
|
+
case `${ESC}[B`:
|
|
8926
|
+
this.moveSelection(1);
|
|
8927
|
+
this.render();
|
|
8928
|
+
break;
|
|
8929
|
+
case "q":
|
|
8930
|
+
case ESC:
|
|
8931
|
+
this.exit(context2);
|
|
8932
|
+
break;
|
|
8933
|
+
case "k":
|
|
8934
|
+
this.actionKill();
|
|
8935
|
+
break;
|
|
8936
|
+
case "s":
|
|
8937
|
+
await this.actionStart();
|
|
8938
|
+
break;
|
|
8939
|
+
case "t":
|
|
8940
|
+
await this.actionStop();
|
|
8941
|
+
break;
|
|
8942
|
+
case "r":
|
|
8943
|
+
await this.actionRestart();
|
|
8944
|
+
break;
|
|
8945
|
+
case "l":
|
|
8946
|
+
this.actionLogs();
|
|
8947
|
+
break;
|
|
8948
|
+
}
|
|
8949
|
+
}
|
|
8950
|
+
onResize(cols, rows, _context) {
|
|
8951
|
+
this.termCols = cols;
|
|
8952
|
+
this.termRows = rows;
|
|
8953
|
+
this.clampScroll();
|
|
8954
|
+
this.render();
|
|
8955
|
+
}
|
|
8956
|
+
onDispose(_context) {
|
|
8957
|
+
this.refreshTimer = null;
|
|
8958
|
+
this.context = null;
|
|
8959
|
+
this.registry = null;
|
|
8960
|
+
this.rows = [];
|
|
8961
|
+
}
|
|
8962
|
+
writeDescription(context2) {
|
|
8963
|
+
const { writer } = context2;
|
|
8964
|
+
writer.writeln("Interactive process and service monitor");
|
|
8965
|
+
writer.writeln();
|
|
8966
|
+
writer.writeln("Usage:");
|
|
8967
|
+
writer.writeln(` ${writer.wrapInColor("htop", 36)}`);
|
|
8968
|
+
}
|
|
8969
|
+
// ── Data ─────────────────────────────────────────────────────────
|
|
8970
|
+
refreshData() {
|
|
8971
|
+
if (!this.registry || !this.context) return;
|
|
8972
|
+
const processEntries = this.registry.list();
|
|
8973
|
+
const serviceEntries = this.context.backgroundServices.list();
|
|
8974
|
+
const servicePids = /* @__PURE__ */ new Set();
|
|
8975
|
+
for (const svc of serviceEntries) {
|
|
8976
|
+
if (svc.pid !== void 0) {
|
|
8977
|
+
servicePids.add(svc.pid);
|
|
8978
|
+
}
|
|
8979
|
+
}
|
|
8980
|
+
const unified = [];
|
|
8981
|
+
for (const p of processEntries) {
|
|
8982
|
+
if (servicePids.has(p.pid)) continue;
|
|
8983
|
+
unified.push({
|
|
8984
|
+
pid: p.pid,
|
|
8985
|
+
name: p.name,
|
|
8986
|
+
type: p.type,
|
|
8987
|
+
status: p.status,
|
|
8988
|
+
time: formatElapsed(Date.now() - p.startTime),
|
|
8989
|
+
mode: "main",
|
|
8990
|
+
source: "process"
|
|
8991
|
+
});
|
|
8992
|
+
}
|
|
8993
|
+
for (const svc of serviceEntries) {
|
|
8994
|
+
let time = "-";
|
|
8995
|
+
if (svc.status === "running" && svc.startedAt) {
|
|
8996
|
+
time = formatElapsed(Date.now() - new Date(svc.startedAt).getTime());
|
|
8997
|
+
}
|
|
8998
|
+
unified.push({
|
|
8999
|
+
pid: svc.pid ?? 0,
|
|
9000
|
+
name: svc.name,
|
|
9001
|
+
type: svc.type,
|
|
9002
|
+
status: svc.status,
|
|
9003
|
+
time,
|
|
9004
|
+
mode: svc.executionMode === "worker" ? "worker" : "main",
|
|
9005
|
+
source: "service",
|
|
9006
|
+
serviceInfo: svc
|
|
9007
|
+
});
|
|
9008
|
+
}
|
|
9009
|
+
unified.sort((a, b) => {
|
|
9010
|
+
const aRunning = a.status === "running" ? 0 : 1;
|
|
9011
|
+
const bRunning = b.status === "running" ? 0 : 1;
|
|
9012
|
+
if (aRunning !== bRunning) return aRunning - bRunning;
|
|
9013
|
+
return b.pid - a.pid;
|
|
9014
|
+
});
|
|
9015
|
+
this.rows = unified;
|
|
9016
|
+
if (this.rows.length === 0) {
|
|
9017
|
+
this.selectedIndex = 0;
|
|
9018
|
+
} else {
|
|
9019
|
+
this.selectedIndex = Math.min(this.selectedIndex, this.rows.length - 1);
|
|
9020
|
+
}
|
|
9021
|
+
this.clampScroll();
|
|
9022
|
+
}
|
|
9023
|
+
// ── Actions ──────────────────────────────────────────────────────
|
|
9024
|
+
actionKill() {
|
|
9025
|
+
const row = this.rows[this.selectedIndex];
|
|
9026
|
+
if (!row || row.status !== "running") return;
|
|
9027
|
+
if (row.name === "htop" && row.source === "process") {
|
|
9028
|
+
if (this.context) this.exit(this.context);
|
|
9029
|
+
return;
|
|
9030
|
+
}
|
|
9031
|
+
try {
|
|
9032
|
+
if (this.registry) {
|
|
9033
|
+
this.registry.kill(row.pid);
|
|
9034
|
+
}
|
|
9035
|
+
} catch (e) {
|
|
9036
|
+
this.showError(e?.message || "Kill failed");
|
|
9037
|
+
}
|
|
9038
|
+
this.refreshData();
|
|
9039
|
+
this.render();
|
|
9040
|
+
}
|
|
9041
|
+
async actionStart() {
|
|
9042
|
+
const row = this.rows[this.selectedIndex];
|
|
9043
|
+
if (!row || row.source !== "service") return;
|
|
9044
|
+
if (row.status !== "stopped" && row.status !== "pending" && row.status !== "failed") return;
|
|
9045
|
+
try {
|
|
9046
|
+
await this.context.backgroundServices.start(row.name);
|
|
9047
|
+
} catch (e) {
|
|
9048
|
+
this.showError(e?.message || "Start failed");
|
|
9049
|
+
}
|
|
9050
|
+
this.refreshData();
|
|
9051
|
+
this.render();
|
|
9052
|
+
}
|
|
9053
|
+
async actionStop() {
|
|
9054
|
+
const row = this.rows[this.selectedIndex];
|
|
9055
|
+
if (!row || row.source !== "service" || row.status !== "running") return;
|
|
9056
|
+
try {
|
|
9057
|
+
await this.context.backgroundServices.stop(row.name);
|
|
9058
|
+
} catch (e) {
|
|
9059
|
+
this.showError(e?.message || "Stop failed");
|
|
9060
|
+
}
|
|
9061
|
+
this.refreshData();
|
|
9062
|
+
this.render();
|
|
9063
|
+
}
|
|
9064
|
+
async actionRestart() {
|
|
9065
|
+
const row = this.rows[this.selectedIndex];
|
|
9066
|
+
if (!row || row.source !== "service") return;
|
|
9067
|
+
try {
|
|
9068
|
+
await this.context.backgroundServices.restart(row.name);
|
|
9069
|
+
} catch (e) {
|
|
9070
|
+
this.showError(e?.message || "Restart failed");
|
|
9071
|
+
}
|
|
9072
|
+
this.refreshData();
|
|
9073
|
+
this.render();
|
|
9074
|
+
}
|
|
9075
|
+
actionLogs() {
|
|
9076
|
+
const row = this.rows[this.selectedIndex];
|
|
9077
|
+
if (!row || row.source !== "service" || !this.context) return;
|
|
9078
|
+
this.showingLogs = true;
|
|
9079
|
+
this.renderLogsOverlay(row.name);
|
|
9080
|
+
}
|
|
9081
|
+
// ── Navigation ───────────────────────────────────────────────────
|
|
9082
|
+
moveSelection(delta) {
|
|
9083
|
+
if (this.rows.length === 0) return;
|
|
9084
|
+
this.selectedIndex += delta;
|
|
9085
|
+
if (this.selectedIndex < 0) {
|
|
9086
|
+
this.selectedIndex = this.rows.length - 1;
|
|
9087
|
+
} else if (this.selectedIndex >= this.rows.length) {
|
|
9088
|
+
this.selectedIndex = 0;
|
|
9089
|
+
}
|
|
9090
|
+
this.clampScroll();
|
|
9091
|
+
}
|
|
9092
|
+
get visibleRows() {
|
|
9093
|
+
return Math.max(1, this.termRows - 4);
|
|
9094
|
+
}
|
|
9095
|
+
clampScroll() {
|
|
9096
|
+
if (this.selectedIndex < this.scrollOffset) {
|
|
9097
|
+
this.scrollOffset = this.selectedIndex;
|
|
9098
|
+
} else if (this.selectedIndex >= this.scrollOffset + this.visibleRows) {
|
|
9099
|
+
this.scrollOffset = this.selectedIndex - this.visibleRows + 1;
|
|
9100
|
+
}
|
|
9101
|
+
this.scrollOffset = Math.max(0, this.scrollOffset);
|
|
9102
|
+
}
|
|
9103
|
+
// ── Rendering ────────────────────────────────────────────────────
|
|
9104
|
+
render() {
|
|
9105
|
+
if (!this.context) return;
|
|
9106
|
+
const buf = [];
|
|
9107
|
+
const cols = this.termCols;
|
|
9108
|
+
buf.push(ansi.clearScreen, ansi.cursorHome, ansi.hideCursor);
|
|
9109
|
+
const runningCount = this.rows.filter((r) => r.status === "running").length;
|
|
9110
|
+
const totalCount = this.rows.length;
|
|
9111
|
+
let headerLeft;
|
|
9112
|
+
if (this.errorMessage) {
|
|
9113
|
+
headerLeft = `${ansi.bold}${ansi.fg.red} htop ${ansi.reset}${ansi.fg.red}${ansi.dim} ${this.errorMessage}${ansi.reset}`;
|
|
9114
|
+
} else {
|
|
9115
|
+
headerLeft = `${ansi.bold}${ansi.fg.cyan} htop${ansi.reset}${ansi.fg.gray} \u2014 ${runningCount} running / ${totalCount} total${ansi.reset}`;
|
|
9116
|
+
}
|
|
9117
|
+
const headerRight = `${ansi.fg.gray}Refresh: 1s${ansi.reset}`;
|
|
9118
|
+
buf.push(ansi.cursorTo(1, 1));
|
|
9119
|
+
buf.push(headerLeft);
|
|
9120
|
+
buf.push(ansi.cursorTo(1, Math.max(1, cols - 11)));
|
|
9121
|
+
buf.push(headerRight);
|
|
9122
|
+
buf.push(ansi.cursorTo(2, 1));
|
|
9123
|
+
buf.push(ansi.fg.gray, BOX.horizontal.repeat(cols), ansi.reset);
|
|
9124
|
+
const colPid = 6;
|
|
9125
|
+
const colType = 9;
|
|
9126
|
+
const colStatus = 11;
|
|
9127
|
+
const colMode = 8;
|
|
9128
|
+
const colTime = 9;
|
|
9129
|
+
const colName = Math.max(10, cols - colPid - colType - colStatus - colMode - colTime - 2);
|
|
9130
|
+
buf.push(ansi.cursorTo(3, 1));
|
|
9131
|
+
buf.push(
|
|
9132
|
+
ansi.bold,
|
|
9133
|
+
ansi.fg.white,
|
|
9134
|
+
" ",
|
|
9135
|
+
pad("PID", colPid),
|
|
9136
|
+
pad("NAME", colName),
|
|
9137
|
+
pad("TYPE", colType),
|
|
9138
|
+
pad("STATUS", colStatus),
|
|
9139
|
+
pad("MODE", colMode),
|
|
9140
|
+
pad("TIME", colTime),
|
|
9141
|
+
ansi.reset
|
|
9142
|
+
);
|
|
9143
|
+
if (this.rows.length === 0) {
|
|
9144
|
+
const emptyMsg = "No processes";
|
|
9145
|
+
const emptyRow = Math.floor(this.visibleRows / 2) + 4;
|
|
9146
|
+
const emptyCol = Math.max(1, Math.floor((cols - emptyMsg.length) / 2));
|
|
9147
|
+
buf.push(ansi.cursorTo(emptyRow, emptyCol));
|
|
9148
|
+
buf.push(ansi.fg.gray, emptyMsg, ansi.reset);
|
|
9149
|
+
} else {
|
|
9150
|
+
const endIdx = Math.min(
|
|
9151
|
+
this.scrollOffset + this.visibleRows,
|
|
9152
|
+
this.rows.length
|
|
9153
|
+
);
|
|
9154
|
+
for (let i = this.scrollOffset; i < endIdx; i++) {
|
|
9155
|
+
const row = this.rows[i];
|
|
9156
|
+
const screenRow = 4 + (i - this.scrollOffset);
|
|
9157
|
+
const isSelected = i === this.selectedIndex;
|
|
9158
|
+
const prefix = isSelected ? ">" : " ";
|
|
9159
|
+
const pidStr = pad(String(row.pid), colPid);
|
|
9160
|
+
const nameStr = pad(truncate(row.name, colName - 1), colName);
|
|
9161
|
+
const typeStr = pad(row.type, colType);
|
|
9162
|
+
const statusStr = pad(row.status, colStatus);
|
|
9163
|
+
const modeStr = pad(row.mode, colMode);
|
|
9164
|
+
const timeStr = pad(row.time, colTime);
|
|
9165
|
+
buf.push(ansi.cursorTo(screenRow, 1));
|
|
9166
|
+
if (isSelected) {
|
|
9167
|
+
buf.push(ansi.inverse);
|
|
9168
|
+
}
|
|
9169
|
+
buf.push(
|
|
9170
|
+
prefix,
|
|
9171
|
+
pidStr,
|
|
9172
|
+
nameStr,
|
|
9173
|
+
typeStr,
|
|
9174
|
+
isSelected ? "" : statusColor(row.status),
|
|
9175
|
+
statusStr,
|
|
9176
|
+
isSelected ? "" : ansi.reset,
|
|
9177
|
+
modeStr,
|
|
9178
|
+
timeStr
|
|
9179
|
+
);
|
|
9180
|
+
const contentLen = 1 + colPid + colName + colType + colStatus + colMode + colTime;
|
|
9181
|
+
if (contentLen < cols) {
|
|
9182
|
+
buf.push(" ".repeat(cols - contentLen));
|
|
9183
|
+
}
|
|
9184
|
+
if (isSelected) {
|
|
9185
|
+
buf.push(ansi.reset);
|
|
9186
|
+
}
|
|
9187
|
+
}
|
|
9188
|
+
if (this.scrollOffset > 0) {
|
|
9189
|
+
buf.push(ansi.cursorTo(4, cols));
|
|
9190
|
+
buf.push(ansi.fg.gray, "\u2191", ansi.reset);
|
|
9191
|
+
}
|
|
9192
|
+
if (endIdx < this.rows.length) {
|
|
9193
|
+
buf.push(ansi.cursorTo(3 + this.visibleRows, cols));
|
|
9194
|
+
buf.push(ansi.fg.gray, "\u2193", ansi.reset);
|
|
9195
|
+
}
|
|
9196
|
+
}
|
|
9197
|
+
const footerSepRow = this.termRows - 1;
|
|
9198
|
+
buf.push(ansi.cursorTo(footerSepRow, 1));
|
|
9199
|
+
buf.push(ansi.fg.gray, BOX.horizontal.repeat(cols), ansi.reset);
|
|
9200
|
+
buf.push(ansi.cursorTo(this.termRows, 1));
|
|
9201
|
+
buf.push(ansi.dim, " ", this.buildFooter(), ansi.reset);
|
|
9202
|
+
this.context.terminal.write(buf.join(""));
|
|
9203
|
+
}
|
|
9204
|
+
buildFooter() {
|
|
9205
|
+
const selected = this.rows[this.selectedIndex];
|
|
9206
|
+
const parts = [];
|
|
9207
|
+
if (selected) {
|
|
9208
|
+
if (selected.status === "running") {
|
|
9209
|
+
parts.push("k:Kill");
|
|
9210
|
+
}
|
|
9211
|
+
if (selected.source === "service" && (selected.status === "stopped" || selected.status === "pending" || selected.status === "failed")) {
|
|
9212
|
+
parts.push("s:Start");
|
|
9213
|
+
}
|
|
9214
|
+
if (selected.source === "service" && selected.status === "running") {
|
|
9215
|
+
parts.push("t:Stop");
|
|
9216
|
+
}
|
|
9217
|
+
if (selected.source === "service") {
|
|
9218
|
+
parts.push("r:Restart");
|
|
9219
|
+
parts.push("l:Logs");
|
|
9220
|
+
}
|
|
9221
|
+
}
|
|
9222
|
+
parts.push("q:Quit");
|
|
9223
|
+
parts.push("\u2191\u2193:Navigate");
|
|
9224
|
+
return parts.join(" ");
|
|
9225
|
+
}
|
|
9226
|
+
// ── Logs overlay ─────────────────────────────────────────────
|
|
9227
|
+
renderLogsOverlay(serviceName) {
|
|
9228
|
+
if (!this.context) return;
|
|
9229
|
+
const logs = this.context.backgroundServices.getLogs(serviceName, 15);
|
|
9230
|
+
const buf = [];
|
|
9231
|
+
const boxWidth = Math.min(this.termCols - 4, 70);
|
|
9232
|
+
const boxHeight = Math.min(this.termRows - 4, logs.length + 4);
|
|
9233
|
+
const startCol = Math.max(1, Math.floor((this.termCols - boxWidth) / 2));
|
|
9234
|
+
const startRow = Math.max(1, Math.floor((this.termRows - boxHeight) / 2));
|
|
9235
|
+
const innerWidth = boxWidth - 2;
|
|
9236
|
+
const title = ` Logs: ${truncate(serviceName, innerWidth - 10)} `;
|
|
9237
|
+
const topBorder = BOX.topLeft + BOX.horizontal + title + BOX.horizontal.repeat(Math.max(0, boxWidth - title.length - 3)) + BOX.topRight;
|
|
9238
|
+
buf.push(ansi.cursorTo(startRow, startCol));
|
|
9239
|
+
buf.push(ansi.fg.cyan, topBorder, ansi.reset);
|
|
9240
|
+
const contentRows = boxHeight - 3;
|
|
9241
|
+
if (logs.length === 0) {
|
|
9242
|
+
const noLogs = "No log entries";
|
|
9243
|
+
buf.push(ansi.cursorTo(startRow + 1, startCol));
|
|
9244
|
+
buf.push(
|
|
9245
|
+
ansi.fg.cyan,
|
|
9246
|
+
BOX.vertical,
|
|
9247
|
+
ansi.reset,
|
|
9248
|
+
" ",
|
|
9249
|
+
ansi.fg.gray,
|
|
9250
|
+
pad(noLogs, innerWidth - 1),
|
|
9251
|
+
ansi.reset,
|
|
9252
|
+
ansi.fg.cyan,
|
|
9253
|
+
BOX.vertical,
|
|
9254
|
+
ansi.reset
|
|
9255
|
+
);
|
|
9256
|
+
for (let r = 2; r <= contentRows; r++) {
|
|
9257
|
+
buf.push(ansi.cursorTo(startRow + r, startCol));
|
|
9258
|
+
buf.push(
|
|
9259
|
+
ansi.fg.cyan,
|
|
9260
|
+
BOX.vertical,
|
|
9261
|
+
ansi.reset,
|
|
9262
|
+
" ".repeat(innerWidth),
|
|
9263
|
+
ansi.fg.cyan,
|
|
9264
|
+
BOX.vertical,
|
|
9265
|
+
ansi.reset
|
|
9266
|
+
);
|
|
9267
|
+
}
|
|
9268
|
+
} else {
|
|
9269
|
+
const displayLogs = logs.slice(-contentRows);
|
|
9270
|
+
let lineIdx = 0;
|
|
9271
|
+
for (const entry of displayLogs) {
|
|
9272
|
+
const ts = new Date(entry.timestamp).toLocaleTimeString();
|
|
9273
|
+
const levelTag = entry.level === "error" ? `${ansi.fg.red}ERR${ansi.reset}` : entry.level === "warn" ? `${ansi.fg.yellow}WRN${ansi.reset}` : `${ansi.fg.cyan}INF${ansi.reset}`;
|
|
9274
|
+
const line = `[${ts}] ${levelTag} ${truncate(entry.message, innerWidth - 18)}`;
|
|
9275
|
+
buf.push(ansi.cursorTo(startRow + 1 + lineIdx, startCol));
|
|
9276
|
+
buf.push(
|
|
9277
|
+
ansi.fg.cyan,
|
|
9278
|
+
BOX.vertical,
|
|
9279
|
+
ansi.reset,
|
|
9280
|
+
" ",
|
|
9281
|
+
line
|
|
9282
|
+
);
|
|
9283
|
+
buf.push(ansi.cursorTo(startRow + 1 + lineIdx, startCol + boxWidth - 1));
|
|
9284
|
+
buf.push(ansi.fg.cyan, BOX.vertical, ansi.reset);
|
|
9285
|
+
lineIdx++;
|
|
9286
|
+
}
|
|
9287
|
+
for (let r = lineIdx; r < contentRows; r++) {
|
|
9288
|
+
buf.push(ansi.cursorTo(startRow + 1 + r, startCol));
|
|
9289
|
+
buf.push(
|
|
9290
|
+
ansi.fg.cyan,
|
|
9291
|
+
BOX.vertical,
|
|
9292
|
+
ansi.reset,
|
|
9293
|
+
" ".repeat(innerWidth),
|
|
9294
|
+
ansi.fg.cyan,
|
|
9295
|
+
BOX.vertical,
|
|
9296
|
+
ansi.reset
|
|
9297
|
+
);
|
|
9298
|
+
}
|
|
9299
|
+
}
|
|
9300
|
+
const pressMsg = "Press any key to close";
|
|
9301
|
+
const pressPad = Math.max(0, Math.floor((innerWidth - pressMsg.length) / 2));
|
|
9302
|
+
buf.push(ansi.cursorTo(startRow + contentRows + 1, startCol));
|
|
9303
|
+
buf.push(
|
|
9304
|
+
ansi.fg.cyan,
|
|
9305
|
+
BOX.vertical,
|
|
9306
|
+
ansi.reset,
|
|
9307
|
+
" ".repeat(pressPad),
|
|
9308
|
+
ansi.fg.gray,
|
|
9309
|
+
pressMsg,
|
|
9310
|
+
ansi.reset,
|
|
9311
|
+
" ".repeat(Math.max(0, innerWidth - pressPad - pressMsg.length)),
|
|
9312
|
+
ansi.fg.cyan,
|
|
9313
|
+
BOX.vertical,
|
|
9314
|
+
ansi.reset
|
|
9315
|
+
);
|
|
9316
|
+
buf.push(ansi.cursorTo(startRow + contentRows + 2, startCol));
|
|
9317
|
+
buf.push(
|
|
9318
|
+
ansi.fg.cyan,
|
|
9319
|
+
BOX.bottomLeft,
|
|
9320
|
+
BOX.horizontal.repeat(boxWidth - 2),
|
|
9321
|
+
BOX.bottomRight,
|
|
9322
|
+
ansi.reset
|
|
9323
|
+
);
|
|
9324
|
+
this.context.terminal.write(buf.join(""));
|
|
9325
|
+
}
|
|
9326
|
+
// ── Utilities ────────────────────────────────────────────────
|
|
9327
|
+
showError(msg) {
|
|
9328
|
+
this.errorMessage = msg;
|
|
9329
|
+
if (this.errorTimeout) {
|
|
9330
|
+
this.errorTimeout.clear();
|
|
9331
|
+
}
|
|
9332
|
+
if (this.context) {
|
|
9333
|
+
this.errorTimeout = this.context.createInterval(() => {
|
|
9334
|
+
this.errorMessage = null;
|
|
9335
|
+
this.errorTimeout?.clear();
|
|
9336
|
+
this.errorTimeout = null;
|
|
9337
|
+
}, 3e3);
|
|
9338
|
+
}
|
|
9339
|
+
}
|
|
9340
|
+
exit(context2) {
|
|
9341
|
+
context2.exitFullScreenMode();
|
|
9342
|
+
this.context = null;
|
|
9343
|
+
}
|
|
9344
|
+
};
|
|
8522
9345
|
|
|
8523
9346
|
// src/lib/processors/system/index.ts
|
|
8524
9347
|
var systemProcessors = [
|
|
@@ -8532,7 +9355,8 @@ var systemProcessors = [
|
|
|
8532
9355
|
new CliNanoCommandProcessor(),
|
|
8533
9356
|
new CliServicesCommandProcessor(),
|
|
8534
9357
|
new CliPsCommandProcessor(),
|
|
8535
|
-
new CliKillCommandProcessor()
|
|
9358
|
+
new CliKillCommandProcessor(),
|
|
9359
|
+
new CliHtopCommandProcessor()
|
|
8536
9360
|
];
|
|
8537
9361
|
var themes = {
|
|
8538
9362
|
...DefaultThemes
|
|
@@ -8728,10 +9552,12 @@ var CliThemeCommandProcessor = class {
|
|
|
8728
9552
|
const selected = await context2.reader.readSelect(
|
|
8729
9553
|
"Select a theme (live preview):",
|
|
8730
9554
|
options,
|
|
8731
|
-
|
|
8732
|
-
|
|
8733
|
-
|
|
8734
|
-
|
|
9555
|
+
{
|
|
9556
|
+
onChange: (value) => {
|
|
9557
|
+
if (themes[value]) {
|
|
9558
|
+
context2.terminal.options.theme = themes[value];
|
|
9559
|
+
this.applyStyles(context2);
|
|
9560
|
+
}
|
|
8735
9561
|
}
|
|
8736
9562
|
}
|
|
8737
9563
|
);
|
|
@@ -9916,6 +10742,10 @@ var CliBackgroundServiceRegistry = class {
|
|
|
9916
10742
|
this.isFullScreen = false;
|
|
9917
10743
|
this.notificationQueue = [];
|
|
9918
10744
|
}
|
|
10745
|
+
/** Attach the process registry so background services get PIDs */
|
|
10746
|
+
setProcessRegistry(registry) {
|
|
10747
|
+
this.processRegistry = registry;
|
|
10748
|
+
}
|
|
9919
10749
|
register(service) {
|
|
9920
10750
|
if (this.entries.has(service.name)) {
|
|
9921
10751
|
throw new Error(`Service "${service.name}" is already registered`);
|
|
@@ -9936,7 +10766,17 @@ var CliBackgroundServiceRegistry = class {
|
|
|
9936
10766
|
if (entry.status === "running") {
|
|
9937
10767
|
throw new Error(`Service "${name}" is already running`);
|
|
9938
10768
|
}
|
|
10769
|
+
if (this.processRegistry) {
|
|
10770
|
+
const svcType = entry.service.type === "daemon" ? "daemon" : entry.service.type === "job" ? "job" : "service";
|
|
10771
|
+
const proc = this.processRegistry.register(name, {
|
|
10772
|
+
name,
|
|
10773
|
+
type: svcType,
|
|
10774
|
+
onKill: () => this.stop(name)
|
|
10775
|
+
});
|
|
10776
|
+
entry.pid = proc.pid;
|
|
10777
|
+
}
|
|
9939
10778
|
const useWorker = entry.service.workerCompatible && entry.service.workerFactory && typeof Worker !== "undefined";
|
|
10779
|
+
const isDaemon = entry.service.type === "daemon";
|
|
9940
10780
|
if (useWorker) {
|
|
9941
10781
|
const workerRunner = new CliWorkerServiceRunner(
|
|
9942
10782
|
entry.service,
|
|
@@ -9950,15 +10790,19 @@ var CliBackgroundServiceRegistry = class {
|
|
|
9950
10790
|
entry.startedAt = /* @__PURE__ */ new Date();
|
|
9951
10791
|
entry.stoppedAt = void 0;
|
|
9952
10792
|
entry.error = void 0;
|
|
9953
|
-
|
|
9954
|
-
|
|
9955
|
-
|
|
9956
|
-
|
|
9957
|
-
|
|
9958
|
-
})
|
|
9959
|
-
|
|
9960
|
-
|
|
10793
|
+
if (isDaemon) {
|
|
10794
|
+
workerRunner.start().catch((e) => this.handleStartError(entry, name, e));
|
|
10795
|
+
} else {
|
|
10796
|
+
try {
|
|
10797
|
+
await workerRunner.start();
|
|
10798
|
+
} catch (e) {
|
|
10799
|
+
this.handleStartError(entry, name, e);
|
|
10800
|
+
}
|
|
9961
10801
|
}
|
|
10802
|
+
this.emitLifecycleEvent("service-started", name, {
|
|
10803
|
+
name,
|
|
10804
|
+
mode: "worker"
|
|
10805
|
+
});
|
|
9962
10806
|
} else {
|
|
9963
10807
|
const mainRunner = new CliMainThreadServiceRunner(
|
|
9964
10808
|
entry.service,
|
|
@@ -9972,21 +10816,25 @@ var CliBackgroundServiceRegistry = class {
|
|
|
9972
10816
|
entry.startedAt = /* @__PURE__ */ new Date();
|
|
9973
10817
|
entry.stoppedAt = void 0;
|
|
9974
10818
|
entry.error = void 0;
|
|
9975
|
-
|
|
9976
|
-
|
|
9977
|
-
|
|
9978
|
-
|
|
9979
|
-
|
|
9980
|
-
})
|
|
9981
|
-
|
|
9982
|
-
|
|
10819
|
+
if (isDaemon) {
|
|
10820
|
+
mainRunner.start().catch((e) => this.handleStartError(entry, name, e));
|
|
10821
|
+
} else {
|
|
10822
|
+
try {
|
|
10823
|
+
await mainRunner.start();
|
|
10824
|
+
} catch (e) {
|
|
10825
|
+
this.handleStartError(entry, name, e);
|
|
10826
|
+
}
|
|
9983
10827
|
}
|
|
10828
|
+
this.emitLifecycleEvent("service-started", name, {
|
|
10829
|
+
name,
|
|
10830
|
+
mode: "main-thread"
|
|
10831
|
+
});
|
|
9984
10832
|
}
|
|
9985
10833
|
}
|
|
9986
10834
|
async stop(name) {
|
|
9987
10835
|
const entry = this.getEntry(name);
|
|
9988
10836
|
if (entry.status !== "running") {
|
|
9989
|
-
|
|
10837
|
+
return;
|
|
9990
10838
|
}
|
|
9991
10839
|
if (entry.runner) {
|
|
9992
10840
|
await entry.runner.stop();
|
|
@@ -9994,6 +10842,7 @@ var CliBackgroundServiceRegistry = class {
|
|
|
9994
10842
|
entry.status = "stopped";
|
|
9995
10843
|
entry.stoppedAt = /* @__PURE__ */ new Date();
|
|
9996
10844
|
entry.runner = null;
|
|
10845
|
+
this.completeInProcessRegistry(entry, 0);
|
|
9997
10846
|
this.emitLifecycleEvent("service-stopped", name, { name });
|
|
9998
10847
|
}
|
|
9999
10848
|
async restart(name) {
|
|
@@ -10022,6 +10871,7 @@ var CliBackgroundServiceRegistry = class {
|
|
|
10022
10871
|
entry.status = "done";
|
|
10023
10872
|
entry.stoppedAt = /* @__PURE__ */ new Date();
|
|
10024
10873
|
entry.runner = null;
|
|
10874
|
+
this.completeInProcessRegistry(entry, 0);
|
|
10025
10875
|
this.emitLifecycleEvent("service-completed", name, {
|
|
10026
10876
|
name,
|
|
10027
10877
|
duration
|
|
@@ -10035,6 +10885,7 @@ var CliBackgroundServiceRegistry = class {
|
|
|
10035
10885
|
entry.error = error.message;
|
|
10036
10886
|
entry.stoppedAt = /* @__PURE__ */ new Date();
|
|
10037
10887
|
entry.runner = null;
|
|
10888
|
+
this.failInProcessRegistry(entry);
|
|
10038
10889
|
this.emitLifecycleEvent("service-failed", name, {
|
|
10039
10890
|
name,
|
|
10040
10891
|
error: error.message
|
|
@@ -10109,6 +10960,7 @@ var CliBackgroundServiceRegistry = class {
|
|
|
10109
10960
|
const executionMode = entry.runner ? entry.runner.mode : "main-thread";
|
|
10110
10961
|
const uptime = entry.status === "running" && entry.startedAt ? Date.now() - entry.startedAt.getTime() : void 0;
|
|
10111
10962
|
return {
|
|
10963
|
+
pid: entry.pid,
|
|
10112
10964
|
name: entry.service.name,
|
|
10113
10965
|
description: entry.service.description,
|
|
10114
10966
|
type: entry.service.type,
|
|
@@ -10125,9 +10977,14 @@ var CliBackgroundServiceRegistry = class {
|
|
|
10125
10977
|
if (!entry) return;
|
|
10126
10978
|
const from = entry.status;
|
|
10127
10979
|
entry.status = status;
|
|
10128
|
-
if (status === "stopped" || status === "
|
|
10980
|
+
if (status === "stopped" || status === "done") {
|
|
10981
|
+
entry.stoppedAt = /* @__PURE__ */ new Date();
|
|
10982
|
+
entry.runner = null;
|
|
10983
|
+
this.completeInProcessRegistry(entry, 0);
|
|
10984
|
+
} else if (status === "failed") {
|
|
10129
10985
|
entry.stoppedAt = /* @__PURE__ */ new Date();
|
|
10130
10986
|
entry.runner = null;
|
|
10987
|
+
this.failInProcessRegistry(entry);
|
|
10131
10988
|
}
|
|
10132
10989
|
this.emitLifecycleEvent("status-change", name, { name, from, to: status });
|
|
10133
10990
|
}
|
|
@@ -10138,6 +10995,7 @@ var CliBackgroundServiceRegistry = class {
|
|
|
10138
10995
|
entry.stoppedAt = /* @__PURE__ */ new Date();
|
|
10139
10996
|
entry.runner = null;
|
|
10140
10997
|
entry.logBuffer.add(`Failed to start: ${error.message}`, "error");
|
|
10998
|
+
this.failInProcessRegistry(entry);
|
|
10141
10999
|
if (entry.service.onError) {
|
|
10142
11000
|
entry.service.onError(error, {});
|
|
10143
11001
|
}
|
|
@@ -10190,6 +11048,16 @@ var CliBackgroundServiceRegistry = class {
|
|
|
10190
11048
|
this.writer.writeln(`\u250C\u2500 [${event.source}] ${message}`);
|
|
10191
11049
|
this.writer.writeln("\u2514" + "\u2500".repeat(40));
|
|
10192
11050
|
}
|
|
11051
|
+
completeInProcessRegistry(entry, exitCode) {
|
|
11052
|
+
if (entry.pid !== void 0 && this.processRegistry) {
|
|
11053
|
+
this.processRegistry.complete(entry.pid, exitCode);
|
|
11054
|
+
}
|
|
11055
|
+
}
|
|
11056
|
+
failInProcessRegistry(entry) {
|
|
11057
|
+
if (entry.pid !== void 0 && this.processRegistry) {
|
|
11058
|
+
this.processRegistry.fail(entry.pid);
|
|
11059
|
+
}
|
|
11060
|
+
}
|
|
10193
11061
|
};
|
|
10194
11062
|
var ANSI_RE = /\x1b\[[0-9;]*m/g;
|
|
10195
11063
|
function visibleLength(text) {
|
|
@@ -10286,8 +11154,8 @@ var CliTerminalWriter = class {
|
|
|
10286
11154
|
}
|
|
10287
11155
|
writeColumns(items, options) {
|
|
10288
11156
|
const cols = options?.columns ?? 3;
|
|
10289
|
-
const
|
|
10290
|
-
const colWidth = Math.max(...items.map((s) => s.length)) +
|
|
11157
|
+
const pad2 = options?.padding ?? 2;
|
|
11158
|
+
const colWidth = Math.max(...items.map((s) => s.length)) + pad2;
|
|
10291
11159
|
for (let i = 0; i < items.length; i += cols) {
|
|
10292
11160
|
const row = items.slice(i, i + cols);
|
|
10293
11161
|
this.writeln(row.map((item) => item.padEnd(colWidth)).join(""));
|
|
@@ -10515,51 +11383,34 @@ var CliTerminalProgressBar = class {
|
|
|
10515
11383
|
}
|
|
10516
11384
|
updateProgressBar() {
|
|
10517
11385
|
const displayProgress = Math.round(this.progress);
|
|
10518
|
-
const totalBars =
|
|
11386
|
+
const totalBars = 30;
|
|
10519
11387
|
const filledBars = Math.round(
|
|
10520
11388
|
displayProgress / this.total * totalBars
|
|
10521
11389
|
);
|
|
10522
11390
|
const emptyBars = totalBars - filledBars;
|
|
10523
11391
|
const RESET4 = "\x1B[0m";
|
|
11392
|
+
const CYAN = "\x1B[36m";
|
|
11393
|
+
const BRIGHT_CYAN = "\x1B[1;36m";
|
|
10524
11394
|
const DIM4 = "\x1B[2m";
|
|
11395
|
+
const shimmerPos = this.tickCount % (filledBars + 4);
|
|
10525
11396
|
let filledStr = "";
|
|
10526
|
-
const shimmerPos = this.tickCount % (filledBars + 6);
|
|
10527
11397
|
for (let i = 0; i < filledBars; i++) {
|
|
10528
|
-
const
|
|
10529
|
-
|
|
10530
|
-
|
|
10531
|
-
|
|
10532
|
-
|
|
10533
|
-
}
|
|
10534
|
-
const
|
|
10535
|
-
const progressBar = `[${filledStr}${emptyStr}]`;
|
|
10536
|
-
const percentage = `${displayProgress}%`.padStart(4, " ");
|
|
10537
|
-
const text = this.text.length > 0 ? ` ${this.text}` : "";
|
|
11398
|
+
const isShimmer = i >= shimmerPos - 1 && i <= shimmerPos;
|
|
11399
|
+
filledStr += `${isShimmer ? BRIGHT_CYAN : CYAN}\u2501${RESET4}`;
|
|
11400
|
+
}
|
|
11401
|
+
const emptyStr = `${DIM4}${"\u2500".repeat(emptyBars)}${RESET4}`;
|
|
11402
|
+
const progressBar = `${CYAN}[${RESET4}${filledStr}${emptyStr}${CYAN}]${RESET4}`;
|
|
11403
|
+
const percentage = `${CYAN}${displayProgress}%${RESET4}`.padStart(4, " ");
|
|
11404
|
+
const text = this.text.length > 0 ? ` ${DIM4}${this.text}${RESET4}` : "";
|
|
10538
11405
|
this.clearCurrentLine();
|
|
10539
11406
|
this.progressText = `${progressBar} ${percentage} ${text}`;
|
|
10540
|
-
this.progressTextPlainLength = 1 + totalBars + 1 + 1 + 4 + 1 + text.length;
|
|
11407
|
+
this.progressTextPlainLength = 1 + totalBars + 1 + 1 + 4 + 1 + this.text.length + 1;
|
|
10541
11408
|
this.terminal.write(this.progressText);
|
|
10542
11409
|
this.lastLineCount = this.calcLineCount(this.progressTextPlainLength);
|
|
10543
11410
|
if (this.context) {
|
|
10544
11411
|
this.context.setCurrentLine(this.progressText);
|
|
10545
11412
|
}
|
|
10546
11413
|
}
|
|
10547
|
-
/**
|
|
10548
|
-
* Returns an ANSI 256-color escape for a position in the progress bar.
|
|
10549
|
-
* Gradient: red (0%) -> yellow (50%) -> green (100%).
|
|
10550
|
-
*/
|
|
10551
|
-
progressGradientColor(ratio) {
|
|
10552
|
-
let r, g;
|
|
10553
|
-
if (ratio < 0.5) {
|
|
10554
|
-
r = 5;
|
|
10555
|
-
g = Math.round(ratio / 0.5 * 5);
|
|
10556
|
-
} else {
|
|
10557
|
-
r = Math.round((1 - ratio) / 0.5 * 5);
|
|
10558
|
-
g = 5;
|
|
10559
|
-
}
|
|
10560
|
-
const colorIndex = 16 + 36 * r + 6 * g + 0;
|
|
10561
|
-
return `\x1B[38;5;${colorIndex}m`;
|
|
10562
|
-
}
|
|
10563
11414
|
clearCurrentLine() {
|
|
10564
11415
|
const lines = Math.max(
|
|
10565
11416
|
this.lastLineCount,
|
|
@@ -10672,243 +11523,1779 @@ var CliClipboard = class {
|
|
|
10672
11523
|
}
|
|
10673
11524
|
};
|
|
10674
11525
|
|
|
10675
|
-
// src/lib/
|
|
10676
|
-
var
|
|
10677
|
-
constructor(host) {
|
|
11526
|
+
// src/lib/input/modes/input-mode-base.ts
|
|
11527
|
+
var InputModeBase = class {
|
|
11528
|
+
constructor(host, resolve) {
|
|
10678
11529
|
this.host = host;
|
|
11530
|
+
this.resolve = resolve;
|
|
11531
|
+
this._resolved = false;
|
|
11532
|
+
/** Number of extra lines rendered below the prompt (help bar, error, etc.) */
|
|
11533
|
+
this.extraLines = 0;
|
|
10679
11534
|
}
|
|
10680
|
-
|
|
10681
|
-
|
|
11535
|
+
/** Whether this mode has already resolved (prevents double-resolve from async operations) */
|
|
11536
|
+
get isResolved() {
|
|
11537
|
+
return this._resolved;
|
|
10682
11538
|
}
|
|
10683
|
-
|
|
10684
|
-
|
|
11539
|
+
/**
|
|
11540
|
+
* Default key event handler. Aborts on Ctrl+C or Escape.
|
|
11541
|
+
* Subclasses may override for custom behavior (e.g., search-enabled modes
|
|
11542
|
+
* clear the filter first, abort on second press).
|
|
11543
|
+
*/
|
|
11544
|
+
handleKeyEvent(event) {
|
|
11545
|
+
if (event.code === "KeyC" && event.ctrlKey || event.code === "Escape") {
|
|
11546
|
+
this.abort();
|
|
11547
|
+
return false;
|
|
11548
|
+
}
|
|
11549
|
+
return true;
|
|
10685
11550
|
}
|
|
10686
|
-
|
|
10687
|
-
|
|
10688
|
-
|
|
10689
|
-
|
|
10690
|
-
|
|
10691
|
-
|
|
10692
|
-
|
|
10693
|
-
|
|
10694
|
-
|
|
10695
|
-
type: "confirm",
|
|
10696
|
-
promptText: displayPrompt,
|
|
10697
|
-
resolve,
|
|
10698
|
-
buffer: "",
|
|
10699
|
-
cursorPosition: 0,
|
|
10700
|
-
defaultValue
|
|
10701
|
-
});
|
|
10702
|
-
});
|
|
11551
|
+
/**
|
|
11552
|
+
* Resolve the input promise with a value and pop this mode from the stack.
|
|
11553
|
+
*/
|
|
11554
|
+
resolveAndPop(value) {
|
|
11555
|
+
if (this._resolved) return;
|
|
11556
|
+
this._resolved = true;
|
|
11557
|
+
this.host.terminal.write("\r\n");
|
|
11558
|
+
this.host.popMode();
|
|
11559
|
+
this.resolve(value);
|
|
10703
11560
|
}
|
|
10704
|
-
|
|
10705
|
-
|
|
10706
|
-
|
|
10707
|
-
|
|
11561
|
+
/**
|
|
11562
|
+
* Abort the input — resolve with null and pop.
|
|
11563
|
+
*/
|
|
11564
|
+
abort() {
|
|
11565
|
+
if (this._resolved) return;
|
|
11566
|
+
this._resolved = true;
|
|
11567
|
+
this.host.terminal.write("\r\n");
|
|
11568
|
+
this.host.popMode();
|
|
11569
|
+
this.resolve(null);
|
|
11570
|
+
}
|
|
11571
|
+
/**
|
|
11572
|
+
* Clear the current line and redraw prompt + display text, positioning
|
|
11573
|
+
* the cursor at the given position.
|
|
11574
|
+
*
|
|
11575
|
+
* All writes are batched into a single `terminal.write()` call to
|
|
11576
|
+
* eliminate visible flicker between clear and redraw.
|
|
11577
|
+
*/
|
|
11578
|
+
redrawLine(promptText, displayText, cursorPosition) {
|
|
11579
|
+
let buf = "";
|
|
11580
|
+
if (this.extraLines > 0) {
|
|
11581
|
+
buf += "\x1B[s";
|
|
11582
|
+
for (let i = 0; i < this.extraLines; i++) {
|
|
11583
|
+
buf += "\x1B[B\x1B[2K";
|
|
11584
|
+
}
|
|
11585
|
+
buf += "\x1B[u";
|
|
11586
|
+
this.extraLines = 0;
|
|
11587
|
+
}
|
|
11588
|
+
buf += "\x1B[2K\r" + promptText + displayText;
|
|
11589
|
+
const cursorOffset = displayText.length - cursorPosition;
|
|
11590
|
+
if (cursorOffset > 0) {
|
|
11591
|
+
buf += `\x1B[${cursorOffset}D`;
|
|
11592
|
+
}
|
|
11593
|
+
this.host.terminal.write(buf);
|
|
11594
|
+
}
|
|
11595
|
+
/**
|
|
11596
|
+
* Render a dimmed help bar below the current line.
|
|
11597
|
+
*/
|
|
11598
|
+
writeHelp(text) {
|
|
11599
|
+
this.extraLines++;
|
|
11600
|
+
this.host.terminal.write(
|
|
11601
|
+
`\r
|
|
11602
|
+
\x1B[2m${text}\x1B[0m\x1B[${this.extraLines}A`
|
|
11603
|
+
);
|
|
11604
|
+
}
|
|
11605
|
+
/**
|
|
11606
|
+
* Write a validation error below the current line.
|
|
11607
|
+
*/
|
|
11608
|
+
writeError(message) {
|
|
11609
|
+
this.extraLines++;
|
|
11610
|
+
this.host.terminal.write(
|
|
11611
|
+
`\r
|
|
11612
|
+
\x1B[31m\u2718 ${message}\x1B[0m\x1B[${this.extraLines}A`
|
|
11613
|
+
);
|
|
11614
|
+
}
|
|
11615
|
+
/**
|
|
11616
|
+
* Clear any extra lines (help bar, errors) rendered below the prompt.
|
|
11617
|
+
*/
|
|
11618
|
+
clearExtraLines() {
|
|
11619
|
+
if (this.extraLines > 0) {
|
|
11620
|
+
let buf = "\x1B[s";
|
|
11621
|
+
for (let i = 0; i < this.extraLines; i++) {
|
|
11622
|
+
buf += "\x1B[B\x1B[2K";
|
|
11623
|
+
}
|
|
11624
|
+
buf += "\x1B[u";
|
|
11625
|
+
this.host.terminal.write(buf);
|
|
11626
|
+
this.extraLines = 0;
|
|
11627
|
+
}
|
|
11628
|
+
}
|
|
11629
|
+
};
|
|
11630
|
+
|
|
11631
|
+
// src/lib/input/modes/line-input-mode.ts
|
|
11632
|
+
var LineInputMode = class extends InputModeBase {
|
|
11633
|
+
constructor(host, resolve, promptText, options) {
|
|
11634
|
+
super(host, resolve);
|
|
11635
|
+
this.promptText = promptText;
|
|
11636
|
+
this.options = options;
|
|
11637
|
+
this.hasError = false;
|
|
11638
|
+
this.buffer = options?.default ?? "";
|
|
11639
|
+
this.cursorPosition = this.buffer.length;
|
|
11640
|
+
}
|
|
11641
|
+
activate() {
|
|
11642
|
+
const prompt = `\x1B[36m?\x1B[0m ${this.promptText}`;
|
|
11643
|
+
this.host.writeToTerminal(prompt);
|
|
11644
|
+
if (this.buffer) {
|
|
11645
|
+
this.host.writeToTerminal(this.buffer);
|
|
11646
|
+
} else if (this.options?.placeholder) {
|
|
11647
|
+
this.host.writeToTerminal(`\x1B[2m${this.options.placeholder}\x1B[0m`);
|
|
11648
|
+
this.host.terminal.write(`\x1B[${this.options.placeholder.length}D`);
|
|
11649
|
+
}
|
|
11650
|
+
}
|
|
11651
|
+
async handleInput(data) {
|
|
11652
|
+
if (this.hasError) {
|
|
11653
|
+
this.hasError = false;
|
|
11654
|
+
this.clearExtraLines();
|
|
11655
|
+
}
|
|
11656
|
+
if (data === "\r") {
|
|
11657
|
+
if (this.options?.validate) {
|
|
11658
|
+
const error = this.options.validate(this.buffer);
|
|
11659
|
+
if (error) {
|
|
11660
|
+
this.hasError = true;
|
|
11661
|
+
this.writeError(error);
|
|
11662
|
+
return;
|
|
11663
|
+
}
|
|
11664
|
+
}
|
|
11665
|
+
this.resolveAndPop(this.buffer);
|
|
11666
|
+
} else if (data === "\x7F") {
|
|
11667
|
+
if (this.cursorPosition > 0) {
|
|
11668
|
+
this.buffer = this.buffer.slice(0, this.cursorPosition - 1) + this.buffer.slice(this.cursorPosition);
|
|
11669
|
+
this.cursorPosition--;
|
|
11670
|
+
this.renderLine();
|
|
11671
|
+
}
|
|
11672
|
+
} else if (data === "\x1B[D") {
|
|
11673
|
+
if (this.cursorPosition > 0) {
|
|
11674
|
+
this.cursorPosition--;
|
|
11675
|
+
this.host.terminal.write(data);
|
|
11676
|
+
}
|
|
11677
|
+
} else if (data === "\x1B[C") {
|
|
11678
|
+
if (this.cursorPosition < this.buffer.length) {
|
|
11679
|
+
this.cursorPosition++;
|
|
11680
|
+
this.host.terminal.write(data);
|
|
11681
|
+
}
|
|
11682
|
+
} else if (data.startsWith("\x1B")) ; else {
|
|
11683
|
+
const text = data.replace(/[\r\n]+/g, "");
|
|
11684
|
+
this.buffer = this.buffer.slice(0, this.cursorPosition) + text + this.buffer.slice(this.cursorPosition);
|
|
11685
|
+
this.cursorPosition += text.length;
|
|
11686
|
+
if (this.cursorPosition === this.buffer.length && this.extraLines === 0 && !this.hasError && !(this.options?.placeholder && this.buffer.length === text.length)) {
|
|
11687
|
+
this.host.terminal.write(text);
|
|
11688
|
+
} else {
|
|
11689
|
+
this.renderLine();
|
|
11690
|
+
}
|
|
11691
|
+
}
|
|
11692
|
+
}
|
|
11693
|
+
onResize() {
|
|
11694
|
+
this.renderLine();
|
|
11695
|
+
}
|
|
11696
|
+
renderLine() {
|
|
11697
|
+
const prompt = `\x1B[36m?\x1B[0m ${this.promptText}`;
|
|
11698
|
+
if (this.buffer.length === 0 && this.options?.placeholder) {
|
|
11699
|
+
const placeholder = `\x1B[2m${this.options.placeholder}\x1B[0m`;
|
|
11700
|
+
this.redrawLine(prompt, placeholder, 0);
|
|
11701
|
+
} else {
|
|
11702
|
+
this.redrawLine(prompt, this.buffer, this.cursorPosition);
|
|
11703
|
+
}
|
|
11704
|
+
}
|
|
11705
|
+
};
|
|
11706
|
+
|
|
11707
|
+
// src/lib/input/modes/password-input-mode.ts
|
|
11708
|
+
var PasswordInputMode = class extends InputModeBase {
|
|
11709
|
+
constructor(host, resolve, promptText) {
|
|
11710
|
+
super(host, resolve);
|
|
11711
|
+
this.promptText = promptText;
|
|
11712
|
+
this.buffer = "";
|
|
11713
|
+
}
|
|
11714
|
+
activate() {
|
|
11715
|
+
const prompt = `\x1B[36m?\x1B[0m ${this.promptText}`;
|
|
11716
|
+
this.host.writeToTerminal(prompt);
|
|
11717
|
+
}
|
|
11718
|
+
async handleInput(data) {
|
|
11719
|
+
if (data === "\r") {
|
|
11720
|
+
this.resolveAndPop(this.buffer);
|
|
11721
|
+
} else if (data === "\x7F") {
|
|
11722
|
+
if (this.buffer.length > 0) {
|
|
11723
|
+
this.buffer = this.buffer.slice(0, -1);
|
|
11724
|
+
this.renderLine();
|
|
11725
|
+
}
|
|
11726
|
+
} else if (data.startsWith("\x1B")) ; else {
|
|
11727
|
+
const text = data.replace(/[\r\n]+/g, "");
|
|
11728
|
+
this.buffer += text;
|
|
11729
|
+
if (this.extraLines === 0) {
|
|
11730
|
+
this.host.terminal.write("*".repeat(text.length));
|
|
11731
|
+
} else {
|
|
11732
|
+
this.renderLine();
|
|
11733
|
+
}
|
|
11734
|
+
}
|
|
11735
|
+
}
|
|
11736
|
+
onResize() {
|
|
11737
|
+
this.renderLine();
|
|
11738
|
+
}
|
|
11739
|
+
renderLine() {
|
|
11740
|
+
const prompt = `\x1B[36m?\x1B[0m ${this.promptText}`;
|
|
11741
|
+
const masked = "*".repeat(this.buffer.length);
|
|
11742
|
+
this.redrawLine(prompt, masked, masked.length);
|
|
11743
|
+
}
|
|
11744
|
+
};
|
|
11745
|
+
|
|
11746
|
+
// src/lib/input/modes/confirm-input-mode.ts
|
|
11747
|
+
var ConfirmInputMode = class extends InputModeBase {
|
|
11748
|
+
constructor(host, resolve, promptText, defaultValue = false) {
|
|
11749
|
+
super(host, resolve);
|
|
11750
|
+
this.promptText = promptText;
|
|
11751
|
+
this.defaultValue = defaultValue;
|
|
11752
|
+
this.buffer = "";
|
|
11753
|
+
}
|
|
11754
|
+
activate() {
|
|
11755
|
+
const hint = this.defaultValue ? "(Y/n)" : "(y/N)";
|
|
11756
|
+
const prompt = `\x1B[36m?\x1B[0m ${this.promptText} ${hint}: `;
|
|
11757
|
+
this.host.writeToTerminal(prompt);
|
|
11758
|
+
}
|
|
11759
|
+
async handleInput(data) {
|
|
11760
|
+
if (data === "\r") {
|
|
11761
|
+
if (this.buffer === "") {
|
|
11762
|
+
this.resolveAndPop(this.defaultValue);
|
|
11763
|
+
} else {
|
|
11764
|
+
const lower = this.buffer.toLowerCase();
|
|
11765
|
+
if (lower === "y") {
|
|
11766
|
+
this.resolveAndPop(true);
|
|
11767
|
+
} else if (lower === "n") {
|
|
11768
|
+
this.resolveAndPop(false);
|
|
11769
|
+
}
|
|
11770
|
+
}
|
|
11771
|
+
} else if (data === "\x7F") {
|
|
11772
|
+
if (this.buffer.length > 0) {
|
|
11773
|
+
this.buffer = this.buffer.slice(0, -1);
|
|
11774
|
+
this.renderLine();
|
|
11775
|
+
}
|
|
11776
|
+
} else if (data.startsWith("\x1B")) ; else {
|
|
11777
|
+
const lower = data.toLowerCase();
|
|
11778
|
+
if (lower === "y" || lower === "n") {
|
|
11779
|
+
this.buffer = lower;
|
|
11780
|
+
this.renderLine();
|
|
11781
|
+
}
|
|
11782
|
+
}
|
|
11783
|
+
}
|
|
11784
|
+
onResize() {
|
|
11785
|
+
this.renderLine();
|
|
11786
|
+
}
|
|
11787
|
+
renderLine() {
|
|
11788
|
+
const hint = this.defaultValue ? "(Y/n)" : "(y/N)";
|
|
11789
|
+
const prompt = `\x1B[36m?\x1B[0m ${this.promptText} ${hint}: `;
|
|
11790
|
+
this.redrawLine(prompt, this.buffer, this.buffer.length);
|
|
11791
|
+
}
|
|
11792
|
+
};
|
|
11793
|
+
|
|
11794
|
+
// src/lib/input/modes/number-input-mode.ts
|
|
11795
|
+
var NumberInputMode = class extends InputModeBase {
|
|
11796
|
+
constructor(host, resolve, promptText, options) {
|
|
11797
|
+
super(host, resolve);
|
|
11798
|
+
this.promptText = promptText;
|
|
11799
|
+
this.options = options;
|
|
11800
|
+
this.hasError = false;
|
|
11801
|
+
this.buffer = options?.default != null ? String(options.default) : "";
|
|
11802
|
+
this.cursorPosition = this.buffer.length;
|
|
11803
|
+
}
|
|
11804
|
+
activate() {
|
|
11805
|
+
const prompt = this.buildPrompt();
|
|
11806
|
+
this.host.writeToTerminal(prompt);
|
|
11807
|
+
if (this.buffer) {
|
|
11808
|
+
this.host.writeToTerminal(this.buffer);
|
|
11809
|
+
}
|
|
11810
|
+
}
|
|
11811
|
+
async handleInput(data) {
|
|
11812
|
+
if (this.hasError) {
|
|
11813
|
+
this.hasError = false;
|
|
11814
|
+
this.clearExtraLines();
|
|
11815
|
+
}
|
|
11816
|
+
if (data === "\r") {
|
|
11817
|
+
if (this.buffer === "" || this.buffer === "-") {
|
|
11818
|
+
if (this.options?.default != null) {
|
|
11819
|
+
this.resolveAndPop(this.options.default);
|
|
11820
|
+
}
|
|
11821
|
+
return;
|
|
11822
|
+
}
|
|
11823
|
+
const value = parseInt(this.buffer, 10);
|
|
11824
|
+
if (isNaN(value)) {
|
|
11825
|
+
this.hasError = true;
|
|
11826
|
+
this.writeError("Please enter a valid number");
|
|
11827
|
+
return;
|
|
11828
|
+
}
|
|
11829
|
+
if (this.options?.min != null && value < this.options.min) {
|
|
11830
|
+
this.hasError = true;
|
|
11831
|
+
this.writeError(`Value must be at least ${this.options.min}`);
|
|
11832
|
+
return;
|
|
11833
|
+
}
|
|
11834
|
+
if (this.options?.max != null && value > this.options.max) {
|
|
11835
|
+
this.hasError = true;
|
|
11836
|
+
this.writeError(`Value must be at most ${this.options.max}`);
|
|
11837
|
+
return;
|
|
11838
|
+
}
|
|
11839
|
+
this.resolveAndPop(value);
|
|
11840
|
+
} else if (data === "\x7F") {
|
|
11841
|
+
if (this.cursorPosition > 0) {
|
|
11842
|
+
this.buffer = this.buffer.slice(0, this.cursorPosition - 1) + this.buffer.slice(this.cursorPosition);
|
|
11843
|
+
this.cursorPosition--;
|
|
11844
|
+
this.renderLine();
|
|
11845
|
+
}
|
|
11846
|
+
} else if (data === "\x1B[D") {
|
|
11847
|
+
if (this.cursorPosition > 0) {
|
|
11848
|
+
this.cursorPosition--;
|
|
11849
|
+
this.host.terminal.write(data);
|
|
11850
|
+
}
|
|
11851
|
+
} else if (data === "\x1B[C") {
|
|
11852
|
+
if (this.cursorPosition < this.buffer.length) {
|
|
11853
|
+
this.cursorPosition++;
|
|
11854
|
+
this.host.terminal.write(data);
|
|
11855
|
+
}
|
|
11856
|
+
} else if (data.startsWith("\x1B")) ; else {
|
|
11857
|
+
for (const ch of data) {
|
|
11858
|
+
if (ch >= "0" && ch <= "9") {
|
|
11859
|
+
this.buffer = this.buffer.slice(0, this.cursorPosition) + ch + this.buffer.slice(this.cursorPosition);
|
|
11860
|
+
this.cursorPosition++;
|
|
11861
|
+
} else if (ch === "-" && this.cursorPosition === 0 && !this.buffer.startsWith("-")) {
|
|
11862
|
+
this.buffer = "-" + this.buffer;
|
|
11863
|
+
this.cursorPosition++;
|
|
11864
|
+
}
|
|
11865
|
+
}
|
|
11866
|
+
this.renderLine();
|
|
11867
|
+
}
|
|
11868
|
+
}
|
|
11869
|
+
onResize() {
|
|
11870
|
+
this.renderLine();
|
|
11871
|
+
}
|
|
11872
|
+
buildPrompt() {
|
|
11873
|
+
const { min, max } = this.options ?? {};
|
|
11874
|
+
let hint = "";
|
|
11875
|
+
if (min != null && max != null) {
|
|
11876
|
+
hint = ` \x1B[2m(${min}\u2013${max})\x1B[0m`;
|
|
11877
|
+
} else if (min != null) {
|
|
11878
|
+
hint = ` \x1B[2m(min: ${min})\x1B[0m`;
|
|
11879
|
+
} else if (max != null) {
|
|
11880
|
+
hint = ` \x1B[2m(max: ${max})\x1B[0m`;
|
|
11881
|
+
}
|
|
11882
|
+
return `\x1B[36m?\x1B[0m ${this.promptText}${hint}`;
|
|
11883
|
+
}
|
|
11884
|
+
renderLine() {
|
|
11885
|
+
const prompt = this.buildPrompt();
|
|
11886
|
+
this.redrawLine(prompt, this.buffer, this.cursorPosition);
|
|
11887
|
+
}
|
|
11888
|
+
};
|
|
11889
|
+
|
|
11890
|
+
// src/lib/input/modes/select-input-mode.ts
|
|
11891
|
+
var SelectInputMode = class extends InputModeBase {
|
|
11892
|
+
constructor(host, resolve, promptText, options, selectOptions) {
|
|
11893
|
+
super(host, resolve);
|
|
11894
|
+
this.promptText = promptText;
|
|
11895
|
+
this.options = options;
|
|
11896
|
+
this.selectOptions = selectOptions;
|
|
11897
|
+
this.selectedIndex = 0;
|
|
11898
|
+
this.scrollOffset = 0;
|
|
11899
|
+
this.filter = "";
|
|
11900
|
+
/** Number of lines rendered for the option list + help bar (for clearing) */
|
|
11901
|
+
this.renderedLines = 0;
|
|
11902
|
+
this.filteredOptions = [...options];
|
|
11903
|
+
this.maxVisible = Math.min(options.length, host.getTerminalRows() - 4);
|
|
11904
|
+
}
|
|
11905
|
+
activate() {
|
|
11906
|
+
this.host.terminal.write("\x1B[?25l");
|
|
11907
|
+
if (this.selectOptions?.default) {
|
|
11908
|
+
const idx = this.filteredOptions.findIndex(
|
|
11909
|
+
(o) => o.value === this.selectOptions.default
|
|
10708
11910
|
);
|
|
11911
|
+
if (idx >= 0) {
|
|
11912
|
+
this.selectedIndex = idx;
|
|
11913
|
+
if (this.selectedIndex >= this.maxVisible) {
|
|
11914
|
+
this.scrollOffset = this.selectedIndex - this.maxVisible + 1;
|
|
11915
|
+
}
|
|
11916
|
+
}
|
|
11917
|
+
} else {
|
|
11918
|
+
this.selectedIndex = this.findNextSelectable(-1, 1);
|
|
10709
11919
|
}
|
|
10710
|
-
|
|
10711
|
-
if (this.host.activeInputRequest) {
|
|
10712
|
-
throw new Error("Another input request is already active");
|
|
10713
|
-
}
|
|
10714
|
-
const termRows = this.host.getTerminalRows?.() ?? 24;
|
|
10715
|
-
const maxVisible = Math.max(3, termRows - 2);
|
|
10716
|
-
this.host.writeToTerminal(prompt + "\r\n");
|
|
10717
|
-
const displayLines = this.renderSelectOptions(options, 0, 0, maxVisible);
|
|
10718
|
-
onChange?.(options[0].value);
|
|
10719
|
-
this.host.setActiveInputRequest({
|
|
10720
|
-
type: "select",
|
|
10721
|
-
promptText: prompt,
|
|
10722
|
-
resolve,
|
|
10723
|
-
buffer: "",
|
|
10724
|
-
cursorPosition: 0,
|
|
10725
|
-
options,
|
|
10726
|
-
selectedIndex: 0,
|
|
10727
|
-
onChange,
|
|
10728
|
-
scrollOffset: 0,
|
|
10729
|
-
maxVisible,
|
|
10730
|
-
displayLines
|
|
10731
|
-
});
|
|
10732
|
-
});
|
|
11920
|
+
this.renderAll();
|
|
10733
11921
|
}
|
|
10734
|
-
|
|
10735
|
-
|
|
10736
|
-
|
|
10737
|
-
|
|
11922
|
+
/**
|
|
11923
|
+
* Handle keyboard events. When searchable and filter is non-empty,
|
|
11924
|
+
* Escape clears the filter instead of aborting.
|
|
11925
|
+
*/
|
|
11926
|
+
handleKeyEvent(event) {
|
|
11927
|
+
if (this.selectOptions?.searchable && this.filter.length > 0) {
|
|
11928
|
+
if (event.code === "Escape") {
|
|
11929
|
+
this.clearFilter();
|
|
11930
|
+
return false;
|
|
11931
|
+
}
|
|
11932
|
+
}
|
|
11933
|
+
return super.handleKeyEvent(event);
|
|
11934
|
+
}
|
|
11935
|
+
/**
|
|
11936
|
+
* Clear rendered option lines and show final answer before resolving.
|
|
11937
|
+
*/
|
|
11938
|
+
resolveAndPop(value) {
|
|
11939
|
+
this.clearRenderedLines();
|
|
11940
|
+
const selectedOpt = this.options.find((o) => o.value === value);
|
|
11941
|
+
const label = selectedOpt ? selectedOpt.label : value;
|
|
11942
|
+
this.host.terminal.write("\x1B[2K\r");
|
|
11943
|
+
this.host.terminal.write(`\x1B[32m\u2714\x1B[0m ${this.promptText} \x1B[36m${label}\x1B[0m`);
|
|
11944
|
+
this.host.terminal.write("\x1B[?25h");
|
|
11945
|
+
super.resolveAndPop(value);
|
|
11946
|
+
}
|
|
11947
|
+
/**
|
|
11948
|
+
* Clear rendered option lines before aborting.
|
|
11949
|
+
*/
|
|
11950
|
+
abort() {
|
|
11951
|
+
this.clearRenderedLines();
|
|
11952
|
+
this.host.terminal.write("\x1B[2K\r");
|
|
11953
|
+
this.host.terminal.write(`\x1B[33m\u2718\x1B[0m ${this.promptText} \x1B[2mcancelled\x1B[0m`);
|
|
11954
|
+
this.host.terminal.write("\x1B[?25h");
|
|
11955
|
+
super.abort();
|
|
11956
|
+
}
|
|
11957
|
+
async handleInput(data) {
|
|
11958
|
+
if (data === "\x1B[A") {
|
|
11959
|
+
this.moveSelection(-1);
|
|
11960
|
+
} else if (data === "\x1B[B") {
|
|
11961
|
+
this.moveSelection(1);
|
|
11962
|
+
} else if (data === "\r") {
|
|
11963
|
+
const items = this.buildDisplayList();
|
|
11964
|
+
const selectableItems = items.filter(
|
|
11965
|
+
(i) => i.type === "option" && !i.option.disabled
|
|
10738
11966
|
);
|
|
11967
|
+
if (selectableItems.length > 0 && this.selectedIndex < selectableItems.length) {
|
|
11968
|
+
const selected = selectableItems[this.selectedIndex];
|
|
11969
|
+
this.resolveAndPop(selected.option.value);
|
|
11970
|
+
}
|
|
11971
|
+
} else if (data === "\x7F") {
|
|
11972
|
+
if (this.selectOptions?.searchable && this.filter.length > 0) {
|
|
11973
|
+
this.filter = this.filter.slice(0, -1);
|
|
11974
|
+
this.applyFilter();
|
|
11975
|
+
this.renderAll();
|
|
11976
|
+
}
|
|
11977
|
+
} else if (data.startsWith("\x1B")) ; else if (this.selectOptions?.searchable && data.length === 1 && data >= " ") {
|
|
11978
|
+
this.filter += data;
|
|
11979
|
+
this.applyFilter();
|
|
11980
|
+
this.renderAll();
|
|
10739
11981
|
}
|
|
10740
|
-
|
|
10741
|
-
|
|
10742
|
-
|
|
10743
|
-
|
|
10744
|
-
|
|
10745
|
-
|
|
10746
|
-
|
|
10747
|
-
|
|
10748
|
-
|
|
10749
|
-
|
|
10750
|
-
|
|
10751
|
-
|
|
10752
|
-
|
|
10753
|
-
|
|
10754
|
-
|
|
10755
|
-
|
|
11982
|
+
}
|
|
11983
|
+
onResize(cols, rows) {
|
|
11984
|
+
this.maxVisible = Math.min(this.filteredOptions.length, rows - 4);
|
|
11985
|
+
if (this.scrollOffset + this.maxVisible > this.getSelectableCount()) {
|
|
11986
|
+
this.scrollOffset = Math.max(0, this.getSelectableCount() - this.maxVisible);
|
|
11987
|
+
}
|
|
11988
|
+
this.renderAll();
|
|
11989
|
+
}
|
|
11990
|
+
// ── Private helpers ──────────────────────────────────────────────
|
|
11991
|
+
/**
|
|
11992
|
+
* Build flattened display list with group headers inserted.
|
|
11993
|
+
*/
|
|
11994
|
+
buildDisplayList() {
|
|
11995
|
+
const items = [];
|
|
11996
|
+
let lastGroup;
|
|
11997
|
+
for (const opt of this.filteredOptions) {
|
|
11998
|
+
if (opt.group && opt.group !== lastGroup) {
|
|
11999
|
+
items.push({ type: "group-header", groupName: opt.group });
|
|
12000
|
+
lastGroup = opt.group;
|
|
12001
|
+
}
|
|
12002
|
+
items.push({ type: "option", option: opt });
|
|
12003
|
+
}
|
|
12004
|
+
return items;
|
|
12005
|
+
}
|
|
12006
|
+
/**
|
|
12007
|
+
* Get the total count of selectable items (non-disabled options).
|
|
12008
|
+
*/
|
|
12009
|
+
getSelectableCount() {
|
|
12010
|
+
return this.filteredOptions.filter((o) => !o.disabled).length;
|
|
12011
|
+
}
|
|
12012
|
+
/**
|
|
12013
|
+
* Move selection in the given direction (+1 or -1), skipping disabled options.
|
|
12014
|
+
* Wraps around at boundaries.
|
|
12015
|
+
*/
|
|
12016
|
+
moveSelection(direction) {
|
|
12017
|
+
const selectableCount = this.getSelectableCount();
|
|
12018
|
+
if (selectableCount === 0) return;
|
|
12019
|
+
let next = this.selectedIndex + direction;
|
|
12020
|
+
if (next < 0) next = selectableCount - 1;
|
|
12021
|
+
if (next >= selectableCount) next = 0;
|
|
12022
|
+
this.selectedIndex = next;
|
|
12023
|
+
if (this.selectedIndex < this.scrollOffset) {
|
|
12024
|
+
this.scrollOffset = this.selectedIndex;
|
|
12025
|
+
} else if (this.selectedIndex >= this.scrollOffset + this.maxVisible) {
|
|
12026
|
+
this.scrollOffset = this.selectedIndex - this.maxVisible + 1;
|
|
12027
|
+
}
|
|
12028
|
+
const selectables = this.filteredOptions.filter((o) => !o.disabled);
|
|
12029
|
+
if (this.selectOptions?.onChange && selectables[this.selectedIndex]) {
|
|
12030
|
+
this.selectOptions.onChange(selectables[this.selectedIndex].value);
|
|
12031
|
+
}
|
|
12032
|
+
this.renderAll();
|
|
12033
|
+
}
|
|
12034
|
+
/**
|
|
12035
|
+
* Find the next selectable (non-disabled) option index starting from `from`.
|
|
12036
|
+
* Returns 0 if none found.
|
|
12037
|
+
*/
|
|
12038
|
+
findNextSelectable(from, direction) {
|
|
12039
|
+
const len = this.filteredOptions.length;
|
|
12040
|
+
if (len === 0) return 0;
|
|
12041
|
+
let idx = from + direction;
|
|
12042
|
+
for (let i = 0; i < len; i++) {
|
|
12043
|
+
if (idx < 0) idx = len - 1;
|
|
12044
|
+
if (idx >= len) idx = 0;
|
|
12045
|
+
if (!this.filteredOptions[idx].disabled) return idx;
|
|
12046
|
+
idx += direction;
|
|
12047
|
+
}
|
|
12048
|
+
return 0;
|
|
12049
|
+
}
|
|
12050
|
+
/**
|
|
12051
|
+
* Apply the current filter text to the options list.
|
|
12052
|
+
*/
|
|
12053
|
+
applyFilter() {
|
|
12054
|
+
if (this.filter.length === 0) {
|
|
12055
|
+
this.filteredOptions = [...this.options];
|
|
12056
|
+
} else {
|
|
12057
|
+
const lower = this.filter.toLowerCase();
|
|
12058
|
+
this.filteredOptions = this.options.filter(
|
|
12059
|
+
(o) => o.label.toLowerCase().includes(lower)
|
|
12060
|
+
);
|
|
12061
|
+
}
|
|
12062
|
+
this.selectedIndex = 0;
|
|
12063
|
+
this.scrollOffset = 0;
|
|
12064
|
+
this.maxVisible = Math.min(
|
|
12065
|
+
this.filteredOptions.length,
|
|
12066
|
+
this.host.getTerminalRows() - 4
|
|
12067
|
+
);
|
|
12068
|
+
const selectables = this.filteredOptions.filter((o) => !o.disabled);
|
|
12069
|
+
if (selectables.length > 0) {
|
|
12070
|
+
this.selectedIndex = 0;
|
|
12071
|
+
}
|
|
12072
|
+
}
|
|
12073
|
+
/**
|
|
12074
|
+
* Clear the filter and restore full option list.
|
|
12075
|
+
*/
|
|
12076
|
+
clearFilter() {
|
|
12077
|
+
this.filter = "";
|
|
12078
|
+
this.applyFilter();
|
|
12079
|
+
this.renderAll();
|
|
12080
|
+
}
|
|
12081
|
+
/**
|
|
12082
|
+
* Clear all previously rendered lines and re-render everything.
|
|
12083
|
+
*/
|
|
12084
|
+
renderAll() {
|
|
12085
|
+
this.clearRenderedLines();
|
|
12086
|
+
this.renderPrompt();
|
|
12087
|
+
this.renderOptions();
|
|
12088
|
+
this.renderHelpBar();
|
|
12089
|
+
if (this.renderedLines > 0) {
|
|
12090
|
+
this.host.terminal.write(`\x1B[${this.renderedLines}A`);
|
|
12091
|
+
}
|
|
12092
|
+
}
|
|
12093
|
+
/**
|
|
12094
|
+
* Clear all lines we previously rendered (below the cursor).
|
|
12095
|
+
*/
|
|
12096
|
+
clearRenderedLines() {
|
|
12097
|
+
if (this.renderedLines > 0) {
|
|
12098
|
+
this.host.terminal.write("\x1B[s");
|
|
12099
|
+
for (let i = 0; i < this.renderedLines; i++) {
|
|
12100
|
+
this.host.terminal.write("\x1B[B\x1B[2K");
|
|
12101
|
+
}
|
|
12102
|
+
this.host.terminal.write("\x1B[u");
|
|
12103
|
+
this.renderedLines = 0;
|
|
12104
|
+
}
|
|
12105
|
+
}
|
|
12106
|
+
/**
|
|
12107
|
+
* Render the prompt line.
|
|
12108
|
+
*/
|
|
12109
|
+
renderPrompt() {
|
|
12110
|
+
this.host.terminal.write("\x1B[2K\r");
|
|
12111
|
+
if (this.selectOptions?.searchable) {
|
|
12112
|
+
if (this.filter.length > 0) {
|
|
12113
|
+
this.host.terminal.write(
|
|
12114
|
+
`\x1B[36m?\x1B[0m ${this.promptText}: ${this.filter}`
|
|
12115
|
+
);
|
|
12116
|
+
} else {
|
|
12117
|
+
this.host.terminal.write(
|
|
12118
|
+
`\x1B[36m?\x1B[0m ${this.promptText}: \x1B[2mType to filter...\x1B[0m`
|
|
12119
|
+
);
|
|
12120
|
+
}
|
|
12121
|
+
} else {
|
|
12122
|
+
this.host.terminal.write(`\x1B[36m?\x1B[0m ${this.promptText}:`);
|
|
12123
|
+
}
|
|
12124
|
+
}
|
|
12125
|
+
/**
|
|
12126
|
+
* Render the visible slice of the option list.
|
|
12127
|
+
*/
|
|
12128
|
+
renderOptions() {
|
|
12129
|
+
const items = this.buildDisplayList();
|
|
12130
|
+
const selectables = this.filteredOptions.filter((o) => !o.disabled);
|
|
12131
|
+
const visibleStart = this.scrollOffset;
|
|
12132
|
+
const visibleEnd = Math.min(
|
|
12133
|
+
selectables.length,
|
|
12134
|
+
this.scrollOffset + this.maxVisible
|
|
12135
|
+
);
|
|
12136
|
+
if (visibleStart > 0) {
|
|
12137
|
+
this.host.terminal.write(`\r
|
|
12138
|
+
\x1B[2m \u2191 ${visibleStart} more above\x1B[0m`);
|
|
12139
|
+
this.renderedLines++;
|
|
12140
|
+
}
|
|
12141
|
+
let selectableIdx = 0;
|
|
12142
|
+
for (const item of items) {
|
|
12143
|
+
if (item.type === "group-header") {
|
|
12144
|
+
if (this.isGroupVisible(item.groupName, items, visibleStart, visibleEnd)) {
|
|
12145
|
+
this.host.terminal.write(
|
|
12146
|
+
`\r
|
|
12147
|
+
\x1B[35m\u2500\u2500 ${item.groupName} \u2500\u2500\x1B[0m`
|
|
12148
|
+
);
|
|
12149
|
+
this.renderedLines++;
|
|
12150
|
+
}
|
|
12151
|
+
} else {
|
|
12152
|
+
const opt = item.option;
|
|
12153
|
+
if (opt.disabled) {
|
|
12154
|
+
if (this.isDisabledOptionVisible(items, item, visibleStart, visibleEnd)) {
|
|
12155
|
+
this.renderOptionLine(opt, false, false);
|
|
12156
|
+
this.renderedLines++;
|
|
12157
|
+
}
|
|
12158
|
+
} else {
|
|
12159
|
+
if (selectableIdx >= visibleStart && selectableIdx < visibleEnd) {
|
|
12160
|
+
const isSelected = selectableIdx === this.selectedIndex;
|
|
12161
|
+
this.renderOptionLine(opt, isSelected, false);
|
|
12162
|
+
this.renderedLines++;
|
|
12163
|
+
}
|
|
12164
|
+
selectableIdx++;
|
|
12165
|
+
}
|
|
12166
|
+
}
|
|
12167
|
+
}
|
|
12168
|
+
const remaining = selectables.length - visibleEnd;
|
|
12169
|
+
if (remaining > 0) {
|
|
12170
|
+
this.host.terminal.write(`\r
|
|
12171
|
+
\x1B[2m \u2193 ${remaining} more below\x1B[0m`);
|
|
12172
|
+
this.renderedLines++;
|
|
12173
|
+
}
|
|
12174
|
+
}
|
|
12175
|
+
/**
|
|
12176
|
+
* Check if a group header should be visible given the current visible range.
|
|
12177
|
+
*/
|
|
12178
|
+
isGroupVisible(groupName, items, visibleStart, visibleEnd) {
|
|
12179
|
+
let selectableIdx = 0;
|
|
12180
|
+
for (const item of items) {
|
|
12181
|
+
if (item.type === "option" && !item.option.disabled) {
|
|
12182
|
+
if (item.option.group === groupName && selectableIdx >= visibleStart && selectableIdx < visibleEnd) {
|
|
12183
|
+
return true;
|
|
12184
|
+
}
|
|
12185
|
+
selectableIdx++;
|
|
12186
|
+
}
|
|
12187
|
+
}
|
|
12188
|
+
return false;
|
|
12189
|
+
}
|
|
12190
|
+
/**
|
|
12191
|
+
* Check if a disabled option should be visible (adjacent to visible selectables).
|
|
12192
|
+
*/
|
|
12193
|
+
isDisabledOptionVisible(items, disabledItem, visibleStart, visibleEnd) {
|
|
12194
|
+
const idx = items.indexOf(disabledItem);
|
|
12195
|
+
let selectableIdx = 0;
|
|
12196
|
+
for (let i = 0; i < items.length; i++) {
|
|
12197
|
+
if (items[i].type === "option" && !items[i].option.disabled) {
|
|
12198
|
+
if (i > idx) {
|
|
12199
|
+
return selectableIdx >= visibleStart && selectableIdx < visibleEnd;
|
|
12200
|
+
}
|
|
12201
|
+
selectableIdx++;
|
|
12202
|
+
}
|
|
12203
|
+
}
|
|
12204
|
+
return selectableIdx > 0 && selectableIdx - 1 >= visibleStart && selectableIdx - 1 < visibleEnd;
|
|
12205
|
+
}
|
|
12206
|
+
/**
|
|
12207
|
+
* Render a single option line.
|
|
12208
|
+
*/
|
|
12209
|
+
renderOptionLine(opt, isSelected, _isGroupHeader) {
|
|
12210
|
+
const label = this.highlightMatch(opt.label);
|
|
12211
|
+
const desc = opt.description ? ` \x1B[2;3m\u2014 ${opt.description}\x1B[0m` : "";
|
|
12212
|
+
if (opt.disabled) {
|
|
12213
|
+
this.host.terminal.write(
|
|
12214
|
+
`\r
|
|
12215
|
+
\x1B[2m${opt.label}${desc} (disabled)\x1B[0m`
|
|
12216
|
+
);
|
|
12217
|
+
} else if (isSelected) {
|
|
12218
|
+
this.host.terminal.write(
|
|
12219
|
+
`\r
|
|
12220
|
+
\x1B[48;5;24m\x1B[38;5;117m\u276F ${label}${desc}\x1B[0m`
|
|
12221
|
+
);
|
|
12222
|
+
} else {
|
|
12223
|
+
this.host.terminal.write(
|
|
12224
|
+
`\r
|
|
12225
|
+
${label}${desc}`
|
|
12226
|
+
);
|
|
12227
|
+
}
|
|
12228
|
+
}
|
|
12229
|
+
/**
|
|
12230
|
+
* Highlight the matched filter text in yellow within the label.
|
|
12231
|
+
*/
|
|
12232
|
+
highlightMatch(label) {
|
|
12233
|
+
if (!this.filter || this.filter.length === 0) return label;
|
|
12234
|
+
const lower = label.toLowerCase();
|
|
12235
|
+
const filterLower = this.filter.toLowerCase();
|
|
12236
|
+
const idx = lower.indexOf(filterLower);
|
|
12237
|
+
if (idx < 0) return label;
|
|
12238
|
+
const before = label.substring(0, idx);
|
|
12239
|
+
const match = label.substring(idx, idx + this.filter.length);
|
|
12240
|
+
const after = label.substring(idx + this.filter.length);
|
|
12241
|
+
return `${before}\x1B[33m${match}\x1B[0m${after}`;
|
|
12242
|
+
}
|
|
12243
|
+
/**
|
|
12244
|
+
* Render the help bar at the bottom.
|
|
12245
|
+
*/
|
|
12246
|
+
renderHelpBar() {
|
|
12247
|
+
let help;
|
|
12248
|
+
if (this.selectOptions?.searchable) {
|
|
12249
|
+
if (this.filter.length > 0) {
|
|
12250
|
+
const matchCount = this.filteredOptions.filter((o) => !o.disabled).length;
|
|
12251
|
+
const totalCount = this.options.filter((o) => !o.disabled).length;
|
|
12252
|
+
help = `${matchCount} of ${totalCount} matches \xB7 backspace to clear filter \xB7 enter to select \xB7 esc to cancel`;
|
|
12253
|
+
} else {
|
|
12254
|
+
help = "\u2191/\u2193 navigate \xB7 type to filter \xB7 enter to select \xB7 esc to cancel";
|
|
12255
|
+
}
|
|
12256
|
+
} else {
|
|
12257
|
+
help = "\u2191/\u2193 navigate \xB7 enter to select \xB7 esc to cancel";
|
|
12258
|
+
}
|
|
12259
|
+
this.host.terminal.write(`\r
|
|
12260
|
+
\x1B[2m${help}\x1B[0m`);
|
|
12261
|
+
this.renderedLines++;
|
|
12262
|
+
}
|
|
12263
|
+
};
|
|
12264
|
+
|
|
12265
|
+
// src/lib/input/modes/inline-select-input-mode.ts
|
|
12266
|
+
var InlineSelectInputMode = class extends InputModeBase {
|
|
12267
|
+
constructor(host, resolve, promptText, options, selectOptions) {
|
|
12268
|
+
super(host, resolve);
|
|
12269
|
+
this.promptText = promptText;
|
|
12270
|
+
this.options = options;
|
|
12271
|
+
this.selectOptions = selectOptions;
|
|
12272
|
+
/** Index into the selectable (non-disabled) options */
|
|
12273
|
+
this.selectedIndex = 0;
|
|
12274
|
+
/** Start index of the visible window (into selectable options) */
|
|
12275
|
+
this.visibleStart = 0;
|
|
12276
|
+
/** End index (exclusive) of the visible window */
|
|
12277
|
+
this.visibleEnd = 0;
|
|
12278
|
+
this.selectableOptions = options.filter((o) => !o.disabled);
|
|
12279
|
+
}
|
|
12280
|
+
activate() {
|
|
12281
|
+
this.host.terminal.write("\x1B[?25l");
|
|
12282
|
+
if (this.selectOptions?.default) {
|
|
12283
|
+
const idx = this.selectableOptions.findIndex(
|
|
12284
|
+
(o) => o.value === this.selectOptions.default
|
|
12285
|
+
);
|
|
12286
|
+
if (idx >= 0) {
|
|
12287
|
+
this.selectedIndex = idx;
|
|
12288
|
+
}
|
|
12289
|
+
}
|
|
12290
|
+
this.calculateVisibleWindow();
|
|
12291
|
+
this.render();
|
|
12292
|
+
}
|
|
12293
|
+
/**
|
|
12294
|
+
* Clear the inline options and help bar, then show final answer before resolving.
|
|
12295
|
+
*/
|
|
12296
|
+
resolveAndPop(value) {
|
|
12297
|
+
this.clearExtraLines();
|
|
12298
|
+
const selectedOpt = this.selectableOptions.find((o) => o.value === value);
|
|
12299
|
+
const label = selectedOpt ? selectedOpt.label : value;
|
|
12300
|
+
this.host.terminal.write("\x1B[2K\r");
|
|
12301
|
+
this.host.terminal.write(`\x1B[32m\u2714\x1B[0m ${this.promptText}: \x1B[36m${label}\x1B[0m`);
|
|
12302
|
+
this.host.terminal.write("\x1B[?25h");
|
|
12303
|
+
super.resolveAndPop(value);
|
|
12304
|
+
}
|
|
12305
|
+
/**
|
|
12306
|
+
* Clear the inline options and help bar before aborting.
|
|
12307
|
+
*/
|
|
12308
|
+
abort() {
|
|
12309
|
+
this.clearExtraLines();
|
|
12310
|
+
this.host.terminal.write("\x1B[2K\r");
|
|
12311
|
+
this.host.terminal.write(`\x1B[33m\u2718\x1B[0m ${this.promptText}: \x1B[2mcancelled\x1B[0m`);
|
|
12312
|
+
this.host.terminal.write("\x1B[?25h");
|
|
12313
|
+
super.abort();
|
|
12314
|
+
}
|
|
12315
|
+
async handleInput(data) {
|
|
12316
|
+
if (data === "\x1B[C") {
|
|
12317
|
+
this.moveSelection(1);
|
|
12318
|
+
} else if (data === "\x1B[D") {
|
|
12319
|
+
this.moveSelection(-1);
|
|
12320
|
+
} else if (data === "\r") {
|
|
12321
|
+
if (this.selectableOptions.length > 0) {
|
|
12322
|
+
this.resolveAndPop(this.selectableOptions[this.selectedIndex].value);
|
|
12323
|
+
}
|
|
12324
|
+
}
|
|
12325
|
+
}
|
|
12326
|
+
onResize(cols, rows) {
|
|
12327
|
+
this.calculateVisibleWindow();
|
|
12328
|
+
this.render();
|
|
12329
|
+
}
|
|
12330
|
+
// ── Private helpers ──────────────────────────────────────────────
|
|
12331
|
+
/**
|
|
12332
|
+
* Move selection in the given direction, skipping disabled options.
|
|
12333
|
+
* Wraps around at boundaries.
|
|
12334
|
+
*/
|
|
12335
|
+
moveSelection(direction) {
|
|
12336
|
+
if (this.selectableOptions.length === 0) return;
|
|
12337
|
+
let next = this.selectedIndex + direction;
|
|
12338
|
+
if (next < 0) next = this.selectableOptions.length - 1;
|
|
12339
|
+
if (next >= this.selectableOptions.length) next = 0;
|
|
12340
|
+
this.selectedIndex = next;
|
|
12341
|
+
this.adjustVisibleWindow();
|
|
12342
|
+
if (this.selectOptions?.onChange) {
|
|
12343
|
+
this.selectOptions.onChange(this.selectableOptions[this.selectedIndex].value);
|
|
12344
|
+
}
|
|
12345
|
+
this.render();
|
|
12346
|
+
}
|
|
12347
|
+
/**
|
|
12348
|
+
* Get the prompt prefix string (without ANSI escapes for width calculation).
|
|
12349
|
+
*/
|
|
12350
|
+
getPromptPrefix() {
|
|
12351
|
+
return `? ${this.promptText}: `;
|
|
12352
|
+
}
|
|
12353
|
+
/**
|
|
12354
|
+
* Get the rendered prompt prefix (with ANSI color codes).
|
|
12355
|
+
*/
|
|
12356
|
+
getRenderedPromptPrefix() {
|
|
12357
|
+
return `\x1B[36m?\x1B[0m ${this.promptText}: `;
|
|
12358
|
+
}
|
|
12359
|
+
/**
|
|
12360
|
+
* Calculate the width needed for a single option.
|
|
12361
|
+
* Selected option: `[ label ]` (label.length + 4)
|
|
12362
|
+
* Unselected option: ` label ` (label.length + 2)
|
|
12363
|
+
*/
|
|
12364
|
+
getOptionWidth(opt, isSelected) {
|
|
12365
|
+
return isSelected ? opt.label.length + 4 : opt.label.length + 2;
|
|
12366
|
+
}
|
|
12367
|
+
/**
|
|
12368
|
+
* Calculate how many options fit in the visible window starting from a given index.
|
|
12369
|
+
*/
|
|
12370
|
+
calculateVisibleWindow() {
|
|
12371
|
+
const cols = this.host.getTerminalCols();
|
|
12372
|
+
const promptWidth = this.getPromptPrefix().length;
|
|
12373
|
+
const available = cols - promptWidth;
|
|
12374
|
+
if (this.selectableOptions.length === 0) {
|
|
12375
|
+
this.visibleStart = 0;
|
|
12376
|
+
this.visibleEnd = 0;
|
|
12377
|
+
return;
|
|
12378
|
+
}
|
|
12379
|
+
let totalWidth = 0;
|
|
12380
|
+
for (let i = 0; i < this.selectableOptions.length; i++) {
|
|
12381
|
+
totalWidth += this.getOptionWidth(this.selectableOptions[i], i === this.selectedIndex);
|
|
12382
|
+
}
|
|
12383
|
+
if (totalWidth <= available) {
|
|
12384
|
+
this.visibleStart = 0;
|
|
12385
|
+
this.visibleEnd = this.selectableOptions.length;
|
|
12386
|
+
return;
|
|
12387
|
+
}
|
|
12388
|
+
this.adjustVisibleWindow();
|
|
12389
|
+
}
|
|
12390
|
+
/**
|
|
12391
|
+
* Adjust the visible window so the selected option is visible.
|
|
12392
|
+
* Accounts for scroll indicator widths.
|
|
12393
|
+
*/
|
|
12394
|
+
adjustVisibleWindow() {
|
|
12395
|
+
const cols = this.host.getTerminalCols();
|
|
12396
|
+
const promptWidth = this.getPromptPrefix().length;
|
|
12397
|
+
const available = cols - promptWidth;
|
|
12398
|
+
if (this.selectableOptions.length === 0) return;
|
|
12399
|
+
let totalWidth = 0;
|
|
12400
|
+
for (let i = 0; i < this.selectableOptions.length; i++) {
|
|
12401
|
+
totalWidth += this.getOptionWidth(this.selectableOptions[i], i === this.selectedIndex);
|
|
12402
|
+
}
|
|
12403
|
+
if (totalWidth <= available) {
|
|
12404
|
+
this.visibleStart = 0;
|
|
12405
|
+
this.visibleEnd = this.selectableOptions.length;
|
|
12406
|
+
return;
|
|
12407
|
+
}
|
|
12408
|
+
const indicatorWidth = 2;
|
|
12409
|
+
if (this.selectedIndex < this.visibleStart) {
|
|
12410
|
+
this.visibleStart = this.selectedIndex;
|
|
12411
|
+
}
|
|
12412
|
+
let usedWidth = 0;
|
|
12413
|
+
const hasLeftIndicator = this.visibleStart > 0;
|
|
12414
|
+
if (hasLeftIndicator) {
|
|
12415
|
+
usedWidth += indicatorWidth;
|
|
12416
|
+
}
|
|
12417
|
+
let end = this.visibleStart;
|
|
12418
|
+
for (let i = this.visibleStart; i < this.selectableOptions.length; i++) {
|
|
12419
|
+
const optWidth = this.getOptionWidth(this.selectableOptions[i], i === this.selectedIndex);
|
|
12420
|
+
const needsRightIndicator = i < this.selectableOptions.length - 1;
|
|
12421
|
+
const rightReserve = needsRightIndicator ? indicatorWidth : 0;
|
|
12422
|
+
if (usedWidth + optWidth + rightReserve <= available) {
|
|
12423
|
+
usedWidth += optWidth;
|
|
12424
|
+
end = i + 1;
|
|
12425
|
+
} else {
|
|
12426
|
+
break;
|
|
12427
|
+
}
|
|
12428
|
+
}
|
|
12429
|
+
if (this.selectedIndex >= end) {
|
|
12430
|
+
end = this.selectedIndex + 1;
|
|
12431
|
+
usedWidth = this.getOptionWidth(
|
|
12432
|
+
this.selectableOptions[this.selectedIndex],
|
|
12433
|
+
true
|
|
12434
|
+
);
|
|
12435
|
+
const hasRightIndicator = end < this.selectableOptions.length;
|
|
12436
|
+
if (hasRightIndicator) {
|
|
12437
|
+
usedWidth += indicatorWidth;
|
|
12438
|
+
}
|
|
12439
|
+
let start = this.selectedIndex;
|
|
12440
|
+
for (let i = this.selectedIndex - 1; i >= 0; i--) {
|
|
12441
|
+
const optWidth = this.getOptionWidth(this.selectableOptions[i], false);
|
|
12442
|
+
const needsLeftIndicator = i > 0;
|
|
12443
|
+
const leftReserve = needsLeftIndicator ? indicatorWidth : 0;
|
|
12444
|
+
const currentLeftReserve = start > 0 ? indicatorWidth : 0;
|
|
12445
|
+
if (usedWidth - currentLeftReserve + optWidth + leftReserve <= available) {
|
|
12446
|
+
usedWidth = usedWidth - currentLeftReserve + optWidth + leftReserve;
|
|
12447
|
+
start = i;
|
|
12448
|
+
} else {
|
|
12449
|
+
break;
|
|
12450
|
+
}
|
|
12451
|
+
}
|
|
12452
|
+
this.visibleStart = start;
|
|
12453
|
+
end = this.selectedIndex + 1;
|
|
12454
|
+
usedWidth = 0;
|
|
12455
|
+
if (this.visibleStart > 0) usedWidth += indicatorWidth;
|
|
12456
|
+
for (let i = this.visibleStart; i < end; i++) {
|
|
12457
|
+
usedWidth += this.getOptionWidth(this.selectableOptions[i], i === this.selectedIndex);
|
|
12458
|
+
}
|
|
12459
|
+
for (let i = end; i < this.selectableOptions.length; i++) {
|
|
12460
|
+
const optWidth = this.getOptionWidth(this.selectableOptions[i], false);
|
|
12461
|
+
const needsRightIndicator = i < this.selectableOptions.length - 1;
|
|
12462
|
+
const rightReserve = needsRightIndicator ? indicatorWidth : 0;
|
|
12463
|
+
if (usedWidth + optWidth + rightReserve <= available) {
|
|
12464
|
+
usedWidth += optWidth;
|
|
12465
|
+
end = i + 1;
|
|
12466
|
+
} else {
|
|
12467
|
+
break;
|
|
12468
|
+
}
|
|
12469
|
+
}
|
|
12470
|
+
}
|
|
12471
|
+
this.visibleEnd = end;
|
|
12472
|
+
}
|
|
12473
|
+
/**
|
|
12474
|
+
* Render the complete inline select display.
|
|
12475
|
+
*/
|
|
12476
|
+
render() {
|
|
12477
|
+
this.clearExtraLines();
|
|
12478
|
+
this.host.terminal.write("\x1B[2K\r");
|
|
12479
|
+
this.host.terminal.write(this.getRenderedPromptPrefix());
|
|
12480
|
+
if (this.selectableOptions.length === 0) return;
|
|
12481
|
+
const hasLeftOverflow = this.visibleStart > 0;
|
|
12482
|
+
const hasRightOverflow = this.visibleEnd < this.selectableOptions.length;
|
|
12483
|
+
if (hasLeftOverflow) {
|
|
12484
|
+
this.host.terminal.write("\x1B[2m\u25C2\x1B[0m ");
|
|
12485
|
+
}
|
|
12486
|
+
for (let i = this.visibleStart; i < this.visibleEnd; i++) {
|
|
12487
|
+
const opt = this.selectableOptions[i];
|
|
12488
|
+
const isSelected = i === this.selectedIndex;
|
|
12489
|
+
if (isSelected) {
|
|
12490
|
+
this.host.terminal.write(`\x1B[36m[ ${opt.label} ]\x1B[0m`);
|
|
12491
|
+
} else {
|
|
12492
|
+
this.host.terminal.write(` ${opt.label} `);
|
|
12493
|
+
}
|
|
12494
|
+
}
|
|
12495
|
+
if (hasRightOverflow) {
|
|
12496
|
+
this.host.terminal.write(" \x1B[2m\u25B8\x1B[0m");
|
|
12497
|
+
}
|
|
12498
|
+
this.writeHelp("\u2190/\u2192 navigate \xB7 enter to select \xB7 esc to cancel");
|
|
12499
|
+
}
|
|
12500
|
+
};
|
|
12501
|
+
|
|
12502
|
+
// src/lib/input/modes/multi-select-input-mode.ts
|
|
12503
|
+
var MultiSelectInputMode = class extends InputModeBase {
|
|
12504
|
+
constructor(host, resolve, promptText, options, selectOptions) {
|
|
12505
|
+
super(host, resolve);
|
|
12506
|
+
this.promptText = promptText;
|
|
12507
|
+
this.options = options;
|
|
12508
|
+
this.selectOptions = selectOptions;
|
|
12509
|
+
this.selectedIndex = 0;
|
|
12510
|
+
this.scrollOffset = 0;
|
|
12511
|
+
this.filter = "";
|
|
12512
|
+
/** Number of lines rendered for the option list + help bar (for clearing) */
|
|
12513
|
+
this.renderedLines = 0;
|
|
12514
|
+
this.filteredOptions = [...options];
|
|
12515
|
+
this.maxVisible = Math.min(options.length, host.getTerminalRows() - 4);
|
|
12516
|
+
this.checkedState = /* @__PURE__ */ new Map();
|
|
12517
|
+
for (const opt of options) {
|
|
12518
|
+
this.checkedState.set(opt.value, opt.checked === true);
|
|
12519
|
+
}
|
|
12520
|
+
}
|
|
12521
|
+
activate() {
|
|
12522
|
+
this.host.terminal.write("\x1B[?25l");
|
|
12523
|
+
this.selectedIndex = 0;
|
|
12524
|
+
const selectables = this.filteredOptions.filter((o) => !o.disabled);
|
|
12525
|
+
if (selectables.length > 0) {
|
|
12526
|
+
this.selectedIndex = 0;
|
|
12527
|
+
}
|
|
12528
|
+
this.renderAll();
|
|
12529
|
+
}
|
|
12530
|
+
/**
|
|
12531
|
+
* Handle keyboard events. When searchable and filter is non-empty,
|
|
12532
|
+
* Escape clears the filter instead of aborting.
|
|
12533
|
+
*/
|
|
12534
|
+
handleKeyEvent(event) {
|
|
12535
|
+
if (this.selectOptions?.searchable && this.filter.length > 0) {
|
|
12536
|
+
if (event.code === "Escape") {
|
|
12537
|
+
this.clearFilter();
|
|
12538
|
+
return false;
|
|
12539
|
+
}
|
|
12540
|
+
}
|
|
12541
|
+
return super.handleKeyEvent(event);
|
|
12542
|
+
}
|
|
12543
|
+
/**
|
|
12544
|
+
* Clear rendered option lines and show final answer before resolving.
|
|
12545
|
+
*/
|
|
12546
|
+
resolveAndPop(value) {
|
|
12547
|
+
this.clearRenderedLines();
|
|
12548
|
+
const labels = value.map((v) => {
|
|
12549
|
+
const opt = this.options.find((o) => o.value === v);
|
|
12550
|
+
return opt ? opt.label : v;
|
|
12551
|
+
});
|
|
12552
|
+
const summary = labels.length > 0 ? labels.join(", ") : "none";
|
|
12553
|
+
this.host.terminal.write("\x1B[2K\r");
|
|
12554
|
+
this.host.terminal.write(`\x1B[32m\u2714\x1B[0m ${this.promptText} \x1B[36m${summary}\x1B[0m`);
|
|
12555
|
+
this.host.terminal.write("\x1B[?25h");
|
|
12556
|
+
super.resolveAndPop(value);
|
|
12557
|
+
}
|
|
12558
|
+
/**
|
|
12559
|
+
* Clear rendered option lines before aborting.
|
|
12560
|
+
*/
|
|
12561
|
+
abort() {
|
|
12562
|
+
this.clearRenderedLines();
|
|
12563
|
+
this.host.terminal.write("\x1B[2K\r");
|
|
12564
|
+
this.host.terminal.write(`\x1B[33m\u2718\x1B[0m ${this.promptText} \x1B[2mcancelled\x1B[0m`);
|
|
12565
|
+
this.host.terminal.write("\x1B[?25h");
|
|
12566
|
+
super.abort();
|
|
12567
|
+
}
|
|
12568
|
+
async handleInput(data) {
|
|
12569
|
+
if (data === "\x1B[A") {
|
|
12570
|
+
this.moveSelection(-1);
|
|
12571
|
+
} else if (data === "\x1B[B") {
|
|
12572
|
+
this.moveSelection(1);
|
|
12573
|
+
} else if (data === " ") {
|
|
12574
|
+
this.toggleCurrent();
|
|
12575
|
+
} else if (data === "a" && (!this.selectOptions?.searchable || this.filter.length === 0)) {
|
|
12576
|
+
this.toggleAll();
|
|
12577
|
+
} else if (data === "\r") {
|
|
12578
|
+
this.resolveAndPop(this.getCheckedValues());
|
|
12579
|
+
} else if (data === "\x7F") {
|
|
12580
|
+
if (this.selectOptions?.searchable && this.filter.length > 0) {
|
|
12581
|
+
this.filter = this.filter.slice(0, -1);
|
|
12582
|
+
this.applyFilter();
|
|
12583
|
+
this.renderAll();
|
|
12584
|
+
}
|
|
12585
|
+
} else if (data.startsWith("\x1B")) ; else if (this.selectOptions?.searchable && data.length === 1 && data >= " ") {
|
|
12586
|
+
this.filter += data;
|
|
12587
|
+
this.applyFilter();
|
|
12588
|
+
this.renderAll();
|
|
12589
|
+
}
|
|
12590
|
+
}
|
|
12591
|
+
onResize(cols, rows) {
|
|
12592
|
+
this.maxVisible = Math.min(this.filteredOptions.length, rows - 4);
|
|
12593
|
+
if (this.scrollOffset + this.maxVisible > this.getSelectableCount()) {
|
|
12594
|
+
this.scrollOffset = Math.max(0, this.getSelectableCount() - this.maxVisible);
|
|
12595
|
+
}
|
|
12596
|
+
this.renderAll();
|
|
12597
|
+
}
|
|
12598
|
+
// ── Private helpers ──────────────────────────────────────────────
|
|
12599
|
+
/**
|
|
12600
|
+
* Build flattened display list with group headers inserted.
|
|
12601
|
+
*/
|
|
12602
|
+
buildDisplayList() {
|
|
12603
|
+
const items = [];
|
|
12604
|
+
let lastGroup;
|
|
12605
|
+
for (const opt of this.filteredOptions) {
|
|
12606
|
+
if (opt.group && opt.group !== lastGroup) {
|
|
12607
|
+
items.push({ type: "group-header", groupName: opt.group });
|
|
12608
|
+
lastGroup = opt.group;
|
|
12609
|
+
}
|
|
12610
|
+
items.push({ type: "option", option: opt });
|
|
12611
|
+
}
|
|
12612
|
+
return items;
|
|
12613
|
+
}
|
|
12614
|
+
/**
|
|
12615
|
+
* Get the total count of selectable items (non-disabled options).
|
|
12616
|
+
*/
|
|
12617
|
+
getSelectableCount() {
|
|
12618
|
+
return this.filteredOptions.filter((o) => !o.disabled).length;
|
|
12619
|
+
}
|
|
12620
|
+
/**
|
|
12621
|
+
* Get an array of currently checked values (preserving original order).
|
|
12622
|
+
*/
|
|
12623
|
+
getCheckedValues() {
|
|
12624
|
+
const result = [];
|
|
12625
|
+
for (const opt of this.options) {
|
|
12626
|
+
if (this.checkedState.get(opt.value) === true) {
|
|
12627
|
+
result.push(opt.value);
|
|
12628
|
+
}
|
|
12629
|
+
}
|
|
12630
|
+
return result;
|
|
12631
|
+
}
|
|
12632
|
+
/**
|
|
12633
|
+
* Move selection in the given direction (+1 or -1), skipping disabled options.
|
|
12634
|
+
* Wraps around at boundaries.
|
|
12635
|
+
*/
|
|
12636
|
+
moveSelection(direction) {
|
|
12637
|
+
const selectableCount = this.getSelectableCount();
|
|
12638
|
+
if (selectableCount === 0) return;
|
|
12639
|
+
let next = this.selectedIndex + direction;
|
|
12640
|
+
if (next < 0) next = selectableCount - 1;
|
|
12641
|
+
if (next >= selectableCount) next = 0;
|
|
12642
|
+
this.selectedIndex = next;
|
|
12643
|
+
if (this.selectedIndex < this.scrollOffset) {
|
|
12644
|
+
this.scrollOffset = this.selectedIndex;
|
|
12645
|
+
} else if (this.selectedIndex >= this.scrollOffset + this.maxVisible) {
|
|
12646
|
+
this.scrollOffset = this.selectedIndex - this.maxVisible + 1;
|
|
12647
|
+
}
|
|
12648
|
+
this.renderAll();
|
|
12649
|
+
}
|
|
12650
|
+
/**
|
|
12651
|
+
* Toggle the checked state of the currently selected option.
|
|
12652
|
+
*/
|
|
12653
|
+
toggleCurrent() {
|
|
12654
|
+
const selectables = this.filteredOptions.filter((o) => !o.disabled);
|
|
12655
|
+
if (selectables.length === 0 || this.selectedIndex >= selectables.length) return;
|
|
12656
|
+
const opt = selectables[this.selectedIndex];
|
|
12657
|
+
const current = this.checkedState.get(opt.value) === true;
|
|
12658
|
+
this.checkedState.set(opt.value, !current);
|
|
12659
|
+
if (this.selectOptions?.onChange) {
|
|
12660
|
+
this.selectOptions.onChange(this.getCheckedValues());
|
|
12661
|
+
}
|
|
12662
|
+
this.renderAll();
|
|
12663
|
+
}
|
|
12664
|
+
/**
|
|
12665
|
+
* Toggle all non-disabled options: if all are checked, uncheck all; otherwise check all.
|
|
12666
|
+
*/
|
|
12667
|
+
toggleAll() {
|
|
12668
|
+
const nonDisabled = this.options.filter((o) => !o.disabled);
|
|
12669
|
+
const allChecked = nonDisabled.every((o) => this.checkedState.get(o.value) === true);
|
|
12670
|
+
for (const opt of nonDisabled) {
|
|
12671
|
+
this.checkedState.set(opt.value, !allChecked);
|
|
12672
|
+
}
|
|
12673
|
+
if (this.selectOptions?.onChange) {
|
|
12674
|
+
this.selectOptions.onChange(this.getCheckedValues());
|
|
12675
|
+
}
|
|
12676
|
+
this.renderAll();
|
|
12677
|
+
}
|
|
12678
|
+
/**
|
|
12679
|
+
* Apply the current filter text to the options list.
|
|
12680
|
+
*/
|
|
12681
|
+
applyFilter() {
|
|
12682
|
+
if (this.filter.length === 0) {
|
|
12683
|
+
this.filteredOptions = [...this.options];
|
|
12684
|
+
} else {
|
|
12685
|
+
const lower = this.filter.toLowerCase();
|
|
12686
|
+
this.filteredOptions = this.options.filter(
|
|
12687
|
+
(o) => o.label.toLowerCase().includes(lower)
|
|
12688
|
+
);
|
|
12689
|
+
}
|
|
12690
|
+
this.selectedIndex = 0;
|
|
12691
|
+
this.scrollOffset = 0;
|
|
12692
|
+
this.maxVisible = Math.min(
|
|
12693
|
+
this.filteredOptions.length,
|
|
12694
|
+
this.host.getTerminalRows() - 4
|
|
12695
|
+
);
|
|
12696
|
+
}
|
|
12697
|
+
/**
|
|
12698
|
+
* Clear the filter and restore full option list.
|
|
12699
|
+
*/
|
|
12700
|
+
clearFilter() {
|
|
12701
|
+
this.filter = "";
|
|
12702
|
+
this.applyFilter();
|
|
12703
|
+
this.renderAll();
|
|
12704
|
+
}
|
|
12705
|
+
/**
|
|
12706
|
+
* Clear all previously rendered lines and re-render everything.
|
|
12707
|
+
*/
|
|
12708
|
+
renderAll() {
|
|
12709
|
+
this.clearRenderedLines();
|
|
12710
|
+
this.renderPrompt();
|
|
12711
|
+
this.renderOptions();
|
|
12712
|
+
this.renderHelpBar();
|
|
12713
|
+
if (this.renderedLines > 0) {
|
|
12714
|
+
this.host.terminal.write(`\x1B[${this.renderedLines}A`);
|
|
12715
|
+
}
|
|
12716
|
+
}
|
|
12717
|
+
/**
|
|
12718
|
+
* Clear all lines we previously rendered (below the cursor).
|
|
12719
|
+
*/
|
|
12720
|
+
clearRenderedLines() {
|
|
12721
|
+
if (this.renderedLines > 0) {
|
|
12722
|
+
this.host.terminal.write("\x1B[s");
|
|
12723
|
+
for (let i = 0; i < this.renderedLines; i++) {
|
|
12724
|
+
this.host.terminal.write("\x1B[B\x1B[2K");
|
|
12725
|
+
}
|
|
12726
|
+
this.host.terminal.write("\x1B[u");
|
|
12727
|
+
this.renderedLines = 0;
|
|
12728
|
+
}
|
|
12729
|
+
}
|
|
12730
|
+
/**
|
|
12731
|
+
* Render the prompt line.
|
|
12732
|
+
*/
|
|
12733
|
+
renderPrompt() {
|
|
12734
|
+
this.host.terminal.write("\x1B[2K\r");
|
|
12735
|
+
if (this.selectOptions?.searchable) {
|
|
12736
|
+
if (this.filter.length > 0) {
|
|
12737
|
+
this.host.terminal.write(
|
|
12738
|
+
`\x1B[36m?\x1B[0m ${this.promptText}: ${this.filter}`
|
|
12739
|
+
);
|
|
12740
|
+
} else {
|
|
12741
|
+
this.host.terminal.write(
|
|
12742
|
+
`\x1B[36m?\x1B[0m ${this.promptText}: \x1B[2mType to filter...\x1B[0m`
|
|
12743
|
+
);
|
|
12744
|
+
}
|
|
12745
|
+
} else {
|
|
12746
|
+
this.host.terminal.write(`\x1B[36m?\x1B[0m ${this.promptText}:`);
|
|
12747
|
+
}
|
|
12748
|
+
}
|
|
12749
|
+
/**
|
|
12750
|
+
* Render the visible slice of the option list.
|
|
12751
|
+
*/
|
|
12752
|
+
renderOptions() {
|
|
12753
|
+
const items = this.buildDisplayList();
|
|
12754
|
+
const selectables = this.filteredOptions.filter((o) => !o.disabled);
|
|
12755
|
+
const visibleStart = this.scrollOffset;
|
|
12756
|
+
const visibleEnd = Math.min(
|
|
12757
|
+
selectables.length,
|
|
12758
|
+
this.scrollOffset + this.maxVisible
|
|
12759
|
+
);
|
|
12760
|
+
if (visibleStart > 0) {
|
|
12761
|
+
this.host.terminal.write(`\r
|
|
12762
|
+
\x1B[2m \u2191 ${visibleStart} more above\x1B[0m`);
|
|
12763
|
+
this.renderedLines++;
|
|
12764
|
+
}
|
|
12765
|
+
let selectableIdx = 0;
|
|
12766
|
+
for (const item of items) {
|
|
12767
|
+
if (item.type === "group-header") {
|
|
12768
|
+
if (this.isGroupVisible(item.groupName, items, visibleStart, visibleEnd)) {
|
|
12769
|
+
this.host.terminal.write(
|
|
12770
|
+
`\r
|
|
12771
|
+
\x1B[35m\u2500\u2500 ${item.groupName} \u2500\u2500\x1B[0m`
|
|
12772
|
+
);
|
|
12773
|
+
this.renderedLines++;
|
|
12774
|
+
}
|
|
12775
|
+
} else {
|
|
12776
|
+
const opt = item.option;
|
|
12777
|
+
if (opt.disabled) {
|
|
12778
|
+
if (this.isDisabledOptionVisible(items, item, visibleStart, visibleEnd)) {
|
|
12779
|
+
this.renderOptionLine(opt, false);
|
|
12780
|
+
this.renderedLines++;
|
|
12781
|
+
}
|
|
12782
|
+
} else {
|
|
12783
|
+
if (selectableIdx >= visibleStart && selectableIdx < visibleEnd) {
|
|
12784
|
+
const isSelected = selectableIdx === this.selectedIndex;
|
|
12785
|
+
this.renderOptionLine(opt, isSelected);
|
|
12786
|
+
this.renderedLines++;
|
|
12787
|
+
}
|
|
12788
|
+
selectableIdx++;
|
|
12789
|
+
}
|
|
12790
|
+
}
|
|
12791
|
+
}
|
|
12792
|
+
const remaining = selectables.length - visibleEnd;
|
|
12793
|
+
if (remaining > 0) {
|
|
12794
|
+
this.host.terminal.write(`\r
|
|
12795
|
+
\x1B[2m \u2193 ${remaining} more below\x1B[0m`);
|
|
12796
|
+
this.renderedLines++;
|
|
12797
|
+
}
|
|
12798
|
+
}
|
|
12799
|
+
/**
|
|
12800
|
+
* Check if a group header should be visible given the current visible range.
|
|
12801
|
+
*/
|
|
12802
|
+
isGroupVisible(groupName, items, visibleStart, visibleEnd) {
|
|
12803
|
+
let selectableIdx = 0;
|
|
12804
|
+
for (const item of items) {
|
|
12805
|
+
if (item.type === "option" && !item.option.disabled) {
|
|
12806
|
+
if (item.option.group === groupName && selectableIdx >= visibleStart && selectableIdx < visibleEnd) {
|
|
12807
|
+
return true;
|
|
12808
|
+
}
|
|
12809
|
+
selectableIdx++;
|
|
12810
|
+
}
|
|
12811
|
+
}
|
|
12812
|
+
return false;
|
|
12813
|
+
}
|
|
12814
|
+
/**
|
|
12815
|
+
* Check if a disabled option should be visible (adjacent to visible selectables).
|
|
12816
|
+
*/
|
|
12817
|
+
isDisabledOptionVisible(items, disabledItem, visibleStart, visibleEnd) {
|
|
12818
|
+
const idx = items.indexOf(disabledItem);
|
|
12819
|
+
let selectableIdx = 0;
|
|
12820
|
+
for (let i = 0; i < items.length; i++) {
|
|
12821
|
+
if (items[i].type === "option" && !items[i].option.disabled) {
|
|
12822
|
+
if (i > idx) {
|
|
12823
|
+
return selectableIdx >= visibleStart && selectableIdx < visibleEnd;
|
|
12824
|
+
}
|
|
12825
|
+
selectableIdx++;
|
|
12826
|
+
}
|
|
12827
|
+
}
|
|
12828
|
+
return selectableIdx > 0 && selectableIdx - 1 >= visibleStart && selectableIdx - 1 < visibleEnd;
|
|
12829
|
+
}
|
|
12830
|
+
/**
|
|
12831
|
+
* Render a single option line with checkbox indicator.
|
|
12832
|
+
*/
|
|
12833
|
+
renderOptionLine(opt, isSelected) {
|
|
12834
|
+
const isChecked = this.checkedState.get(opt.value) === true;
|
|
12835
|
+
const label = this.highlightMatch(opt.label);
|
|
12836
|
+
const desc = opt.description ? ` \x1B[2;3m\u2014 ${opt.description}\x1B[0m` : "";
|
|
12837
|
+
const checkbox = isChecked ? "\x1B[32m\u25C9\x1B[0m" : "\x1B[2m\u25CB\x1B[0m";
|
|
12838
|
+
if (opt.disabled) {
|
|
12839
|
+
const disabledCheckbox = isChecked ? "\x1B[2m\u25C9\x1B[0m" : "\x1B[2m\u25CB\x1B[0m";
|
|
12840
|
+
this.host.terminal.write(
|
|
12841
|
+
`\r
|
|
12842
|
+
${disabledCheckbox} \x1B[2m${opt.label}${desc} (disabled)\x1B[0m`
|
|
12843
|
+
);
|
|
12844
|
+
} else if (isSelected) {
|
|
12845
|
+
this.host.terminal.write(
|
|
12846
|
+
`\r
|
|
12847
|
+
\x1B[48;5;24m\x1B[38;5;117m\u276F ${checkbox} ${label}${desc}\x1B[0m`
|
|
12848
|
+
);
|
|
12849
|
+
} else {
|
|
12850
|
+
this.host.terminal.write(
|
|
12851
|
+
`\r
|
|
12852
|
+
${checkbox} ${label}${desc}`
|
|
12853
|
+
);
|
|
12854
|
+
}
|
|
12855
|
+
}
|
|
12856
|
+
/**
|
|
12857
|
+
* Highlight the matched filter text in yellow within the label.
|
|
12858
|
+
*/
|
|
12859
|
+
highlightMatch(label) {
|
|
12860
|
+
if (!this.filter || this.filter.length === 0) return label;
|
|
12861
|
+
const lower = label.toLowerCase();
|
|
12862
|
+
const filterLower = this.filter.toLowerCase();
|
|
12863
|
+
const idx = lower.indexOf(filterLower);
|
|
12864
|
+
if (idx < 0) return label;
|
|
12865
|
+
const before = label.substring(0, idx);
|
|
12866
|
+
const match = label.substring(idx, idx + this.filter.length);
|
|
12867
|
+
const after = label.substring(idx + this.filter.length);
|
|
12868
|
+
return `${before}\x1B[33m${match}\x1B[0m${after}`;
|
|
12869
|
+
}
|
|
12870
|
+
/**
|
|
12871
|
+
* Render the help bar at the bottom.
|
|
12872
|
+
*/
|
|
12873
|
+
renderHelpBar() {
|
|
12874
|
+
const checkedCount = this.getCheckedValues().length;
|
|
12875
|
+
let help;
|
|
12876
|
+
if (this.selectOptions?.searchable) {
|
|
12877
|
+
if (this.filter.length > 0) {
|
|
12878
|
+
const matchCount = this.filteredOptions.filter((o) => !o.disabled).length;
|
|
12879
|
+
const totalCount = this.options.filter((o) => !o.disabled).length;
|
|
12880
|
+
help = `${checkedCount} selected \xB7 ${matchCount} of ${totalCount} matches \xB7 space to toggle \xB7 enter to confirm`;
|
|
12881
|
+
} else {
|
|
12882
|
+
help = `${checkedCount} selected \xB7 space to toggle \xB7 a to select all \xB7 type to filter \xB7 enter to confirm`;
|
|
12883
|
+
}
|
|
12884
|
+
} else {
|
|
12885
|
+
help = `${checkedCount} selected \xB7 space to toggle \xB7 a to select all \xB7 enter to confirm`;
|
|
12886
|
+
}
|
|
12887
|
+
this.host.terminal.write(`\r
|
|
12888
|
+
\x1B[2m${help}\x1B[0m`);
|
|
12889
|
+
this.renderedLines++;
|
|
12890
|
+
}
|
|
12891
|
+
};
|
|
12892
|
+
|
|
12893
|
+
// src/lib/input/modes/date-input-mode.ts
|
|
12894
|
+
var DateInputMode = class extends InputModeBase {
|
|
12895
|
+
constructor(host, resolve, promptText, options) {
|
|
12896
|
+
super(host, resolve);
|
|
12897
|
+
this.promptText = promptText;
|
|
12898
|
+
this.options = options;
|
|
12899
|
+
this.hasError = false;
|
|
12900
|
+
this.format = options?.format ?? "YYYY-MM-DD";
|
|
12901
|
+
this.separator = this.extractSeparator(this.format);
|
|
12902
|
+
this.buffer = options?.default ?? "";
|
|
12903
|
+
this.cursorPosition = this.buffer.length;
|
|
12904
|
+
}
|
|
12905
|
+
activate() {
|
|
12906
|
+
const prompt = this.buildPrompt();
|
|
12907
|
+
this.host.writeToTerminal(prompt);
|
|
12908
|
+
if (this.buffer) {
|
|
12909
|
+
this.host.writeToTerminal(this.buffer);
|
|
12910
|
+
}
|
|
12911
|
+
}
|
|
12912
|
+
async handleInput(data) {
|
|
12913
|
+
if (this.hasError) {
|
|
12914
|
+
this.hasError = false;
|
|
12915
|
+
this.clearExtraLines();
|
|
12916
|
+
}
|
|
12917
|
+
if (data === "\r") {
|
|
12918
|
+
const error = this.validateBuffer();
|
|
12919
|
+
if (error) {
|
|
12920
|
+
this.hasError = true;
|
|
12921
|
+
this.writeError(error);
|
|
12922
|
+
return;
|
|
12923
|
+
}
|
|
12924
|
+
this.resolveAndPop(this.buffer);
|
|
12925
|
+
} else if (data === "\x7F") {
|
|
12926
|
+
if (this.cursorPosition > 0) {
|
|
12927
|
+
this.buffer = this.buffer.slice(0, this.cursorPosition - 1) + this.buffer.slice(this.cursorPosition);
|
|
12928
|
+
this.cursorPosition--;
|
|
12929
|
+
this.renderLine();
|
|
12930
|
+
}
|
|
12931
|
+
} else if (data === "\x1B[D") {
|
|
12932
|
+
if (this.cursorPosition > 0) {
|
|
12933
|
+
this.cursorPosition--;
|
|
12934
|
+
this.host.terminal.write(data);
|
|
12935
|
+
}
|
|
12936
|
+
} else if (data === "\x1B[C") {
|
|
12937
|
+
if (this.cursorPosition < this.buffer.length) {
|
|
12938
|
+
this.cursorPosition++;
|
|
12939
|
+
this.host.terminal.write(data);
|
|
12940
|
+
}
|
|
12941
|
+
} else if (data.startsWith("\x1B")) ; else {
|
|
12942
|
+
for (const ch of data) {
|
|
12943
|
+
if (ch >= "0" && ch <= "9" || ch === this.separator) {
|
|
12944
|
+
this.buffer = this.buffer.slice(0, this.cursorPosition) + ch + this.buffer.slice(this.cursorPosition);
|
|
12945
|
+
this.cursorPosition++;
|
|
12946
|
+
}
|
|
12947
|
+
}
|
|
12948
|
+
this.renderLine();
|
|
12949
|
+
}
|
|
12950
|
+
}
|
|
12951
|
+
onResize() {
|
|
12952
|
+
this.renderLine();
|
|
12953
|
+
}
|
|
12954
|
+
buildPrompt() {
|
|
12955
|
+
return `\x1B[36m?\x1B[0m ${this.promptText} \x1B[2m(${this.format})\x1B[0m`;
|
|
12956
|
+
}
|
|
12957
|
+
renderLine() {
|
|
12958
|
+
const prompt = this.buildPrompt();
|
|
12959
|
+
this.redrawLine(prompt, this.buffer, this.cursorPosition);
|
|
12960
|
+
}
|
|
12961
|
+
/**
|
|
12962
|
+
* Extract the separator character from a format string.
|
|
12963
|
+
* Returns the first non-alphabetic character found (e.g., '-' from 'YYYY-MM-DD').
|
|
12964
|
+
* Defaults to '-' if none is found.
|
|
12965
|
+
*/
|
|
12966
|
+
extractSeparator(format) {
|
|
12967
|
+
for (const ch of format) {
|
|
12968
|
+
if (ch < "A" || ch > "z" || ch > "Z" && ch < "a") {
|
|
12969
|
+
return ch;
|
|
12970
|
+
}
|
|
12971
|
+
}
|
|
12972
|
+
return "-";
|
|
12973
|
+
}
|
|
12974
|
+
/**
|
|
12975
|
+
* Parse the buffer according to the current format string.
|
|
12976
|
+
* Returns { year, month, day } or null if parsing fails.
|
|
12977
|
+
*/
|
|
12978
|
+
parseBuffer() {
|
|
12979
|
+
const parts = this.buffer.split(this.separator);
|
|
12980
|
+
const formatParts = this.format.split(this.separator);
|
|
12981
|
+
if (parts.length !== formatParts.length) return null;
|
|
12982
|
+
let year = 0, month = 0, day = 0;
|
|
12983
|
+
for (let i = 0; i < formatParts.length; i++) {
|
|
12984
|
+
const token = formatParts[i].toUpperCase();
|
|
12985
|
+
const value = parseInt(parts[i], 10);
|
|
12986
|
+
if (isNaN(value)) return null;
|
|
12987
|
+
if (token === "YYYY") year = value;
|
|
12988
|
+
else if (token === "MM") month = value;
|
|
12989
|
+
else if (token === "DD") day = value;
|
|
12990
|
+
}
|
|
12991
|
+
return { year, month, day };
|
|
12992
|
+
}
|
|
12993
|
+
/**
|
|
12994
|
+
* Validate the buffer as a real calendar date.
|
|
12995
|
+
* Returns an error message string or null if valid.
|
|
12996
|
+
*/
|
|
12997
|
+
validateBuffer() {
|
|
12998
|
+
if (!this.buffer) {
|
|
12999
|
+
return `Please enter a date in ${this.format} format`;
|
|
13000
|
+
}
|
|
13001
|
+
const parsed = this.parseBuffer();
|
|
13002
|
+
if (!parsed) {
|
|
13003
|
+
return `Invalid date format, expected ${this.format}`;
|
|
13004
|
+
}
|
|
13005
|
+
const { year, month, day } = parsed;
|
|
13006
|
+
if (month < 1 || month > 12) {
|
|
13007
|
+
return "Month must be between 1 and 12";
|
|
13008
|
+
}
|
|
13009
|
+
const maxDay = this.daysInMonth(year, month);
|
|
13010
|
+
if (day < 1 || day > maxDay) {
|
|
13011
|
+
return `Day must be between 1 and ${maxDay} for the given month`;
|
|
13012
|
+
}
|
|
13013
|
+
if (this.options?.min) {
|
|
13014
|
+
const minDate = this.parseDate(this.options.min);
|
|
13015
|
+
const current = new Date(year, month - 1, day);
|
|
13016
|
+
if (minDate && current < minDate) {
|
|
13017
|
+
return `Date must not be before ${this.options.min}`;
|
|
13018
|
+
}
|
|
13019
|
+
}
|
|
13020
|
+
if (this.options?.max) {
|
|
13021
|
+
const maxDate = this.parseDate(this.options.max);
|
|
13022
|
+
const current = new Date(year, month - 1, day);
|
|
13023
|
+
if (maxDate && current > maxDate) {
|
|
13024
|
+
return `Date must not be after ${this.options.max}`;
|
|
13025
|
+
}
|
|
13026
|
+
}
|
|
13027
|
+
return null;
|
|
13028
|
+
}
|
|
13029
|
+
/**
|
|
13030
|
+
* Returns the number of days in a given month (accounting for leap years).
|
|
13031
|
+
*/
|
|
13032
|
+
daysInMonth(year, month) {
|
|
13033
|
+
return new Date(year, month, 0).getDate();
|
|
13034
|
+
}
|
|
13035
|
+
/**
|
|
13036
|
+
* Parse a date string (in the same format as this mode) into a Date object.
|
|
13037
|
+
*/
|
|
13038
|
+
parseDate(dateStr) {
|
|
13039
|
+
const parts = dateStr.split(this.separator);
|
|
13040
|
+
const formatParts = this.format.split(this.separator);
|
|
13041
|
+
if (parts.length !== formatParts.length) return null;
|
|
13042
|
+
let year = 0, month = 0, day = 0;
|
|
13043
|
+
for (let i = 0; i < formatParts.length; i++) {
|
|
13044
|
+
const token = formatParts[i].toUpperCase();
|
|
13045
|
+
const value = parseInt(parts[i], 10);
|
|
13046
|
+
if (isNaN(value)) return null;
|
|
13047
|
+
if (token === "YYYY") year = value;
|
|
13048
|
+
else if (token === "MM") month = value;
|
|
13049
|
+
else if (token === "DD") day = value;
|
|
13050
|
+
}
|
|
13051
|
+
return new Date(year, month - 1, day);
|
|
13052
|
+
}
|
|
13053
|
+
};
|
|
13054
|
+
|
|
13055
|
+
// src/lib/input/modes/file-input-mode.ts
|
|
13056
|
+
var FileInputMode = class extends InputModeBase {
|
|
13057
|
+
constructor(host, resolve, promptText, options) {
|
|
13058
|
+
super(host, resolve);
|
|
13059
|
+
this.promptText = promptText;
|
|
13060
|
+
this.options = options;
|
|
13061
|
+
this.waiting = false;
|
|
13062
|
+
}
|
|
13063
|
+
activate() {
|
|
13064
|
+
const provider = this.host.filePickerProvider;
|
|
13065
|
+
const acceptHint = this.options?.accept ? ` \x1B[2m(${this.options.accept})\x1B[0m` : "";
|
|
13066
|
+
this.host.writeToTerminal(`\x1B[36m?\x1B[0m ${this.promptText}${acceptHint}: `);
|
|
13067
|
+
if (!provider.isSupported) {
|
|
13068
|
+
this.host.writeToTerminal("\x1B[2m(file picker not available)\x1B[0m");
|
|
13069
|
+
this.abort();
|
|
13070
|
+
return;
|
|
13071
|
+
}
|
|
13072
|
+
this.host.writeToTerminal("\x1B[33mOpening file picker...\x1B[0m");
|
|
13073
|
+
this.waiting = true;
|
|
13074
|
+
this.pickFiles(provider);
|
|
13075
|
+
}
|
|
13076
|
+
async pickFiles(provider) {
|
|
13077
|
+
try {
|
|
13078
|
+
let results;
|
|
13079
|
+
if (this.options?.directory) {
|
|
13080
|
+
const dir = await provider.pickDirectory();
|
|
13081
|
+
results = dir ? [dir] : null;
|
|
13082
|
+
} else {
|
|
13083
|
+
results = await provider.pickFiles(this.options);
|
|
13084
|
+
}
|
|
13085
|
+
this.waiting = false;
|
|
13086
|
+
if (!results || this.isResolved) {
|
|
13087
|
+
this.host.terminal.write("\x1B[2K\r");
|
|
13088
|
+
this.host.writeToTerminal(`\x1B[36m?\x1B[0m ${this.promptText}: \x1B[2m(cancelled)\x1B[0m\r
|
|
13089
|
+
`);
|
|
13090
|
+
if (!this.isResolved) this.abort();
|
|
13091
|
+
return;
|
|
13092
|
+
}
|
|
13093
|
+
this.host.terminal.write("\x1B[2K\r");
|
|
13094
|
+
this.host.writeToTerminal(`\x1B[36m?\x1B[0m ${this.promptText}:\r
|
|
13095
|
+
`);
|
|
13096
|
+
let totalSize = 0;
|
|
13097
|
+
for (const file of results) {
|
|
13098
|
+
const sizeStr = this.formatSize(file.size);
|
|
13099
|
+
this.host.writeToTerminal(` \x1B[32m\u2714\x1B[0m ${file.name} \x1B[2m(${sizeStr})\x1B[0m\r
|
|
13100
|
+
`);
|
|
13101
|
+
totalSize += file.size;
|
|
13102
|
+
}
|
|
13103
|
+
if (results.length > 1) {
|
|
13104
|
+
this.host.writeToTerminal(` \x1B[2m${results.length} files selected \xB7 ${this.formatSize(totalSize)} total\x1B[0m\r
|
|
13105
|
+
`);
|
|
13106
|
+
}
|
|
13107
|
+
this.resolveAndPop(results);
|
|
13108
|
+
} catch {
|
|
13109
|
+
this.waiting = false;
|
|
13110
|
+
if (this.isResolved) return;
|
|
13111
|
+
this.host.terminal.write("\x1B[2K\r");
|
|
13112
|
+
this.host.writeToTerminal(`\x1B[36m?\x1B[0m ${this.promptText}: \x1B[31m(error)\x1B[0m\r
|
|
13113
|
+
`);
|
|
13114
|
+
this.abort();
|
|
13115
|
+
}
|
|
13116
|
+
}
|
|
13117
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
13118
|
+
async handleInput(_data) {
|
|
13119
|
+
}
|
|
13120
|
+
formatSize(bytes) {
|
|
13121
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
13122
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
13123
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
13124
|
+
}
|
|
13125
|
+
};
|
|
13126
|
+
|
|
13127
|
+
// src/lib/services/cli-input-reader.ts
|
|
13128
|
+
var CliInputReader = class {
|
|
13129
|
+
constructor(host) {
|
|
13130
|
+
this.host = host;
|
|
13131
|
+
}
|
|
13132
|
+
readLine(prompt, options) {
|
|
13133
|
+
return new Promise((resolve) => {
|
|
13134
|
+
const mode = new LineInputMode(this.host, resolve, prompt, options);
|
|
13135
|
+
this.host.pushMode(mode);
|
|
13136
|
+
});
|
|
13137
|
+
}
|
|
13138
|
+
readPassword(prompt) {
|
|
13139
|
+
return new Promise((resolve) => {
|
|
13140
|
+
const mode = new PasswordInputMode(this.host, resolve, prompt);
|
|
13141
|
+
this.host.pushMode(mode);
|
|
13142
|
+
});
|
|
13143
|
+
}
|
|
13144
|
+
readConfirm(prompt, defaultValue = false) {
|
|
13145
|
+
return new Promise((resolve) => {
|
|
13146
|
+
const mode = new ConfirmInputMode(this.host, resolve, prompt, defaultValue);
|
|
13147
|
+
this.host.pushMode(mode);
|
|
13148
|
+
});
|
|
13149
|
+
}
|
|
13150
|
+
readSelect(prompt, options, selectOptions) {
|
|
13151
|
+
if (!options || options.length === 0) {
|
|
13152
|
+
return Promise.reject(new Error("readSelect requires at least one option"));
|
|
13153
|
+
}
|
|
13154
|
+
return new Promise((resolve) => {
|
|
13155
|
+
const mode = new SelectInputMode(this.host, resolve, prompt, options, selectOptions);
|
|
13156
|
+
this.host.pushMode(mode);
|
|
13157
|
+
});
|
|
13158
|
+
}
|
|
13159
|
+
readSelectInline(prompt, options, selectOptions) {
|
|
13160
|
+
if (!options || options.length === 0) {
|
|
13161
|
+
return Promise.reject(new Error("readSelectInline requires at least one option"));
|
|
13162
|
+
}
|
|
13163
|
+
return new Promise((resolve) => {
|
|
13164
|
+
const mode = new InlineSelectInputMode(this.host, resolve, prompt, options, selectOptions);
|
|
13165
|
+
this.host.pushMode(mode);
|
|
13166
|
+
});
|
|
13167
|
+
}
|
|
13168
|
+
readMultiSelect(prompt, options, selectOptions) {
|
|
13169
|
+
if (!options || options.length === 0) {
|
|
13170
|
+
return Promise.reject(new Error("readMultiSelect requires at least one option"));
|
|
13171
|
+
}
|
|
13172
|
+
return new Promise((resolve) => {
|
|
13173
|
+
const mode = new MultiSelectInputMode(this.host, resolve, prompt, options, selectOptions);
|
|
13174
|
+
this.host.pushMode(mode);
|
|
13175
|
+
});
|
|
13176
|
+
}
|
|
13177
|
+
readNumber(prompt, options) {
|
|
13178
|
+
return new Promise((resolve) => {
|
|
13179
|
+
const mode = new NumberInputMode(this.host, resolve, prompt, options);
|
|
13180
|
+
this.host.pushMode(mode);
|
|
13181
|
+
});
|
|
13182
|
+
}
|
|
13183
|
+
readDate(prompt, options) {
|
|
13184
|
+
return new Promise((resolve) => {
|
|
13185
|
+
const mode = new DateInputMode(this.host, resolve, prompt, options);
|
|
13186
|
+
this.host.pushMode(mode);
|
|
13187
|
+
});
|
|
13188
|
+
}
|
|
13189
|
+
readFile(prompt, options) {
|
|
13190
|
+
return new Promise((resolve) => {
|
|
13191
|
+
const mode = new FileInputMode(this.host, resolve, prompt, options);
|
|
13192
|
+
this.host.pushMode(mode);
|
|
13193
|
+
});
|
|
13194
|
+
}
|
|
13195
|
+
};
|
|
13196
|
+
|
|
13197
|
+
// src/lib/services/file-picker/browser-file-picker-provider.ts
|
|
13198
|
+
var BrowserFilePickerProvider = class {
|
|
13199
|
+
constructor() {
|
|
13200
|
+
this.isSupported = typeof document !== "undefined";
|
|
13201
|
+
}
|
|
13202
|
+
async pickFiles(options) {
|
|
13203
|
+
if (!this.isSupported) return null;
|
|
13204
|
+
if (options?.directory) {
|
|
13205
|
+
const dir = await this.pickDirectory();
|
|
13206
|
+
return dir ? [dir] : null;
|
|
13207
|
+
}
|
|
13208
|
+
return new Promise((resolve) => {
|
|
13209
|
+
const input = document.createElement("input");
|
|
13210
|
+
input.type = "file";
|
|
13211
|
+
input.style.display = "none";
|
|
13212
|
+
if (options?.accept) input.accept = options.accept;
|
|
13213
|
+
if (options?.multiple) input.multiple = true;
|
|
13214
|
+
input.addEventListener("change", async () => {
|
|
13215
|
+
const files = input.files;
|
|
13216
|
+
if (!files || files.length === 0) {
|
|
13217
|
+
resolve(null);
|
|
13218
|
+
input.remove();
|
|
13219
|
+
return;
|
|
13220
|
+
}
|
|
13221
|
+
const readAs = options?.readAs ?? "text";
|
|
13222
|
+
const results = [];
|
|
13223
|
+
for (let i = 0; i < files.length; i++) {
|
|
13224
|
+
const file = files[i];
|
|
13225
|
+
const content = await this.readFile(file, readAs);
|
|
13226
|
+
results.push({
|
|
13227
|
+
name: file.name,
|
|
13228
|
+
content,
|
|
13229
|
+
size: file.size,
|
|
13230
|
+
type: file.type || "application/octet-stream"
|
|
13231
|
+
});
|
|
13232
|
+
}
|
|
13233
|
+
resolve(results);
|
|
13234
|
+
input.remove();
|
|
13235
|
+
});
|
|
13236
|
+
input.addEventListener("cancel", () => {
|
|
13237
|
+
resolve(null);
|
|
13238
|
+
input.remove();
|
|
10756
13239
|
});
|
|
13240
|
+
document.body.appendChild(input);
|
|
13241
|
+
input.click();
|
|
10757
13242
|
});
|
|
10758
13243
|
}
|
|
10759
|
-
|
|
10760
|
-
if (!
|
|
10761
|
-
return Promise.reject(
|
|
10762
|
-
new Error("readMultiSelect requires at least one option")
|
|
10763
|
-
);
|
|
10764
|
-
}
|
|
13244
|
+
async pickDirectory() {
|
|
13245
|
+
if (!this.isSupported) return null;
|
|
10765
13246
|
return new Promise((resolve) => {
|
|
10766
|
-
|
|
10767
|
-
|
|
10768
|
-
|
|
10769
|
-
|
|
10770
|
-
|
|
10771
|
-
|
|
10772
|
-
|
|
13247
|
+
const input = document.createElement("input");
|
|
13248
|
+
input.type = "file";
|
|
13249
|
+
input.style.display = "none";
|
|
13250
|
+
input.webkitdirectory = true;
|
|
13251
|
+
input.addEventListener("change", () => {
|
|
13252
|
+
const files = input.files;
|
|
13253
|
+
if (!files || files.length === 0) {
|
|
13254
|
+
resolve(null);
|
|
13255
|
+
} else {
|
|
13256
|
+
const pathParts = files[0].webkitRelativePath?.split("/") ?? [];
|
|
13257
|
+
resolve({
|
|
13258
|
+
name: pathParts[0] || "selected-directory",
|
|
13259
|
+
content: "",
|
|
13260
|
+
size: 0,
|
|
13261
|
+
type: "inode/directory"
|
|
13262
|
+
});
|
|
10773
13263
|
}
|
|
13264
|
+
input.remove();
|
|
10774
13265
|
});
|
|
10775
|
-
|
|
10776
|
-
|
|
10777
|
-
|
|
10778
|
-
const displayLines = this.renderMultiSelectOptions(options, 0, checkedIndices, 0, maxVisible);
|
|
10779
|
-
this.host.setActiveInputRequest({
|
|
10780
|
-
type: "multi-select",
|
|
10781
|
-
promptText: prompt,
|
|
10782
|
-
resolve,
|
|
10783
|
-
buffer: "",
|
|
10784
|
-
cursorPosition: 0,
|
|
10785
|
-
options,
|
|
10786
|
-
selectedIndex: 0,
|
|
10787
|
-
checkedIndices,
|
|
10788
|
-
scrollOffset: 0,
|
|
10789
|
-
maxVisible,
|
|
10790
|
-
displayLines
|
|
13266
|
+
input.addEventListener("cancel", () => {
|
|
13267
|
+
resolve(null);
|
|
13268
|
+
input.remove();
|
|
10791
13269
|
});
|
|
13270
|
+
document.body.appendChild(input);
|
|
13271
|
+
input.click();
|
|
10792
13272
|
});
|
|
10793
13273
|
}
|
|
10794
|
-
|
|
10795
|
-
return new Promise((resolve) => {
|
|
10796
|
-
|
|
10797
|
-
|
|
10798
|
-
|
|
10799
|
-
|
|
10800
|
-
|
|
10801
|
-
if (options?.min !== void 0 || options?.max !== void 0) {
|
|
10802
|
-
const minStr = options?.min !== void 0 ? String(options.min) : "";
|
|
10803
|
-
const maxStr = options?.max !== void 0 ? String(options.max) : "";
|
|
10804
|
-
if (minStr && maxStr) {
|
|
10805
|
-
hints.push(`${minStr}-${maxStr}`);
|
|
10806
|
-
} else if (minStr) {
|
|
10807
|
-
hints.push(`>=${minStr}`);
|
|
10808
|
-
} else if (maxStr) {
|
|
10809
|
-
hints.push(`<=${maxStr}`);
|
|
10810
|
-
}
|
|
10811
|
-
}
|
|
10812
|
-
if (options?.default !== void 0) {
|
|
10813
|
-
hints.push(`default: ${options.default}`);
|
|
10814
|
-
}
|
|
10815
|
-
if (hints.length > 0) {
|
|
10816
|
-
displayPrompt += ` (${hints.join(", ")}): `;
|
|
13274
|
+
readFile(file, readAs) {
|
|
13275
|
+
return new Promise((resolve, reject) => {
|
|
13276
|
+
const reader = new FileReader();
|
|
13277
|
+
reader.onload = () => resolve(reader.result);
|
|
13278
|
+
reader.onerror = () => reject(reader.error);
|
|
13279
|
+
if (readAs === "arraybuffer") {
|
|
13280
|
+
reader.readAsArrayBuffer(file);
|
|
10817
13281
|
} else {
|
|
10818
|
-
|
|
13282
|
+
reader.readAsText(file);
|
|
10819
13283
|
}
|
|
10820
|
-
this.host.writeToTerminal(displayPrompt);
|
|
10821
|
-
this.host.setActiveInputRequest({
|
|
10822
|
-
type: "number",
|
|
10823
|
-
promptText: displayPrompt,
|
|
10824
|
-
resolve,
|
|
10825
|
-
buffer: "",
|
|
10826
|
-
cursorPosition: 0,
|
|
10827
|
-
numberOptions: options
|
|
10828
|
-
});
|
|
10829
13284
|
});
|
|
10830
13285
|
}
|
|
10831
|
-
|
|
10832
|
-
|
|
10833
|
-
|
|
10834
|
-
|
|
10835
|
-
|
|
10836
|
-
|
|
10837
|
-
if (needsScroll) {
|
|
10838
|
-
const upLabel = scrollOffset > 0 ? "\u2191 more" : "";
|
|
10839
|
-
this.host.writeToTerminal(` \x1B[2m${upLabel}\x1B[0m\r
|
|
10840
|
-
`);
|
|
10841
|
-
lines++;
|
|
10842
|
-
}
|
|
10843
|
-
for (let i = scrollOffset; i < end; i++) {
|
|
10844
|
-
const prefix = i === selectedIndex ? " \x1B[36m> " : " ";
|
|
10845
|
-
const suffix = i === selectedIndex ? "\x1B[0m" : "";
|
|
10846
|
-
this.host.writeToTerminal(
|
|
10847
|
-
`${prefix}${options[i].label}${suffix}\r
|
|
10848
|
-
`
|
|
10849
|
-
);
|
|
10850
|
-
lines++;
|
|
10851
|
-
}
|
|
10852
|
-
if (needsScroll) {
|
|
10853
|
-
const downLabel = end < options.length ? "\u2193 more" : "";
|
|
10854
|
-
this.host.writeToTerminal(` \x1B[2m${downLabel}\x1B[0m\r
|
|
10855
|
-
`);
|
|
10856
|
-
lines++;
|
|
10857
|
-
}
|
|
10858
|
-
return lines;
|
|
10859
|
-
}
|
|
10860
|
-
renderInlineSelectOptions(options, selectedIndex) {
|
|
10861
|
-
return options.map((opt, i) => {
|
|
10862
|
-
if (i === selectedIndex) {
|
|
10863
|
-
return `\x1B[36m[ ${opt.label} ]\x1B[0m`;
|
|
10864
|
-
}
|
|
10865
|
-
return ` ${opt.label} `;
|
|
10866
|
-
}).join("");
|
|
10867
|
-
}
|
|
10868
|
-
renderMultiSelectOptions(options, selectedIndex, checkedIndices, scrollOffset = 0, maxVisible = options.length) {
|
|
10869
|
-
const needsScroll = options.length > maxVisible;
|
|
10870
|
-
const itemSlots = needsScroll ? maxVisible - 2 : options.length;
|
|
10871
|
-
const visibleCount = Math.max(1, Math.min(itemSlots, options.length));
|
|
10872
|
-
const end = Math.min(scrollOffset + visibleCount, options.length);
|
|
10873
|
-
let lines = 0;
|
|
10874
|
-
if (needsScroll) {
|
|
10875
|
-
const upLabel = scrollOffset > 0 ? "\u2191 more" : "";
|
|
10876
|
-
this.host.writeToTerminal(` \x1B[2m${upLabel}\x1B[0m\r
|
|
10877
|
-
`);
|
|
10878
|
-
lines++;
|
|
10879
|
-
}
|
|
10880
|
-
for (let i = scrollOffset; i < end; i++) {
|
|
10881
|
-
const checkbox = checkedIndices.has(i) ? "[x]" : "[ ]";
|
|
10882
|
-
const prefix = i === selectedIndex ? ` \x1B[36m> ${checkbox} ` : ` ${checkbox} `;
|
|
10883
|
-
const suffix = i === selectedIndex ? "\x1B[0m" : "";
|
|
10884
|
-
this.host.writeToTerminal(
|
|
10885
|
-
`${prefix}${options[i].label}${suffix}\r
|
|
10886
|
-
`
|
|
10887
|
-
);
|
|
10888
|
-
lines++;
|
|
10889
|
-
}
|
|
10890
|
-
if (needsScroll) {
|
|
10891
|
-
const downLabel = end < options.length ? "\u2193 more" : "";
|
|
10892
|
-
this.host.writeToTerminal(` \x1B[2m${downLabel}\x1B[0m\r
|
|
10893
|
-
`);
|
|
10894
|
-
lines++;
|
|
10895
|
-
}
|
|
10896
|
-
return lines;
|
|
13286
|
+
};
|
|
13287
|
+
|
|
13288
|
+
// src/lib/services/file-picker/noop-file-picker-provider.ts
|
|
13289
|
+
var NoopFilePickerProvider = class {
|
|
13290
|
+
constructor() {
|
|
13291
|
+
this.isSupported = false;
|
|
10897
13292
|
}
|
|
10898
|
-
|
|
10899
|
-
|
|
10900
|
-
|
|
10901
|
-
|
|
10902
|
-
|
|
10903
|
-
|
|
10904
|
-
this.host.setActiveInputRequest({
|
|
10905
|
-
type,
|
|
10906
|
-
promptText: prompt,
|
|
10907
|
-
resolve,
|
|
10908
|
-
buffer: "",
|
|
10909
|
-
cursorPosition: 0
|
|
10910
|
-
});
|
|
10911
|
-
});
|
|
13293
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
13294
|
+
async pickFiles(_options) {
|
|
13295
|
+
return null;
|
|
13296
|
+
}
|
|
13297
|
+
async pickDirectory() {
|
|
13298
|
+
return null;
|
|
10912
13299
|
}
|
|
10913
13300
|
};
|
|
10914
13301
|
|
|
@@ -11433,400 +13820,6 @@ var CommandLineMode = class {
|
|
|
11433
13820
|
}
|
|
11434
13821
|
};
|
|
11435
13822
|
|
|
11436
|
-
// src/lib/input/reader-mode.ts
|
|
11437
|
-
var ReaderMode = class {
|
|
11438
|
-
constructor(host) {
|
|
11439
|
-
this.host = host;
|
|
11440
|
-
}
|
|
11441
|
-
async handleInput(data) {
|
|
11442
|
-
const request = this.host.getActiveInputRequest();
|
|
11443
|
-
if (!request) {
|
|
11444
|
-
return;
|
|
11445
|
-
}
|
|
11446
|
-
switch (request.type) {
|
|
11447
|
-
case "line":
|
|
11448
|
-
this.handleLineInput(request, data);
|
|
11449
|
-
break;
|
|
11450
|
-
case "password":
|
|
11451
|
-
this.handlePasswordInput(request, data);
|
|
11452
|
-
break;
|
|
11453
|
-
case "confirm":
|
|
11454
|
-
this.handleConfirmInput(request, data);
|
|
11455
|
-
break;
|
|
11456
|
-
case "select":
|
|
11457
|
-
this.handleSelectInput(request, data);
|
|
11458
|
-
break;
|
|
11459
|
-
case "select-inline":
|
|
11460
|
-
this.handleSelectInlineInput(request, data);
|
|
11461
|
-
break;
|
|
11462
|
-
case "multi-select":
|
|
11463
|
-
this.handleMultiSelectInput(request, data);
|
|
11464
|
-
break;
|
|
11465
|
-
case "number":
|
|
11466
|
-
this.handleNumberInput(request, data);
|
|
11467
|
-
break;
|
|
11468
|
-
}
|
|
11469
|
-
}
|
|
11470
|
-
handleKeyEvent(event) {
|
|
11471
|
-
if (event.code === "KeyC" && event.ctrlKey) {
|
|
11472
|
-
const request = this.host.getActiveInputRequest();
|
|
11473
|
-
if (request) {
|
|
11474
|
-
request.resolve(null);
|
|
11475
|
-
this.host.setActiveInputRequest(null);
|
|
11476
|
-
this.host.terminal.writeln("");
|
|
11477
|
-
this.host.popMode();
|
|
11478
|
-
}
|
|
11479
|
-
return false;
|
|
11480
|
-
}
|
|
11481
|
-
if (event.code === "Escape") {
|
|
11482
|
-
const request = this.host.getActiveInputRequest();
|
|
11483
|
-
if (request) {
|
|
11484
|
-
request.resolve(null);
|
|
11485
|
-
this.host.setActiveInputRequest(null);
|
|
11486
|
-
this.host.terminal.writeln("");
|
|
11487
|
-
this.host.popMode();
|
|
11488
|
-
}
|
|
11489
|
-
return false;
|
|
11490
|
-
}
|
|
11491
|
-
return true;
|
|
11492
|
-
}
|
|
11493
|
-
handleLineInput(request, data) {
|
|
11494
|
-
if (data === "\r") {
|
|
11495
|
-
this.host.terminal.write("\r\n");
|
|
11496
|
-
const value = request.buffer;
|
|
11497
|
-
this.host.setActiveInputRequest(null);
|
|
11498
|
-
this.host.popMode();
|
|
11499
|
-
request.resolve(value);
|
|
11500
|
-
} else if (data === "\x7F") {
|
|
11501
|
-
if (request.cursorPosition > 0) {
|
|
11502
|
-
request.buffer = request.buffer.slice(0, request.cursorPosition - 1) + request.buffer.slice(request.cursorPosition);
|
|
11503
|
-
request.cursorPosition--;
|
|
11504
|
-
this.redrawReaderLine(request, request.buffer);
|
|
11505
|
-
}
|
|
11506
|
-
} else if (data === "\x1B[D") {
|
|
11507
|
-
if (request.cursorPosition > 0) {
|
|
11508
|
-
request.cursorPosition--;
|
|
11509
|
-
this.host.terminal.write(data);
|
|
11510
|
-
}
|
|
11511
|
-
} else if (data === "\x1B[C") {
|
|
11512
|
-
if (request.cursorPosition < request.buffer.length) {
|
|
11513
|
-
request.cursorPosition++;
|
|
11514
|
-
this.host.terminal.write(data);
|
|
11515
|
-
}
|
|
11516
|
-
} else if (data.startsWith("\x1B")) ; else {
|
|
11517
|
-
const text = data.replace(/[\r\n]+/g, "");
|
|
11518
|
-
request.buffer = request.buffer.slice(0, request.cursorPosition) + text + request.buffer.slice(request.cursorPosition);
|
|
11519
|
-
request.cursorPosition += text.length;
|
|
11520
|
-
this.redrawReaderLine(request, request.buffer);
|
|
11521
|
-
}
|
|
11522
|
-
}
|
|
11523
|
-
handlePasswordInput(request, data) {
|
|
11524
|
-
if (data === "\r") {
|
|
11525
|
-
this.host.terminal.write("\r\n");
|
|
11526
|
-
const value = request.buffer;
|
|
11527
|
-
this.host.setActiveInputRequest(null);
|
|
11528
|
-
this.host.popMode();
|
|
11529
|
-
request.resolve(value);
|
|
11530
|
-
} else if (data === "\x7F") {
|
|
11531
|
-
if (request.cursorPosition > 0) {
|
|
11532
|
-
request.buffer = request.buffer.slice(0, request.cursorPosition - 1) + request.buffer.slice(request.cursorPosition);
|
|
11533
|
-
request.cursorPosition--;
|
|
11534
|
-
this.redrawReaderLine(
|
|
11535
|
-
request,
|
|
11536
|
-
"*".repeat(request.buffer.length)
|
|
11537
|
-
);
|
|
11538
|
-
}
|
|
11539
|
-
} else if (data.startsWith("\x1B")) ; else {
|
|
11540
|
-
const text = data.replace(/[\r\n]+/g, "");
|
|
11541
|
-
request.buffer = request.buffer.slice(0, request.cursorPosition) + text + request.buffer.slice(request.cursorPosition);
|
|
11542
|
-
request.cursorPosition += text.length;
|
|
11543
|
-
this.redrawReaderLine(request, "*".repeat(request.buffer.length));
|
|
11544
|
-
}
|
|
11545
|
-
}
|
|
11546
|
-
handleConfirmInput(request, data) {
|
|
11547
|
-
if (data === "\r") {
|
|
11548
|
-
this.host.terminal.write("\r\n");
|
|
11549
|
-
this.host.setActiveInputRequest(null);
|
|
11550
|
-
this.host.popMode();
|
|
11551
|
-
const buf = request.buffer.toLowerCase();
|
|
11552
|
-
if (buf === "y") {
|
|
11553
|
-
request.resolve(true);
|
|
11554
|
-
} else if (buf === "n") {
|
|
11555
|
-
request.resolve(false);
|
|
11556
|
-
} else {
|
|
11557
|
-
request.resolve(request.defaultValue ?? false);
|
|
11558
|
-
}
|
|
11559
|
-
} else if (data === "\x7F") {
|
|
11560
|
-
if (request.cursorPosition > 0) {
|
|
11561
|
-
request.buffer = request.buffer.slice(0, -1);
|
|
11562
|
-
request.cursorPosition--;
|
|
11563
|
-
this.redrawReaderLine(request, request.buffer);
|
|
11564
|
-
}
|
|
11565
|
-
} else if (data.startsWith("\x1B")) ; else {
|
|
11566
|
-
const char = data.toLowerCase();
|
|
11567
|
-
if (char === "y" || char === "n") {
|
|
11568
|
-
request.buffer = data;
|
|
11569
|
-
request.cursorPosition = 1;
|
|
11570
|
-
this.redrawReaderLine(request, request.buffer);
|
|
11571
|
-
}
|
|
11572
|
-
}
|
|
11573
|
-
}
|
|
11574
|
-
handleSelectInput(request, data) {
|
|
11575
|
-
const options = request.options;
|
|
11576
|
-
const selectedIndex = request.selectedIndex;
|
|
11577
|
-
if (data === "\r") {
|
|
11578
|
-
this.host.terminal.write("\r\n");
|
|
11579
|
-
this.host.setActiveInputRequest(null);
|
|
11580
|
-
this.host.popMode();
|
|
11581
|
-
request.resolve(options[selectedIndex].value);
|
|
11582
|
-
} else if (data === "\x1B[A") {
|
|
11583
|
-
if (selectedIndex > 0) {
|
|
11584
|
-
request.selectedIndex = selectedIndex - 1;
|
|
11585
|
-
this.adjustScrollOffset(request);
|
|
11586
|
-
this.redrawSelectOptions(request);
|
|
11587
|
-
request.onChange?.(options[request.selectedIndex].value);
|
|
11588
|
-
}
|
|
11589
|
-
} else if (data === "\x1B[B") {
|
|
11590
|
-
if (selectedIndex < options.length - 1) {
|
|
11591
|
-
request.selectedIndex = selectedIndex + 1;
|
|
11592
|
-
this.adjustScrollOffset(request);
|
|
11593
|
-
this.redrawSelectOptions(request);
|
|
11594
|
-
request.onChange?.(options[request.selectedIndex].value);
|
|
11595
|
-
}
|
|
11596
|
-
}
|
|
11597
|
-
}
|
|
11598
|
-
adjustScrollOffset(request) {
|
|
11599
|
-
const options = request.options;
|
|
11600
|
-
const maxVisible = request.maxVisible ?? options.length;
|
|
11601
|
-
const needsScroll = options.length > maxVisible;
|
|
11602
|
-
const itemSlots = needsScroll ? maxVisible - 2 : options.length;
|
|
11603
|
-
const visibleCount = Math.max(1, Math.min(itemSlots, options.length));
|
|
11604
|
-
const scrollOffset = request.scrollOffset ?? 0;
|
|
11605
|
-
const selectedIndex = request.selectedIndex;
|
|
11606
|
-
if (selectedIndex < scrollOffset) {
|
|
11607
|
-
request.scrollOffset = selectedIndex;
|
|
11608
|
-
} else if (selectedIndex >= scrollOffset + visibleCount) {
|
|
11609
|
-
request.scrollOffset = selectedIndex - visibleCount + 1;
|
|
11610
|
-
}
|
|
11611
|
-
}
|
|
11612
|
-
redrawReaderLine(request, displayText) {
|
|
11613
|
-
this.host.terminal.write("\x1B[2K\r");
|
|
11614
|
-
this.host.terminal.write(request.promptText + displayText);
|
|
11615
|
-
const cursorOffset = request.buffer.length - request.cursorPosition;
|
|
11616
|
-
if (cursorOffset > 0) {
|
|
11617
|
-
this.host.terminal.write(`\x1B[${cursorOffset}D`);
|
|
11618
|
-
}
|
|
11619
|
-
}
|
|
11620
|
-
redrawSelectOptions(request) {
|
|
11621
|
-
const options = request.options;
|
|
11622
|
-
const selectedIndex = request.selectedIndex;
|
|
11623
|
-
const maxVisible = request.maxVisible ?? options.length;
|
|
11624
|
-
const scrollOffset = request.scrollOffset ?? 0;
|
|
11625
|
-
const displayLines = request.displayLines ?? options.length;
|
|
11626
|
-
const needsScroll = options.length > maxVisible;
|
|
11627
|
-
const itemSlots = needsScroll ? maxVisible - 2 : options.length;
|
|
11628
|
-
const visibleCount = Math.max(1, Math.min(itemSlots, options.length));
|
|
11629
|
-
const end = Math.min(scrollOffset + visibleCount, options.length);
|
|
11630
|
-
if (displayLines > 0) {
|
|
11631
|
-
this.host.terminal.write(`\x1B[${displayLines}A`);
|
|
11632
|
-
}
|
|
11633
|
-
if (needsScroll) {
|
|
11634
|
-
this.host.terminal.write("\x1B[2K\r");
|
|
11635
|
-
const upLabel = scrollOffset > 0 ? "\u2191 more" : "";
|
|
11636
|
-
this.host.terminal.write(` \x1B[2m${upLabel}\x1B[0m\r
|
|
11637
|
-
`);
|
|
11638
|
-
}
|
|
11639
|
-
for (let i = scrollOffset; i < end; i++) {
|
|
11640
|
-
this.host.terminal.write("\x1B[2K\r");
|
|
11641
|
-
const prefix = i === selectedIndex ? " \x1B[36m> " : " ";
|
|
11642
|
-
const suffix = i === selectedIndex ? "\x1B[0m" : "";
|
|
11643
|
-
this.host.terminal.write(
|
|
11644
|
-
`${prefix}${options[i].label}${suffix}\r
|
|
11645
|
-
`
|
|
11646
|
-
);
|
|
11647
|
-
}
|
|
11648
|
-
if (needsScroll) {
|
|
11649
|
-
this.host.terminal.write("\x1B[2K\r");
|
|
11650
|
-
const downLabel = end < options.length ? "\u2193 more" : "";
|
|
11651
|
-
this.host.terminal.write(` \x1B[2m${downLabel}\x1B[0m\r
|
|
11652
|
-
`);
|
|
11653
|
-
}
|
|
11654
|
-
}
|
|
11655
|
-
handleSelectInlineInput(request, data) {
|
|
11656
|
-
const options = request.options;
|
|
11657
|
-
const selectedIndex = request.selectedIndex;
|
|
11658
|
-
if (data === "\r") {
|
|
11659
|
-
this.host.terminal.write("\r\n");
|
|
11660
|
-
this.host.setActiveInputRequest(null);
|
|
11661
|
-
this.host.popMode();
|
|
11662
|
-
request.resolve(options[selectedIndex].value);
|
|
11663
|
-
} else if (data === "\x1B[D") {
|
|
11664
|
-
if (selectedIndex > 0) {
|
|
11665
|
-
request.selectedIndex = selectedIndex - 1;
|
|
11666
|
-
this.redrawInlineSelectOptions(request);
|
|
11667
|
-
request.onChange?.(options[request.selectedIndex].value);
|
|
11668
|
-
}
|
|
11669
|
-
} else if (data === "\x1B[C") {
|
|
11670
|
-
if (selectedIndex < options.length - 1) {
|
|
11671
|
-
request.selectedIndex = selectedIndex + 1;
|
|
11672
|
-
this.redrawInlineSelectOptions(request);
|
|
11673
|
-
request.onChange?.(options[request.selectedIndex].value);
|
|
11674
|
-
}
|
|
11675
|
-
}
|
|
11676
|
-
}
|
|
11677
|
-
redrawInlineSelectOptions(request) {
|
|
11678
|
-
const options = request.options;
|
|
11679
|
-
const selectedIndex = request.selectedIndex;
|
|
11680
|
-
this.host.terminal.write("\x1B[2K\r");
|
|
11681
|
-
const inlineText = options.map((opt, i) => {
|
|
11682
|
-
if (i === selectedIndex) {
|
|
11683
|
-
return `\x1B[36m[ ${opt.label} ]\x1B[0m`;
|
|
11684
|
-
}
|
|
11685
|
-
return ` ${opt.label} `;
|
|
11686
|
-
}).join("");
|
|
11687
|
-
this.host.terminal.write(`${request.promptText} ${inlineText}`);
|
|
11688
|
-
}
|
|
11689
|
-
handleMultiSelectInput(request, data) {
|
|
11690
|
-
const options = request.options;
|
|
11691
|
-
const selectedIndex = request.selectedIndex;
|
|
11692
|
-
const checkedIndices = request.checkedIndices;
|
|
11693
|
-
if (data === "\r") {
|
|
11694
|
-
this.host.terminal.write("\r\n");
|
|
11695
|
-
this.host.setActiveInputRequest(null);
|
|
11696
|
-
this.host.popMode();
|
|
11697
|
-
const selected = options.filter((_, i) => checkedIndices.has(i)).map((opt) => opt.value);
|
|
11698
|
-
request.resolve(selected);
|
|
11699
|
-
} else if (data === " ") {
|
|
11700
|
-
if (checkedIndices.has(selectedIndex)) {
|
|
11701
|
-
checkedIndices.delete(selectedIndex);
|
|
11702
|
-
} else {
|
|
11703
|
-
checkedIndices.add(selectedIndex);
|
|
11704
|
-
}
|
|
11705
|
-
this.redrawMultiSelectOptions(request);
|
|
11706
|
-
} else if (data === "\x1B[A") {
|
|
11707
|
-
if (selectedIndex > 0) {
|
|
11708
|
-
request.selectedIndex = selectedIndex - 1;
|
|
11709
|
-
this.adjustScrollOffset(request);
|
|
11710
|
-
this.redrawMultiSelectOptions(request);
|
|
11711
|
-
}
|
|
11712
|
-
} else if (data === "\x1B[B") {
|
|
11713
|
-
if (selectedIndex < options.length - 1) {
|
|
11714
|
-
request.selectedIndex = selectedIndex + 1;
|
|
11715
|
-
this.adjustScrollOffset(request);
|
|
11716
|
-
this.redrawMultiSelectOptions(request);
|
|
11717
|
-
}
|
|
11718
|
-
}
|
|
11719
|
-
}
|
|
11720
|
-
redrawMultiSelectOptions(request) {
|
|
11721
|
-
const options = request.options;
|
|
11722
|
-
const selectedIndex = request.selectedIndex;
|
|
11723
|
-
const checkedIndices = request.checkedIndices;
|
|
11724
|
-
const maxVisible = request.maxVisible ?? options.length;
|
|
11725
|
-
const scrollOffset = request.scrollOffset ?? 0;
|
|
11726
|
-
const displayLines = request.displayLines ?? options.length;
|
|
11727
|
-
const needsScroll = options.length > maxVisible;
|
|
11728
|
-
const itemSlots = needsScroll ? maxVisible - 2 : options.length;
|
|
11729
|
-
const visibleCount = Math.max(1, Math.min(itemSlots, options.length));
|
|
11730
|
-
const end = Math.min(scrollOffset + visibleCount, options.length);
|
|
11731
|
-
if (displayLines > 0) {
|
|
11732
|
-
this.host.terminal.write(`\x1B[${displayLines}A`);
|
|
11733
|
-
}
|
|
11734
|
-
if (needsScroll) {
|
|
11735
|
-
this.host.terminal.write("\x1B[2K\r");
|
|
11736
|
-
const upLabel = scrollOffset > 0 ? "\u2191 more" : "";
|
|
11737
|
-
this.host.terminal.write(` \x1B[2m${upLabel}\x1B[0m\r
|
|
11738
|
-
`);
|
|
11739
|
-
}
|
|
11740
|
-
for (let i = scrollOffset; i < end; i++) {
|
|
11741
|
-
this.host.terminal.write("\x1B[2K\r");
|
|
11742
|
-
const checkbox = checkedIndices.has(i) ? "[x]" : "[ ]";
|
|
11743
|
-
const prefix = i === selectedIndex ? ` \x1B[36m> ${checkbox} ` : ` ${checkbox} `;
|
|
11744
|
-
const suffix = i === selectedIndex ? "\x1B[0m" : "";
|
|
11745
|
-
this.host.terminal.write(
|
|
11746
|
-
`${prefix}${options[i].label}${suffix}\r
|
|
11747
|
-
`
|
|
11748
|
-
);
|
|
11749
|
-
}
|
|
11750
|
-
if (needsScroll) {
|
|
11751
|
-
this.host.terminal.write("\x1B[2K\r");
|
|
11752
|
-
const downLabel = end < options.length ? "\u2193 more" : "";
|
|
11753
|
-
this.host.terminal.write(` \x1B[2m${downLabel}\x1B[0m\r
|
|
11754
|
-
`);
|
|
11755
|
-
}
|
|
11756
|
-
}
|
|
11757
|
-
handleNumberInput(request, data) {
|
|
11758
|
-
if (data === "\r") {
|
|
11759
|
-
const numOptions = request.numberOptions;
|
|
11760
|
-
if (request.buffer === "") {
|
|
11761
|
-
if (numOptions?.default !== void 0) {
|
|
11762
|
-
this.host.terminal.write("\r\n");
|
|
11763
|
-
this.host.setActiveInputRequest(null);
|
|
11764
|
-
this.host.popMode();
|
|
11765
|
-
request.resolve(numOptions.default);
|
|
11766
|
-
} else {
|
|
11767
|
-
this.host.terminal.write("\r\n");
|
|
11768
|
-
this.host.terminal.write(
|
|
11769
|
-
"\x1B[31mPlease enter a number.\x1B[0m\r\n"
|
|
11770
|
-
);
|
|
11771
|
-
this.host.terminal.write(request.promptText);
|
|
11772
|
-
}
|
|
11773
|
-
return;
|
|
11774
|
-
}
|
|
11775
|
-
const value = Number(request.buffer);
|
|
11776
|
-
if (isNaN(value)) {
|
|
11777
|
-
this.host.terminal.write("\r\n");
|
|
11778
|
-
this.host.terminal.write("\x1B[31mInvalid number.\x1B[0m\r\n");
|
|
11779
|
-
request.buffer = "";
|
|
11780
|
-
request.cursorPosition = 0;
|
|
11781
|
-
this.host.terminal.write(request.promptText);
|
|
11782
|
-
return;
|
|
11783
|
-
}
|
|
11784
|
-
if (numOptions?.min !== void 0 && value < numOptions.min) {
|
|
11785
|
-
this.host.terminal.write("\r\n");
|
|
11786
|
-
this.host.terminal.write(
|
|
11787
|
-
`\x1B[31mValue must be at least ${numOptions.min}.\x1B[0m\r
|
|
11788
|
-
`
|
|
11789
|
-
);
|
|
11790
|
-
request.buffer = "";
|
|
11791
|
-
request.cursorPosition = 0;
|
|
11792
|
-
this.host.terminal.write(request.promptText);
|
|
11793
|
-
return;
|
|
11794
|
-
}
|
|
11795
|
-
if (numOptions?.max !== void 0 && value > numOptions.max) {
|
|
11796
|
-
this.host.terminal.write("\r\n");
|
|
11797
|
-
this.host.terminal.write(
|
|
11798
|
-
`\x1B[31mValue must be at most ${numOptions.max}.\x1B[0m\r
|
|
11799
|
-
`
|
|
11800
|
-
);
|
|
11801
|
-
request.buffer = "";
|
|
11802
|
-
request.cursorPosition = 0;
|
|
11803
|
-
this.host.terminal.write(request.promptText);
|
|
11804
|
-
return;
|
|
11805
|
-
}
|
|
11806
|
-
this.host.terminal.write("\r\n");
|
|
11807
|
-
this.host.setActiveInputRequest(null);
|
|
11808
|
-
this.host.popMode();
|
|
11809
|
-
request.resolve(value);
|
|
11810
|
-
} else if (data === "\x7F") {
|
|
11811
|
-
if (request.cursorPosition > 0) {
|
|
11812
|
-
request.buffer = request.buffer.slice(0, request.cursorPosition - 1) + request.buffer.slice(request.cursorPosition);
|
|
11813
|
-
request.cursorPosition--;
|
|
11814
|
-
this.redrawReaderLine(request, request.buffer);
|
|
11815
|
-
}
|
|
11816
|
-
} else if (data === "-") {
|
|
11817
|
-
if (request.cursorPosition === 0 && !request.buffer.includes("-")) {
|
|
11818
|
-
request.buffer = "-" + request.buffer;
|
|
11819
|
-
request.cursorPosition = 1;
|
|
11820
|
-
this.redrawReaderLine(request, request.buffer);
|
|
11821
|
-
}
|
|
11822
|
-
} else if (/^\d$/.test(data)) {
|
|
11823
|
-
request.buffer = request.buffer.slice(0, request.cursorPosition) + data + request.buffer.slice(request.cursorPosition);
|
|
11824
|
-
request.cursorPosition += 1;
|
|
11825
|
-
this.redrawReaderLine(request, request.buffer);
|
|
11826
|
-
}
|
|
11827
|
-
}
|
|
11828
|
-
};
|
|
11829
|
-
|
|
11830
13823
|
// src/lib/input/raw-mode.ts
|
|
11831
13824
|
var RawMode = class {
|
|
11832
13825
|
constructor(processor, context2, fullScreen = false) {
|
|
@@ -11919,7 +13912,6 @@ var CliExecutionContext = class {
|
|
|
11919
13912
|
this.completionEngine = new CliCompletionEngine();
|
|
11920
13913
|
this.promptLength = 0;
|
|
11921
13914
|
this.lineBuffer = new CliLineBuffer();
|
|
11922
|
-
this._activeInputRequest = null;
|
|
11923
13915
|
this.modeStack = [];
|
|
11924
13916
|
this.managedTimers = /* @__PURE__ */ new Set();
|
|
11925
13917
|
this.resizeDisposable = null;
|
|
@@ -11958,6 +13950,7 @@ var CliExecutionContext = class {
|
|
|
11958
13950
|
this.textAnimator = textAnimator;
|
|
11959
13951
|
this.clipboard = new CliClipboard(this);
|
|
11960
13952
|
this.process = new CliExecutionProcess(this);
|
|
13953
|
+
this.filePickerProvider = typeof document !== "undefined" ? new BrowserFilePickerProvider() : new NoopFilePickerProvider();
|
|
11961
13954
|
this.reader = new CliInputReader(this);
|
|
11962
13955
|
this.logger = deps.logger;
|
|
11963
13956
|
this.logger.setCliLogLevel(cliOptions?.logLevel || CliLogLevel.ERROR);
|
|
@@ -11970,26 +13963,15 @@ var CliExecutionContext = class {
|
|
|
11970
13963
|
);
|
|
11971
13964
|
}
|
|
11972
13965
|
// -- Public API (ICliExecutionContext) --
|
|
11973
|
-
get activeInputRequest() {
|
|
11974
|
-
return this._activeInputRequest;
|
|
11975
|
-
}
|
|
11976
|
-
/**
|
|
11977
|
-
* Sets the active input request. When a non-null request is provided,
|
|
11978
|
-
* pushes ReaderMode onto the mode stack. ReaderMode pops itself on
|
|
11979
|
-
* completion, so passing null here does NOT pop the mode.
|
|
11980
|
-
*/
|
|
11981
|
-
setActiveInputRequest(request) {
|
|
11982
|
-
this._activeInputRequest = request;
|
|
11983
|
-
if (request !== null) {
|
|
11984
|
-
this.pushMode(new ReaderMode(this));
|
|
11985
|
-
}
|
|
11986
|
-
}
|
|
11987
13966
|
writeToTerminal(text) {
|
|
11988
13967
|
this.terminal.write(text);
|
|
11989
13968
|
}
|
|
11990
13969
|
getTerminalRows() {
|
|
11991
13970
|
return this.terminal.rows;
|
|
11992
13971
|
}
|
|
13972
|
+
getTerminalCols() {
|
|
13973
|
+
return this.terminal.cols;
|
|
13974
|
+
}
|
|
11993
13975
|
get currentLine() {
|
|
11994
13976
|
return this.lineBuffer.text;
|
|
11995
13977
|
}
|
|
@@ -12094,7 +14076,12 @@ var CliExecutionContext = class {
|
|
|
12094
14076
|
);
|
|
12095
14077
|
return;
|
|
12096
14078
|
}
|
|
12097
|
-
if (this.isProgressRunning() || this.isRawModeActive()
|
|
14079
|
+
if (this.isProgressRunning() || this.isRawModeActive()) {
|
|
14080
|
+
return;
|
|
14081
|
+
}
|
|
14082
|
+
const mode = this.currentMode;
|
|
14083
|
+
if (mode?.onResize) {
|
|
14084
|
+
mode.onResize(this.terminal.cols, this.terminal.rows);
|
|
12098
14085
|
return;
|
|
12099
14086
|
}
|
|
12100
14087
|
const buf = this.terminal.buffer.active;
|
|
@@ -12208,10 +14195,6 @@ var CliExecutionContext = class {
|
|
|
12208
14195
|
getExecutionContext() {
|
|
12209
14196
|
return this;
|
|
12210
14197
|
}
|
|
12211
|
-
// -- ReaderModeHost interface --
|
|
12212
|
-
getActiveInputRequest() {
|
|
12213
|
-
return this._activeInputRequest;
|
|
12214
|
-
}
|
|
12215
14198
|
// -- Managed timer cleanup --
|
|
12216
14199
|
clearAllManagedTimers() {
|
|
12217
14200
|
for (const timer of this.managedTimers) {
|
|
@@ -12654,6 +14637,7 @@ var CliCommandExecutor = class {
|
|
|
12654
14637
|
const aliasProcessor = this.registry.findProcessor("alias", []);
|
|
12655
14638
|
const aliases = aliasProcessor?.userAliases ?? {};
|
|
12656
14639
|
if (aliases[mainCommand]) {
|
|
14640
|
+
this.completeProcess(processEntry, context2, 0);
|
|
12657
14641
|
return await this.executeSingleCommand(
|
|
12658
14642
|
aliases[mainCommand],
|
|
12659
14643
|
data,
|
|
@@ -12666,6 +14650,7 @@ var CliCommandExecutor = class {
|
|
|
12666
14650
|
context2
|
|
12667
14651
|
);
|
|
12668
14652
|
if (executed) {
|
|
14653
|
+
this.completeProcess(processEntry, context2, 0);
|
|
12669
14654
|
process.end();
|
|
12670
14655
|
return;
|
|
12671
14656
|
}
|
|
@@ -12680,6 +14665,7 @@ var CliCommandExecutor = class {
|
|
|
12680
14665
|
context2.writer.writeInfo(
|
|
12681
14666
|
`\u{1F4E6} Use ${context2.writer.wrapInColor("pkg add <name>", CliForegroundColor.Cyan)} to install additional commands`
|
|
12682
14667
|
);
|
|
14668
|
+
this.failProcess(processEntry, context2);
|
|
12683
14669
|
context2.process.exit(-1, {
|
|
12684
14670
|
silent: true
|
|
12685
14671
|
});
|
|
@@ -12698,6 +14684,7 @@ var CliCommandExecutor = class {
|
|
|
12698
14684
|
continue;
|
|
12699
14685
|
}
|
|
12700
14686
|
if (await handler.handle(args, processor, commandToProcess, context2)) {
|
|
14687
|
+
this.completeProcess(processEntry, context2, 0);
|
|
12701
14688
|
process.end();
|
|
12702
14689
|
return;
|
|
12703
14690
|
}
|
|
@@ -12766,26 +14753,18 @@ var CliCommandExecutor = class {
|
|
|
12766
14753
|
if (!process.outputCalled && capturingWriter.hasOutput()) {
|
|
12767
14754
|
process.data = capturingWriter.getCapturedData();
|
|
12768
14755
|
}
|
|
12769
|
-
|
|
12770
|
-
try {
|
|
12771
|
-
const procRegistry = context2.services.get(CliProcessRegistry_TOKEN);
|
|
12772
|
-
procRegistry.complete(processEntry.pid, context2.process.exitCode ?? 0);
|
|
12773
|
-
} catch {
|
|
12774
|
-
}
|
|
12775
|
-
}
|
|
14756
|
+
this.completeProcess(processEntry, context2, context2.process.exitCode ?? 0);
|
|
12776
14757
|
if (!process.exited) {
|
|
12777
14758
|
process.end();
|
|
12778
14759
|
}
|
|
12779
14760
|
} catch (e) {
|
|
12780
14761
|
context2.spinner?.hide();
|
|
12781
|
-
if (processEntry) {
|
|
12782
|
-
try {
|
|
12783
|
-
const procRegistry = context2.services.get(CliProcessRegistry_TOKEN);
|
|
12784
|
-
procRegistry.fail(processEntry.pid);
|
|
12785
|
-
} catch {
|
|
12786
|
-
}
|
|
12787
|
-
}
|
|
12788
14762
|
if (e instanceof ProcessExitedError) {
|
|
14763
|
+
if (e.code === 0) {
|
|
14764
|
+
this.completeProcess(processEntry, context2, 0);
|
|
14765
|
+
} else {
|
|
14766
|
+
this.failProcess(processEntry, context2);
|
|
14767
|
+
}
|
|
12789
14768
|
cancellable?.cancel();
|
|
12790
14769
|
context2.abort?.();
|
|
12791
14770
|
if (e.code !== 0) {
|
|
@@ -12798,6 +14777,7 @@ var CliCommandExecutor = class {
|
|
|
12798
14777
|
);
|
|
12799
14778
|
}
|
|
12800
14779
|
} else {
|
|
14780
|
+
this.failProcess(processEntry, context2);
|
|
12801
14781
|
context2.writer.writeError(`Error executing command: ${e}`);
|
|
12802
14782
|
context2.process.exit(-1);
|
|
12803
14783
|
}
|
|
@@ -12805,6 +14785,22 @@ var CliCommandExecutor = class {
|
|
|
12805
14785
|
abortSub.unsubscribe();
|
|
12806
14786
|
}
|
|
12807
14787
|
}
|
|
14788
|
+
completeProcess(processEntry, context2, exitCode) {
|
|
14789
|
+
if (!processEntry) return;
|
|
14790
|
+
try {
|
|
14791
|
+
const procRegistry = context2.services.get(CliProcessRegistry_TOKEN);
|
|
14792
|
+
procRegistry.complete(processEntry.pid, exitCode);
|
|
14793
|
+
} catch {
|
|
14794
|
+
}
|
|
14795
|
+
}
|
|
14796
|
+
failProcess(processEntry, context2) {
|
|
14797
|
+
if (!processEntry) return;
|
|
14798
|
+
try {
|
|
14799
|
+
const procRegistry = context2.services.get(CliProcessRegistry_TOKEN);
|
|
14800
|
+
procRegistry.fail(processEntry.pid);
|
|
14801
|
+
} catch {
|
|
14802
|
+
}
|
|
14803
|
+
}
|
|
12808
14804
|
async writeStderrToFile(filePath, context2) {
|
|
12809
14805
|
const stderr = this.lastCapturingWriter?.getCapturedStderr();
|
|
12810
14806
|
if (!stderr) return;
|
|
@@ -13885,6 +15881,167 @@ var CliStateStoreManager = class {
|
|
|
13885
15881
|
}
|
|
13886
15882
|
};
|
|
13887
15883
|
|
|
15884
|
+
// src/lib/editor/syntax/registry.ts
|
|
15885
|
+
var SyntaxHighlighterRegistry = class {
|
|
15886
|
+
constructor() {
|
|
15887
|
+
this.highlighters = /* @__PURE__ */ new Map();
|
|
15888
|
+
this.extensionMap = /* @__PURE__ */ new Map();
|
|
15889
|
+
}
|
|
15890
|
+
register(highlighter) {
|
|
15891
|
+
this.highlighters.set(highlighter.id, highlighter);
|
|
15892
|
+
for (const ext of highlighter.extensions) {
|
|
15893
|
+
this.extensionMap.set(ext, highlighter.id);
|
|
15894
|
+
}
|
|
15895
|
+
}
|
|
15896
|
+
unregister(id) {
|
|
15897
|
+
const h = this.highlighters.get(id);
|
|
15898
|
+
if (h) {
|
|
15899
|
+
for (const ext of h.extensions) {
|
|
15900
|
+
this.extensionMap.delete(ext);
|
|
15901
|
+
}
|
|
15902
|
+
this.highlighters.delete(id);
|
|
15903
|
+
}
|
|
15904
|
+
}
|
|
15905
|
+
getByExtension(ext) {
|
|
15906
|
+
const id = this.extensionMap.get(ext);
|
|
15907
|
+
return id ? this.highlighters.get(id) ?? null : null;
|
|
15908
|
+
}
|
|
15909
|
+
getRegisteredLanguages() {
|
|
15910
|
+
return Array.from(this.highlighters.keys());
|
|
15911
|
+
}
|
|
15912
|
+
};
|
|
15913
|
+
|
|
15914
|
+
// src/lib/editor/syntax/highlighters/json-highlighter.ts
|
|
15915
|
+
var JsonHighlighter = class {
|
|
15916
|
+
constructor() {
|
|
15917
|
+
this.id = "json";
|
|
15918
|
+
this.extensions = [".json", ".jsonc"];
|
|
15919
|
+
this.initialState = 0;
|
|
15920
|
+
}
|
|
15921
|
+
getRules(state) {
|
|
15922
|
+
if (state === 1) {
|
|
15923
|
+
return [
|
|
15924
|
+
{ pattern: /.*?\*\//, tokenType: "comment" },
|
|
15925
|
+
{ pattern: /.*/, tokenType: "comment" }
|
|
15926
|
+
];
|
|
15927
|
+
}
|
|
15928
|
+
return [
|
|
15929
|
+
{ pattern: /\/\/.*$/, tokenType: "comment" },
|
|
15930
|
+
{ pattern: /\/\*.*?\*\//, tokenType: "comment" },
|
|
15931
|
+
{ pattern: /\/\*.*$/, tokenType: "comment" },
|
|
15932
|
+
{ pattern: /"(?:[^"\\]|\\.)*"/, tokenType: "string" },
|
|
15933
|
+
{ pattern: /-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?/, tokenType: "number" },
|
|
15934
|
+
{ pattern: /\b(?:true|false|null)\b/, tokenType: "keyword" },
|
|
15935
|
+
{ pattern: /[{}\[\]:,]/, tokenType: "punctuation" }
|
|
15936
|
+
];
|
|
15937
|
+
}
|
|
15938
|
+
getNextLineState(line, currentState) {
|
|
15939
|
+
if (currentState === 1) {
|
|
15940
|
+
return line.includes("*/") ? 0 : 1;
|
|
15941
|
+
}
|
|
15942
|
+
const stripped = line.replace(/"(?:[^"\\]|\\.)*"/g, "").replace(/\/\/.*$/, "");
|
|
15943
|
+
const openIdx = stripped.lastIndexOf("/*");
|
|
15944
|
+
if (openIdx === -1) return 0;
|
|
15945
|
+
const closeIdx = stripped.indexOf("*/", openIdx);
|
|
15946
|
+
return closeIdx === -1 ? 1 : 0;
|
|
15947
|
+
}
|
|
15948
|
+
};
|
|
15949
|
+
|
|
15950
|
+
// src/lib/editor/syntax/highlighters/html-highlighter.ts
|
|
15951
|
+
var HtmlHighlighter = class {
|
|
15952
|
+
constructor() {
|
|
15953
|
+
this.id = "html";
|
|
15954
|
+
this.extensions = [".html", ".htm", ".xml", ".svg"];
|
|
15955
|
+
this.initialState = 0;
|
|
15956
|
+
}
|
|
15957
|
+
getRules(state) {
|
|
15958
|
+
if (state === 1) {
|
|
15959
|
+
return [
|
|
15960
|
+
{ pattern: /.*?-->/, tokenType: "comment" },
|
|
15961
|
+
{ pattern: /.*/, tokenType: "comment" }
|
|
15962
|
+
];
|
|
15963
|
+
}
|
|
15964
|
+
return [
|
|
15965
|
+
{ pattern: /<!--.*?-->/, tokenType: "comment" },
|
|
15966
|
+
{ pattern: /<!--.*$/, tokenType: "comment" },
|
|
15967
|
+
{ pattern: /(<\/?)([\w-]+)/, tokenType: "tag", captureGroup: 2 },
|
|
15968
|
+
{ pattern: /"[^"]*"/, tokenType: "value" },
|
|
15969
|
+
{ pattern: /'[^']*'/, tokenType: "value" },
|
|
15970
|
+
{ pattern: /([\w-]+)(\s*=)/, tokenType: "attribute", captureGroup: 1 },
|
|
15971
|
+
{ pattern: /\/?>/, tokenType: "punctuation" },
|
|
15972
|
+
{ pattern: /<\/?/, tokenType: "punctuation" },
|
|
15973
|
+
{ pattern: /=/, tokenType: "punctuation" }
|
|
15974
|
+
];
|
|
15975
|
+
}
|
|
15976
|
+
getNextLineState(line, currentState) {
|
|
15977
|
+
if (currentState === 1) {
|
|
15978
|
+
return line.includes("-->") ? 0 : 1;
|
|
15979
|
+
}
|
|
15980
|
+
const openIdx = line.lastIndexOf("<!--");
|
|
15981
|
+
if (openIdx === -1) return 0;
|
|
15982
|
+
const closeIdx = line.indexOf("-->", openIdx);
|
|
15983
|
+
return closeIdx === -1 ? 1 : 0;
|
|
15984
|
+
}
|
|
15985
|
+
};
|
|
15986
|
+
|
|
15987
|
+
// src/lib/editor/syntax/highlighters/markdown-highlighter.ts
|
|
15988
|
+
var MarkdownHighlighter = class {
|
|
15989
|
+
constructor() {
|
|
15990
|
+
this.id = "markdown";
|
|
15991
|
+
this.extensions = [".md", ".mdx", ".markdown"];
|
|
15992
|
+
this.initialState = 0;
|
|
15993
|
+
}
|
|
15994
|
+
getRules(state) {
|
|
15995
|
+
if (state === 1) {
|
|
15996
|
+
return [
|
|
15997
|
+
{ pattern: /^```/, tokenType: "string" },
|
|
15998
|
+
{ pattern: /.*/, tokenType: "code" }
|
|
15999
|
+
];
|
|
16000
|
+
}
|
|
16001
|
+
return [
|
|
16002
|
+
{ pattern: /^#{1,6}\s+.*$/, tokenType: "heading" },
|
|
16003
|
+
{ pattern: /^```/, tokenType: "string" },
|
|
16004
|
+
{ pattern: /`[^`]+`/, tokenType: "code" },
|
|
16005
|
+
{ pattern: /\[[^\]]+\]\([^)]+\)/, tokenType: "link" },
|
|
16006
|
+
{ pattern: /\*\*[^*]+\*\*/, tokenType: "bold" },
|
|
16007
|
+
{ pattern: /__[^_]+__/, tokenType: "bold" },
|
|
16008
|
+
{ pattern: /\*[^*]+\*/, tokenType: "italic" },
|
|
16009
|
+
{ pattern: /_[^_]+_/, tokenType: "italic" },
|
|
16010
|
+
{ pattern: /^[-*>]\s/, tokenType: "punctuation" }
|
|
16011
|
+
];
|
|
16012
|
+
}
|
|
16013
|
+
getNextLineState(line, currentState) {
|
|
16014
|
+
const trimmed = line.trimStart();
|
|
16015
|
+
if (trimmed.startsWith("```")) {
|
|
16016
|
+
return currentState === 0 ? 1 : 0;
|
|
16017
|
+
}
|
|
16018
|
+
return currentState;
|
|
16019
|
+
}
|
|
16020
|
+
};
|
|
16021
|
+
|
|
16022
|
+
// src/lib/editor/syntax/highlighters/yaml-highlighter.ts
|
|
16023
|
+
var YamlHighlighter = class {
|
|
16024
|
+
constructor() {
|
|
16025
|
+
this.id = "yaml";
|
|
16026
|
+
this.extensions = [".yaml", ".yml"];
|
|
16027
|
+
this.initialState = 0;
|
|
16028
|
+
}
|
|
16029
|
+
getRules() {
|
|
16030
|
+
return [
|
|
16031
|
+
{ pattern: /#.*$/, tokenType: "comment" },
|
|
16032
|
+
{ pattern: /"(?:[^"\\]|\\.)*"/, tokenType: "string" },
|
|
16033
|
+
{ pattern: /'[^']*'/, tokenType: "string" },
|
|
16034
|
+
{ pattern: /\b(?:true|false|null|~)\b/, tokenType: "keyword" },
|
|
16035
|
+
{ pattern: /-?\d+(?:\.\d+)?/, tokenType: "number" },
|
|
16036
|
+
{ pattern: /([\w][\w.\-]*)(\s*:)/, tokenType: "tag", captureGroup: 1 },
|
|
16037
|
+
{ pattern: /[-:\[\]{}]/, tokenType: "punctuation" }
|
|
16038
|
+
];
|
|
16039
|
+
}
|
|
16040
|
+
getNextLineState() {
|
|
16041
|
+
return 0;
|
|
16042
|
+
}
|
|
16043
|
+
};
|
|
16044
|
+
|
|
13888
16045
|
// src/lib/services/cli-translation-service.ts
|
|
13889
16046
|
var CliTranslationService = class {
|
|
13890
16047
|
constructor() {
|
|
@@ -14863,6 +17020,12 @@ var CliEngine = class {
|
|
|
14863
17020
|
}
|
|
14864
17021
|
]);
|
|
14865
17022
|
}
|
|
17023
|
+
const syntaxRegistry = new SyntaxHighlighterRegistry();
|
|
17024
|
+
syntaxRegistry.register(new JsonHighlighter());
|
|
17025
|
+
syntaxRegistry.register(new HtmlHighlighter());
|
|
17026
|
+
syntaxRegistry.register(new MarkdownHighlighter());
|
|
17027
|
+
syntaxRegistry.register(new YamlHighlighter());
|
|
17028
|
+
services.set([{ provide: "syntax-highlighter-registry", useValue: syntaxRegistry }]);
|
|
14866
17029
|
if (!pendingTokens.has(ICliEnvironment_TOKEN)) {
|
|
14867
17030
|
services.set([
|
|
14868
17031
|
{
|
|
@@ -14895,6 +17058,7 @@ var CliEngine = class {
|
|
|
14895
17058
|
{ ...this.options ?? {}, terminalOptions }
|
|
14896
17059
|
);
|
|
14897
17060
|
this.executionContext.initializeTerminalListeners();
|
|
17061
|
+
this.executionContext.backgroundServices.setProcessRegistry(processRegistry);
|
|
14898
17062
|
services.set([{
|
|
14899
17063
|
provide: CliBackgroundServiceRegistry_TOKEN,
|
|
14900
17064
|
useValue: this.executionContext.backgroundServices
|
|
@@ -15451,7 +17615,9 @@ function createQueuedReader(queue) {
|
|
|
15451
17615
|
readSelect: async () => next() ?? "",
|
|
15452
17616
|
readSelectInline: async () => next() ?? "",
|
|
15453
17617
|
readMultiSelect: async () => next() ?? [],
|
|
15454
|
-
readNumber: async () => next() ?? 0
|
|
17618
|
+
readNumber: async () => next() ?? 0,
|
|
17619
|
+
readDate: async () => next() ?? null,
|
|
17620
|
+
readFile: async () => next() ?? null
|
|
15455
17621
|
};
|
|
15456
17622
|
}
|
|
15457
17623
|
function createNoopBackgroundServices() {
|
|
@@ -15482,6 +17648,6 @@ function createInMemoryKvStore() {
|
|
|
15482
17648
|
};
|
|
15483
17649
|
}
|
|
15484
17650
|
|
|
15485
|
-
export { API_VERSION, CLi_Name_Art, CapturingTerminalWriter, CliAliasCommandProcessor, CliArgsParser, CliBackgroundServiceRegistry_TOKEN, CliBoot, CliCalCommandProcessor, CliCaptureCommandProcessor, CliClearCommandProcessor, CliClipboard, CliClipboardCommandProcessor, CliColorCommandProcessor, CliCommandExecutionContext, CliCommandExecutor, CliCommandHistory, CliCommandHistory_TOKEN, CliCommandProcessorRegistry, CliConfigureCommandProcessor, CliConvertCommandProcessor, CliDebugCommandProcessor, CliDefaultPingServerService, CliDiffCommandProcessor, CliDragDropService, CliEchoCommandProcessor, CliEngine, CliEnvCommandProcessor, CliEnvironment, CliEvalCommandProcessor, CliExecutionContext, CliExecutionProcess, CliExportCommandProcessor, CliFeedbackCommandProcessor, CliFontSizeCommandProcessor, CliHelpCommandProcessor, CliHistoryCommandProcessor, CliHotKeysCommandProcessor, CliInputReader, CliJsonCommandProcessor, CliKeyValueStore, CliKillCommandProcessor, CliLogger, CliLoremCommandProcessor, CliModuleRegistry_TOKEN, CliNanoCommandProcessor, CliOpenCommandProcessor, CliPackageManagerService, CliPackageManagerService_TOKEN, CliPackagesCommandProcessor, CliPingCommandProcessor, CliProcessRegistry, CliProcessRegistry_TOKEN, CliProcessorsRegistry_TOKEN, CliPsCommandProcessor, CliRandomCommandProcessor, CliScreenCommandProcessor, CliSeqCommandProcessor, CliServerCommandProcessor, CliServerConnection, CliServerManager, CliServerManager_TOKEN, CliServerMultiProxyProcessor, CliServerProxyProcessor, CliServiceContainer, CliServicesCommandProcessor, CliSleepCommandProcessor, CliSnippetCommandProcessor, CliSshCommandProcessor, CliStateStore, CliStateStoreManager, CliStateStoreManager_TOKEN, CliTerminalProgressBar, CliTerminalSpinner, CliTerminalTextAnimator, CliTerminalWriter, CliTestHarness, CliThemeCommandProcessor, CliTimeCommandProcessor, CliTimestampCommandProcessor, CliUnAliasCommandProcessor, CliUnameCommandProcessor, CliUnsetCommandProcessor, CliUptimeCommandProcessor, CliVersionCommandProcessor, CliYesCommandProcessor, CommandParser, ICliEnvironment_TOKEN, ICliPingServerService_TOKEN, JsFallbackAccelerator, LIBRARY_VERSION, OverlayAddon, ProcessExitedError, ScriptLoaderService, builtinProcessors, computeDiff, contextGlobalParameter, createServerModule, executeOnServer, formatDiff, getAccelerator, getCliNameArt, getGreetingBasedOnTime, groupBy, helpGlobalParameter, hotkeysInfo, initWasmAccelerator, isWasmAccelerated, miscProcessors, openLink, reconcileArgs, renderServerResponse, systemProcessors, themeInfos, themes, versionGlobalParameter, welcomeModule };
|
|
17651
|
+
export { API_VERSION, CLi_Name_Art, CapturingTerminalWriter, CliAliasCommandProcessor, CliArgsParser, CliBackgroundServiceRegistry_TOKEN, CliBoot, CliCalCommandProcessor, CliCaptureCommandProcessor, CliClearCommandProcessor, CliClipboard, CliClipboardCommandProcessor, CliColorCommandProcessor, CliCommandExecutionContext, CliCommandExecutor, CliCommandHistory, CliCommandHistory_TOKEN, CliCommandProcessorRegistry, CliConfigureCommandProcessor, CliConvertCommandProcessor, CliDebugCommandProcessor, CliDefaultPingServerService, CliDiffCommandProcessor, CliDragDropService, CliEchoCommandProcessor, CliEngine, CliEnvCommandProcessor, CliEnvironment, CliEvalCommandProcessor, CliExecutionContext, CliExecutionProcess, CliExportCommandProcessor, CliFeedbackCommandProcessor, CliFontSizeCommandProcessor, CliHelpCommandProcessor, CliHistoryCommandProcessor, CliHotKeysCommandProcessor, CliHtopCommandProcessor, CliInputReader, CliJsonCommandProcessor, CliKeyValueStore, CliKillCommandProcessor, CliLogger, CliLoremCommandProcessor, CliModuleRegistry_TOKEN, CliNanoCommandProcessor, CliOpenCommandProcessor, CliPackageManagerService, CliPackageManagerService_TOKEN, CliPackagesCommandProcessor, CliPingCommandProcessor, CliProcessRegistry, CliProcessRegistry_TOKEN, CliProcessorsRegistry_TOKEN, CliPsCommandProcessor, CliRandomCommandProcessor, CliScreenCommandProcessor, CliSeqCommandProcessor, CliServerCommandProcessor, CliServerConnection, CliServerManager, CliServerManager_TOKEN, CliServerMultiProxyProcessor, CliServerProxyProcessor, CliServiceContainer, CliServicesCommandProcessor, CliSleepCommandProcessor, CliSnippetCommandProcessor, CliSshCommandProcessor, CliStateStore, CliStateStoreManager, CliStateStoreManager_TOKEN, CliTerminalProgressBar, CliTerminalSpinner, CliTerminalTextAnimator, CliTerminalWriter, CliTestHarness, CliThemeCommandProcessor, CliTimeCommandProcessor, CliTimestampCommandProcessor, CliUnAliasCommandProcessor, CliUnameCommandProcessor, CliUnsetCommandProcessor, CliUptimeCommandProcessor, CliVersionCommandProcessor, CliYesCommandProcessor, CommandParser, ICliEnvironment_TOKEN, ICliPingServerService_TOKEN, JsFallbackAccelerator, LIBRARY_VERSION, OverlayAddon, ProcessExitedError, ScriptLoaderService, builtinProcessors, computeDiff, contextGlobalParameter, createServerModule, executeOnServer, formatDiff, getAccelerator, getCliNameArt, getGreetingBasedOnTime, groupBy, helpGlobalParameter, hotkeysInfo, initWasmAccelerator, isWasmAccelerated, miscProcessors, openLink, reconcileArgs, renderServerResponse, systemProcessors, themeInfos, themes, versionGlobalParameter, welcomeModule };
|
|
15486
17652
|
//# sourceMappingURL=public-api.mjs.map
|
|
15487
17653
|
//# sourceMappingURL=public-api.mjs.map
|