loop-task 2.0.2 → 2.0.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/config/constants.js +10 -0
- package/dist/i18n/en.json +12 -0
- package/dist/loop-config.js +59 -0
- package/dist/shared/useUndoRedo.js +79 -0
- package/dist/tui/components/CodeEditorModal.js +245 -0
- package/dist/tui/components/CodeEditorPreview.js +14 -0
- package/dist/tui/components/CreateForm.js +12 -10
- package/dist/tui/components/TaskForm.js +12 -12
- package/dist/tui/utils/syntax.js +128 -0
- package/package.json +2 -2
- package/dist/board/App.js +0 -405
- package/dist/board/board-log.js +0 -18
- package/dist/board/components/ActionButtons.js +0 -46
- package/dist/board/components/BoardButton.js +0 -58
- package/dist/board/components/ConfirmModal.js +0 -36
- package/dist/board/components/ContextHelpModal.js +0 -22
- package/dist/board/components/CreateForm.js +0 -271
- package/dist/board/components/CreateProjectModal.js +0 -97
- package/dist/board/components/DeleteProjectConfirm.js +0 -53
- package/dist/board/components/DetailView.js +0 -25
- package/dist/board/components/EditProjectModal.js +0 -99
- package/dist/board/components/FilterBar.js +0 -17
- package/dist/board/components/Footer.js +0 -52
- package/dist/board/components/Header.js +0 -46
- package/dist/board/components/HelpModal.js +0 -65
- package/dist/board/components/Inspector.js +0 -12
- package/dist/board/components/LogModal.js +0 -82
- package/dist/board/components/Navigator.js +0 -84
- package/dist/board/components/ProjectsModal.js +0 -70
- package/dist/board/components/ProjectsPage.js +0 -134
- package/dist/board/components/RunHistory.js +0 -83
- package/dist/board/components/SearchBox.js +0 -10
- package/dist/board/components/SearchSelect.js +0 -132
- package/dist/board/components/TaskBrowser.js +0 -95
- package/dist/board/components/TaskFilterBar.js +0 -6
- package/dist/board/components/TaskForm.js +0 -155
- package/dist/board/components/Timeline.js +0 -5
- package/dist/board/daemon.js +0 -117
- package/dist/board/focus-context.js +0 -98
- package/dist/board/hooks/useBoardKeybindings.js +0 -344
- package/dist/board/hooks/useBreakpoint.js +0 -6
- package/dist/board/hooks/useHoverState.js +0 -11
- package/dist/board/hooks/useInputShortcuts.js +0 -61
- package/dist/board/hooks/useLogStream.js +0 -32
- package/dist/board/hooks/useLoopPolling.js +0 -28
- package/dist/board/hooks/useTabNav.js +0 -60
- package/dist/board/hooks/useTaskKeybindings.js +0 -120
- package/dist/board/index.js +0 -32
- package/dist/board/router.js +0 -16
- package/dist/board/state.js +0 -99
- package/dist/board/toast.js +0 -65
- package/dist/board/types.js +0 -1
- package/dist/cli.d.ts +0 -3
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/client/commands.d.ts +0 -11
- package/dist/client/commands.d.ts.map +0 -1
- package/dist/client/commands.js.map +0 -1
- package/dist/client/ipc.d.ts +0 -5
- package/dist/client/ipc.d.ts.map +0 -1
- package/dist/client/ipc.js.map +0 -1
- package/dist/daemon/index.d.ts +0 -2
- package/dist/daemon/index.d.ts.map +0 -1
- package/dist/daemon/index.js.map +0 -1
- package/dist/daemon/manager.d.ts +0 -17
- package/dist/daemon/manager.d.ts.map +0 -1
- package/dist/daemon/manager.js.map +0 -1
- package/dist/daemon/server.d.ts +0 -15
- package/dist/daemon/server.d.ts.map +0 -1
- package/dist/daemon/server.js.map +0 -1
- package/dist/daemon/spawner.d.ts +0 -3
- package/dist/daemon/spawner.d.ts.map +0 -1
- package/dist/daemon/spawner.js.map +0 -1
- package/dist/daemon/state.d.ts +0 -15
- package/dist/daemon/state.d.ts.map +0 -1
- package/dist/daemon/state.js.map +0 -1
- package/dist/duration.d.ts +0 -3
- package/dist/duration.d.ts.map +0 -1
- package/dist/duration.js.map +0 -1
- package/dist/logger.d.ts +0 -11
- package/dist/logger.d.ts.map +0 -1
- package/dist/logger.js.map +0 -1
- package/dist/loop-config.d.ts +0 -13
- package/dist/loop-config.d.ts.map +0 -1
- package/dist/loop-config.js.map +0 -1
- package/dist/loop.d.ts +0 -46
- package/dist/loop.d.ts.map +0 -1
- package/dist/loop.js +0 -363
- package/dist/loop.js.map +0 -1
- package/dist/open-board.d.ts +0 -2
- package/dist/open-board.d.ts.map +0 -1
- package/dist/open-board.js +0 -19
- package/dist/open-board.js.map +0 -1
- package/dist/tui/app.d.ts +0 -57
- package/dist/tui/app.d.ts.map +0 -1
- package/dist/tui/app.js.map +0 -1
- package/dist/tui/components/ActionButtons.js +0 -49
- package/dist/tui/components/CommandBuilderField.js +0 -143
- package/dist/tui/components/CommandEditorModal.js +0 -159
- package/dist/tui/components/ConfirmModal.js +0 -9
- package/dist/tui/components/FilterBar.js +0 -13
- package/dist/tui/components/FocusableSearchSelect.js +0 -82
- package/dist/tui/components/Footer.js +0 -42
- package/dist/tui/components/InlineCommandEditor.js +0 -143
- package/dist/tui/components/PatchEditForm.js +0 -78
- package/dist/tui/components/SearchSelect.js +0 -67
- package/dist/tui/components/TaskFilterBar.js +0 -9
- package/dist/tui/create-loop.d.ts +0 -3
- package/dist/tui/create-loop.d.ts.map +0 -1
- package/dist/tui/create-loop.js +0 -90
- package/dist/tui/create-loop.js.map +0 -1
- package/dist/tui/dashboard.d.ts +0 -2
- package/dist/tui/dashboard.d.ts.map +0 -1
- package/dist/tui/dashboard.js +0 -45
- package/dist/tui/dashboard.js.map +0 -1
- package/dist/tui/events.d.ts +0 -3
- package/dist/tui/events.d.ts.map +0 -1
- package/dist/tui/events.js +0 -244
- package/dist/tui/events.js.map +0 -1
- package/dist/tui/popup.d.ts +0 -3
- package/dist/tui/popup.d.ts.map +0 -1
- package/dist/tui/popup.js +0 -58
- package/dist/tui/popup.js.map +0 -1
- package/dist/tui/render.d.ts +0 -9
- package/dist/tui/render.d.ts.map +0 -1
- package/dist/tui/render.js +0 -305
- package/dist/tui/render.js.map +0 -1
- package/dist/types.d.ts +0 -94
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
- /package/dist/tui/{app.js → App.js} +0 -0
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { Box, Text } from "ink";
|
|
3
|
-
import { darkTheme as theme } from "../theme.js";
|
|
4
|
-
import { t } from "../../i18n/index.js";
|
|
5
|
-
import { FocusableButton } from "./FocusableButton.js";
|
|
6
|
-
const STOP_ACTION = { key: "stop", label: t("board.actionStop") };
|
|
7
|
-
const PAUSE_ACTION = { key: "pause", label: t("board.actionPause") };
|
|
8
|
-
const PLAY_ACTION = { key: "play", label: t("board.actionPlay") };
|
|
9
|
-
const STATUS_ACTIONS = {
|
|
10
|
-
running: [STOP_ACTION],
|
|
11
|
-
waiting: [PAUSE_ACTION, STOP_ACTION],
|
|
12
|
-
paused: [STOP_ACTION, PLAY_ACTION],
|
|
13
|
-
idle: [PLAY_ACTION],
|
|
14
|
-
stopped: [PLAY_ACTION],
|
|
15
|
-
};
|
|
16
|
-
export function getActions(status) {
|
|
17
|
-
const actions = [
|
|
18
|
-
{ key: "edit", label: t("board.actionEdit") },
|
|
19
|
-
{ key: "delete", label: t("board.actionDelete") },
|
|
20
|
-
];
|
|
21
|
-
actions.push(...STATUS_ACTIONS[status]);
|
|
22
|
-
if (status !== "running") {
|
|
23
|
-
actions.push({ key: "clone", label: t("board.actionClone") });
|
|
24
|
-
actions.push({ key: "trigger", label: t("board.actionTrigger") });
|
|
25
|
-
}
|
|
26
|
-
return actions;
|
|
27
|
-
}
|
|
28
|
-
export function getActionKeys(status) {
|
|
29
|
-
return getActions(status).map((a) => a.key);
|
|
30
|
-
}
|
|
31
|
-
export function getActionCount(status) {
|
|
32
|
-
return getActions(status).length;
|
|
33
|
-
}
|
|
34
|
-
export function ActionButtons(props) {
|
|
35
|
-
const { loop, onAction } = props;
|
|
36
|
-
const actions = loop ? getActions(loop.status) : [];
|
|
37
|
-
if (!loop) {
|
|
38
|
-
return (_jsx(Box, { borderStyle: "single", borderColor: theme.border.default, flexDirection: "row", height: 3, flexShrink: 0, backgroundColor: theme.bg.surface, alignItems: "center", children: _jsx(Text, { color: theme.text.muted, children: t("board.noActions") }) }));
|
|
39
|
-
}
|
|
40
|
-
return (_jsx(Box, { borderStyle: "single", borderColor: theme.border.default, flexDirection: "row", flexShrink: 0, backgroundColor: theme.bg.surface, alignItems: "center", paddingX: 1, children: actions.map((action) => {
|
|
41
|
-
const color = action.key === "delete" || action.key === "stop"
|
|
42
|
-
? theme.semantic.danger
|
|
43
|
-
: theme.accent.focus;
|
|
44
|
-
const variant = action.key === "delete" || action.key === "stop"
|
|
45
|
-
? "danger"
|
|
46
|
-
: "default";
|
|
47
|
-
return (_jsx(FocusableButton, { label: action.label, color: color, variant: variant, bordered: false, onPress: () => onAction(action.key) }, action.key));
|
|
48
|
-
}) }));
|
|
49
|
-
}
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useState, useCallback } 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 { copyToClipboard } from "../../shared/clipboard.js";
|
|
7
|
-
import { COMMAND_TEMPLATES } from "../../config/constants.js";
|
|
8
|
-
function hasUnbalancedQuotes(text) {
|
|
9
|
-
let single = 0;
|
|
10
|
-
let double = 0;
|
|
11
|
-
for (const ch of text) {
|
|
12
|
-
if (ch === "'")
|
|
13
|
-
single++;
|
|
14
|
-
if (ch === '"')
|
|
15
|
-
double++;
|
|
16
|
-
}
|
|
17
|
-
return single % 2 !== 0 || double % 2 !== 0;
|
|
18
|
-
}
|
|
19
|
-
function parseParts(command) {
|
|
20
|
-
const trimmed = command.trim();
|
|
21
|
-
if (!trimmed)
|
|
22
|
-
return { executable: "", args: "" };
|
|
23
|
-
const firstSpace = trimmed.indexOf(" ");
|
|
24
|
-
if (firstSpace === -1)
|
|
25
|
-
return { executable: trimmed, args: "" };
|
|
26
|
-
return { executable: trimmed.slice(0, firstSpace), args: trimmed.slice(firstSpace + 1) };
|
|
27
|
-
}
|
|
28
|
-
export function CommandBuilderField(props) {
|
|
29
|
-
const { isActive, onChange, onAdvance } = props;
|
|
30
|
-
const initial = parseParts(props.value);
|
|
31
|
-
const [executable, setExecutable] = useState(initial.executable);
|
|
32
|
-
const [args, setArgs] = useState(initial.args);
|
|
33
|
-
const [activeSub, setActiveSub] = useState(initial.executable ? "args" : "executable");
|
|
34
|
-
const [templateIdx, setTemplateIdx] = useState(0);
|
|
35
|
-
const [validationMsg, setValidationMsg] = useState(null);
|
|
36
|
-
const assembled = [executable, args].filter(Boolean).join(" ");
|
|
37
|
-
const commitValue = useCallback(() => {
|
|
38
|
-
onChange(assembled);
|
|
39
|
-
}, [assembled, onChange]);
|
|
40
|
-
const handleTemplateSelect = useCallback(() => {
|
|
41
|
-
const tmpl = COMMAND_TEMPLATES[templateIdx];
|
|
42
|
-
if (!tmpl)
|
|
43
|
-
return;
|
|
44
|
-
setExecutable(tmpl.command);
|
|
45
|
-
setArgs(tmpl.args);
|
|
46
|
-
setActiveSub("args");
|
|
47
|
-
const next = [tmpl.command, tmpl.args].filter(Boolean).join(" ");
|
|
48
|
-
onChange(next);
|
|
49
|
-
}, [templateIdx, onChange]);
|
|
50
|
-
useInput((input, key) => {
|
|
51
|
-
if (!isActive)
|
|
52
|
-
return;
|
|
53
|
-
if (activeSub === "templates") {
|
|
54
|
-
if (key.upArrow) {
|
|
55
|
-
setTemplateIdx((prev) => (prev > 0 ? prev - 1 : COMMAND_TEMPLATES.length - 1));
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
if (key.downArrow) {
|
|
59
|
-
setTemplateIdx((prev) => (prev < COMMAND_TEMPLATES.length - 1 ? prev + 1 : 0));
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
if (key.return) {
|
|
63
|
-
handleTemplateSelect();
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
if (key.escape) {
|
|
67
|
-
setActiveSub("executable");
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
if (key.ctrl && input === "y") {
|
|
73
|
-
copyToClipboard(assembled);
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
if (key.escape) {
|
|
77
|
-
commitValue();
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
if (activeSub === "executable") {
|
|
81
|
-
if (key.return || key.tab) {
|
|
82
|
-
if (!executable.trim()) {
|
|
83
|
-
setValidationMsg(t("errors.commandEmpty"));
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
setValidationMsg(null);
|
|
87
|
-
setActiveSub("args");
|
|
88
|
-
commitValue();
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
if (key.delete || key.backspace) {
|
|
92
|
-
const next = executable.slice(0, -1);
|
|
93
|
-
setExecutable(next);
|
|
94
|
-
if (next.trim())
|
|
95
|
-
setValidationMsg(null);
|
|
96
|
-
onChange([next, args].filter(Boolean).join(" "));
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
if (input.length === 1 && input >= " " && input <= "~") {
|
|
100
|
-
const next = executable + input;
|
|
101
|
-
setExecutable(next);
|
|
102
|
-
if (next.trim())
|
|
103
|
-
setValidationMsg(null);
|
|
104
|
-
if (!next.trim())
|
|
105
|
-
setActiveSub("templates");
|
|
106
|
-
onChange([next, args].filter(Boolean).join(" "));
|
|
107
|
-
}
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
if (activeSub === "args") {
|
|
111
|
-
if (key.return) {
|
|
112
|
-
if (hasUnbalancedQuotes(args)) {
|
|
113
|
-
setValidationMsg(t("errors.unbalancedQuote"));
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
setValidationMsg(null);
|
|
117
|
-
commitValue();
|
|
118
|
-
onAdvance();
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
if (key.delete || key.backspace) {
|
|
122
|
-
const next = args.slice(0, -1);
|
|
123
|
-
setArgs(next);
|
|
124
|
-
onChange([executable, next].filter(Boolean).join(" "));
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
if (input.length === 1 && input >= " " && input <= "~") {
|
|
128
|
-
const next = args + input;
|
|
129
|
-
setArgs(next);
|
|
130
|
-
onChange([executable, next].filter(Boolean).join(" "));
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}, { isActive });
|
|
134
|
-
const execBorderColor = isActive && activeSub === "executable" ? theme.accent.brand : theme.border.dim;
|
|
135
|
-
const execBgColor = isActive && activeSub === "executable" ? theme.bg.input : undefined;
|
|
136
|
-
const argsBorderColor = isActive && activeSub === "args" ? theme.accent.brand : theme.border.dim;
|
|
137
|
-
const argsBgColor = isActive && activeSub === "args" ? theme.bg.input : undefined;
|
|
138
|
-
const showTemplates = isActive && activeSub === "executable" && !executable.trim();
|
|
139
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { flexDirection: "row", gap: 1, children: [_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Text, { color: theme.text.muted, children: t("commandBuilder.executableLabel") }), _jsxs(Box, { borderStyle: "single", borderColor: execBorderColor, backgroundColor: execBgColor, paddingLeft: 1, children: [_jsx(Text, { color: executable ? theme.text.primary : theme.text.muted, children: executable || t("commandBuilder.executableHint") }), isActive && activeSub === "executable" ? _jsx(Text, { inverse: true, children: " " }) : null] })] }), _jsxs(Box, { flexDirection: "column", flexGrow: 2, children: [_jsx(Text, { color: theme.text.muted, children: t("commandBuilder.argsLabel") }), _jsxs(Box, { borderStyle: "single", borderColor: argsBorderColor, backgroundColor: argsBgColor, paddingLeft: 1, children: [_jsx(Text, { color: args ? theme.text.primary : theme.text.muted, children: args || t("commandBuilder.argsHint") }), isActive && activeSub === "args" ? _jsx(Text, { inverse: true, children: " " }) : null] })] })] }), assembled ? (_jsxs(Box, { marginTop: 1, children: [_jsxs(Text, { color: theme.text.muted, children: [t("commandBuilder.previewLabel"), " "] }), _jsx(Text, { color: theme.accent.task, bold: true, children: assembled })] })) : null, isActive ? (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: theme.text.muted, children: [t("commandBuilder.copyHint"), " . ", t("commandBuilder.navHint")] }) })) : null, validationMsg ? (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.semantic.danger, children: validationMsg }) })) : null, showTemplates ? (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: theme.text.muted, children: t("commandBuilder.templatesLabel") }), COMMAND_TEMPLATES.map((tmpl, i) => {
|
|
140
|
-
const isSel = i === templateIdx;
|
|
141
|
-
return (_jsxs(Box, { children: [_jsx(Text, { color: isSel ? theme.accent.brand : theme.text.muted, children: isSel ? "\u276F " : " " }), _jsx(Text, { color: isSel ? theme.accent.brand : theme.text.secondary, children: tmpl.label }), _jsxs(Text, { color: theme.text.muted, children: [" \u2014 ", tmpl.command, " ", tmpl.args] })] }, tmpl.label));
|
|
142
|
-
})] })) : null] }));
|
|
143
|
-
}
|
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useState, useCallback } 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
|
-
const MAX_VISIBLE_LINES = 14;
|
|
7
|
-
export function CommandEditorModal(props) {
|
|
8
|
-
const [lines, setLines] = useState(() => {
|
|
9
|
-
const initial = props.initial.trim();
|
|
10
|
-
if (!initial)
|
|
11
|
-
return [""];
|
|
12
|
-
return initial.split(/\r?\n/);
|
|
13
|
-
});
|
|
14
|
-
const [cursorRow, setCursorRow] = useState(0);
|
|
15
|
-
const [cursorCol, setCursorCol] = useState(0);
|
|
16
|
-
const [scrollOffset, setScrollOffset] = useState(0);
|
|
17
|
-
const currentLine = lines[cursorRow] ?? "";
|
|
18
|
-
const commit = useCallback(() => {
|
|
19
|
-
const value = lines.join("\n").trim();
|
|
20
|
-
props.onSelect(value);
|
|
21
|
-
}, [lines, props]);
|
|
22
|
-
const insertChar = useCallback((ch) => {
|
|
23
|
-
setLines((prev) => {
|
|
24
|
-
const next = [...prev];
|
|
25
|
-
const line = next[cursorRow] ?? "";
|
|
26
|
-
next[cursorRow] = line.slice(0, cursorCol) + ch + line.slice(cursorCol);
|
|
27
|
-
return next;
|
|
28
|
-
});
|
|
29
|
-
setCursorCol((c) => c + 1);
|
|
30
|
-
}, [cursorRow, cursorCol]);
|
|
31
|
-
const handleBackspace = useCallback(() => {
|
|
32
|
-
if (cursorCol > 0) {
|
|
33
|
-
setLines((prev) => {
|
|
34
|
-
const next = [...prev];
|
|
35
|
-
const line = next[cursorRow] ?? "";
|
|
36
|
-
next[cursorRow] = line.slice(0, cursorCol - 1) + line.slice(cursorCol);
|
|
37
|
-
return next;
|
|
38
|
-
});
|
|
39
|
-
setCursorCol((c) => c - 1);
|
|
40
|
-
}
|
|
41
|
-
else if (cursorRow > 0) {
|
|
42
|
-
const prevLine = lines[cursorRow - 1] ?? "";
|
|
43
|
-
const merged = prevLine + currentLine;
|
|
44
|
-
setLines((prev) => {
|
|
45
|
-
const next = [...prev];
|
|
46
|
-
next[cursorRow - 1] = merged;
|
|
47
|
-
next.splice(cursorRow, 1);
|
|
48
|
-
return next;
|
|
49
|
-
});
|
|
50
|
-
setCursorRow((r) => r - 1);
|
|
51
|
-
setCursorCol(prevLine.length);
|
|
52
|
-
}
|
|
53
|
-
}, [cursorCol, cursorRow, currentLine, lines]);
|
|
54
|
-
const handleEnter = useCallback(() => {
|
|
55
|
-
setLines((prev) => {
|
|
56
|
-
const next = [...prev];
|
|
57
|
-
const line = next[cursorRow] ?? "";
|
|
58
|
-
next.splice(cursorRow, 1, line.slice(0, cursorCol), line.slice(cursorCol));
|
|
59
|
-
return next;
|
|
60
|
-
});
|
|
61
|
-
setCursorRow((r) => r + 1);
|
|
62
|
-
setCursorCol(0);
|
|
63
|
-
}, [cursorRow, cursorCol]);
|
|
64
|
-
const moveUp = useCallback(() => {
|
|
65
|
-
if (cursorRow > 0) {
|
|
66
|
-
setCursorRow((r) => r - 1);
|
|
67
|
-
setCursorCol((c) => Math.min(c, (lines[cursorRow - 1] ?? "").length));
|
|
68
|
-
if (cursorRow - 1 < scrollOffset) {
|
|
69
|
-
setScrollOffset((s) => Math.max(0, s - 1));
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}, [cursorRow, cursorCol, lines, scrollOffset]);
|
|
73
|
-
const moveDown = useCallback(() => {
|
|
74
|
-
if (cursorRow < lines.length - 1) {
|
|
75
|
-
setCursorRow((r) => r + 1);
|
|
76
|
-
setCursorCol((c) => Math.min(c, (lines[cursorRow + 1] ?? "").length));
|
|
77
|
-
if (cursorRow + 1 >= scrollOffset + MAX_VISIBLE_LINES) {
|
|
78
|
-
setScrollOffset((s) => s + 1);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}, [cursorRow, cursorCol, lines, scrollOffset]);
|
|
82
|
-
const moveLeft = useCallback(() => {
|
|
83
|
-
if (cursorCol > 0) {
|
|
84
|
-
setCursorCol((c) => c - 1);
|
|
85
|
-
}
|
|
86
|
-
else if (cursorRow > 0) {
|
|
87
|
-
const prevLine = lines[cursorRow - 1] ?? "";
|
|
88
|
-
setCursorRow((r) => r - 1);
|
|
89
|
-
setCursorCol(prevLine.length);
|
|
90
|
-
}
|
|
91
|
-
}, [cursorCol, cursorRow, lines]);
|
|
92
|
-
const moveRight = useCallback(() => {
|
|
93
|
-
if (cursorCol < currentLine.length) {
|
|
94
|
-
setCursorCol((c) => c + 1);
|
|
95
|
-
}
|
|
96
|
-
else if (cursorRow < lines.length - 1) {
|
|
97
|
-
setCursorRow((r) => r + 1);
|
|
98
|
-
setCursorCol(0);
|
|
99
|
-
}
|
|
100
|
-
}, [cursorCol, cursorRow, currentLine, lines]);
|
|
101
|
-
useInput((input, key) => {
|
|
102
|
-
if (key.ctrl) {
|
|
103
|
-
if (input === "s") {
|
|
104
|
-
commit();
|
|
105
|
-
}
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
if (key.escape) {
|
|
109
|
-
props.onClose();
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
if (key.return) {
|
|
113
|
-
handleEnter();
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
if (key.upArrow) {
|
|
117
|
-
moveUp();
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
if (key.downArrow) {
|
|
121
|
-
moveDown();
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
if (key.leftArrow) {
|
|
125
|
-
moveLeft();
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
if (key.rightArrow) {
|
|
129
|
-
moveRight();
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
if (key.backspace || key.delete) {
|
|
133
|
-
handleBackspace();
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
if (input.length === 1 && input >= " " && input <= "~") {
|
|
137
|
-
insertChar(input);
|
|
138
|
-
}
|
|
139
|
-
});
|
|
140
|
-
const totalLines = lines.length;
|
|
141
|
-
const visibleStart = scrollOffset;
|
|
142
|
-
const visibleEnd = Math.min(totalLines, visibleStart + MAX_VISIBLE_LINES);
|
|
143
|
-
const visibleLines = lines.slice(visibleStart, visibleEnd);
|
|
144
|
-
const lineNumWidth = String(totalLines).length;
|
|
145
|
-
function renderLine(lineText, rowIdx) {
|
|
146
|
-
const isCursor = rowIdx === cursorRow;
|
|
147
|
-
const displayRow = rowIdx + 1;
|
|
148
|
-
const prefix = String(displayRow).padStart(lineNumWidth, " ");
|
|
149
|
-
const lineNumColor = isCursor ? theme.accent.brand : theme.text.muted;
|
|
150
|
-
if (isCursor) {
|
|
151
|
-
const before = lineText.slice(0, cursorCol);
|
|
152
|
-
const char = cursorCol < lineText.length ? lineText[cursorCol] : " ";
|
|
153
|
-
const after = cursorCol < lineText.length ? lineText.slice(cursorCol + 1) : "";
|
|
154
|
-
return (_jsxs(Box, { children: [_jsxs(Text, { color: lineNumColor, children: [prefix, " "] }), _jsx(Text, { color: theme.text.primary, children: before }), _jsx(Text, { inverse: true, children: char }), _jsx(Text, { color: theme.text.primary, children: after })] }, rowIdx));
|
|
155
|
-
}
|
|
156
|
-
return (_jsxs(Box, { children: [_jsxs(Text, { color: lineNumColor, children: [prefix, " "] }), _jsx(Text, { color: theme.text.secondary, children: lineText })] }, rowIdx));
|
|
157
|
-
}
|
|
158
|
-
return (_jsx(Box, { position: "absolute", top: 0, left: 0, width: "100%", height: "100%", justifyContent: "center", alignItems: "center", children: _jsxs(Box, { width: 70, flexDirection: "column", backgroundColor: theme.bg.elevated, borderStyle: "round", borderColor: theme.accent.brand, paddingX: 1, paddingY: 0, children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsx(Text, { color: theme.accent.brand, bold: true, children: t("cmdEditor.title") }), _jsxs(Text, { color: theme.text.muted, children: [totalLines, " ", t("cmdEditor.lines")] })] }), _jsx(Box, { flexDirection: "column", marginTop: 0, children: visibleLines.map((line, visIdx) => renderLine(line, visibleStart + visIdx)) }), visibleEnd < totalLines ? (_jsx(Text, { color: theme.text.muted, children: "\u2193 " + (totalLines - visibleEnd) + " more" })) : null, _jsxs(Box, { marginTop: 1, justifyContent: "space-between", children: [_jsx(Text, { color: theme.text.muted, children: t("cmdEditor.hint") }), _jsxs(Text, { color: theme.text.muted, children: [visibleEnd, "/", totalLines] })] })] }) }));
|
|
159
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Box } from "ink";
|
|
3
|
-
import { darkTheme as theme } from "../theme.js";
|
|
4
|
-
import { Modal } from "./Modal.js";
|
|
5
|
-
import { FocusableButton } from "./FocusableButton.js";
|
|
6
|
-
import { t } from "../../i18n/index.js";
|
|
7
|
-
export function ConfirmModal(props) {
|
|
8
|
-
return (_jsx(Modal, { title: props.message, onClose: props.onNo, width: "50%", children: _jsxs(Box, { flexDirection: "row", marginTop: 1, children: [_jsx(FocusableButton, { label: t("board.yes"), color: theme.accent.focus, onPress: props.onYes }), _jsx(FocusableButton, { label: t("board.no"), color: theme.semantic.danger, onPress: props.onNo, variant: "danger" })] }) }));
|
|
9
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Box, Text } from "ink";
|
|
3
|
-
import { darkTheme as theme } from "../theme.js";
|
|
4
|
-
import { t } from "../../i18n/index.js";
|
|
5
|
-
import { FocusableButton } from "./FocusableButton.js";
|
|
6
|
-
import { FocusableInput } from "./FocusableInput.js";
|
|
7
|
-
export function FilterBar(props) {
|
|
8
|
-
const { filters, sort, onStatusCycle, onSortCycle, onSelectProject, currentProjectName, onQueryChange, onSearchDismiss, } = props;
|
|
9
|
-
const statusLabel = `${t("board.statusFilterTitle")} ${filters.status}`;
|
|
10
|
-
const sortLabel = `${t("board.sortTitle")} ${sort}`;
|
|
11
|
-
const projectLabel = currentProjectName ?? t("project.showAll");
|
|
12
|
-
return (_jsxs(Box, { flexDirection: "column", height: 4, children: [_jsxs(Box, { children: [_jsx(Box, { width: currentProjectName ? 36 : 48, children: _jsx(FocusableInput, { value: filters.query, onChange: onQueryChange, onSubmit: onSearchDismiss, placeholder: t("board.searchPlaceholder") }) }), onSelectProject ? (_jsx(Box, { marginLeft: 1, children: _jsx(FocusableButton, { label: projectLabel, color: theme.accent.project, onPress: onSelectProject }) })) : null, _jsx(Box, { marginLeft: 1, children: _jsx(FocusableButton, { label: statusLabel, color: theme.accent.focus, onPress: onStatusCycle }) }), _jsx(Box, { marginLeft: 1, children: _jsx(FocusableButton, { label: sortLabel, color: theme.accent.focus, onPress: onSortCycle }) })] }), _jsx(Box, { children: _jsx(Text, { color: theme.text.muted, children: t("board.searchEmpty") }) })] }));
|
|
13
|
-
}
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useState, useMemo } from "react";
|
|
3
|
-
import { Box, Text, useFocus, useInput } from "ink";
|
|
4
|
-
import { darkTheme as theme } from "../theme.js";
|
|
5
|
-
const MAX_VISIBLE = 6;
|
|
6
|
-
export function FocusableSearchSelect(props) {
|
|
7
|
-
const { options, value, onChange } = props;
|
|
8
|
-
const placeholder = props.placeholder ?? "type to filter...";
|
|
9
|
-
const { isFocused } = useFocus();
|
|
10
|
-
const [filter, setFilter] = useState("");
|
|
11
|
-
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
12
|
-
const filtered = useMemo(() => {
|
|
13
|
-
if (!filter)
|
|
14
|
-
return options;
|
|
15
|
-
const q = filter.toLowerCase();
|
|
16
|
-
return options.filter((o) => o.name.toLowerCase().includes(q) ||
|
|
17
|
-
o.value.toLowerCase().includes(q));
|
|
18
|
-
}, [options, filter]);
|
|
19
|
-
const clampedIndex = Math.min(selectedIndex, Math.max(0, filtered.length - 1));
|
|
20
|
-
const visibleStart = clampedIndex < MAX_VISIBLE
|
|
21
|
-
? 0
|
|
22
|
-
: clampedIndex - MAX_VISIBLE + 1;
|
|
23
|
-
const visible = filtered.slice(visibleStart, visibleStart + MAX_VISIBLE);
|
|
24
|
-
useInput((input, key) => {
|
|
25
|
-
if (key.upArrow) {
|
|
26
|
-
setSelectedIndex((prev) => prev <= 0 ? filtered.length - 1 : prev - 1);
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
if (key.downArrow) {
|
|
30
|
-
setSelectedIndex((prev) => prev >= filtered.length - 1 ? 0 : prev + 1);
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
if (key.return) {
|
|
34
|
-
const option = filtered[clampedIndex];
|
|
35
|
-
if (option) {
|
|
36
|
-
onChange(option.value);
|
|
37
|
-
}
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
if (key.escape) {
|
|
41
|
-
setFilter("");
|
|
42
|
-
setSelectedIndex(0);
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
if (key.backspace || key.delete) {
|
|
46
|
-
setFilter((f) => f.slice(0, -1));
|
|
47
|
-
setSelectedIndex(0);
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
if (input &&
|
|
51
|
-
!key.ctrl &&
|
|
52
|
-
!key.meta &&
|
|
53
|
-
input.length === 1 &&
|
|
54
|
-
/[a-z0-9 _\-./]/i.test(input)) {
|
|
55
|
-
setFilter((f) => f + input);
|
|
56
|
-
setSelectedIndex(0);
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
}, { isActive: isFocused });
|
|
60
|
-
const borderColor = isFocused ? theme.accent.focus : theme.border.default;
|
|
61
|
-
const backgroundColor = isFocused
|
|
62
|
-
? theme.bg.input
|
|
63
|
-
: theme.bg.surface;
|
|
64
|
-
const selectedOption = options.find((o) => o.value === value);
|
|
65
|
-
const displayLabel = selectedOption?.name ?? placeholder;
|
|
66
|
-
if (!isFocused) {
|
|
67
|
-
return (_jsx(Box, { borderStyle: "single", borderColor: theme.border.dim, paddingLeft: 1, children: _jsx(Text, { color: value ? theme.text.primary : theme.text.muted, children: displayLabel }) }));
|
|
68
|
-
}
|
|
69
|
-
return (_jsxs(Box, { borderStyle: "single", borderColor: borderColor, backgroundColor: backgroundColor, flexDirection: "column", children: [_jsxs(Box, { paddingLeft: 1, children: [_jsx(Text, { color: theme.text.muted, children: "/ " }), _jsx(Text, { color: filter ? theme.text.primary : theme.text.muted, children: filter || placeholder })] }), filtered.length > 0 ? (_jsx(Box, { flexDirection: "column", children: visible.map((option, i) => {
|
|
70
|
-
const realIndex = visibleStart + i;
|
|
71
|
-
const isSelected = realIndex === clampedIndex;
|
|
72
|
-
const isActive = option.value === value;
|
|
73
|
-
const prefix = isSelected ? "\u203a " : " ";
|
|
74
|
-
const bg = isSelected ? theme.bg.active : undefined;
|
|
75
|
-
const fg = isSelected
|
|
76
|
-
? theme.text.inverse
|
|
77
|
-
: isActive
|
|
78
|
-
? theme.accent.focus
|
|
79
|
-
: option.color ?? theme.text.secondary;
|
|
80
|
-
return (_jsx(Box, { backgroundColor: bg, children: _jsxs(Text, { color: fg, children: [prefix, option.name] }) }, option.value));
|
|
81
|
-
}) })) : null] }));
|
|
82
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { Box, Text } from "ink";
|
|
3
|
-
import { darkTheme as theme } from "../theme.js";
|
|
4
|
-
function modeBadgeColor(mode) {
|
|
5
|
-
switch (mode) {
|
|
6
|
-
case "normal": return "#38bdf8";
|
|
7
|
-
case "search": return "#38bdf8";
|
|
8
|
-
case "create": return "#38bdf8";
|
|
9
|
-
case "task": return "#a78bfa";
|
|
10
|
-
case "help": return "#38bdf8";
|
|
11
|
-
case "confirm": return "#f87171";
|
|
12
|
-
case "projects": return "#34d399";
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
function modeLabelText(mode) {
|
|
16
|
-
switch (mode) {
|
|
17
|
-
case "normal": return "Loops";
|
|
18
|
-
case "search": return "Search";
|
|
19
|
-
case "create": return "Create";
|
|
20
|
-
case "task": return "Tasks";
|
|
21
|
-
case "help": return "Help";
|
|
22
|
-
case "confirm": return "Confirm";
|
|
23
|
-
case "projects": return "Projects";
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
function modeHints(mode) {
|
|
27
|
-
switch (mode) {
|
|
28
|
-
case "normal": return "Tab:panel /:search h:help Ctrl+C:quit";
|
|
29
|
-
case "search": return "Enter:apply Esc:cancel";
|
|
30
|
-
case "create": return "Tab:next Enter:save Esc:cancel";
|
|
31
|
-
case "task": return "Tab:next Enter:save Esc:cancel";
|
|
32
|
-
case "help": return "Esc:close";
|
|
33
|
-
case "confirm": return "arrows:choose Enter:confirm y/n Esc:cancel";
|
|
34
|
-
case "projects": return "Tab:navigate n:new e:edit d:delete Esc:back";
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
export function Footer(props) {
|
|
38
|
-
const badgeColor = modeBadgeColor(props.mode);
|
|
39
|
-
const label = modeLabelText(props.mode);
|
|
40
|
-
const hints = modeHints(props.mode);
|
|
41
|
-
return (_jsxs(Box, { height: 1, justifyContent: "space-between", children: [_jsx(Box, { children: _jsxs(Text, { backgroundColor: badgeColor, color: theme.bg.base, bold: true, children: [" ", label, " "] }) }), _jsx(Box, { children: _jsx(Text, { color: theme.text.muted, children: hints }) })] }));
|
|
42
|
-
}
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useState, useCallback } 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 { copyToClipboard } from "../../shared/clipboard.js";
|
|
7
|
-
import { sanitizePaste } from "../utils/paste.js";
|
|
8
|
-
const MAX_VISIBLE = 8;
|
|
9
|
-
export function InlineCommandEditor({ value, hint, isActive, onChange, }) {
|
|
10
|
-
const [lines, setLines] = useState(() => value ? value.split("\n") : [""]);
|
|
11
|
-
const [cursorRow, setCursorRow] = useState(0);
|
|
12
|
-
const [cursorCol, setCursorCol] = useState(0);
|
|
13
|
-
const emit = useCallback((nextLines) => {
|
|
14
|
-
onChange(nextLines.join("\n"));
|
|
15
|
-
}, [onChange]);
|
|
16
|
-
useInput((input, key) => {
|
|
17
|
-
// Ctrl+Y: copy full command text
|
|
18
|
-
if (key.ctrl && input === "y") {
|
|
19
|
-
copyToClipboard(value);
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
// Let WizardForm handle these
|
|
23
|
-
if (key.ctrl || key.escape || key.tab)
|
|
24
|
-
return;
|
|
25
|
-
if (key.return) {
|
|
26
|
-
const next = [...lines];
|
|
27
|
-
const line = next[cursorRow] ?? "";
|
|
28
|
-
next.splice(cursorRow, 1, line.slice(0, cursorCol), line.slice(cursorCol));
|
|
29
|
-
setLines(next);
|
|
30
|
-
setCursorRow((r) => r + 1);
|
|
31
|
-
setCursorCol(0);
|
|
32
|
-
emit(next);
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
if (key.upArrow) {
|
|
36
|
-
if (cursorRow > 0) {
|
|
37
|
-
setCursorRow((r) => r - 1);
|
|
38
|
-
setCursorCol((c) => Math.min(c, (lines[cursorRow - 1] ?? "").length));
|
|
39
|
-
}
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
if (key.downArrow) {
|
|
43
|
-
if (cursorRow < lines.length - 1) {
|
|
44
|
-
setCursorRow((r) => r + 1);
|
|
45
|
-
setCursorCol((c) => Math.min(c, (lines[cursorRow + 1] ?? "").length));
|
|
46
|
-
}
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
if (key.leftArrow) {
|
|
50
|
-
if (cursorCol > 0)
|
|
51
|
-
setCursorCol((c) => c - 1);
|
|
52
|
-
else if (cursorRow > 0) {
|
|
53
|
-
setCursorRow((r) => r - 1);
|
|
54
|
-
setCursorCol((lines[cursorRow - 1] ?? "").length);
|
|
55
|
-
}
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
if (key.rightArrow) {
|
|
59
|
-
const curLen = (lines[cursorRow] ?? "").length;
|
|
60
|
-
if (cursorCol < curLen)
|
|
61
|
-
setCursorCol((c) => c + 1);
|
|
62
|
-
else if (cursorRow < lines.length - 1) {
|
|
63
|
-
setCursorRow((r) => r + 1);
|
|
64
|
-
setCursorCol(0);
|
|
65
|
-
}
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
if (key.backspace || key.delete) {
|
|
69
|
-
if (cursorCol > 0) {
|
|
70
|
-
const next = [...lines];
|
|
71
|
-
const line = next[cursorRow] ?? "";
|
|
72
|
-
next[cursorRow] = line.slice(0, cursorCol - 1) + line.slice(cursorCol);
|
|
73
|
-
setLines(next);
|
|
74
|
-
setCursorCol((c) => c - 1);
|
|
75
|
-
emit(next);
|
|
76
|
-
}
|
|
77
|
-
else if (cursorRow > 0) {
|
|
78
|
-
const next = [...lines];
|
|
79
|
-
const prev = next[cursorRow - 1] ?? "";
|
|
80
|
-
const cur = next[cursorRow] ?? "";
|
|
81
|
-
next.splice(cursorRow - 1, 2, prev + cur);
|
|
82
|
-
setLines(next);
|
|
83
|
-
setCursorRow((r) => r - 1);
|
|
84
|
-
setCursorCol(prev.length);
|
|
85
|
-
emit(next);
|
|
86
|
-
}
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
// Bracketed paste: content wrapped in ESC[200~ ... ESC[201~
|
|
90
|
-
if (input.includes("\x1b[200~")) {
|
|
91
|
-
const pasted = sanitizePaste(input);
|
|
92
|
-
if (pasted) {
|
|
93
|
-
const next = [...lines];
|
|
94
|
-
const line = next[cursorRow] ?? "";
|
|
95
|
-
next[cursorRow] = line.slice(0, cursorCol) + pasted + line.slice(cursorCol);
|
|
96
|
-
setLines(next);
|
|
97
|
-
setCursorCol((c) => c + pasted.length);
|
|
98
|
-
emit(next);
|
|
99
|
-
}
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
// Multi-char containing CR/LF with no bracketed markers — ignore
|
|
103
|
-
if (input.length > 1 && (input.includes("\r") || input.includes("\n")))
|
|
104
|
-
return;
|
|
105
|
-
// Multi-char printable input = unbracketed single-line paste (e.g. right-click)
|
|
106
|
-
if (input.length > 1 && !key.meta) {
|
|
107
|
-
const pasted = sanitizePaste(input);
|
|
108
|
-
if (pasted) {
|
|
109
|
-
const next = [...lines];
|
|
110
|
-
const line = next[cursorRow] ?? "";
|
|
111
|
-
next[cursorRow] = line.slice(0, cursorCol) + pasted + line.slice(cursorCol);
|
|
112
|
-
setLines(next);
|
|
113
|
-
setCursorCol((c) => c + pasted.length);
|
|
114
|
-
emit(next);
|
|
115
|
-
}
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
if (input.length === 1 && input >= " " && input <= "~") {
|
|
119
|
-
const next = [...lines];
|
|
120
|
-
const line = next[cursorRow] ?? "";
|
|
121
|
-
next[cursorRow] = line.slice(0, cursorCol) + input + line.slice(cursorCol);
|
|
122
|
-
setLines(next);
|
|
123
|
-
setCursorCol((c) => c + 1);
|
|
124
|
-
emit(next);
|
|
125
|
-
}
|
|
126
|
-
}, { isActive });
|
|
127
|
-
const scrollStart = Math.max(0, cursorRow - MAX_VISIBLE + 1);
|
|
128
|
-
const visible = lines.slice(scrollStart, scrollStart + MAX_VISIBLE);
|
|
129
|
-
const lineNumWidth = String(lines.length).length;
|
|
130
|
-
const isEmpty = lines.length === 1 && lines[0] === "";
|
|
131
|
-
return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: isActive ? theme.accent.brand : theme.border.dim, backgroundColor: isActive ? theme.bg.input : undefined, width: "100%", paddingX: 1, children: [isEmpty && !isActive ? (_jsx(Text, { color: theme.text.muted, children: hint })) : (visible.map((line, visIdx) => {
|
|
132
|
-
const rowIdx = scrollStart + visIdx;
|
|
133
|
-
const isCursor = isActive && rowIdx === cursorRow;
|
|
134
|
-
const lineNum = String(rowIdx + 1).padStart(lineNumWidth, " ");
|
|
135
|
-
if (isCursor) {
|
|
136
|
-
const before = line.slice(0, cursorCol);
|
|
137
|
-
const cur = cursorCol < line.length ? line[cursorCol] : " ";
|
|
138
|
-
const after = cursorCol < line.length ? line.slice(cursorCol + 1) : "";
|
|
139
|
-
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));
|
|
140
|
-
}
|
|
141
|
-
return (_jsxs(Box, { children: [_jsxs(Text, { color: theme.text.muted, children: [lineNum, " "] }), _jsx(Text, { color: theme.text.secondary, children: line })] }, rowIdx));
|
|
142
|
-
})), isActive ? (_jsx(Box, { marginTop: 0, children: _jsx(Text, { color: theme.text.muted, children: t("cmdEditor.inlineHint") }) })) : null] }));
|
|
143
|
-
}
|