ideacode 1.3.2 → 1.3.4
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/repl.js +143 -88
- package/dist/ui/theme.js +3 -2
- package/package.json +1 -1
package/dist/repl.js
CHANGED
|
@@ -50,15 +50,15 @@ const PARALLEL_SAFE_TOOLS = new Set([
|
|
|
50
50
|
]);
|
|
51
51
|
const LOADING_TICK_MS = 80;
|
|
52
52
|
const MAX_EMPTY_ASSISTANT_RETRIES = 3;
|
|
53
|
-
const TYPING_LAYOUT_FREEZE_MS =
|
|
53
|
+
const TYPING_LAYOUT_FREEZE_MS = 120;
|
|
54
|
+
const INPUT_COMMIT_INTERVAL_MS = 45;
|
|
55
|
+
const SLASH_SUGGESTION_ROWS = Math.max(1, COMMANDS.length);
|
|
54
56
|
const TRUNCATE_NOTE = "\n\n(Output truncated to save context. Use read with offset/limit, grep with a specific pattern, or tail with fewer lines to get more.)";
|
|
55
57
|
function truncateToolResult(content) {
|
|
56
58
|
if (content.length <= MAX_TOOL_RESULT_CHARS)
|
|
57
59
|
return content;
|
|
58
60
|
return content.slice(0, MAX_TOOL_RESULT_CHARS) + TRUNCATE_NOTE;
|
|
59
61
|
}
|
|
60
|
-
const isMac = process.platform === "darwin";
|
|
61
|
-
const pasteShortcut = isMac ? "Cmd+V" : "Ctrl+V";
|
|
62
62
|
function listFilesWithFilter(cwd, filter) {
|
|
63
63
|
try {
|
|
64
64
|
const pattern = path.join(cwd, "**", "*").replace(/\\/g, "/");
|
|
@@ -404,6 +404,11 @@ export function Repl({ apiKey, cwd, onQuit }) {
|
|
|
404
404
|
const [showHelpModal, setShowHelpModal] = useState(false);
|
|
405
405
|
const [slashSuggestionIndex, setSlashSuggestionIndex] = useState(0);
|
|
406
406
|
const [inputCursor, setInputCursor] = useState(0);
|
|
407
|
+
const inputValueRef = useRef(inputValue);
|
|
408
|
+
const inputCursorRef = useRef(inputCursor);
|
|
409
|
+
const inputCommitTimerRef = useRef(null);
|
|
410
|
+
const [layoutInputLineCount, setLayoutInputLineCount] = useState(1);
|
|
411
|
+
const shrinkLineTimerRef = useRef(null);
|
|
407
412
|
const [isInputLayoutFrozen, setIsInputLayoutFrozen] = useState(false);
|
|
408
413
|
const [frozenFooterLines, setFrozenFooterLines] = useState(2);
|
|
409
414
|
const skipNextSubmitRef = useRef(false);
|
|
@@ -412,7 +417,6 @@ export function Repl({ apiKey, cwd, onQuit }) {
|
|
|
412
417
|
const [logScrollOffset, setLogScrollOffset] = useState(0);
|
|
413
418
|
const scrollBoundsRef = useRef({ maxLogScrollOffset: 0, logViewportHeight: 1 });
|
|
414
419
|
const prevEscRef = useRef(false);
|
|
415
|
-
const inputChangeMountedRef = useRef(false);
|
|
416
420
|
const typingFreezeTimerRef = useRef(null);
|
|
417
421
|
useEffect(() => {
|
|
418
422
|
// Enable SGR mouse + basic tracking so trackpad wheel scrolling works.
|
|
@@ -488,21 +492,69 @@ export function Repl({ apiKey, cwd, onQuit }) {
|
|
|
488
492
|
const lines = inputValue.split("\n");
|
|
489
493
|
return lines.reduce((sum, line) => sum + Math.max(1, Math.ceil(line.length / wrapWidth)), 0);
|
|
490
494
|
}, [inputValue, wrapWidth]);
|
|
491
|
-
const [stableInputLineCount, setStableInputLineCount] = useState(inputLineCount);
|
|
492
495
|
useEffect(() => {
|
|
493
|
-
if (inputLineCount
|
|
494
|
-
|
|
496
|
+
if (inputLineCount >= layoutInputLineCount) {
|
|
497
|
+
if (shrinkLineTimerRef.current) {
|
|
498
|
+
clearTimeout(shrinkLineTimerRef.current);
|
|
499
|
+
shrinkLineTimerRef.current = null;
|
|
500
|
+
}
|
|
501
|
+
setLayoutInputLineCount(inputLineCount);
|
|
495
502
|
return;
|
|
496
503
|
}
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
504
|
+
if (shrinkLineTimerRef.current) {
|
|
505
|
+
clearTimeout(shrinkLineTimerRef.current);
|
|
506
|
+
}
|
|
507
|
+
shrinkLineTimerRef.current = setTimeout(() => {
|
|
508
|
+
setLayoutInputLineCount(inputLineCount);
|
|
509
|
+
shrinkLineTimerRef.current = null;
|
|
510
|
+
}, 120);
|
|
511
|
+
return () => {
|
|
512
|
+
if (shrinkLineTimerRef.current) {
|
|
513
|
+
clearTimeout(shrinkLineTimerRef.current);
|
|
514
|
+
}
|
|
515
|
+
};
|
|
516
|
+
}, [inputLineCount, layoutInputLineCount]);
|
|
517
|
+
useEffect(() => {
|
|
518
|
+
inputValueRef.current = inputValue;
|
|
519
|
+
}, [inputValue]);
|
|
520
|
+
useEffect(() => {
|
|
521
|
+
inputCursorRef.current = inputCursor;
|
|
522
|
+
}, [inputCursor]);
|
|
523
|
+
const commitInputState = useCallback((immediate = false) => {
|
|
524
|
+
const flush = () => {
|
|
525
|
+
inputCommitTimerRef.current = null;
|
|
526
|
+
setInputValue(inputValueRef.current);
|
|
527
|
+
setInputCursor(inputCursorRef.current);
|
|
528
|
+
};
|
|
529
|
+
if (immediate) {
|
|
530
|
+
if (inputCommitTimerRef.current) {
|
|
531
|
+
clearTimeout(inputCommitTimerRef.current);
|
|
532
|
+
inputCommitTimerRef.current = null;
|
|
533
|
+
}
|
|
534
|
+
flush();
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
if (inputCommitTimerRef.current)
|
|
538
|
+
return;
|
|
539
|
+
inputCommitTimerRef.current = setTimeout(flush, INPUT_COMMIT_INTERVAL_MS);
|
|
540
|
+
}, []);
|
|
541
|
+
const applyInputMutation = useCallback((nextValue, nextCursor, immediate = false) => {
|
|
542
|
+
inputValueRef.current = nextValue;
|
|
543
|
+
inputCursorRef.current = Math.max(0, Math.min(nextCursor, nextValue.length));
|
|
544
|
+
// Keep cursor/text rendering strictly in sync while typing.
|
|
545
|
+
void immediate;
|
|
546
|
+
commitInputState(true);
|
|
547
|
+
}, [commitInputState]);
|
|
500
548
|
useEffect(() => {
|
|
501
549
|
setInputCursor((c) => Math.min(c, Math.max(0, inputValue.length)));
|
|
502
550
|
}, [inputValue.length]);
|
|
503
551
|
useEffect(() => {
|
|
504
|
-
if (
|
|
505
|
-
|
|
552
|
+
if (inputLineCount > layoutInputLineCount) {
|
|
553
|
+
setIsInputLayoutFrozen(false);
|
|
554
|
+
if (typingFreezeTimerRef.current) {
|
|
555
|
+
clearTimeout(typingFreezeTimerRef.current);
|
|
556
|
+
typingFreezeTimerRef.current = null;
|
|
557
|
+
}
|
|
506
558
|
return;
|
|
507
559
|
}
|
|
508
560
|
setIsInputLayoutFrozen(true);
|
|
@@ -513,13 +565,21 @@ export function Repl({ apiKey, cwd, onQuit }) {
|
|
|
513
565
|
setIsInputLayoutFrozen(false);
|
|
514
566
|
typingFreezeTimerRef.current = null;
|
|
515
567
|
}, TYPING_LAYOUT_FREEZE_MS);
|
|
516
|
-
}, [inputValue, inputCursor]);
|
|
568
|
+
}, [inputValue, inputCursor, inputLineCount, layoutInputLineCount]);
|
|
517
569
|
useEffect(() => {
|
|
518
570
|
return () => {
|
|
519
571
|
if (typingFreezeTimerRef.current) {
|
|
520
572
|
clearTimeout(typingFreezeTimerRef.current);
|
|
521
573
|
typingFreezeTimerRef.current = null;
|
|
522
574
|
}
|
|
575
|
+
if (shrinkLineTimerRef.current) {
|
|
576
|
+
clearTimeout(shrinkLineTimerRef.current);
|
|
577
|
+
shrinkLineTimerRef.current = null;
|
|
578
|
+
}
|
|
579
|
+
if (inputCommitTimerRef.current) {
|
|
580
|
+
clearTimeout(inputCommitTimerRef.current);
|
|
581
|
+
inputCommitTimerRef.current = null;
|
|
582
|
+
}
|
|
523
583
|
};
|
|
524
584
|
}, []);
|
|
525
585
|
useEffect(() => {
|
|
@@ -937,11 +997,11 @@ export function Repl({ apiKey, cwd, onQuit }) {
|
|
|
937
997
|
}
|
|
938
998
|
if (showSlashSuggestions && filteredSlashCommands.length > 0) {
|
|
939
999
|
if (key.upArrow) {
|
|
940
|
-
setSlashSuggestionIndex((i) => Math.
|
|
1000
|
+
setSlashSuggestionIndex((i) => Math.min(filteredSlashCommands.length - 1, i + 1));
|
|
941
1001
|
return;
|
|
942
1002
|
}
|
|
943
1003
|
if (key.downArrow) {
|
|
944
|
-
setSlashSuggestionIndex((i) => Math.
|
|
1004
|
+
setSlashSuggestionIndex((i) => Math.max(0, i - 1));
|
|
945
1005
|
return;
|
|
946
1006
|
}
|
|
947
1007
|
if (key.tab) {
|
|
@@ -1017,11 +1077,13 @@ export function Repl({ apiKey, cwd, onQuit }) {
|
|
|
1017
1077
|
}
|
|
1018
1078
|
}
|
|
1019
1079
|
if (!showModelSelector && !showPalette) {
|
|
1080
|
+
const inputNow = inputValueRef.current;
|
|
1081
|
+
const cursorNow = inputCursorRef.current;
|
|
1020
1082
|
const withModifier = key.ctrl || key.meta || key.shift;
|
|
1021
1083
|
const scrollUp = key.pageUp ||
|
|
1022
|
-
(key.upArrow && (withModifier || !
|
|
1084
|
+
(key.upArrow && (withModifier || !inputNow.trim()));
|
|
1023
1085
|
const scrollDown = key.pageDown ||
|
|
1024
|
-
(key.downArrow && (withModifier || !
|
|
1086
|
+
(key.downArrow && (withModifier || !inputNow.trim()));
|
|
1025
1087
|
if (scrollUp) {
|
|
1026
1088
|
setLogScrollOffset((prev) => Math.min(maxLogScrollOffset, prev + logViewportHeight));
|
|
1027
1089
|
return;
|
|
@@ -1032,26 +1094,24 @@ export function Repl({ apiKey, cwd, onQuit }) {
|
|
|
1032
1094
|
}
|
|
1033
1095
|
if (!key.escape)
|
|
1034
1096
|
prevEscRef.current = false;
|
|
1035
|
-
const len =
|
|
1036
|
-
const cur =
|
|
1097
|
+
const len = inputNow.length;
|
|
1098
|
+
const cur = cursorNow;
|
|
1037
1099
|
if (key.tab) {
|
|
1038
|
-
if (
|
|
1039
|
-
queuedMessageRef.current =
|
|
1040
|
-
|
|
1041
|
-
setInputCursor(0);
|
|
1100
|
+
if (inputNow.trim()) {
|
|
1101
|
+
queuedMessageRef.current = inputNow;
|
|
1102
|
+
applyInputMutation("", 0, true);
|
|
1042
1103
|
appendLog(colors.muted(" Message queued. Send to run after this turn."));
|
|
1043
1104
|
appendLog("");
|
|
1044
1105
|
}
|
|
1045
1106
|
return;
|
|
1046
1107
|
}
|
|
1047
1108
|
if (key.escape) {
|
|
1048
|
-
if (
|
|
1109
|
+
if (inputNow.length === 0) {
|
|
1049
1110
|
if (prevEscRef.current) {
|
|
1050
1111
|
prevEscRef.current = false;
|
|
1051
1112
|
const last = lastUserMessageRef.current;
|
|
1052
1113
|
if (last) {
|
|
1053
|
-
|
|
1054
|
-
setInputCursor(last.length);
|
|
1114
|
+
applyInputMutation(last, last.length, true);
|
|
1055
1115
|
setMessages((prev) => (prev.length > 0 && prev[prev.length - 1]?.role === "user" ? prev.slice(0, -1) : prev));
|
|
1056
1116
|
appendLog(colors.muted(" Editing previous message. Submit to replace."));
|
|
1057
1117
|
appendLog("");
|
|
@@ -1061,113 +1121,107 @@ export function Repl({ apiKey, cwd, onQuit }) {
|
|
|
1061
1121
|
prevEscRef.current = true;
|
|
1062
1122
|
}
|
|
1063
1123
|
else {
|
|
1064
|
-
|
|
1065
|
-
setInputCursor(0);
|
|
1124
|
+
applyInputMutation("", 0, true);
|
|
1066
1125
|
prevEscRef.current = false;
|
|
1067
1126
|
}
|
|
1068
1127
|
return;
|
|
1069
1128
|
}
|
|
1070
1129
|
if (key.return) {
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1130
|
+
commitInputState(true);
|
|
1131
|
+
handleSubmit(inputValueRef.current);
|
|
1132
|
+
applyInputMutation("", 0, true);
|
|
1074
1133
|
return;
|
|
1075
1134
|
}
|
|
1076
1135
|
if (key.ctrl && input === "u") {
|
|
1077
|
-
|
|
1078
|
-
setInputCursor(0);
|
|
1136
|
+
applyInputMutation(inputNow.slice(cur), 0);
|
|
1079
1137
|
return;
|
|
1080
1138
|
}
|
|
1081
1139
|
if (key.ctrl && input === "k") {
|
|
1082
|
-
|
|
1140
|
+
applyInputMutation(inputNow.slice(0, cur), cur);
|
|
1083
1141
|
return;
|
|
1084
1142
|
}
|
|
1085
1143
|
const killWordBefore = (key.ctrl && input === "w") ||
|
|
1086
1144
|
(key.meta && key.backspace) ||
|
|
1087
1145
|
(key.meta && key.delete && cur > 0);
|
|
1088
1146
|
if (killWordBefore) {
|
|
1089
|
-
const start = wordStartBackward(
|
|
1147
|
+
const start = wordStartBackward(inputNow, cur);
|
|
1090
1148
|
if (start < cur) {
|
|
1091
|
-
|
|
1092
|
-
setInputCursor(start);
|
|
1149
|
+
applyInputMutation(inputNow.slice(0, start) + inputNow.slice(cur), start);
|
|
1093
1150
|
}
|
|
1094
1151
|
return;
|
|
1095
1152
|
}
|
|
1096
1153
|
if (key.meta && input === "d") {
|
|
1097
|
-
const end = wordEndForward(
|
|
1154
|
+
const end = wordEndForward(inputNow, cur);
|
|
1098
1155
|
if (end > cur) {
|
|
1099
|
-
|
|
1156
|
+
applyInputMutation(inputNow.slice(0, cur) + inputNow.slice(end), cur);
|
|
1100
1157
|
}
|
|
1101
1158
|
return;
|
|
1102
1159
|
}
|
|
1103
1160
|
if ((key.meta && key.leftArrow) || (key.ctrl && key.leftArrow)) {
|
|
1104
|
-
|
|
1161
|
+
applyInputMutation(inputNow, wordStartBackward(inputNow, cur));
|
|
1105
1162
|
return;
|
|
1106
1163
|
}
|
|
1107
1164
|
if ((key.meta && key.rightArrow) || (key.ctrl && key.rightArrow)) {
|
|
1108
|
-
|
|
1165
|
+
applyInputMutation(inputNow, wordEndForward(inputNow, cur));
|
|
1109
1166
|
return;
|
|
1110
1167
|
}
|
|
1111
1168
|
if (key.meta && (input === "b" || input === "f")) {
|
|
1112
1169
|
if (input === "b")
|
|
1113
|
-
|
|
1170
|
+
applyInputMutation(inputNow, wordStartBackward(inputNow, cur));
|
|
1114
1171
|
else
|
|
1115
|
-
|
|
1172
|
+
applyInputMutation(inputNow, wordEndForward(inputNow, cur));
|
|
1116
1173
|
return;
|
|
1117
1174
|
}
|
|
1118
1175
|
if (key.ctrl && (input === "f" || input === "b")) {
|
|
1119
1176
|
if (input === "f")
|
|
1120
|
-
|
|
1177
|
+
applyInputMutation(inputNow, Math.min(len, cur + 1));
|
|
1121
1178
|
else
|
|
1122
|
-
|
|
1179
|
+
applyInputMutation(inputNow, Math.max(0, cur - 1));
|
|
1123
1180
|
return;
|
|
1124
1181
|
}
|
|
1125
1182
|
if (key.ctrl && input === "j") {
|
|
1126
|
-
|
|
1127
|
-
setInputCursor(cur + 1);
|
|
1183
|
+
applyInputMutation(inputNow.slice(0, cur) + "\n" + inputNow.slice(cur), cur + 1);
|
|
1128
1184
|
return;
|
|
1129
1185
|
}
|
|
1130
1186
|
if (key.ctrl && input === "a") {
|
|
1131
|
-
|
|
1187
|
+
applyInputMutation(inputNow, 0);
|
|
1132
1188
|
return;
|
|
1133
1189
|
}
|
|
1134
1190
|
if (key.ctrl && input === "e") {
|
|
1135
|
-
|
|
1191
|
+
applyInputMutation(inputNow, len);
|
|
1136
1192
|
return;
|
|
1137
1193
|
}
|
|
1138
1194
|
if (key.ctrl && input === "h") {
|
|
1139
1195
|
if (cur > 0) {
|
|
1140
|
-
|
|
1141
|
-
setInputCursor(cur - 1);
|
|
1196
|
+
applyInputMutation(inputNow.slice(0, cur - 1) + inputNow.slice(cur), cur - 1);
|
|
1142
1197
|
}
|
|
1143
1198
|
return;
|
|
1144
1199
|
}
|
|
1145
1200
|
if (key.ctrl && input === "d") {
|
|
1146
1201
|
if (cur < len) {
|
|
1147
|
-
|
|
1202
|
+
applyInputMutation(inputNow.slice(0, cur) + inputNow.slice(cur + 1), cur);
|
|
1148
1203
|
}
|
|
1149
1204
|
return;
|
|
1150
1205
|
}
|
|
1151
1206
|
if (key.backspace || (key.delete && cur > 0)) {
|
|
1152
1207
|
if (cur > 0) {
|
|
1153
|
-
|
|
1154
|
-
setInputCursor(cur - 1);
|
|
1208
|
+
applyInputMutation(inputNow.slice(0, cur - 1) + inputNow.slice(cur), cur - 1);
|
|
1155
1209
|
}
|
|
1156
1210
|
return;
|
|
1157
1211
|
}
|
|
1158
1212
|
if (key.delete && cur < len) {
|
|
1159
|
-
|
|
1213
|
+
applyInputMutation(inputNow.slice(0, cur) + inputNow.slice(cur + 1), cur);
|
|
1160
1214
|
return;
|
|
1161
1215
|
}
|
|
1162
1216
|
if (key.leftArrow) {
|
|
1163
|
-
|
|
1217
|
+
applyInputMutation(inputNow, Math.max(0, cur - 1));
|
|
1164
1218
|
return;
|
|
1165
1219
|
}
|
|
1166
1220
|
if (key.rightArrow) {
|
|
1167
|
-
|
|
1221
|
+
applyInputMutation(inputNow, Math.min(len, cur + 1));
|
|
1168
1222
|
return;
|
|
1169
1223
|
}
|
|
1170
|
-
if (input === "?" && !
|
|
1224
|
+
if (input === "?" && !inputNow.trim()) {
|
|
1171
1225
|
setShowHelpModal(true);
|
|
1172
1226
|
return;
|
|
1173
1227
|
}
|
|
@@ -1175,8 +1229,7 @@ export function Repl({ apiKey, cwd, onQuit }) {
|
|
|
1175
1229
|
return;
|
|
1176
1230
|
}
|
|
1177
1231
|
if (input && !key.ctrl && !key.meta) {
|
|
1178
|
-
|
|
1179
|
-
setInputCursor(cur + input.length);
|
|
1232
|
+
applyInputMutation(inputNow.slice(0, cur) + input + inputNow.slice(cur), cur + input.length);
|
|
1180
1233
|
return;
|
|
1181
1234
|
}
|
|
1182
1235
|
}
|
|
@@ -1187,29 +1240,15 @@ export function Repl({ apiKey, cwd, onQuit }) {
|
|
|
1187
1240
|
handleQuit();
|
|
1188
1241
|
}
|
|
1189
1242
|
});
|
|
1190
|
-
if (showModelSelector) {
|
|
1191
|
-
const modelModalMaxHeight = 18;
|
|
1192
|
-
const modelModalWidth = 108;
|
|
1193
|
-
const modelModalHeight = Math.min(filteredModelList.length + 4, modelModalMaxHeight);
|
|
1194
|
-
const topPad = Math.max(0, Math.floor((termRows - modelModalHeight) / 2));
|
|
1195
|
-
const leftPad = Math.max(0, Math.floor((termColumns - modelModalWidth) / 2));
|
|
1196
|
-
const visibleModelCount = Math.min(filteredModelList.length, modelModalHeight - 4);
|
|
1197
|
-
const modelScrollOffset = Math.max(0, Math.min(modelIndex - Math.floor(visibleModelCount / 2), filteredModelList.length - visibleModelCount));
|
|
1198
|
-
const visibleModels = filteredModelList.slice(modelScrollOffset, modelScrollOffset + visibleModelCount);
|
|
1199
|
-
return (_jsxs(Box, { flexDirection: "column", height: termRows, overflow: "hidden", children: [_jsx(Box, { height: topPad }), _jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { width: leftPad }), _jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: inkColors.primary, paddingX: 2, paddingY: 1, width: modelModalWidth, minHeight: modelModalHeight, children: [_jsx(Text, { bold: true, children: " Select model " }), _jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: inkColors.textSecondary, children: " Filter: " }), _jsx(Text, { children: modelSearchFilter || " " }), modelSearchFilter.length > 0 && (_jsxs(Text, { color: inkColors.textDisabled, children: [" ", "(", filteredModelList.length, " match", filteredModelList.length !== 1 ? "es" : "", ")"] }))] }), visibleModels.length === 0 ? (_jsx(Text, { color: inkColors.textSecondary, children: " No match \u2014 type to search by id or name " })) : (visibleModels.map((m, i) => {
|
|
1200
|
-
const actualIndex = modelScrollOffset + i;
|
|
1201
|
-
return (_jsxs(Text, { color: actualIndex === modelIndex ? inkColors.primary : undefined, children: [actualIndex === modelIndex ? "› " : " ", m.name ? `${m.id} — ${m.name}` : m.id] }, m.id));
|
|
1202
|
-
})), _jsx(Text, { color: inkColors.textSecondary, children: " \u2191/\u2193 select Enter confirm Esc cancel Type to filter " })] })] }), _jsx(Box, { flexGrow: 1 })] }));
|
|
1203
|
-
}
|
|
1204
1243
|
const slashSuggestionBoxLines = showSlashSuggestions
|
|
1205
|
-
? 3 +
|
|
1244
|
+
? 3 + SLASH_SUGGESTION_ROWS
|
|
1206
1245
|
: 0;
|
|
1207
1246
|
const atSuggestionBoxLines = cursorInAtSegment
|
|
1208
1247
|
? 4 + Math.max(1, filteredFilePaths.length)
|
|
1209
1248
|
: 0;
|
|
1210
1249
|
const suggestionBoxLines = slashSuggestionBoxLines || atSuggestionBoxLines;
|
|
1211
1250
|
// Keep a fixed loading row reserved to avoid viewport jumps/flicker when loading starts/stops.
|
|
1212
|
-
const reservedLines = 1 +
|
|
1251
|
+
const reservedLines = 1 + layoutInputLineCount + 2;
|
|
1213
1252
|
const logViewportHeight = Math.max(1, termRows - reservedLines - suggestionBoxLines);
|
|
1214
1253
|
const effectiveLogLines = logLines;
|
|
1215
1254
|
const maxLogScrollOffset = Math.max(0, effectiveLogLines.length - logViewportHeight);
|
|
@@ -1221,6 +1260,28 @@ export function Repl({ apiKey, cwd, onQuit }) {
|
|
|
1221
1260
|
const sliceEnd = logStartIndex + logViewportHeight;
|
|
1222
1261
|
const visibleLogLines = useMemo(() => effectiveLogLines.slice(logStartIndex, sliceEnd), [effectiveLogLines, logStartIndex, sliceEnd]);
|
|
1223
1262
|
const useSimpleInputRenderer = inputLineCount > 1;
|
|
1263
|
+
const calculatedFooterLines = suggestionBoxLines + 1 + layoutInputLineCount;
|
|
1264
|
+
useEffect(() => {
|
|
1265
|
+
if (!isInputLayoutFrozen) {
|
|
1266
|
+
setFrozenFooterLines(calculatedFooterLines);
|
|
1267
|
+
}
|
|
1268
|
+
}, [isInputLayoutFrozen, calculatedFooterLines]);
|
|
1269
|
+
const footerLines = isInputLayoutFrozen ? frozenFooterLines : calculatedFooterLines;
|
|
1270
|
+
loadingFooterLinesRef.current = footerLines;
|
|
1271
|
+
if (showModelSelector) {
|
|
1272
|
+
const modelModalMaxHeight = 18;
|
|
1273
|
+
const modelModalWidth = 108;
|
|
1274
|
+
const modelModalHeight = Math.min(filteredModelList.length + 4, modelModalMaxHeight);
|
|
1275
|
+
const topPad = Math.max(0, Math.floor((termRows - modelModalHeight) / 2));
|
|
1276
|
+
const leftPad = Math.max(0, Math.floor((termColumns - modelModalWidth) / 2));
|
|
1277
|
+
const visibleModelCount = Math.min(filteredModelList.length, modelModalHeight - 4);
|
|
1278
|
+
const modelScrollOffset = Math.max(0, Math.min(modelIndex - Math.floor(visibleModelCount / 2), filteredModelList.length - visibleModelCount));
|
|
1279
|
+
const visibleModels = filteredModelList.slice(modelScrollOffset, modelScrollOffset + visibleModelCount);
|
|
1280
|
+
return (_jsxs(Box, { flexDirection: "column", height: termRows, overflow: "hidden", children: [_jsx(Box, { height: topPad }), _jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { width: leftPad }), _jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: inkColors.primary, paddingX: 2, paddingY: 1, width: modelModalWidth, minHeight: modelModalHeight, children: [_jsx(Text, { bold: true, children: " Select model " }), _jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { color: inkColors.textSecondary, children: " Filter: " }), _jsx(Text, { children: modelSearchFilter || " " }), modelSearchFilter.length > 0 && (_jsxs(Text, { color: inkColors.textDisabled, children: [" ", "(", filteredModelList.length, " match", filteredModelList.length !== 1 ? "es" : "", ")"] }))] }), visibleModels.length === 0 ? (_jsx(Text, { color: inkColors.textSecondary, children: " No match \u2014 type to search by id or name " })) : (visibleModels.map((m, i) => {
|
|
1281
|
+
const actualIndex = modelScrollOffset + i;
|
|
1282
|
+
return (_jsxs(Text, { color: actualIndex === modelIndex ? inkColors.primary : undefined, children: [actualIndex === modelIndex ? "› " : " ", m.name ? `${m.id} — ${m.name}` : m.id] }, m.id));
|
|
1283
|
+
})), _jsx(Text, { color: inkColors.textSecondary, children: " \u2191/\u2193 select Enter confirm Esc cancel Type to filter " })] })] }), _jsx(Box, { flexGrow: 1 })] }));
|
|
1284
|
+
}
|
|
1224
1285
|
if (showHelpModal) {
|
|
1225
1286
|
const helpModalWidth = Math.min(88, Math.max(80, termColumns - 4));
|
|
1226
1287
|
const helpContentRows = 20;
|
|
@@ -1243,21 +1304,15 @@ export function Repl({ apiKey, cwd, onQuit }) {
|
|
|
1243
1304
|
const leftPad = Math.max(0, Math.floor((termColumns - paletteModalWidth) / 2));
|
|
1244
1305
|
return (_jsxs(Box, { flexDirection: "column", height: termRows, overflow: "hidden", children: [_jsx(Box, { height: topPad }), _jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { width: leftPad }), _jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: inkColors.primary, paddingX: 2, paddingY: 1, width: paletteModalWidth, minHeight: paletteModalHeight, children: [_jsx(Text, { bold: true, children: " Command palette " }), COMMANDS.map((c, i) => (_jsxs(Text, { color: i === paletteIndex ? inkColors.primary : undefined, children: [i === paletteIndex ? "› " : " ", c.cmd, _jsxs(Text, { color: inkColors.textSecondary, children: [" \u2014 ", c.desc] })] }, c.cmd))), _jsxs(Text, { color: paletteIndex === COMMANDS.length ? inkColors.primary : undefined, children: [paletteIndex === COMMANDS.length ? "› " : " ", "Cancel (Esc)"] }), _jsx(Text, { color: inkColors.textSecondary, children: " \u2191/\u2193 select, Enter confirm, Esc close " })] })] }), _jsx(Box, { flexGrow: 1 })] }));
|
|
1245
1306
|
}
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
if (!isInputLayoutFrozen) {
|
|
1249
|
-
setFrozenFooterLines(calculatedFooterLines);
|
|
1250
|
-
}
|
|
1251
|
-
}, [isInputLayoutFrozen, calculatedFooterLines]);
|
|
1252
|
-
const footerLines = isInputLayoutFrozen ? frozenFooterLines : calculatedFooterLines;
|
|
1253
|
-
loadingFooterLinesRef.current = footerLines;
|
|
1254
|
-
return (_jsxs(Box, { flexDirection: "column", height: termRows, overflow: "hidden", children: [_jsxs(Box, { flexDirection: "column", flexGrow: 1, minHeight: 0, overflow: "hidden", children: [_jsx(LogViewport, { lines: visibleLogLines, startIndex: logStartIndex, height: logViewportHeight }), _jsx(Box, { flexDirection: "row", marginTop: 1, marginBottom: 0, children: _jsx(Text, { color: inkColors.textSecondary, children: "\u00A0" }) })] }), _jsxs(Box, { flexDirection: "column", flexShrink: 0, height: footerLines, children: [showSlashSuggestions && (_jsxs(Box, { flexDirection: "column", marginBottom: 0, paddingLeft: 2, borderStyle: "single", borderColor: inkColors.textDisabled, children: [filteredSlashCommands.length === 0 ? (_jsx(Text, { color: inkColors.textSecondary, children: " No match " })) : ([...filteredSlashCommands].reverse().map((c, rev) => {
|
|
1255
|
-
const i = filteredSlashCommands.length - 1 - rev;
|
|
1307
|
+
return (_jsxs(Box, { flexDirection: "column", height: termRows, overflow: "hidden", children: [_jsxs(Box, { flexDirection: "column", flexGrow: 1, minHeight: 0, overflow: "hidden", children: [_jsx(LogViewport, { lines: visibleLogLines, startIndex: logStartIndex, height: logViewportHeight }), _jsx(Box, { flexDirection: "row", marginTop: 1, marginBottom: 0, children: _jsx(Text, { color: inkColors.textSecondary, children: "\u00A0" }) })] }), _jsxs(Box, { flexDirection: "column", flexShrink: 0, height: footerLines, children: [showSlashSuggestions && (_jsxs(Box, { flexDirection: "column", marginBottom: 0, paddingLeft: 2, borderStyle: "single", borderColor: inkColors.textDisabled, height: slashSuggestionBoxLines, children: [filteredSlashCommands.length === 0 ? (_jsx(Text, { color: inkColors.textSecondary, children: " No match " })) : ([...filteredSlashCommands.slice(0, SLASH_SUGGESTION_ROWS)].reverse().map((c, rev) => {
|
|
1308
|
+
const i = Math.min(filteredSlashCommands.length, SLASH_SUGGESTION_ROWS) - 1 - rev;
|
|
1256
1309
|
return (_jsxs(Text, { color: i === clampedSlashIndex ? inkColors.primary : undefined, children: [i === clampedSlashIndex ? "› " : " ", c.cmd, _jsxs(Text, { color: inkColors.textSecondary, children: [" \u2014 ", c.desc] })] }, c.cmd));
|
|
1257
|
-
})),
|
|
1310
|
+
})), Array.from({
|
|
1311
|
+
length: Math.max(0, SLASH_SUGGESTION_ROWS - Math.min(filteredSlashCommands.length, SLASH_SUGGESTION_ROWS)),
|
|
1312
|
+
}).map((_, idx) => (_jsx(Text, { children: "\u00A0" }, `slash-pad-${idx}`))), _jsx(Text, { color: inkColors.textSecondary, children: " Commands (\u2191/\u2193 select, Enter run, Esc clear) " })] })), cursorInAtSegment && !showSlashSuggestions && (_jsxs(Box, { flexDirection: "column", marginBottom: 0, paddingLeft: 2, borderStyle: "single", borderColor: inkColors.textDisabled, children: [filteredFilePaths.length === 0 ? (_jsxs(Text, { color: inkColors.textSecondary, children: [" ", hasCharsAfterAt ? "No match" : "Type to search files", " "] })) : ([...filteredFilePaths].reverse().map((p, rev) => {
|
|
1258
1313
|
const i = filteredFilePaths.length - 1 - rev;
|
|
1259
1314
|
return (_jsxs(Text, { color: i === clampedAtFileIndex ? inkColors.primary : undefined, children: [i === clampedAtFileIndex ? "› " : " ", p] }, p));
|
|
1260
|
-
})), _jsx(Box, { flexDirection: "row", marginTop: 1, children: _jsx(Text, { color: inkColors.textSecondary, children: " Files (\u2191/\u2193 select, Enter/Tab complete, Esc clear) " }) })] })), _jsxs(Box, { flexDirection: "row", marginTop: 0, children: [_jsxs(Text, { color:
|
|
1315
|
+
})), _jsx(Box, { flexDirection: "row", marginTop: 1, children: _jsx(Text, { color: inkColors.textSecondary, children: " Files (\u2191/\u2193 select, Enter/Tab complete, Esc clear) " }) })] })), _jsxs(Box, { flexDirection: "row", marginTop: 0, children: [_jsxs(Text, { color: inkColors.footerHint, children: [" ", icons.tool, " ", tokenDisplay] }), _jsx(Text, { color: inkColors.footerHint, children: ` · / ! @ trackpad/↑/↓ scroll Ctrl+J newline Tab queue Esc Esc edit` })] }), _jsx(Box, { flexDirection: "column", marginTop: 0, children: inputValue.length === 0 ? (_jsxs(Box, { flexDirection: "row", children: [_jsxs(Text, { color: inkColors.primary, children: [icons.prompt, " "] }), cursorBlinkOn ? (_jsx(Text, { inverse: true, color: inkColors.primary, children: " " })) : (_jsx(Text, { color: inkColors.primary, children: " " })), _jsx(Text, { color: inkColors.textSecondary, children: "Message or / for commands, @ for files, ! for shell, ? for help..." })] })) : ((() => {
|
|
1261
1316
|
const lines = inputValue.split("\n");
|
|
1262
1317
|
let lineStart = 0;
|
|
1263
1318
|
return (_jsx(_Fragment, { children: lines.flatMap((lineText, lineIdx) => {
|
package/dist/ui/theme.js
CHANGED
|
@@ -22,7 +22,7 @@ const THEME_DARK = {
|
|
|
22
22
|
error: { main: "#f87171", dim: "#dc2626" },
|
|
23
23
|
muted: { main: "#8a9a7a", dim: "#6a7a5a", dark: "#4a5a3a" },
|
|
24
24
|
background: { dark: "#1a1a1a", darker: "#0f0f0f" },
|
|
25
|
-
text: { primary: "#e2e8f0", secondary: "#98a08f", disabled: "#6f7867" },
|
|
25
|
+
text: { primary: "#e2e8f0", secondary: "#98a08f", disabled: "#6f7867", footerHint: "#635e4b" },
|
|
26
26
|
},
|
|
27
27
|
ui: {
|
|
28
28
|
borderColor: "#7F9A65",
|
|
@@ -51,7 +51,7 @@ const THEME_LIGHT = {
|
|
|
51
51
|
error: { main: "#dc2626", dim: "#b91c1c" },
|
|
52
52
|
muted: { main: "#6b7a5a", dim: "#5a6a4a", dark: "#4a5a3a" },
|
|
53
53
|
background: { dark: "#f1f5f9", darker: "#e2e8f0" },
|
|
54
|
-
text: { primary: "#1e293b", secondary: "#5f6758", disabled: "#778070" },
|
|
54
|
+
text: { primary: "#1e293b", secondary: "#5f6758", disabled: "#778070", footerHint: "#635e4b" },
|
|
55
55
|
},
|
|
56
56
|
ui: {
|
|
57
57
|
borderColor: "#5a7247",
|
|
@@ -125,5 +125,6 @@ export const inkColors = {
|
|
|
125
125
|
textPrimary: theme.colors.text.primary,
|
|
126
126
|
textSecondary: theme.colors.text.secondary,
|
|
127
127
|
textDisabled: theme.colors.text.disabled,
|
|
128
|
+
footerHint: theme.colors.text.footerHint,
|
|
128
129
|
};
|
|
129
130
|
export const icons = theme.icons;
|