@nmakarov/cli-toolkit 0.67.0 → 0.68.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli-runner.cjs +255 -45
- package/dist/cli-runner.cjs.map +1 -1
- package/dist/cli-runner.js +249 -39
- package/dist/cli-runner.js.map +1 -1
- package/dist/index.cjs +261 -45
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +249 -39
- package/dist/index.js.map +1 -1
- package/dist/init.cjs +255 -45
- package/dist/init.cjs.map +1 -1
- package/dist/init.js +249 -39
- package/dist/init.js.map +1 -1
- package/dist/screen.cjs +252 -47
- package/dist/screen.cjs.map +1 -1
- package/dist/screen.js +236 -39
- package/dist/screen.js.map +1 -1
- package/package.json +1 -1
package/dist/init.js
CHANGED
|
@@ -477,6 +477,59 @@ var init_list_components = __esm({
|
|
|
477
477
|
}
|
|
478
478
|
});
|
|
479
479
|
|
|
480
|
+
// src/screen/key-bindings.js
|
|
481
|
+
function bindingIdentity(b) {
|
|
482
|
+
return [
|
|
483
|
+
String(b?.key ?? ""),
|
|
484
|
+
b?.meta ? "m" : "",
|
|
485
|
+
b?.ctrl ? "c" : "",
|
|
486
|
+
b?.shift ? "s" : ""
|
|
487
|
+
].join("|");
|
|
488
|
+
}
|
|
489
|
+
function bindingMatchesInput(binding, input, key) {
|
|
490
|
+
if (!binding?.key) return false;
|
|
491
|
+
let keyMatches = false;
|
|
492
|
+
if (key?.[binding.key]) {
|
|
493
|
+
keyMatches = true;
|
|
494
|
+
} else if (input === binding.key) {
|
|
495
|
+
keyMatches = true;
|
|
496
|
+
} else if (key?.name === binding.key) {
|
|
497
|
+
keyMatches = true;
|
|
498
|
+
}
|
|
499
|
+
if (!keyMatches) return false;
|
|
500
|
+
const modOk = (flag, pressed) => {
|
|
501
|
+
if (flag === true) return !!pressed;
|
|
502
|
+
if (flag === false) return !pressed;
|
|
503
|
+
return !pressed;
|
|
504
|
+
};
|
|
505
|
+
if (!modOk(binding.meta, key?.meta)) return false;
|
|
506
|
+
if (!modOk(binding.ctrl, key?.ctrl)) return false;
|
|
507
|
+
if (binding.shift !== void 0 && !!key?.shift !== !!binding.shift) return false;
|
|
508
|
+
return true;
|
|
509
|
+
}
|
|
510
|
+
function formatBindingKey(binding) {
|
|
511
|
+
let label = KEY_LABELS[binding.key] || binding.key;
|
|
512
|
+
if (binding.shift) label = `\u21E7${label}`;
|
|
513
|
+
if (binding.ctrl) label = `^${label}`;
|
|
514
|
+
if (binding.meta) label = `\u2325${label}`;
|
|
515
|
+
return label;
|
|
516
|
+
}
|
|
517
|
+
var KEY_LABELS;
|
|
518
|
+
var init_key_bindings = __esm({
|
|
519
|
+
"src/screen/key-bindings.js"() {
|
|
520
|
+
KEY_LABELS = {
|
|
521
|
+
escape: "esc",
|
|
522
|
+
leftArrow: "\u2190",
|
|
523
|
+
rightArrow: "\u2192",
|
|
524
|
+
upArrow: "\u2191",
|
|
525
|
+
downArrow: "\u2193",
|
|
526
|
+
return: "enter",
|
|
527
|
+
pageUp: "PgUp",
|
|
528
|
+
pageDown: "PgDn"
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
|
|
480
533
|
// src/screen/screens.js
|
|
481
534
|
import { useState as useState2, createElement as h3 } from "react";
|
|
482
535
|
import { render, useInput, Text as Text3 } from "ink";
|
|
@@ -492,7 +545,7 @@ function groupKeyBindings(bindings) {
|
|
|
492
545
|
order: binding.order || 999
|
|
493
546
|
};
|
|
494
547
|
}
|
|
495
|
-
groups[caption].keys.push(binding
|
|
548
|
+
groups[caption].keys.push(formatBindingKey(binding));
|
|
496
549
|
});
|
|
497
550
|
return Object.values(groups);
|
|
498
551
|
}
|
|
@@ -532,12 +585,14 @@ function formatKeyBindings(bindings, mode = "long") {
|
|
|
532
585
|
}
|
|
533
586
|
function formatKeys(keys) {
|
|
534
587
|
const keyMap = {
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
588
|
+
escape: "esc",
|
|
589
|
+
leftArrow: "\u2190",
|
|
590
|
+
rightArrow: "\u2192",
|
|
591
|
+
upArrow: "\u2191",
|
|
592
|
+
downArrow: "\u2193",
|
|
593
|
+
return: "enter",
|
|
594
|
+
pageUp: "PgUp",
|
|
595
|
+
pageDown: "PgDn"
|
|
541
596
|
};
|
|
542
597
|
return keys.map((k) => keyMap[k] || k).join("/");
|
|
543
598
|
}
|
|
@@ -577,7 +632,10 @@ async function showScreen(config2) {
|
|
|
577
632
|
setKeyBinding: (bindingOrBindings) => {
|
|
578
633
|
const bindingsToSet = Array.isArray(bindingOrBindings) ? bindingOrBindings : [bindingOrBindings];
|
|
579
634
|
bindingsToSet.forEach((binding) => {
|
|
580
|
-
const
|
|
635
|
+
const id = bindingIdentity(binding);
|
|
636
|
+
const existingIndex = keyBindings.findIndex(
|
|
637
|
+
(b) => bindingIdentity(b) === id
|
|
638
|
+
);
|
|
581
639
|
if (existingIndex >= 0) {
|
|
582
640
|
const existing = keyBindings[existingIndex];
|
|
583
641
|
if (existing.protected) {
|
|
@@ -601,7 +659,10 @@ async function showScreen(config2) {
|
|
|
601
659
|
});
|
|
602
660
|
},
|
|
603
661
|
updateKeyBinding: (keyName, updates) => {
|
|
604
|
-
const
|
|
662
|
+
const id = updates && (updates.meta != null || updates.ctrl != null || updates.shift != null) ? bindingIdentity({ key: keyName, ...updates }) : null;
|
|
663
|
+
const index = keyBindings.findIndex(
|
|
664
|
+
(b) => id ? bindingIdentity(b) === id : b.key === keyName
|
|
665
|
+
);
|
|
605
666
|
if (index >= 0) {
|
|
606
667
|
keyBindings[index] = {
|
|
607
668
|
...keyBindings[index],
|
|
@@ -609,11 +670,13 @@ async function showScreen(config2) {
|
|
|
609
670
|
};
|
|
610
671
|
}
|
|
611
672
|
},
|
|
612
|
-
removeKeyBinding: (
|
|
613
|
-
const index = keyBindings.findIndex(
|
|
673
|
+
removeKeyBinding: (keyNameOrBinding) => {
|
|
674
|
+
const index = typeof keyNameOrBinding === "object" && keyNameOrBinding ? keyBindings.findIndex(
|
|
675
|
+
(b) => bindingIdentity(b) === bindingIdentity(keyNameOrBinding)
|
|
676
|
+
) : keyBindings.findIndex((b) => b.key === keyNameOrBinding);
|
|
614
677
|
if (index >= 0) {
|
|
615
678
|
if (keyBindings[index].protected) {
|
|
616
|
-
console.warn(`Cannot remove protected key: ${
|
|
679
|
+
console.warn(`Cannot remove protected key: ${keyNameOrBinding}`);
|
|
617
680
|
return;
|
|
618
681
|
}
|
|
619
682
|
keyBindings.splice(index, 1);
|
|
@@ -654,24 +717,11 @@ async function showScreen(config2) {
|
|
|
654
717
|
}
|
|
655
718
|
let matchedBinding = null;
|
|
656
719
|
for (const binding of keyBindings) {
|
|
657
|
-
|
|
658
|
-
if (
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
} else if (key?.name === binding.key) {
|
|
663
|
-
keyMatches = true;
|
|
664
|
-
}
|
|
665
|
-
if (keyMatches) {
|
|
666
|
-
if (binding.enabled === false) {
|
|
667
|
-
continue;
|
|
668
|
-
}
|
|
669
|
-
if (binding.condition && !binding.condition(context)) {
|
|
670
|
-
continue;
|
|
671
|
-
}
|
|
672
|
-
matchedBinding = binding;
|
|
673
|
-
break;
|
|
674
|
-
}
|
|
720
|
+
if (binding.enabled === false) continue;
|
|
721
|
+
if (!bindingMatchesInput(binding, input, key)) continue;
|
|
722
|
+
if (binding.condition && !binding.condition(context)) continue;
|
|
723
|
+
matchedBinding = binding;
|
|
724
|
+
break;
|
|
675
725
|
}
|
|
676
726
|
if (matchedBinding && actions[matchedBinding.action]) {
|
|
677
727
|
actions[matchedBinding.action]({
|
|
@@ -807,6 +857,7 @@ var init_screens = __esm({
|
|
|
807
857
|
"src/screen/screens.js"() {
|
|
808
858
|
init_components();
|
|
809
859
|
init_list_components();
|
|
860
|
+
init_key_bindings();
|
|
810
861
|
showMenuScreen = showListScreen;
|
|
811
862
|
showWordGridScreen = showMultiColumnListScreen;
|
|
812
863
|
}
|
|
@@ -896,6 +947,157 @@ var init_ui_elements = __esm({
|
|
|
896
947
|
}
|
|
897
948
|
});
|
|
898
949
|
|
|
950
|
+
// src/screen/scrollable-text.js
|
|
951
|
+
import { useState as useState3, useEffect as useEffect2, useMemo, useRef as useRef2, createElement as createElement2 } from "react";
|
|
952
|
+
import { Box as Box4, Text as Text5 } from "ink";
|
|
953
|
+
function wrapTextLines(text, cols) {
|
|
954
|
+
const w = Math.max(1, Math.floor(Number(cols) || 1));
|
|
955
|
+
const out = [];
|
|
956
|
+
for (const raw of String(text ?? "").split("\n")) {
|
|
957
|
+
if (raw.length === 0) {
|
|
958
|
+
out.push("");
|
|
959
|
+
continue;
|
|
960
|
+
}
|
|
961
|
+
let rest = raw;
|
|
962
|
+
while (rest.length > w) {
|
|
963
|
+
out.push(rest.slice(0, w));
|
|
964
|
+
rest = rest.slice(w);
|
|
965
|
+
}
|
|
966
|
+
if (rest.length > 0) out.push(rest);
|
|
967
|
+
}
|
|
968
|
+
return out;
|
|
969
|
+
}
|
|
970
|
+
function scrollbarGlyphs(viewportRows, totalLines, scrollTop) {
|
|
971
|
+
const view = Math.max(1, Math.floor(viewportRows));
|
|
972
|
+
const total = Math.max(0, Math.floor(totalLines));
|
|
973
|
+
if (total <= view) return null;
|
|
974
|
+
const maxScroll = total - view;
|
|
975
|
+
const thumbSize = Math.max(1, Math.round(view / total * view));
|
|
976
|
+
const travel = Math.max(0, view - thumbSize);
|
|
977
|
+
const s = Math.min(Math.max(0, Math.floor(scrollTop)), maxScroll);
|
|
978
|
+
const thumbStart = maxScroll === 0 ? 0 : Math.round(s / maxScroll * travel);
|
|
979
|
+
const glyphs = [];
|
|
980
|
+
for (let i = 0; i < view; i++) {
|
|
981
|
+
glyphs.push(i >= thumbStart && i < thumbStart + thumbSize ? "\u2588" : "\u2502");
|
|
982
|
+
}
|
|
983
|
+
return glyphs;
|
|
984
|
+
}
|
|
985
|
+
function padEndVisible(s, w) {
|
|
986
|
+
const t = String(s ?? "");
|
|
987
|
+
if (t.length >= w) return t.slice(0, w);
|
|
988
|
+
return t + " ".repeat(w - t.length);
|
|
989
|
+
}
|
|
990
|
+
function ScrollableText({
|
|
991
|
+
ctx,
|
|
992
|
+
text = "",
|
|
993
|
+
lines: linesProp,
|
|
994
|
+
maxHeight,
|
|
995
|
+
wrap = true,
|
|
996
|
+
showScrollbar = true,
|
|
997
|
+
showStatus = true,
|
|
998
|
+
bindKeys = true,
|
|
999
|
+
header = null
|
|
1000
|
+
}) {
|
|
1001
|
+
const [scrollTop, setScrollTop] = useState3(0);
|
|
1002
|
+
const [, bump] = useState3(0);
|
|
1003
|
+
const termCols = process.stdout.columns || 80;
|
|
1004
|
+
const termRows = process.stdout.rows || 24;
|
|
1005
|
+
const viewportRows = Math.max(
|
|
1006
|
+
4,
|
|
1007
|
+
maxHeight != null ? Math.floor(maxHeight) : Math.max(8, termRows - 8)
|
|
1008
|
+
);
|
|
1009
|
+
const provisionalBar = showScrollbar ? 1 : 0;
|
|
1010
|
+
const wrapCols = Math.max(8, termCols - provisionalBar);
|
|
1011
|
+
const allLines = useMemo(() => {
|
|
1012
|
+
if (Array.isArray(linesProp)) return linesProp.map((l) => String(l ?? ""));
|
|
1013
|
+
return wrap ? wrapTextLines(text, wrapCols) : String(text ?? "").split("\n");
|
|
1014
|
+
}, [linesProp, text, wrap, wrapCols]);
|
|
1015
|
+
const needsBar = showScrollbar && allLines.length > viewportRows;
|
|
1016
|
+
const textWidth = Math.max(8, termCols - (needsBar ? 1 : 0));
|
|
1017
|
+
const maxScroll = Math.max(0, allLines.length - viewportRows);
|
|
1018
|
+
const clamped = Math.min(Math.max(0, scrollTop), maxScroll);
|
|
1019
|
+
const visible = allLines.slice(clamped, clamped + viewportRows);
|
|
1020
|
+
const bar = needsBar ? scrollbarGlyphs(viewportRows, allLines.length, clamped) : null;
|
|
1021
|
+
useEffect2(() => {
|
|
1022
|
+
setScrollTop((s) => Math.min(s, maxScroll));
|
|
1023
|
+
}, [maxScroll]);
|
|
1024
|
+
const maxScrollRef = useRef2(maxScroll);
|
|
1025
|
+
const pageSizeRef = useRef2(viewportRows);
|
|
1026
|
+
maxScrollRef.current = maxScroll;
|
|
1027
|
+
pageSizeRef.current = viewportRows;
|
|
1028
|
+
useEffect2(() => {
|
|
1029
|
+
if (!ctx || !bindKeys) return void 0;
|
|
1030
|
+
ctx.setKeyBinding(SCROLL_KEYS);
|
|
1031
|
+
ctx.setAction("scrollUp", () => {
|
|
1032
|
+
setScrollTop((s) => Math.max(0, s - 1));
|
|
1033
|
+
bump((n) => n + 1);
|
|
1034
|
+
ctx.update?.();
|
|
1035
|
+
});
|
|
1036
|
+
ctx.setAction("scrollDown", () => {
|
|
1037
|
+
setScrollTop((s) => Math.min(maxScrollRef.current, s + 1));
|
|
1038
|
+
bump((n) => n + 1);
|
|
1039
|
+
ctx.update?.();
|
|
1040
|
+
});
|
|
1041
|
+
ctx.setAction("pageUp", () => {
|
|
1042
|
+
setScrollTop((s) => Math.max(0, s - pageSizeRef.current));
|
|
1043
|
+
bump((n) => n + 1);
|
|
1044
|
+
ctx.update?.();
|
|
1045
|
+
});
|
|
1046
|
+
ctx.setAction("pageDown", () => {
|
|
1047
|
+
setScrollTop((s) => Math.min(maxScrollRef.current, s + pageSizeRef.current));
|
|
1048
|
+
bump((n) => n + 1);
|
|
1049
|
+
ctx.update?.();
|
|
1050
|
+
});
|
|
1051
|
+
return void 0;
|
|
1052
|
+
}, [ctx, bindKeys]);
|
|
1053
|
+
const status = allLines.length === 0 ? "empty" : `lines ${clamped + 1}-${Math.min(clamped + visible.length, allLines.length)} of ${allLines.length}` + (needsBar ? " \xB7 \u2325\u2191/\u2193 or PgUp/Dn page" : "");
|
|
1054
|
+
const rowNodes = visible.map((line, i) => {
|
|
1055
|
+
const body = padEndVisible(line, textWidth);
|
|
1056
|
+
const glyph = bar ? bar[i] ?? "\u2502" : "";
|
|
1057
|
+
return h5(
|
|
1058
|
+
Box4,
|
|
1059
|
+
{ key: `L${clamped + i}`, flexDirection: "row" },
|
|
1060
|
+
h5(Text5, {}, body),
|
|
1061
|
+
glyph ? h5(Text5, { color: bar[i] === "\u2588" ? "cyan" : "gray" }, glyph) : null
|
|
1062
|
+
);
|
|
1063
|
+
});
|
|
1064
|
+
if (bar && visible.length < viewportRows) {
|
|
1065
|
+
for (let i = visible.length; i < viewportRows; i++) {
|
|
1066
|
+
rowNodes.push(
|
|
1067
|
+
h5(
|
|
1068
|
+
Box4,
|
|
1069
|
+
{ key: `pad${i}`, flexDirection: "row" },
|
|
1070
|
+
h5(Text5, {}, padEndVisible("", textWidth)),
|
|
1071
|
+
h5(Text5, { color: bar[i] === "\u2588" ? "cyan" : "gray" }, bar[i] ?? "\u2502")
|
|
1072
|
+
)
|
|
1073
|
+
);
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
return h5(
|
|
1077
|
+
Box4,
|
|
1078
|
+
{ flexDirection: "column" },
|
|
1079
|
+
header == null ? null : typeof header === "string" ? h5(Text5, { color: "gray" }, header) : header,
|
|
1080
|
+
showStatus ? h5(Text5, { color: "gray" }, status) : null,
|
|
1081
|
+
...rowNodes
|
|
1082
|
+
);
|
|
1083
|
+
}
|
|
1084
|
+
var h5, SCROLL_KEYS;
|
|
1085
|
+
var init_scrollable_text = __esm({
|
|
1086
|
+
"src/screen/scrollable-text.js"() {
|
|
1087
|
+
h5 = createElement2;
|
|
1088
|
+
SCROLL_KEYS = [
|
|
1089
|
+
{ key: "upArrow", caption: "scroll", action: "scrollUp", order: 0 },
|
|
1090
|
+
{ key: "downArrow", caption: "scroll", action: "scrollDown", order: 0 },
|
|
1091
|
+
{ key: "upArrow", meta: true, caption: "page", action: "pageUp", order: 0 },
|
|
1092
|
+
{ key: "downArrow", meta: true, caption: "page", action: "pageDown", order: 0 },
|
|
1093
|
+
{ key: "upArrow", ctrl: true, caption: "page", action: "pageUp", order: 0 },
|
|
1094
|
+
{ key: "downArrow", ctrl: true, caption: "page", action: "pageDown", order: 0 },
|
|
1095
|
+
{ key: "pageUp", caption: "page", action: "pageUp", order: 0 },
|
|
1096
|
+
{ key: "pageDown", caption: "page", action: "pageDown", order: 0 }
|
|
1097
|
+
];
|
|
1098
|
+
}
|
|
1099
|
+
});
|
|
1100
|
+
|
|
899
1101
|
// src/screen/utils.js
|
|
900
1102
|
function buildBreadcrumb(parts) {
|
|
901
1103
|
if (parts.length === 0) return "";
|
|
@@ -1027,7 +1229,7 @@ var init_footer_builder = __esm({
|
|
|
1027
1229
|
// src/screen/index.js
|
|
1028
1230
|
var screen_exports = {};
|
|
1029
1231
|
__export(screen_exports, {
|
|
1030
|
-
Box: () =>
|
|
1232
|
+
Box: () => Box5,
|
|
1031
1233
|
Divider: () => Divider,
|
|
1032
1234
|
FooterPresets: () => FooterPresets,
|
|
1033
1235
|
GridCell: () => GridCell,
|
|
@@ -1043,15 +1245,20 @@ __export(screen_exports, {
|
|
|
1043
1245
|
ScreenFooter: () => ScreenFooter,
|
|
1044
1246
|
ScreenRow: () => ScreenRow,
|
|
1045
1247
|
ScreenTitle: () => ScreenTitle,
|
|
1046
|
-
|
|
1248
|
+
ScrollableText: () => ScrollableText,
|
|
1249
|
+
Text: () => Text6,
|
|
1047
1250
|
TextBlock: () => TextBlock,
|
|
1251
|
+
bindingIdentity: () => bindingIdentity,
|
|
1252
|
+
bindingMatchesInput: () => bindingMatchesInput,
|
|
1048
1253
|
buildBreadcrumb: () => buildBreadcrumb,
|
|
1049
1254
|
buildDetailBreadcrumb: () => buildDetailBreadcrumb,
|
|
1050
1255
|
buildFooter: () => buildFooter,
|
|
1051
|
-
|
|
1256
|
+
formatBindingKey: () => formatBindingKey,
|
|
1257
|
+
h: () => createElement3,
|
|
1052
1258
|
load: () => load,
|
|
1053
1259
|
memo: () => memo,
|
|
1054
1260
|
organizeFooterMessages: () => organizeFooterMessages,
|
|
1261
|
+
scrollbarGlyphs: () => scrollbarGlyphs,
|
|
1055
1262
|
showListScreen: () => showListScreen,
|
|
1056
1263
|
showMenuScreen: () => showMenuScreen,
|
|
1057
1264
|
showMultiColumnListScreen: () => showMultiColumnListScreen,
|
|
@@ -1059,15 +1266,16 @@ __export(screen_exports, {
|
|
|
1059
1266
|
showScreen: () => showScreen,
|
|
1060
1267
|
showWordGridScreen: () => showWordGridScreen,
|
|
1061
1268
|
useCallback: () => useCallback,
|
|
1062
|
-
useEffect: () =>
|
|
1269
|
+
useEffect: () => useEffect3,
|
|
1063
1270
|
useInput: () => useInput2,
|
|
1064
1271
|
useLayoutEffect: () => useLayoutEffect,
|
|
1065
|
-
useMemo: () =>
|
|
1066
|
-
useRef: () =>
|
|
1067
|
-
useState: () =>
|
|
1272
|
+
useMemo: () => useMemo2,
|
|
1273
|
+
useRef: () => useRef3,
|
|
1274
|
+
useState: () => useState4,
|
|
1275
|
+
wrapTextLines: () => wrapTextLines
|
|
1068
1276
|
});
|
|
1069
|
-
import React2, { useState as
|
|
1070
|
-
import { Box as
|
|
1277
|
+
import React2, { useState as useState4, useEffect as useEffect3, useLayoutEffect, useRef as useRef3, useMemo as useMemo2, useCallback, memo, createElement as createElement3 } from "react";
|
|
1278
|
+
import { Box as Box5, Text as Text6, useInput as useInput2 } from "ink";
|
|
1071
1279
|
async function load() {
|
|
1072
1280
|
if (loadPromise) return loadPromise;
|
|
1073
1281
|
loadPromise = Promise.all([
|
|
@@ -1084,6 +1292,8 @@ var init_screen = __esm({
|
|
|
1084
1292
|
init_list_components();
|
|
1085
1293
|
init_components();
|
|
1086
1294
|
init_ui_elements();
|
|
1295
|
+
init_scrollable_text();
|
|
1296
|
+
init_key_bindings();
|
|
1087
1297
|
init_utils();
|
|
1088
1298
|
init_footer_builder();
|
|
1089
1299
|
loadPromise = null;
|