loop-task 2.1.4 → 2.1.5
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/app/App.js +119 -0
- package/dist/{tui → app}/index.js +4 -5
- package/dist/app/providers/index.js +1 -0
- package/dist/cli/import-writer.js +1 -1
- package/dist/cli.js +9 -9
- package/dist/client/cli-format.js +7 -0
- package/dist/client/commands.js +2 -164
- package/dist/client/ipc.js +3 -3
- package/dist/client/project-commands.js +159 -0
- package/dist/core/{command-runner.js → command/command-runner.js} +3 -3
- package/dist/core/{foreground-loop.js → foreground/index.js} +4 -4
- package/dist/core/{log-rotator.js → logging/log-rotator.js} +1 -1
- package/dist/core/loop/chain-executor.js +67 -0
- package/dist/core/loop/delay-utils.js +65 -0
- package/dist/core/loop/loop-controller.js +191 -0
- package/dist/core/loop/loop-runner.js +133 -0
- package/dist/core/loop/run-executor.js +90 -0
- package/dist/core/loop/types.js +1 -0
- package/dist/daemon/daemon-log.js +1 -1
- package/dist/daemon/http/helpers.js +81 -0
- package/dist/daemon/http/openapi.js +92 -0
- package/dist/daemon/http/route-loops.js +218 -0
- package/dist/daemon/http/route-misc.js +36 -0
- package/dist/daemon/http/route-projects.js +55 -0
- package/dist/daemon/http/route-tasks.js +53 -0
- package/dist/daemon/http/routes.js +17 -0
- package/dist/daemon/http/server.js +64 -0
- package/dist/daemon/http/sse.js +36 -0
- package/dist/daemon/index.js +7 -9
- package/dist/{ipc/handlers → daemon/ipc}/logs-stream.js +1 -1
- package/dist/daemon/managers/loop-entry.js +24 -0
- package/dist/daemon/{manager.js → managers/loop-manager.js} +32 -152
- package/dist/daemon/managers/loop-options.js +16 -0
- package/dist/daemon/managers/loop-serialization.js +43 -0
- package/dist/daemon/{projects.js → managers/project-manager.js} +6 -4
- package/dist/daemon/{task-manager.js → managers/task-manager.js} +5 -3
- package/dist/daemon/server/handlers/index.js +86 -0
- package/dist/daemon/server/handlers/log-handlers.js +130 -0
- package/dist/daemon/server/handlers/loop-handlers.js +54 -0
- package/dist/daemon/server/handlers/project-handlers.js +37 -0
- package/dist/daemon/server/handlers/task-handlers.js +20 -0
- package/dist/daemon/server/index.js +113 -0
- package/dist/daemon/{spawner.js → spawner/index.js} +4 -4
- package/dist/daemon/{state.js → state/index.js} +4 -4
- package/dist/daemon/{file-watcher.js → watcher/index.js} +8 -6
- package/dist/duration.js +1 -1
- package/dist/{tui/state.js → entities/loops/filters.js} +0 -76
- package/dist/entities/loops/index.js +1 -0
- package/dist/entities/projects/filters.js +69 -0
- package/dist/entities/projects/index.js +1 -0
- package/dist/entities/tasks/filters.js +41 -0
- package/dist/entities/tasks/index.js +1 -0
- package/dist/{tui/components → features/chain-editor}/ChainEditor.js +7 -5
- package/dist/features/code-editor/CodeEditorModal.js +84 -0
- package/dist/{tui/components → features/code-editor}/CodeEditorPreview.js +3 -3
- package/dist/features/code-editor/useEditorKeyboard.js +147 -0
- package/dist/features/code-editor/useModalDimensions.js +9 -0
- package/dist/{tui → features/commands}/commands.js +2 -2
- package/dist/features/commands/useCommandHandlers.js +147 -0
- package/dist/features/commands/useContextualActions.js +75 -0
- package/dist/features/commands/useGlobalShortcuts.js +152 -0
- package/dist/features/forms/FormRouter.js +44 -0
- package/dist/{tui/components → features/overlays}/ContextHelpModal.js +3 -3
- package/dist/{tui/components → features/overlays}/ExportModal.js +3 -3
- package/dist/{tui/components → features/overlays}/HelpGuideModal.js +3 -3
- package/dist/{tui/components → features/overlays}/HelpModal.js +3 -3
- package/dist/features/overlays/OverlayStack.js +9 -0
- package/dist/{tui/components → features/overlays}/ProjectsModal.js +2 -2
- package/dist/{tui/components → features/overlays}/TaskPickerModal.js +2 -2
- package/dist/{tui/components → features/overlays}/WelcomeScreen.js +5 -3
- package/dist/features/overlays/useOverlayStack.js +51 -0
- package/dist/features/state/useAppState.js +150 -0
- package/dist/logger.js +0 -1
- package/dist/loop-config.js +9 -4
- package/dist/{config → shared/config}/constants.js +1 -1
- package/dist/shared/container/index.js +22 -0
- package/dist/{tui → shared}/hooks/useBreakpoint.js +1 -1
- package/dist/shared/hooks/useInject.js +6 -0
- package/dist/{tui → shared}/hooks/useLogStream.js +7 -5
- package/dist/{hooks → shared/hooks}/useLoopFormValidation.js +2 -2
- package/dist/{tui → shared}/hooks/useLoopPolling.js +6 -4
- package/dist/shared/{useUndoRedo.js → hooks/useUndoRedo.js} +2 -4
- package/dist/shared/providers/InversifyProvider.js +6 -0
- package/dist/shared/services/export-service.js +43 -0
- package/dist/shared/services/log-service.js +28 -0
- package/dist/shared/services/loop-service.js +78 -0
- package/dist/shared/services/project-service.js +39 -0
- package/dist/shared/services/task-service.js +41 -0
- package/dist/shared/services/types.js +7 -0
- package/dist/{tui/components → shared/ui}/Button.js +1 -1
- package/dist/{tui/components → shared/ui}/DebugPanel.js +1 -1
- package/dist/{tui/components → shared/ui}/FocusableButton.js +1 -1
- package/dist/{tui/components → shared/ui}/FocusableInput.js +1 -1
- package/dist/{tui/components → shared/ui}/FocusableList.js +1 -1
- package/dist/{tui/components → shared/ui}/Modal.js +1 -1
- package/dist/{tui/components → shared/ui}/SelectModal.js +2 -2
- package/dist/{tui/components → shared/ui}/Toast.js +1 -1
- package/dist/shared/ui/state.js +9 -0
- package/dist/{tui → shared}/utils/paste.js +2 -2
- package/dist/{tui → shared}/utils/validation.js +1 -1
- package/dist/widgets/command-input/CommandDropdown.js +69 -0
- package/dist/widgets/command-input/CommandInput.js +129 -0
- package/dist/widgets/command-input/HintBar.js +9 -0
- package/dist/widgets/command-input/InputModes.js +68 -0
- package/dist/{tui/components → widgets/commands-browser}/CommandsBrowserModal.js +3 -3
- package/dist/{tui/components → widgets/header}/Header.js +3 -3
- package/dist/{tui/components → widgets/header}/TabBar.js +1 -1
- package/dist/{tui/components → widgets/left-panel}/LeftPanel.js +3 -3
- package/dist/{tui/components → widgets/left-panel}/Navigator.js +3 -3
- package/dist/{tui/components → widgets/left-panel}/ProjectsPage.js +9 -34
- package/dist/widgets/left-panel/ProjectsPageParts.js +34 -0
- package/dist/{tui/components → widgets/left-panel}/TaskBrowser.js +4 -4
- package/dist/{tui/components → widgets/log-modal}/LogModal.js +8 -6
- package/dist/widgets/loop-form/CreateForm.js +75 -0
- package/dist/widgets/loop-form/TextField.js +9 -0
- package/dist/{tui/components → widgets/loop-form}/WizardForm.js +5 -10
- package/dist/widgets/loop-form/useCreateSteps.js +107 -0
- package/dist/widgets/loop-form/useHandleComplete.js +74 -0
- package/dist/{tui/components → widgets/project-form}/ProjectForm.js +9 -7
- package/dist/{tui/components → widgets/right-panel}/Inspector.js +4 -4
- package/dist/{tui/components → widgets/right-panel}/RightPanel.js +4 -4
- package/dist/{tui/components → widgets/right-panel}/RunHistory.js +52 -4
- package/dist/{tui/components → widgets/task-form}/TaskForm.js +11 -9
- package/package.json +2 -1
- package/dist/core/loop-controller.js +0 -511
- package/dist/daemon/http-server.js +0 -599
- package/dist/daemon/server.js +0 -374
- package/dist/tui/App.js +0 -680
- package/dist/tui/components/CodeEditorModal.js +0 -244
- package/dist/tui/components/CommandInput.js +0 -273
- package/dist/tui/components/CreateForm.js +0 -224
- package/dist/tui/daemon.js +0 -142
- package/dist/tui/format.js +0 -111
- /package/dist/{tui/router.js → app/router/index.js} +0 -0
- /package/dist/{tui → app}/types.js +0 -0
- /package/dist/core/{resolve-cwd.js → command/resolve-cwd.js} +0 -0
- /package/dist/core/{context-parser.js → context/context-parser.js} +0 -0
- /package/dist/core/{template.js → context/template.js} +0 -0
- /package/dist/core/{log-parser.js → logging/log-parser.js} +0 -0
- /package/dist/core/{scheduling.js → scheduling/index.js} +0 -0
- /package/dist/{ipc → daemon/ipc}/send.js +0 -0
- /package/dist/{config → shared/config}/paths.js +0 -0
- /package/dist/{i18n → shared/i18n}/en.json +0 -0
- /package/dist/{i18n → shared/i18n}/index.js +0 -0
- /package/dist/{board → shared/ui}/format.js +0 -0
- /package/dist/{tui → shared/ui}/hooks/useHoverState.js +0 -0
- /package/dist/{tui → shared/ui}/theme.js +0 -0
- /package/dist/{tui → shared}/utils/syntax.js +0 -0
|
@@ -1,244 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import React, { useState, useCallback, useMemo } from "react";
|
|
3
|
-
import { Box, Text, useInput } from "ink";
|
|
4
|
-
import { darkTheme as theme } from "../theme.js";
|
|
5
|
-
import { t } from "../../i18n/index.js";
|
|
6
|
-
import { useUndoRedo } from "../../shared/useUndoRedo.js";
|
|
7
|
-
import { highlightSegments } from "../utils/syntax.js";
|
|
8
|
-
import { joinCommandLines } from "../../loop-config.js";
|
|
9
|
-
import { copyToClipboard, readFromClipboard } from "../../shared/clipboard.js";
|
|
10
|
-
import { sanitizeMultilinePaste, wrapCommand } from "../utils/paste.js";
|
|
11
|
-
import { CODE_EDITOR_MODAL_HEIGHT, CODE_EDITOR_MODAL_WIDTH, CODE_EDITOR_UNDO_LIMIT, CODE_EDITOR_SYNTAX_COLORS, } from "../../config/constants.js";
|
|
12
|
-
// Cap modal to terminal size: never exceed (terminal - 2) so the border
|
|
13
|
-
// and a 1-row margin are always visible. Ideal max is the constant.
|
|
14
|
-
function useModalDimensions() {
|
|
15
|
-
const termRows = process.stdout.rows || 24;
|
|
16
|
-
const termCols = process.stdout.columns || 80;
|
|
17
|
-
const modalHeight = Math.min(CODE_EDITOR_MODAL_HEIGHT, termRows - 2);
|
|
18
|
-
const modalWidth = Math.min(CODE_EDITOR_MODAL_WIDTH, termCols - 2);
|
|
19
|
-
// total height - title - preview - buttons - hint - 2 padding = -6
|
|
20
|
-
const editorVisibleLines = Math.max(3, modalHeight - 6);
|
|
21
|
-
return { modalWidth, modalHeight, editorVisibleLines };
|
|
22
|
-
}
|
|
23
|
-
export function CodeEditorModal(props) {
|
|
24
|
-
const { initialValue, onSave, onCancel } = props;
|
|
25
|
-
const { modalWidth, modalHeight, editorVisibleLines: EDITOR_VISIBLE_LINES } = useModalDimensions();
|
|
26
|
-
const { value, setValue, undo, redo } = useUndoRedo(initialValue, CODE_EDITOR_UNDO_LIMIT);
|
|
27
|
-
const [cursorRow, setCursorRow] = useState(() => {
|
|
28
|
-
const lines = initialValue.split("\n");
|
|
29
|
-
return Math.max(0, lines.length - 1);
|
|
30
|
-
});
|
|
31
|
-
const [cursorCol, setCursorCol] = useState(() => {
|
|
32
|
-
const lines = initialValue.split("\n");
|
|
33
|
-
return (lines[lines.length - 1] ?? "").length;
|
|
34
|
-
});
|
|
35
|
-
const [flashMsg, setFlashMsg] = useState(null);
|
|
36
|
-
const lines = useMemo(() => (value ? value.split("\n") : [""]), [value]);
|
|
37
|
-
const lineCount = lines.length;
|
|
38
|
-
const lineNumWidth = String(lineCount).length;
|
|
39
|
-
// Flash message auto-clear
|
|
40
|
-
React.useEffect(() => {
|
|
41
|
-
if (flashMsg) {
|
|
42
|
-
const timer = setTimeout(() => setFlashMsg(null), 1500);
|
|
43
|
-
return () => clearTimeout(timer);
|
|
44
|
-
}
|
|
45
|
-
}, [flashMsg]);
|
|
46
|
-
// Scroll: keep cursor visible in the editor area
|
|
47
|
-
const scrollStart = useMemo(() => {
|
|
48
|
-
if (cursorRow < EDITOR_VISIBLE_LINES)
|
|
49
|
-
return 0;
|
|
50
|
-
return cursorRow - EDITOR_VISIBLE_LINES + 1;
|
|
51
|
-
}, [cursorRow]);
|
|
52
|
-
const visibleLines = useMemo(() => lines.slice(scrollStart, scrollStart + EDITOR_VISIBLE_LINES), [lines, scrollStart]);
|
|
53
|
-
// ---- Helper: apply a text mutation via setValue and update cursor ----
|
|
54
|
-
const applyMutation = useCallback((nextLines, newCursorRow, newCursorCol) => {
|
|
55
|
-
setValue(nextLines.join("\n"));
|
|
56
|
-
setCursorRow(newCursorRow);
|
|
57
|
-
setCursorCol(newCursorCol);
|
|
58
|
-
}, [setValue]);
|
|
59
|
-
// ---- Keyboard handling ----
|
|
60
|
-
useInput((input, key) => {
|
|
61
|
-
// Bracketed paste: content wrapped in ESC[200~ ... ESC[201~
|
|
62
|
-
// Must be detected BEFORE the escape check — the leading ESC trips
|
|
63
|
-
// key.escape and would close the modal before paste is handled.
|
|
64
|
-
if (input.includes("\x1b[200~")) {
|
|
65
|
-
const pasted = wrapCommand(sanitizeMultilinePaste(input));
|
|
66
|
-
if (pasted) {
|
|
67
|
-
const next = [...lines];
|
|
68
|
-
const line = next[cursorRow] ?? "";
|
|
69
|
-
next[cursorRow] =
|
|
70
|
-
line.slice(0, cursorCol) + pasted + line.slice(cursorCol);
|
|
71
|
-
applyMutation(next, cursorRow, cursorCol + pasted.length);
|
|
72
|
-
}
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
// Esc → cancel
|
|
76
|
-
if (key.escape) {
|
|
77
|
-
onCancel();
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
// Ctrl+S → save (also accept raw \x13 fallback)
|
|
81
|
-
if ((key.ctrl && input === "s") || input === "\x13") {
|
|
82
|
-
onSave(value);
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
// Ctrl+Z → undo (also accept raw \x1a fallback)
|
|
86
|
-
if ((key.ctrl && !key.shift && input === "z") || input === "\x1a") {
|
|
87
|
-
undo();
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
// Ctrl+Shift+Z → redo
|
|
91
|
-
if (key.ctrl && key.shift && input === "z") {
|
|
92
|
-
redo();
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
// Ctrl+X → cut (copy all to clipboard, then clear; also accept \x18)
|
|
96
|
-
if ((key.ctrl && input === "x") || input === "\x18") {
|
|
97
|
-
copyToClipboard(value);
|
|
98
|
-
setValue("");
|
|
99
|
-
setCursorRow(0);
|
|
100
|
-
setCursorCol(0);
|
|
101
|
-
setFlashMsg(t("codeEditor.copied"));
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
// Ctrl+V → paste from clipboard (also accept raw \x16 fallback for
|
|
105
|
-
// terminals that don't set key.ctrl on the SYN control byte)
|
|
106
|
-
if ((key.ctrl && input === "v") || input === "\x16") {
|
|
107
|
-
const clip = readFromClipboard();
|
|
108
|
-
if (clip) {
|
|
109
|
-
const pasted = sanitizeMultilinePaste(clip);
|
|
110
|
-
if (pasted) {
|
|
111
|
-
const next = [...lines];
|
|
112
|
-
const line = next[cursorRow] ?? "";
|
|
113
|
-
next[cursorRow] =
|
|
114
|
-
line.slice(0, cursorCol) + pasted + line.slice(cursorCol);
|
|
115
|
-
applyMutation(next, cursorRow, cursorCol + pasted.length);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
else {
|
|
119
|
-
// No clipboard tool available (common over SSH). Bracketed paste
|
|
120
|
-
// (right-click in the terminal) still works — point the user at it.
|
|
121
|
-
setFlashMsg(t("codeEditor.clipboardEmpty"));
|
|
122
|
-
}
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
// Ctrl+L → clear all
|
|
126
|
-
if ((key.ctrl && input === "l") || input === "\x0c") {
|
|
127
|
-
setValue("");
|
|
128
|
-
setCursorRow(0);
|
|
129
|
-
setCursorCol(0);
|
|
130
|
-
setFlashMsg(t("codeEditor.cleared"));
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
// Enter → insert newline at cursor
|
|
134
|
-
if (key.return) {
|
|
135
|
-
const next = [...lines];
|
|
136
|
-
const line = next[cursorRow] ?? "";
|
|
137
|
-
next.splice(cursorRow, 1, line.slice(0, cursorCol), line.slice(cursorCol));
|
|
138
|
-
applyMutation(next, cursorRow + 1, 0);
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
// Arrow keys
|
|
142
|
-
if (key.upArrow) {
|
|
143
|
-
if (cursorRow > 0) {
|
|
144
|
-
const newRow = cursorRow - 1;
|
|
145
|
-
setCursorRow(newRow);
|
|
146
|
-
setCursorCol((c) => Math.min(c, (lines[newRow] ?? "").length));
|
|
147
|
-
}
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
if (key.downArrow) {
|
|
151
|
-
if (cursorRow < lines.length - 1) {
|
|
152
|
-
const newRow = cursorRow + 1;
|
|
153
|
-
setCursorRow(newRow);
|
|
154
|
-
setCursorCol((c) => Math.min(c, (lines[newRow] ?? "").length));
|
|
155
|
-
}
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
if (key.leftArrow) {
|
|
159
|
-
if (cursorCol > 0) {
|
|
160
|
-
setCursorCol((c) => c - 1);
|
|
161
|
-
}
|
|
162
|
-
else if (cursorRow > 0) {
|
|
163
|
-
setCursorRow((r) => r - 1);
|
|
164
|
-
setCursorCol((lines[cursorRow - 1] ?? "").length);
|
|
165
|
-
}
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
if (key.rightArrow) {
|
|
169
|
-
const curLen = (lines[cursorRow] ?? "").length;
|
|
170
|
-
if (cursorCol < curLen) {
|
|
171
|
-
setCursorCol((c) => c + 1);
|
|
172
|
-
}
|
|
173
|
-
else if (cursorRow < lines.length - 1) {
|
|
174
|
-
setCursorRow((r) => r + 1);
|
|
175
|
-
setCursorCol(0);
|
|
176
|
-
}
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
// Backspace / Delete
|
|
180
|
-
if (key.backspace || key.delete) {
|
|
181
|
-
if (cursorCol > 0) {
|
|
182
|
-
const next = [...lines];
|
|
183
|
-
const line = next[cursorRow] ?? "";
|
|
184
|
-
next[cursorRow] =
|
|
185
|
-
line.slice(0, cursorCol - 1) + line.slice(cursorCol);
|
|
186
|
-
applyMutation(next, cursorRow, cursorCol - 1);
|
|
187
|
-
}
|
|
188
|
-
else if (cursorRow > 0) {
|
|
189
|
-
const next = [...lines];
|
|
190
|
-
const prev = next[cursorRow - 1] ?? "";
|
|
191
|
-
const cur = next[cursorRow] ?? "";
|
|
192
|
-
next.splice(cursorRow - 1, 2, prev + cur);
|
|
193
|
-
applyMutation(next, cursorRow - 1, prev.length);
|
|
194
|
-
}
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
// Multi-char printable input = unbracketed paste (e.g. right-click
|
|
198
|
-
// in terminals that don't support DECSET 2004). Preserve newlines.
|
|
199
|
-
if (input.length > 1 && !key.meta) {
|
|
200
|
-
const pasted = wrapCommand(sanitizeMultilinePaste(input));
|
|
201
|
-
if (pasted) {
|
|
202
|
-
const next = [...lines];
|
|
203
|
-
const line = next[cursorRow] ?? "";
|
|
204
|
-
next[cursorRow] =
|
|
205
|
-
line.slice(0, cursorCol) + pasted + line.slice(cursorCol);
|
|
206
|
-
applyMutation(next, cursorRow, cursorCol + pasted.length);
|
|
207
|
-
}
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
// Single printable char → insert at cursor
|
|
211
|
-
if (input.length === 1 && input >= " " && input <= "~") {
|
|
212
|
-
const next = [...lines];
|
|
213
|
-
const line = next[cursorRow] ?? "";
|
|
214
|
-
next[cursorRow] =
|
|
215
|
-
line.slice(0, cursorCol) + input + line.slice(cursorCol);
|
|
216
|
-
applyMutation(next, cursorRow, cursorCol + 1);
|
|
217
|
-
}
|
|
218
|
-
}, { isActive: true });
|
|
219
|
-
// ---- Live preview footer ----
|
|
220
|
-
const joined = joinCommandLines(value);
|
|
221
|
-
const innerWidth = modalWidth - 2;
|
|
222
|
-
const truncatedPreview = joined.length > innerWidth
|
|
223
|
-
? joined.slice(0, innerWidth - 1) + t("codeEditor.previewTruncated")
|
|
224
|
-
: joined;
|
|
225
|
-
const accent = theme.accent.brand;
|
|
226
|
-
return (_jsx(Box, { position: "absolute", top: 0, left: 0, width: "100%", height: "100%", justifyContent: "center", alignItems: "center", children: _jsxs(Box, { width: modalWidth, height: modalHeight, flexDirection: "column", backgroundColor: theme.bg.elevated, borderStyle: "round", borderColor: accent, paddingX: 1, children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsx(Text, { color: accent, bold: true, children: t("codeEditor.title") }), _jsxs(Text, { color: theme.text.muted, children: [lineCount, " ", t("cmdEditor.lines")] })] }), _jsx(Box, { flexDirection: "column", flexGrow: 1, overflow: "hidden", children: visibleLines.map((line, visIdx) => {
|
|
227
|
-
const rowIdx = scrollStart + visIdx;
|
|
228
|
-
const isCursor = rowIdx === cursorRow;
|
|
229
|
-
const lineNum = String(rowIdx + 1).padStart(lineNumWidth, " ");
|
|
230
|
-
if (isCursor) {
|
|
231
|
-
const before = line.slice(0, cursorCol);
|
|
232
|
-
const cur = cursorCol < line.length ? line[cursorCol] : " ";
|
|
233
|
-
const after = cursorCol < line.length ? line.slice(cursorCol + 1) : "";
|
|
234
|
-
// Cursor line: render with cursor (no syntax highlight on cursor line)
|
|
235
|
-
return (_jsxs(Box, { children: [_jsxs(Text, { color: theme.text.muted, children: [lineNum, " "] }), _jsx(Text, { color: theme.text.primary, children: before }), _jsx(Text, { inverse: true, children: cur }), _jsx(Text, { color: theme.text.primary, children: after })] }, rowIdx));
|
|
236
|
-
}
|
|
237
|
-
// Non-cursor line: syntax highlighted (whitespace preserved)
|
|
238
|
-
const segments = highlightSegments(line, CODE_EDITOR_SYNTAX_COLORS, theme.text.muted);
|
|
239
|
-
if (segments.length === 0) {
|
|
240
|
-
return (_jsxs(Box, { children: [_jsxs(Text, { color: theme.text.muted, children: [lineNum, " "] }), _jsx(Text, { children: " " })] }, rowIdx));
|
|
241
|
-
}
|
|
242
|
-
return (_jsxs(Box, { children: [_jsxs(Text, { color: theme.text.muted, children: [lineNum, " "] }), segments.map((seg, ti) => (_jsx(Text, { color: seg.color, children: seg.value }, ti)))] }, rowIdx));
|
|
243
|
-
}) }), _jsxs(Box, { children: [_jsxs(Text, { color: theme.text.muted, children: [t("codeEditor.previewLabel"), " "] }), _jsx(Text, { color: theme.text.secondary, children: truncatedPreview || t("codeEditor.emptyPlaceholder") })] }), _jsxs(Box, { justifyContent: "space-between", children: [_jsxs(Box, { gap: 2, children: [_jsx(Text, { color: theme.accent.brand, children: t("codeEditor.buttonCopy") }), _jsx(Text, { color: theme.text.muted, children: "ctrl+x" }), _jsx(Text, { color: theme.accent.brand, children: t("codeEditor.buttonPaste") }), _jsx(Text, { color: theme.text.muted, children: "ctrl+v" }), _jsx(Text, { color: theme.accent.brand, children: t("codeEditor.buttonClear") }), _jsx(Text, { color: theme.text.muted, children: "ctrl+l" })] }), flashMsg ? (_jsx(Text, { color: theme.semantic.success, children: flashMsg })) : null] }), _jsx(Box, { children: _jsx(Text, { color: theme.text.muted, children: t("codeEditor.hint") }) })] }) }));
|
|
244
|
-
}
|
|
@@ -1,273 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useState, useEffect, useMemo, useCallback } from "react";
|
|
3
|
-
import { Box, Text, useInput } from "ink";
|
|
4
|
-
import { useAutocompleteState, } from "ink-combobox";
|
|
5
|
-
import { darkTheme as theme } from "../theme.js";
|
|
6
|
-
import { t } from "../../i18n/index.js";
|
|
7
|
-
import { buildCommands, rankCommands } from "../commands.js";
|
|
8
|
-
import { COMMAND_INPUT_DROPDOWN_MAX_VISIBLE, COMMAND_INPUT_HEIGHT, } from "../../config/constants.js";
|
|
9
|
-
import { sanitizePaste } from "../utils/paste.js";
|
|
10
|
-
export { sanitizePaste } from "../utils/paste.js";
|
|
11
|
-
function renderInputLine(value, cursorOffset) {
|
|
12
|
-
if (value.length === 0) {
|
|
13
|
-
return "\x1b[7m \x1b[27m";
|
|
14
|
-
}
|
|
15
|
-
let result = "";
|
|
16
|
-
for (let i = 0; i < value.length; i++) {
|
|
17
|
-
if (i === cursorOffset) {
|
|
18
|
-
result += "\x1b[7m" + value[i] + "\x1b[27m";
|
|
19
|
-
}
|
|
20
|
-
else {
|
|
21
|
-
result += value[i];
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
if (cursorOffset >= value.length) {
|
|
25
|
-
result += "\x1b[7m \x1b[27m";
|
|
26
|
-
}
|
|
27
|
-
return result;
|
|
28
|
-
}
|
|
29
|
-
function renderHighlightedLabel(label, matchRanges, isFocused) {
|
|
30
|
-
if (matchRanges.length === 0) {
|
|
31
|
-
return isFocused
|
|
32
|
-
? `\x1b[38;2;251;191;36m${label}\x1b[39m`
|
|
33
|
-
: label;
|
|
34
|
-
}
|
|
35
|
-
let result = "";
|
|
36
|
-
let pos = 0;
|
|
37
|
-
for (const range of matchRanges) {
|
|
38
|
-
if (pos < range.start) {
|
|
39
|
-
const segment = label.slice(pos, range.start);
|
|
40
|
-
result += isFocused
|
|
41
|
-
? `\x1b[38;2;251;191;36m${segment}\x1b[39m`
|
|
42
|
-
: segment;
|
|
43
|
-
}
|
|
44
|
-
const matched = label.slice(range.start, range.end);
|
|
45
|
-
result += `\x1b[1m\x1b[38;2;251;191;36m${matched}\x1b[39m\x1b[22m`;
|
|
46
|
-
pos = range.end;
|
|
47
|
-
}
|
|
48
|
-
if (pos < label.length) {
|
|
49
|
-
const segment = label.slice(pos);
|
|
50
|
-
result += isFocused
|
|
51
|
-
? `\x1b[38;2;251;191;36m${segment}\x1b[39m`
|
|
52
|
-
: segment;
|
|
53
|
-
}
|
|
54
|
-
return result;
|
|
55
|
-
}
|
|
56
|
-
function CommandDropdown({ state, rankedFiltered, }) {
|
|
57
|
-
if (!state.isOpen || state.isLoading || state.error)
|
|
58
|
-
return null;
|
|
59
|
-
const filtered = rankedFiltered;
|
|
60
|
-
const visibleOptions = filtered.slice(state.visibleFromIndex, state.visibleToIndex);
|
|
61
|
-
if (visibleOptions.length === 0) {
|
|
62
|
-
if (state.inputValue.length > 0) {
|
|
63
|
-
return (_jsx(Box, { paddingLeft: 3, position: "absolute", bottom: 3, borderStyle: "single", borderColor: theme.border.dim, children: _jsx(Text, { color: theme.text.muted, children: t("cmdInput.noMatches") }) }));
|
|
64
|
-
}
|
|
65
|
-
return null;
|
|
66
|
-
}
|
|
67
|
-
const aboveCount = state.visibleFromIndex;
|
|
68
|
-
const belowCount = filtered.length - state.visibleToIndex;
|
|
69
|
-
return (_jsxs(Box, { flexDirection: "column", paddingLeft: 3, position: "absolute", bottom: 3, children: [aboveCount > 0 && (_jsx(Text, { color: theme.text.muted, children: ` \u2191 ${aboveCount} more` })), visibleOptions.map((match, i) => {
|
|
70
|
-
const actualIndex = state.visibleFromIndex + i;
|
|
71
|
-
const isFocused = actualIndex === state.focusedIndex;
|
|
72
|
-
const label = renderHighlightedLabel(match.option.label, match.matchRanges, isFocused);
|
|
73
|
-
return (_jsxs(Box, { backgroundColor: isFocused ? theme.bg.active : undefined, children: [_jsx(Text, { color: isFocused ? theme.text.inverse : theme.text.muted, children: isFocused ? "\u276f " : " " }), _jsx(Text, { color: isFocused ? theme.text.inverse : undefined, children: label })] }, match.option.value));
|
|
74
|
-
}), belowCount > 0 && (_jsx(Text, { color: theme.text.muted, children: ` \u2193 ${belowCount} more` }))] }));
|
|
75
|
-
}
|
|
76
|
-
function HintBar({ leftHint, rightHint, }) {
|
|
77
|
-
return (_jsxs(Box, { justifyContent: "space-between", paddingX: 1, children: [_jsx(Box, { children: leftHint }), _jsx(Box, { children: rightHint })] }));
|
|
78
|
-
}
|
|
79
|
-
function KeyHint({ keyLabel, action }) {
|
|
80
|
-
return (_jsxs(Box, { marginRight: 2, children: [_jsx(Text, { bold: true, color: theme.text.primary, children: keyLabel }), _jsx(Text, { color: theme.text.muted, children: " " + action })] }));
|
|
81
|
-
}
|
|
82
|
-
function CommandMode({ context, onCommand, onCopy, onPanelAction, disabled, navOwner, onInputStateChange, }) {
|
|
83
|
-
const commands = useMemo(() => buildCommands(context), [context]);
|
|
84
|
-
const options = useMemo(() => commands.map((cmd) => ({ label: cmd.label, value: cmd.value })), [commands]);
|
|
85
|
-
const { state, dispatch } = useAutocompleteState({
|
|
86
|
-
options,
|
|
87
|
-
visibleOptionCount: COMMAND_INPUT_DROPDOWN_MAX_VISIBLE,
|
|
88
|
-
onSelect: onCommand,
|
|
89
|
-
});
|
|
90
|
-
// Re-rank filteredOptions: exact match → prefix match → fuzzy (existing order)
|
|
91
|
-
const rankedFiltered = useMemo(() => {
|
|
92
|
-
const fo = state.filteredOptions;
|
|
93
|
-
if (fo.length === 0 || state.inputValue.length === 0)
|
|
94
|
-
return fo;
|
|
95
|
-
const byValue = new Map();
|
|
96
|
-
for (const m of fo)
|
|
97
|
-
byValue.set(m.option.value, m);
|
|
98
|
-
const ranked = rankCommands(state.inputValue, fo.map((m) => ({ label: m.option.label, value: m.option.value })));
|
|
99
|
-
return ranked
|
|
100
|
-
.map((opt) => byValue.get(opt.value))
|
|
101
|
-
.filter((m) => m !== undefined);
|
|
102
|
-
}, [state.filteredOptions, state.inputValue]);
|
|
103
|
-
// Report input state changes to parent for InputOwner resolution (1.2)
|
|
104
|
-
useEffect(() => {
|
|
105
|
-
onInputStateChange?.(state.inputValue.length > 0, state.isOpen);
|
|
106
|
-
}, [state.inputValue, state.isOpen, onInputStateChange]);
|
|
107
|
-
const insertText = useCallback((text) => {
|
|
108
|
-
for (const ch of text)
|
|
109
|
-
dispatch({ type: "INSERT_TEXT", text: ch });
|
|
110
|
-
}, [dispatch]);
|
|
111
|
-
const clearInput = useCallback(() => {
|
|
112
|
-
dispatch({ type: "MOVE_CURSOR_END" });
|
|
113
|
-
for (let i = 0; i < state.inputValue.length; i++)
|
|
114
|
-
dispatch({ type: "DELETE_BACKWARD" });
|
|
115
|
-
}, [dispatch, state.inputValue.length]);
|
|
116
|
-
useInput((input, key) => {
|
|
117
|
-
// Bracketed paste: content wrapped in ESC[200~ ... ESC[201~ arrives as one
|
|
118
|
-
// chunk. Detect first — the leading ESC can otherwise trip the ctrl/escape guards.
|
|
119
|
-
if (input.includes("\x1b[200~")) {
|
|
120
|
-
insertText(sanitizePaste(input));
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
// Ctrl+U clears the line (the "select all + delete" gesture). Handle before
|
|
124
|
-
// the ctrl guard below. Some terminals send it as the raw NAK control char.
|
|
125
|
-
if ((key.ctrl && input === "u") || input === "\x15") {
|
|
126
|
-
clearInput();
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
if (key.ctrl)
|
|
130
|
-
return;
|
|
131
|
-
// Multi-char containing CR/LF with no bracketed markers = the VS Code
|
|
132
|
-
// Ctrl+Enter escape sequence, handled by App's global useInput. Ignore here.
|
|
133
|
-
if (input.length > 1 && (input.includes("\r") || input.includes("\n")))
|
|
134
|
-
return;
|
|
135
|
-
// When the command bar is empty and no dropdown is open, panels own
|
|
136
|
-
// navigation keys — return early so j/k/arrows reach the panel layer.
|
|
137
|
-
if (navOwner === "panel" && state.inputValue.length === 0 && !state.isOpen) {
|
|
138
|
-
if (input === "j" || input === "k" || key.upArrow || key.downArrow) {
|
|
139
|
-
return; // panels own these keys
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
// `c` with no modifiers + no open dropdown = contextual copy shortcut
|
|
143
|
-
if (onCopy && input === "c" && !state.isOpen) {
|
|
144
|
-
onCopy();
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
if (key.escape) {
|
|
148
|
-
dispatch({ type: "CLOSE" });
|
|
149
|
-
if (state.inputValue.length > 0) {
|
|
150
|
-
clearInput();
|
|
151
|
-
}
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
if (key.return) {
|
|
155
|
-
if (state.isOpen && rankedFiltered.length > 0 && state.focusedIndex < rankedFiltered.length) {
|
|
156
|
-
const focused = rankedFiltered[state.focusedIndex];
|
|
157
|
-
dispatch({ type: "CLOSE" });
|
|
158
|
-
for (let i = 0; i <= state.inputValue.length; i++) {
|
|
159
|
-
dispatch({ type: "DELETE_BACKWARD" });
|
|
160
|
-
}
|
|
161
|
-
onCommand(focused.option.value);
|
|
162
|
-
}
|
|
163
|
-
else if (state.inputValue.length === 0) {
|
|
164
|
-
// Empty command bar: Enter triggers the focused panel's contextual
|
|
165
|
-
// action (edit / logs) for terminals that collapse Ctrl+Enter to Enter.
|
|
166
|
-
onPanelAction?.();
|
|
167
|
-
}
|
|
168
|
-
return;
|
|
169
|
-
}
|
|
170
|
-
if (key.tab) {
|
|
171
|
-
dispatch({ type: "ACCEPT" });
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
if (key.downArrow) {
|
|
175
|
-
dispatch({ type: "FOCUS_NEXT" });
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
if (key.upArrow) {
|
|
179
|
-
dispatch({ type: "FOCUS_PREV" });
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
if (key.backspace || key.delete) {
|
|
183
|
-
dispatch({ type: "DELETE_BACKWARD" });
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
if (key.leftArrow) {
|
|
187
|
-
dispatch({ type: "MOVE_CURSOR_LEFT" });
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
if (key.rightArrow) {
|
|
191
|
-
dispatch({ type: "MOVE_CURSOR_RIGHT" });
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
// Multi-char printable input = an unbracketed single-line paste (e.g. right-click).
|
|
195
|
-
if (input.length > 1 && !key.meta) {
|
|
196
|
-
insertText(sanitizePaste(input));
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
199
|
-
if (input.length === 1 && !key.ctrl && !key.meta && !key.return && !key.tab && !key.escape && input >= " " && input <= "~") {
|
|
200
|
-
dispatch({ type: "INSERT_TEXT", text: input });
|
|
201
|
-
}
|
|
202
|
-
}, { isActive: !disabled });
|
|
203
|
-
const isEmpty = state.inputValue.length === 0;
|
|
204
|
-
const cursor = "\x1b[7m \x1b[27m";
|
|
205
|
-
const inputContent = isEmpty ? cursor : renderInputLine(state.inputValue, state.cursorOffset);
|
|
206
|
-
return (_jsxs(_Fragment, { children: [_jsx(CommandDropdown, { state: state, rankedFiltered: rankedFiltered }), _jsxs(Box, { children: [_jsx(Text, { color: theme.accent.brand, children: "│ " }), isEmpty ? (_jsx(Text, { color: theme.text.muted, children: t("cmdInput.placeholder") })) : (_jsx(Text, { children: inputContent }))] }), _jsx(HintBar, { leftHint: _jsxs(Box, { children: [_jsx(Text, { color: theme.text.muted, children: "\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7 " }), _jsx(KeyHint, { keyLabel: "esc", action: "cancel" })] }), rightHint: _jsxs(Box, { children: [_jsx(KeyHint, { keyLabel: "enter", action: "edit/logs" }), _jsx(KeyHint, { keyLabel: "ctrl+u", action: "clear" }), _jsx(KeyHint, { keyLabel: "c", action: "copy" }), _jsx(KeyHint, { keyLabel: "tab", action: "panels" }), _jsx(KeyHint, { keyLabel: "ctrl+\u2190\u2192", action: "tabs" }), _jsx(KeyHint, { keyLabel: "ctrl+p", action: "commands" })] }) })] }));
|
|
207
|
-
}
|
|
208
|
-
function ConfirmMode({ confirmState, onConfirmYes, onConfirmCancel, disabled, }) {
|
|
209
|
-
const [value, setValue] = useState("");
|
|
210
|
-
useInput((input, key) => {
|
|
211
|
-
if (key.ctrl)
|
|
212
|
-
return;
|
|
213
|
-
if (input.length > 1 && (input.includes("\r") || input.includes("\n")))
|
|
214
|
-
return;
|
|
215
|
-
if (key.escape) {
|
|
216
|
-
setValue("");
|
|
217
|
-
onConfirmCancel();
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
220
|
-
if (key.return) {
|
|
221
|
-
if (value.toLowerCase() === "yes") {
|
|
222
|
-
setValue("");
|
|
223
|
-
onConfirmYes();
|
|
224
|
-
}
|
|
225
|
-
else {
|
|
226
|
-
setValue("");
|
|
227
|
-
onConfirmCancel();
|
|
228
|
-
}
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
if (key.backspace || key.delete) {
|
|
232
|
-
setValue((v) => v.slice(0, -1));
|
|
233
|
-
return;
|
|
234
|
-
}
|
|
235
|
-
if (input && !key.ctrl && !key.meta && input.length === 1) {
|
|
236
|
-
setValue((v) => v + input);
|
|
237
|
-
return;
|
|
238
|
-
}
|
|
239
|
-
}, { isActive: !disabled });
|
|
240
|
-
const cursor = "\x1b[7m \x1b[27m";
|
|
241
|
-
return (_jsxs(_Fragment, { children: [_jsxs(Box, { children: [_jsx(Text, { color: theme.semantic.danger, children: "│ " }), _jsx(Text, { color: theme.text.muted, children: confirmState.prompt + " " }), value.length > 0 ? (_jsx(Text, { children: value + cursor })) : (_jsx(Text, { children: cursor }))] }), _jsx(HintBar, { leftHint: _jsxs(Box, { children: [_jsx(Text, { color: theme.text.muted, children: "\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7 " }), _jsx(KeyHint, { keyLabel: "esc", action: "cancel" })] }), rightHint: _jsx(KeyHint, { keyLabel: "enter", action: "confirm" }) })] }));
|
|
242
|
-
}
|
|
243
|
-
function SearchMode({ value, onSearchChange, onSearchSubmit, onSearchCancel, disabled, }) {
|
|
244
|
-
useInput((input, key) => {
|
|
245
|
-
if (key.ctrl)
|
|
246
|
-
return;
|
|
247
|
-
if (input.length > 1 && (input.includes("\r") || input.includes("\n")))
|
|
248
|
-
return;
|
|
249
|
-
if (key.escape) {
|
|
250
|
-
onSearchCancel();
|
|
251
|
-
return;
|
|
252
|
-
}
|
|
253
|
-
if (key.return) {
|
|
254
|
-
onSearchSubmit();
|
|
255
|
-
return;
|
|
256
|
-
}
|
|
257
|
-
if (key.backspace || key.delete) {
|
|
258
|
-
onSearchChange(value.slice(0, -1));
|
|
259
|
-
return;
|
|
260
|
-
}
|
|
261
|
-
if (input && !key.ctrl && !key.meta && input.length === 1) {
|
|
262
|
-
onSearchChange(value + input);
|
|
263
|
-
return;
|
|
264
|
-
}
|
|
265
|
-
}, { isActive: !disabled });
|
|
266
|
-
const placeholder = t("cmdInput.searchPlaceholder");
|
|
267
|
-
const cursor = "\x1b[7m \x1b[27m";
|
|
268
|
-
return (_jsxs(_Fragment, { children: [_jsxs(Box, { children: [_jsx(Text, { color: theme.accent.project, children: "│ " }), value.length > 0 ? (_jsx(Text, { children: value + cursor })) : (_jsx(Text, { color: theme.text.muted, children: placeholder }))] }), _jsx(HintBar, { leftHint: _jsxs(Box, { children: [_jsx(Text, { color: theme.text.muted, children: "\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7 " }), _jsx(KeyHint, { keyLabel: "esc", action: "cancel" })] }), rightHint: _jsx(KeyHint, { keyLabel: "enter", action: "apply" }) })] }));
|
|
269
|
-
}
|
|
270
|
-
export function CommandInput(props) {
|
|
271
|
-
const { context, onCommand, confirmState, searchState, searchValue, onSearchChange, onSearchSubmit, onSearchCancel, onConfirmYes, onConfirmCancel, onCopy, onPanelAction, navOwner, onInputStateChange, } = props;
|
|
272
|
-
return (_jsx(Box, { flexDirection: "column", height: COMMAND_INPUT_HEIGHT, borderStyle: "single", borderColor: theme.border.dim, paddingY: 1, children: searchState?.active ? (_jsx(SearchMode, { value: searchValue, onSearchChange: onSearchChange, onSearchSubmit: onSearchSubmit, onSearchCancel: onSearchCancel, disabled: props.disabled })) : confirmState === null ? (_jsx(CommandMode, { context: context, onCommand: onCommand, onCopy: onCopy, onPanelAction: onPanelAction, disabled: props.disabled, navOwner: navOwner, onInputStateChange: onInputStateChange })) : (_jsx(ConfirmMode, { confirmState: confirmState, onConfirmYes: onConfirmYes, onConfirmCancel: onConfirmCancel, disabled: props.disabled })) }));
|
|
273
|
-
}
|