@nmakarov/cli-toolkit 0.80.0 → 0.82.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 +132 -44
- package/dist/cli-runner.cjs.map +1 -1
- package/dist/cli-runner.js +134 -46
- package/dist/cli-runner.js.map +1 -1
- package/dist/index.cjs +140 -44
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +134 -46
- package/dist/index.js.map +1 -1
- package/dist/init.cjs +132 -44
- package/dist/init.cjs.map +1 -1
- package/dist/init.js +134 -46
- package/dist/init.js.map +1 -1
- package/dist/screen.cjs +128 -44
- package/dist/screen.cjs.map +1 -1
- package/dist/screen.js +120 -44
- package/dist/screen.js.map +1 -1
- package/package.json +1 -1
package/dist/cli-runner.cjs
CHANGED
|
@@ -1018,7 +1018,68 @@ var init_ui_elements = __esm({
|
|
|
1018
1018
|
}
|
|
1019
1019
|
});
|
|
1020
1020
|
|
|
1021
|
-
// src/screen/
|
|
1021
|
+
// src/screen/follow-scroll.js
|
|
1022
|
+
function clampScroll(scrollTop, maxScroll) {
|
|
1023
|
+
const max = Math.max(0, Number(maxScroll) || 0);
|
|
1024
|
+
const top = Number(scrollTop) || 0;
|
|
1025
|
+
return Math.min(Math.max(0, top), max);
|
|
1026
|
+
}
|
|
1027
|
+
function isScrolledToBottom(scrollTop, maxScroll) {
|
|
1028
|
+
return clampScroll(scrollTop, maxScroll) >= Math.max(0, Number(maxScroll) || 0);
|
|
1029
|
+
}
|
|
1030
|
+
function nextScrollAfterUserMove(scrollTop, maxScroll, delta) {
|
|
1031
|
+
const next = clampScroll((Number(scrollTop) || 0) + (Number(delta) || 0), maxScroll);
|
|
1032
|
+
return { scrollTop: next, following: isScrolledToBottom(next, maxScroll) };
|
|
1033
|
+
}
|
|
1034
|
+
function nextScrollAfterContentChange({ following, scrollTop, maxScroll }) {
|
|
1035
|
+
const max = Math.max(0, Number(maxScroll) || 0);
|
|
1036
|
+
if (following) return { scrollTop: max, following: true };
|
|
1037
|
+
const next = clampScroll(scrollTop, max);
|
|
1038
|
+
return { scrollTop: next, following: isScrolledToBottom(next, max) };
|
|
1039
|
+
}
|
|
1040
|
+
var init_follow_scroll = __esm({
|
|
1041
|
+
"src/screen/follow-scroll.js"() {
|
|
1042
|
+
}
|
|
1043
|
+
});
|
|
1044
|
+
|
|
1045
|
+
// src/screen/visible-text.js
|
|
1046
|
+
function stripAnsi(text) {
|
|
1047
|
+
return String(text ?? "").replace(ANSI_RE, "");
|
|
1048
|
+
}
|
|
1049
|
+
function visibleWidth(text) {
|
|
1050
|
+
return [...stripAnsi(text)].length;
|
|
1051
|
+
}
|
|
1052
|
+
function sliceVisible(text, cols) {
|
|
1053
|
+
const w = Math.max(0, Math.floor(Number(cols) || 0));
|
|
1054
|
+
const s = String(text ?? "");
|
|
1055
|
+
if (w === 0) return "";
|
|
1056
|
+
let vis = 0;
|
|
1057
|
+
let out = "";
|
|
1058
|
+
let i = 0;
|
|
1059
|
+
while (i < s.length && vis < w) {
|
|
1060
|
+
ANSI_RE.lastIndex = i;
|
|
1061
|
+
const m = ANSI_RE.exec(s);
|
|
1062
|
+
if (m && m.index === i) {
|
|
1063
|
+
out += m[0];
|
|
1064
|
+
i += m[0].length;
|
|
1065
|
+
continue;
|
|
1066
|
+
}
|
|
1067
|
+
const cp = s.codePointAt(i);
|
|
1068
|
+
const ch = String.fromCodePoint(cp);
|
|
1069
|
+
out += ch;
|
|
1070
|
+
i += ch.length;
|
|
1071
|
+
vis += 1;
|
|
1072
|
+
}
|
|
1073
|
+
return out;
|
|
1074
|
+
}
|
|
1075
|
+
function padEndVisible(text, cols) {
|
|
1076
|
+
const w = Math.max(0, Math.floor(Number(cols) || 0));
|
|
1077
|
+
const s = String(text ?? "");
|
|
1078
|
+
const vis = visibleWidth(s);
|
|
1079
|
+
if (vis === w) return s;
|
|
1080
|
+
if (vis > w) return sliceVisible(s, w);
|
|
1081
|
+
return s + " ".repeat(w - vis);
|
|
1082
|
+
}
|
|
1022
1083
|
function wrapTextLines(text, cols) {
|
|
1023
1084
|
const w = Math.max(1, Math.floor(Number(cols) || 1));
|
|
1024
1085
|
const out = [];
|
|
@@ -1027,20 +1088,28 @@ function wrapTextLines(text, cols) {
|
|
|
1027
1088
|
out.push("");
|
|
1028
1089
|
continue;
|
|
1029
1090
|
}
|
|
1091
|
+
if (visibleWidth(raw) <= w) {
|
|
1092
|
+
out.push(raw);
|
|
1093
|
+
continue;
|
|
1094
|
+
}
|
|
1030
1095
|
let rest = raw;
|
|
1031
|
-
while (rest
|
|
1032
|
-
|
|
1033
|
-
|
|
1096
|
+
while (visibleWidth(rest) > w) {
|
|
1097
|
+
const head = sliceVisible(rest, w);
|
|
1098
|
+
out.push(head);
|
|
1099
|
+
rest = rest.slice(head.length);
|
|
1034
1100
|
}
|
|
1035
1101
|
if (rest.length > 0) out.push(rest);
|
|
1036
1102
|
}
|
|
1037
1103
|
return out;
|
|
1038
1104
|
}
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
}
|
|
1105
|
+
var ANSI_RE;
|
|
1106
|
+
var init_visible_text = __esm({
|
|
1107
|
+
"src/screen/visible-text.js"() {
|
|
1108
|
+
ANSI_RE = /\u001b(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g;
|
|
1109
|
+
}
|
|
1110
|
+
});
|
|
1111
|
+
|
|
1112
|
+
// src/screen/scrollable-text.js
|
|
1044
1113
|
function ScrollableText({
|
|
1045
1114
|
ctx,
|
|
1046
1115
|
text = "",
|
|
@@ -1050,9 +1119,11 @@ function ScrollableText({
|
|
|
1050
1119
|
showScrollbar = true,
|
|
1051
1120
|
showStatus = true,
|
|
1052
1121
|
bindKeys = true,
|
|
1053
|
-
header = null
|
|
1122
|
+
header = null,
|
|
1123
|
+
followBottom = false
|
|
1054
1124
|
}) {
|
|
1055
1125
|
const [scrollTop, setScrollTop] = (0, import_react5.useState)(0);
|
|
1126
|
+
const [following, setFollowing] = (0, import_react5.useState)(() => !!followBottom);
|
|
1056
1127
|
const [, bump] = (0, import_react5.useState)(0);
|
|
1057
1128
|
const termRows = process.stdout.rows || 24;
|
|
1058
1129
|
const viewportRows = Math.max(
|
|
@@ -1071,48 +1142,52 @@ function ScrollableText({
|
|
|
1071
1142
|
const clamped = Math.min(Math.max(0, scrollTop), maxScroll);
|
|
1072
1143
|
const visible = allLines.slice(clamped, clamped + viewportRows);
|
|
1073
1144
|
const bar = needsBar ? scrollbarGlyphs(viewportRows, allLines.length, clamped) : null;
|
|
1074
|
-
(0, import_react5.useEffect)(() => {
|
|
1075
|
-
setScrollTop((s) => Math.min(s, maxScroll));
|
|
1076
|
-
}, [maxScroll]);
|
|
1077
1145
|
const maxScrollRef = (0, import_react5.useRef)(maxScroll);
|
|
1078
1146
|
const pageSizeRef = (0, import_react5.useRef)(viewportRows);
|
|
1147
|
+
const scrollTopRef = (0, import_react5.useRef)(scrollTop);
|
|
1148
|
+
const followingRef = (0, import_react5.useRef)(following);
|
|
1079
1149
|
maxScrollRef.current = maxScroll;
|
|
1080
1150
|
pageSizeRef.current = viewportRows;
|
|
1151
|
+
scrollTopRef.current = scrollTop;
|
|
1152
|
+
followingRef.current = following;
|
|
1153
|
+
(0, import_react5.useEffect)(() => {
|
|
1154
|
+
const next = nextScrollAfterContentChange({
|
|
1155
|
+
following: followBottom && followingRef.current,
|
|
1156
|
+
scrollTop: scrollTopRef.current,
|
|
1157
|
+
maxScroll
|
|
1158
|
+
});
|
|
1159
|
+
if (next.scrollTop !== scrollTopRef.current) setScrollTop(next.scrollTop);
|
|
1160
|
+
if (followBottom && next.following !== followingRef.current) setFollowing(next.following);
|
|
1161
|
+
}, [maxScroll, followBottom]);
|
|
1162
|
+
const applyUserScroll = (delta) => {
|
|
1163
|
+
const next = nextScrollAfterUserMove(scrollTopRef.current, maxScrollRef.current, delta);
|
|
1164
|
+
setScrollTop(next.scrollTop);
|
|
1165
|
+
if (followBottom) setFollowing(next.following);
|
|
1166
|
+
bump((n) => n + 1);
|
|
1167
|
+
ctx?.update?.();
|
|
1168
|
+
};
|
|
1081
1169
|
(0, import_react5.useEffect)(() => {
|
|
1082
1170
|
if (!ctx || !bindKeys) return void 0;
|
|
1083
1171
|
ctx.setKeyBinding(SCROLL_KEYS);
|
|
1084
|
-
ctx.setAction("scrollUp", () =>
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
});
|
|
1089
|
-
ctx.setAction("scrollDown", () => {
|
|
1090
|
-
setScrollTop((s) => Math.min(maxScrollRef.current, s + 1));
|
|
1091
|
-
bump((n) => n + 1);
|
|
1092
|
-
ctx.update?.();
|
|
1093
|
-
});
|
|
1094
|
-
ctx.setAction("pageUp", () => {
|
|
1095
|
-
setScrollTop((s) => Math.max(0, s - pageSizeRef.current));
|
|
1096
|
-
bump((n) => n + 1);
|
|
1097
|
-
ctx.update?.();
|
|
1098
|
-
});
|
|
1099
|
-
ctx.setAction("pageDown", () => {
|
|
1100
|
-
setScrollTop((s) => Math.min(maxScrollRef.current, s + pageSizeRef.current));
|
|
1101
|
-
bump((n) => n + 1);
|
|
1102
|
-
ctx.update?.();
|
|
1103
|
-
});
|
|
1172
|
+
ctx.setAction("scrollUp", () => applyUserScroll(-1));
|
|
1173
|
+
ctx.setAction("scrollDown", () => applyUserScroll(1));
|
|
1174
|
+
ctx.setAction("pageUp", () => applyUserScroll(-pageSizeRef.current));
|
|
1175
|
+
ctx.setAction("pageDown", () => applyUserScroll(pageSizeRef.current));
|
|
1104
1176
|
return void 0;
|
|
1105
|
-
}, [ctx, bindKeys]);
|
|
1106
|
-
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" : "");
|
|
1177
|
+
}, [ctx, bindKeys, followBottom]);
|
|
1178
|
+
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" : "") + (followBottom && following ? " \xB7 follow" : followBottom ? " \xB7 follow off" : "");
|
|
1107
1179
|
const rowNodes = visible.map((line, i) => {
|
|
1108
|
-
const body = padEndVisible(line, textWidth);
|
|
1109
1180
|
const glyph = bar ? bar[i] ?? BAR_TRACK : showScrollbar ? " " : "";
|
|
1110
1181
|
const isThumb = glyph === BAR_THUMB;
|
|
1111
1182
|
return h5(
|
|
1112
|
-
import_ink5.
|
|
1113
|
-
{ key: `L${clamped + i}
|
|
1114
|
-
|
|
1115
|
-
|
|
1183
|
+
import_ink5.Box,
|
|
1184
|
+
{ key: `L${clamped + i}`, flexDirection: "row", width: contentCols, flexShrink: 0 },
|
|
1185
|
+
h5(
|
|
1186
|
+
import_ink5.Box,
|
|
1187
|
+
{ width: textWidth, flexShrink: 0 },
|
|
1188
|
+
h5(import_ink5.Text, { wrap: "truncate" }, padEndVisible(line, textWidth))
|
|
1189
|
+
),
|
|
1190
|
+
showScrollbar ? h5(import_ink5.Box, { width: 1, flexShrink: 0 }, h5(import_ink5.Text, { color: isThumb ? "cyan" : "gray" }, glyph)) : null
|
|
1116
1191
|
);
|
|
1117
1192
|
});
|
|
1118
1193
|
if (bar && visible.length < viewportRows) {
|
|
@@ -1120,10 +1195,10 @@ function ScrollableText({
|
|
|
1120
1195
|
const glyph = bar[i] ?? BAR_TRACK;
|
|
1121
1196
|
rowNodes.push(
|
|
1122
1197
|
h5(
|
|
1123
|
-
import_ink5.
|
|
1124
|
-
{ key: `pad${i}
|
|
1125
|
-
padEndVisible("", textWidth),
|
|
1126
|
-
h5(import_ink5.Text, { color: glyph === BAR_THUMB ? "cyan" : "gray" }, glyph)
|
|
1198
|
+
import_ink5.Box,
|
|
1199
|
+
{ key: `pad${i}`, flexDirection: "row", width: contentCols, flexShrink: 0 },
|
|
1200
|
+
h5(import_ink5.Box, { width: textWidth, flexShrink: 0 }, h5(import_ink5.Text, {}, padEndVisible("", textWidth))),
|
|
1201
|
+
h5(import_ink5.Box, { width: 1, flexShrink: 0 }, h5(import_ink5.Text, { color: glyph === BAR_THUMB ? "cyan" : "gray" }, glyph))
|
|
1127
1202
|
)
|
|
1128
1203
|
);
|
|
1129
1204
|
}
|
|
@@ -1143,7 +1218,10 @@ var init_scrollable_text = __esm({
|
|
|
1143
1218
|
import_ink5 = require("ink");
|
|
1144
1219
|
init_components();
|
|
1145
1220
|
init_scrollbar();
|
|
1221
|
+
init_follow_scroll();
|
|
1222
|
+
init_visible_text();
|
|
1146
1223
|
init_scrollbar();
|
|
1224
|
+
init_visible_text();
|
|
1147
1225
|
h5 = import_react5.createElement;
|
|
1148
1226
|
SCROLL_KEYS = [
|
|
1149
1227
|
{ key: "upArrow", caption: "scroll", action: "scrollUp", order: 0 },
|
|
@@ -1311,11 +1389,16 @@ __export(screen_exports, {
|
|
|
1311
1389
|
buildBreadcrumb: () => buildBreadcrumb,
|
|
1312
1390
|
buildDetailBreadcrumb: () => buildDetailBreadcrumb,
|
|
1313
1391
|
buildFooter: () => buildFooter,
|
|
1392
|
+
clampScroll: () => clampScroll,
|
|
1314
1393
|
formatBindingKey: () => formatBindingKey,
|
|
1315
1394
|
h: () => import_react6.createElement,
|
|
1395
|
+
isScrolledToBottom: () => isScrolledToBottom,
|
|
1316
1396
|
load: () => load,
|
|
1317
1397
|
memo: () => import_react6.memo,
|
|
1398
|
+
nextScrollAfterContentChange: () => nextScrollAfterContentChange,
|
|
1399
|
+
nextScrollAfterUserMove: () => nextScrollAfterUserMove,
|
|
1318
1400
|
organizeFooterMessages: () => organizeFooterMessages,
|
|
1401
|
+
padEndVisible: () => padEndVisible,
|
|
1319
1402
|
scrollbarGlyphs: () => scrollbarGlyphs,
|
|
1320
1403
|
showListScreen: () => showListScreen,
|
|
1321
1404
|
showMenuScreen: () => showMenuScreen,
|
|
@@ -1323,6 +1406,8 @@ __export(screen_exports, {
|
|
|
1323
1406
|
showMultiColumnListWithPreviewScreen: () => showMultiColumnListWithPreviewScreen,
|
|
1324
1407
|
showScreen: () => showScreen,
|
|
1325
1408
|
showWordGridScreen: () => showWordGridScreen,
|
|
1409
|
+
sliceVisible: () => sliceVisible,
|
|
1410
|
+
stripAnsi: () => stripAnsi,
|
|
1326
1411
|
useCallback: () => import_react6.useCallback,
|
|
1327
1412
|
useEffect: () => import_react6.useEffect,
|
|
1328
1413
|
useInput: () => import_ink6.useInput,
|
|
@@ -1330,6 +1415,7 @@ __export(screen_exports, {
|
|
|
1330
1415
|
useMemo: () => import_react6.useMemo,
|
|
1331
1416
|
useRef: () => import_react6.useRef,
|
|
1332
1417
|
useState: () => import_react6.useState,
|
|
1418
|
+
visibleWidth: () => visibleWidth,
|
|
1333
1419
|
wrapTextLines: () => wrapTextLines
|
|
1334
1420
|
});
|
|
1335
1421
|
async function load() {
|
|
@@ -1351,6 +1437,8 @@ var init_screen = __esm({
|
|
|
1351
1437
|
init_components();
|
|
1352
1438
|
init_ui_elements();
|
|
1353
1439
|
init_scrollable_text();
|
|
1440
|
+
init_visible_text();
|
|
1441
|
+
init_follow_scroll();
|
|
1354
1442
|
init_scrollbar();
|
|
1355
1443
|
init_key_bindings();
|
|
1356
1444
|
init_utils();
|