@akiojin/gwt 4.11.6 → 4.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/gwt.js +1 -1
- package/dist/claude.d.ts +1 -0
- package/dist/claude.d.ts.map +1 -1
- package/dist/claude.js +50 -24
- package/dist/claude.js.map +1 -1
- package/dist/cli/ui/App.solid.d.ts.map +1 -1
- package/dist/cli/ui/App.solid.js +247 -49
- package/dist/cli/ui/App.solid.js.map +1 -1
- package/dist/cli/ui/components/solid/QuickStartStep.d.ts.map +1 -1
- package/dist/cli/ui/components/solid/QuickStartStep.js +35 -22
- package/dist/cli/ui/components/solid/QuickStartStep.js.map +1 -1
- package/dist/cli/ui/components/solid/SelectInput.d.ts.map +1 -1
- package/dist/cli/ui/components/solid/SelectInput.js +2 -1
- package/dist/cli/ui/components/solid/SelectInput.js.map +1 -1
- package/dist/cli/ui/components/solid/WizardController.d.ts.map +1 -1
- package/dist/cli/ui/components/solid/WizardController.js +19 -11
- package/dist/cli/ui/components/solid/WizardController.js.map +1 -1
- package/dist/cli/ui/components/solid/WizardSteps.d.ts.map +1 -1
- package/dist/cli/ui/components/solid/WizardSteps.js +26 -69
- package/dist/cli/ui/components/solid/WizardSteps.js.map +1 -1
- package/dist/cli/ui/core/theme.d.ts +9 -0
- package/dist/cli/ui/core/theme.d.ts.map +1 -1
- package/dist/cli/ui/core/theme.js +21 -0
- package/dist/cli/ui/core/theme.js.map +1 -1
- package/dist/cli/ui/screens/solid/BranchListScreen.d.ts +9 -2
- package/dist/cli/ui/screens/solid/BranchListScreen.d.ts.map +1 -1
- package/dist/cli/ui/screens/solid/BranchListScreen.js +101 -28
- package/dist/cli/ui/screens/solid/BranchListScreen.js.map +1 -1
- package/dist/cli/ui/screens/solid/ConfirmScreen.d.ts +2 -1
- package/dist/cli/ui/screens/solid/ConfirmScreen.d.ts.map +1 -1
- package/dist/cli/ui/screens/solid/ConfirmScreen.js +11 -3
- package/dist/cli/ui/screens/solid/ConfirmScreen.js.map +1 -1
- package/dist/cli/ui/screens/solid/EnvironmentScreen.d.ts.map +1 -1
- package/dist/cli/ui/screens/solid/EnvironmentScreen.js +9 -10
- package/dist/cli/ui/screens/solid/EnvironmentScreen.js.map +1 -1
- package/dist/cli/ui/screens/solid/LogScreen.d.ts +7 -1
- package/dist/cli/ui/screens/solid/LogScreen.d.ts.map +1 -1
- package/dist/cli/ui/screens/solid/LogScreen.js +254 -16
- package/dist/cli/ui/screens/solid/LogScreen.js.map +1 -1
- package/dist/cli/ui/screens/solid/ProfileEnvScreen.d.ts.map +1 -1
- package/dist/cli/ui/screens/solid/ProfileEnvScreen.js +8 -5
- package/dist/cli/ui/screens/solid/ProfileEnvScreen.js.map +1 -1
- package/dist/cli/ui/screens/solid/SelectorScreen.d.ts.map +1 -1
- package/dist/cli/ui/screens/solid/SelectorScreen.js +12 -4
- package/dist/cli/ui/screens/solid/SelectorScreen.js.map +1 -1
- package/dist/cli/ui/types.d.ts +1 -0
- package/dist/cli/ui/types.d.ts.map +1 -1
- package/dist/cli/ui/utils/branchFormatter.d.ts +1 -0
- package/dist/cli/ui/utils/branchFormatter.d.ts.map +1 -1
- package/dist/cli/ui/utils/branchFormatter.js +29 -7
- package/dist/cli/ui/utils/branchFormatter.js.map +1 -1
- package/dist/cli/ui/utils/continueSession.d.ts +14 -0
- package/dist/cli/ui/utils/continueSession.d.ts.map +1 -1
- package/dist/cli/ui/utils/continueSession.js +61 -3
- package/dist/cli/ui/utils/continueSession.js.map +1 -1
- package/dist/cli/ui/utils/versionCache.d.ts +37 -0
- package/dist/cli/ui/utils/versionCache.d.ts.map +1 -0
- package/dist/cli/ui/utils/versionCache.js +70 -0
- package/dist/cli/ui/utils/versionCache.js.map +1 -0
- package/dist/cli/ui/utils/versionFetcher.d.ts +41 -0
- package/dist/cli/ui/utils/versionFetcher.d.ts.map +1 -0
- package/dist/cli/ui/utils/versionFetcher.js +89 -0
- package/dist/cli/ui/utils/versionFetcher.js.map +1 -0
- package/dist/codex.d.ts +1 -0
- package/dist/codex.d.ts.map +1 -1
- package/dist/codex.js +48 -19
- package/dist/codex.js.map +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +10 -1
- package/dist/config/index.js.map +1 -1
- package/dist/gemini.d.ts +1 -0
- package/dist/gemini.d.ts.map +1 -1
- package/dist/gemini.js +36 -3
- package/dist/gemini.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +32 -2
- package/dist/index.js.map +1 -1
- package/dist/launcher.d.ts.map +1 -1
- package/dist/launcher.js +43 -8
- package/dist/launcher.js.map +1 -1
- package/dist/logging/agentOutput.d.ts +21 -0
- package/dist/logging/agentOutput.d.ts.map +1 -0
- package/dist/logging/agentOutput.js +164 -0
- package/dist/logging/agentOutput.js.map +1 -0
- package/dist/logging/formatter.d.ts.map +1 -1
- package/dist/logging/formatter.js +18 -4
- package/dist/logging/formatter.js.map +1 -1
- package/dist/logging/logger.d.ts.map +1 -1
- package/dist/logging/logger.js +2 -0
- package/dist/logging/logger.js.map +1 -1
- package/dist/logging/reader.d.ts +21 -0
- package/dist/logging/reader.d.ts.map +1 -1
- package/dist/logging/reader.js +79 -0
- package/dist/logging/reader.js.map +1 -1
- package/dist/opentui/index.solid.js +2306 -653
- package/dist/services/dependency-installer.js +2 -2
- package/dist/services/dependency-installer.js.map +1 -1
- package/dist/utils/session/common.d.ts +8 -0
- package/dist/utils/session/common.d.ts.map +1 -1
- package/dist/utils/session/common.js +22 -0
- package/dist/utils/session/common.js.map +1 -1
- package/dist/utils/session/parsers/claude.d.ts +10 -4
- package/dist/utils/session/parsers/claude.d.ts.map +1 -1
- package/dist/utils/session/parsers/claude.js +64 -18
- package/dist/utils/session/parsers/claude.js.map +1 -1
- package/dist/utils/session/parsers/codex.d.ts.map +1 -1
- package/dist/utils/session/parsers/codex.js +48 -28
- package/dist/utils/session/parsers/codex.js.map +1 -1
- package/dist/utils/session/parsers/gemini.d.ts.map +1 -1
- package/dist/utils/session/parsers/gemini.js +43 -6
- package/dist/utils/session/parsers/gemini.js.map +1 -1
- package/dist/utils/session/parsers/opencode.d.ts.map +1 -1
- package/dist/utils/session/parsers/opencode.js +43 -6
- package/dist/utils/session/parsers/opencode.js.map +1 -1
- package/dist/utils/session/types.d.ts +7 -0
- package/dist/utils/session/types.d.ts.map +1 -1
- package/dist/web/client/src/components/ui/alert.d.ts +1 -1
- package/dist/worktree.d.ts +4 -1
- package/dist/worktree.d.ts.map +1 -1
- package/dist/worktree.js +21 -15
- package/dist/worktree.js.map +1 -1
- package/package.json +2 -1
- package/src/claude.ts +64 -28
- package/src/cli/ui/App.solid.tsx +324 -51
- package/src/cli/ui/__tests__/solid/AppSolid.cleanup.test.tsx +830 -1
- package/src/cli/ui/__tests__/solid/BranchListScreen.test.tsx +105 -5
- package/src/cli/ui/__tests__/solid/ConfirmScreen.test.tsx +77 -0
- package/src/cli/ui/__tests__/solid/LogScreen.test.tsx +351 -0
- package/src/cli/ui/__tests__/solid/components/QuickStartStep.test.tsx +73 -2
- package/src/cli/ui/__tests__/solid/components/WizardSteps.test.tsx +4 -1
- package/src/cli/ui/__tests__/utils/branchFormatter.test.ts +72 -45
- package/src/cli/ui/components/solid/QuickStartStep.tsx +35 -23
- package/src/cli/ui/components/solid/SearchInput.tsx +1 -1
- package/src/cli/ui/components/solid/SelectInput.tsx +4 -0
- package/src/cli/ui/components/solid/WizardController.tsx +20 -11
- package/src/cli/ui/components/solid/WizardSteps.tsx +29 -86
- package/src/cli/ui/core/theme.ts +32 -0
- package/src/cli/ui/hooks/solid/useAsyncOperation.ts +8 -6
- package/src/cli/ui/hooks/solid/useGitOperations.ts +6 -5
- package/src/cli/ui/screens/solid/BranchListScreen.tsx +135 -32
- package/src/cli/ui/screens/solid/ConfirmScreen.tsx +20 -8
- package/src/cli/ui/screens/solid/EnvironmentScreen.tsx +22 -20
- package/src/cli/ui/screens/solid/LogScreen.tsx +364 -35
- package/src/cli/ui/screens/solid/ProfileEnvScreen.tsx +19 -15
- package/src/cli/ui/screens/solid/SelectorScreen.tsx +25 -14
- package/src/cli/ui/screens/solid/SettingsScreen.tsx +5 -3
- package/src/cli/ui/types.ts +1 -0
- package/src/cli/ui/utils/__tests__/branchFormatter.test.ts +53 -6
- package/src/cli/ui/utils/branchFormatter.ts +35 -7
- package/src/cli/ui/utils/continueSession.ts +90 -3
- package/src/cli/ui/utils/versionCache.ts +93 -0
- package/src/cli/ui/utils/versionFetcher.ts +120 -0
- package/src/codex.ts +62 -20
- package/src/config/__tests__/saveSession.test.ts +2 -2
- package/src/config/index.ts +11 -1
- package/src/gemini.ts +50 -4
- package/src/index.test.ts +16 -10
- package/src/index.ts +38 -1
- package/src/launcher.ts +49 -8
- package/src/logging/agentOutput.ts +216 -0
- package/src/logging/formatter.ts +23 -4
- package/src/logging/logger.ts +2 -0
- package/src/logging/reader.ts +117 -0
- package/src/services/__tests__/BatchMergeService.test.ts +34 -14
- package/src/services/dependency-installer.ts +2 -2
- package/src/utils/session/common.ts +28 -0
- package/src/utils/session/parsers/claude.ts +79 -29
- package/src/utils/session/parsers/codex.ts +50 -26
- package/src/utils/session/parsers/gemini.ts +46 -5
- package/src/utils/session/parsers/opencode.ts +46 -5
- package/src/utils/session/types.ts +4 -0
- package/src/worktree.ts +28 -15
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
/** @jsxImportSource @opentui/solid */
|
|
2
2
|
import { useKeyboard } from "@opentui/solid";
|
|
3
|
-
import { createMemo } from "solid-js";
|
|
3
|
+
import { createEffect, createMemo, createSignal, mergeProps } from "solid-js";
|
|
4
4
|
import { TextAttributes } from "@opentui/core";
|
|
5
5
|
import { Header } from "../../components/solid/Header.js";
|
|
6
6
|
import { Footer } from "../../components/solid/Footer.js";
|
|
7
7
|
import { useTerminalSize } from "../../hooks/solid/useTerminalSize.js";
|
|
8
8
|
import type { FormattedLogEntry } from "../../../../logging/formatter.js";
|
|
9
9
|
import { useScrollableList } from "../../hooks/solid/useScrollableList.js";
|
|
10
|
+
import stringWidth from "string-width";
|
|
11
|
+
import { getLogLevelColor, selectionStyle } from "../../core/theme.js";
|
|
10
12
|
|
|
11
13
|
export interface LogScreenProps {
|
|
12
14
|
entries: FormattedLogEntry[];
|
|
@@ -16,65 +18,312 @@ export interface LogScreenProps {
|
|
|
16
18
|
onSelect: (entry: FormattedLogEntry) => void;
|
|
17
19
|
onCopy: (entry: FormattedLogEntry) => void;
|
|
18
20
|
onPickDate?: () => void;
|
|
21
|
+
onReload?: () => void;
|
|
22
|
+
onToggleTail?: () => void;
|
|
23
|
+
onReset?: () => void;
|
|
19
24
|
notification?: { message: string; tone: "success" | "error" } | null;
|
|
20
25
|
version?: string | null;
|
|
21
26
|
selectedDate?: string | null;
|
|
27
|
+
branchLabel?: string | null;
|
|
28
|
+
sourceLabel?: string | null;
|
|
29
|
+
tailing?: boolean;
|
|
22
30
|
helpVisible?: boolean;
|
|
23
31
|
}
|
|
24
32
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
type LevelMode = "ALL" | "INFO+" | "WARN+" | "ERROR+";
|
|
34
|
+
|
|
35
|
+
const LEVEL_ORDER: LevelMode[] = ["ALL", "INFO+", "WARN+", "ERROR+"];
|
|
36
|
+
|
|
37
|
+
const LEVEL_THRESHOLDS: Record<LevelMode, number> = {
|
|
38
|
+
ALL: 0,
|
|
39
|
+
"INFO+": 30,
|
|
40
|
+
"WARN+": 40,
|
|
41
|
+
"ERROR+": 50,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const LEVEL_LABEL_VALUES: Record<string, number> = {
|
|
45
|
+
TRACE: 10,
|
|
46
|
+
DEBUG: 20,
|
|
47
|
+
INFO: 30,
|
|
48
|
+
WARN: 40,
|
|
49
|
+
ERROR: 50,
|
|
50
|
+
FATAL: 60,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const measureWidth = (value: string): number => stringWidth(value);
|
|
54
|
+
|
|
55
|
+
const truncateToWidth = (value: string, maxWidth: number): string => {
|
|
56
|
+
if (maxWidth <= 0) {
|
|
57
|
+
return "";
|
|
58
|
+
}
|
|
59
|
+
if (measureWidth(value) <= maxWidth) {
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
const ellipsis = "...";
|
|
63
|
+
const ellipsisWidth = measureWidth(ellipsis);
|
|
64
|
+
if (ellipsisWidth >= maxWidth) {
|
|
65
|
+
return ellipsis.slice(0, maxWidth);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let currentWidth = 0;
|
|
69
|
+
let result = "";
|
|
70
|
+
for (const char of Array.from(value)) {
|
|
71
|
+
const charWidth = measureWidth(char);
|
|
72
|
+
if (currentWidth + charWidth + ellipsisWidth > maxWidth) {
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
result += char;
|
|
76
|
+
currentWidth += charWidth;
|
|
77
|
+
}
|
|
78
|
+
return `${result}${ellipsis}`;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const padToWidth = (value: string, width: number): string => {
|
|
82
|
+
if (width <= 0) {
|
|
83
|
+
return "";
|
|
84
|
+
}
|
|
85
|
+
const truncated = truncateToWidth(value, width);
|
|
86
|
+
const padding = Math.max(0, width - measureWidth(truncated));
|
|
87
|
+
return `${truncated}${" ".repeat(padding)}`;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
interface TextSegment {
|
|
91
|
+
text: string;
|
|
92
|
+
fg?: string;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const appendSegment = (segments: TextSegment[], segment: TextSegment) => {
|
|
96
|
+
if (!segment.text) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
segments.push(segment);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const measureSegmentsWidth = (segments: TextSegment[]): number =>
|
|
103
|
+
segments.reduce((total, segment) => total + measureWidth(segment.text), 0);
|
|
104
|
+
|
|
105
|
+
const truncateSegmentsToWidth = (
|
|
106
|
+
segments: TextSegment[],
|
|
107
|
+
maxWidth: number,
|
|
108
|
+
): TextSegment[] => {
|
|
109
|
+
if (maxWidth <= 0) {
|
|
110
|
+
return [];
|
|
111
|
+
}
|
|
112
|
+
const result: TextSegment[] = [];
|
|
113
|
+
let currentWidth = 0;
|
|
114
|
+
for (const segment of segments) {
|
|
115
|
+
if (currentWidth >= maxWidth) {
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
const segmentWidth = measureWidth(segment.text);
|
|
119
|
+
if (currentWidth + segmentWidth <= maxWidth) {
|
|
120
|
+
result.push(segment);
|
|
121
|
+
currentWidth += segmentWidth;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const remaining = maxWidth - currentWidth;
|
|
125
|
+
const truncated = truncateToWidth(segment.text, remaining);
|
|
126
|
+
if (truncated) {
|
|
127
|
+
result.push({ ...segment, text: truncated });
|
|
128
|
+
currentWidth += measureWidth(truncated);
|
|
129
|
+
}
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
return result;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const segmentsToText = (segments: TextSegment[]): string =>
|
|
136
|
+
segments.map((segment) => segment.text).join("");
|
|
137
|
+
|
|
138
|
+
const padLine = (value: string, width: number): string => {
|
|
139
|
+
const padding = Math.max(0, width - measureWidth(value));
|
|
140
|
+
return padding > 0 ? `${value}${" ".repeat(padding)}` : value;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const resolveEntryLevel = (entry: FormattedLogEntry): number => {
|
|
144
|
+
const rawLevel = entry.raw?.level;
|
|
145
|
+
if (typeof rawLevel === "number") {
|
|
146
|
+
return rawLevel;
|
|
147
|
+
}
|
|
148
|
+
const label = entry.levelLabel?.toUpperCase() ?? "";
|
|
149
|
+
return LEVEL_LABEL_VALUES[label] ?? 0;
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
export function LogScreen(props: LogScreenProps) {
|
|
153
|
+
const merged = mergeProps(
|
|
154
|
+
{
|
|
155
|
+
loading: false,
|
|
156
|
+
error: null,
|
|
157
|
+
branchLabel: null,
|
|
158
|
+
sourceLabel: null,
|
|
159
|
+
tailing: false,
|
|
160
|
+
helpVisible: false,
|
|
161
|
+
},
|
|
162
|
+
props,
|
|
163
|
+
);
|
|
38
164
|
const terminal = useTerminalSize();
|
|
165
|
+
const [filterQuery, setFilterQuery] = createSignal("");
|
|
166
|
+
const [filterMode, setFilterMode] = createSignal(false);
|
|
167
|
+
const [levelMode, setLevelMode] = createSignal<LevelMode>("ALL");
|
|
168
|
+
|
|
169
|
+
const filteredEntries = createMemo(() => {
|
|
170
|
+
let result = merged.entries;
|
|
171
|
+
const threshold = LEVEL_THRESHOLDS[levelMode()];
|
|
172
|
+
if (threshold > 0) {
|
|
173
|
+
result = result.filter((entry) => resolveEntryLevel(entry) >= threshold);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const query = filterQuery().trim().toLowerCase();
|
|
177
|
+
if (!query) {
|
|
178
|
+
return result;
|
|
179
|
+
}
|
|
180
|
+
return result.filter((entry) => {
|
|
181
|
+
const target =
|
|
182
|
+
`${entry.category} ${entry.levelLabel} ${entry.message}`.toLowerCase();
|
|
183
|
+
return target.includes(query);
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
const totalCount = createMemo(() => merged.entries.length);
|
|
188
|
+
const filteredCount = createMemo(() => filteredEntries().length);
|
|
189
|
+
const showFilterCount = createMemo(
|
|
190
|
+
() => filterMode() || filterQuery().trim().length > 0,
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
const levelWidth = createMemo(() => {
|
|
194
|
+
const MIN = 5;
|
|
195
|
+
return Math.max(
|
|
196
|
+
MIN,
|
|
197
|
+
...filteredEntries().map((entry) => measureWidth(entry.levelLabel)),
|
|
198
|
+
);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const categoryWidth = createMemo(() => {
|
|
202
|
+
const MIN = 4;
|
|
203
|
+
const MAX = 20;
|
|
204
|
+
const maxWidth = Math.max(
|
|
205
|
+
MIN,
|
|
206
|
+
...filteredEntries().map((entry) => measureWidth(entry.category)),
|
|
207
|
+
);
|
|
208
|
+
return Math.min(MAX, maxWidth);
|
|
209
|
+
});
|
|
210
|
+
|
|
39
211
|
const listHeight = createMemo(() => {
|
|
40
212
|
const headerRows = 2;
|
|
41
|
-
const infoRows =
|
|
213
|
+
const infoRows = 3;
|
|
42
214
|
const footerRows = 1;
|
|
43
|
-
const notificationRows = notification ? 1 : 0;
|
|
215
|
+
const notificationRows = merged.notification ? 1 : 0;
|
|
44
216
|
const reserved = headerRows + infoRows + footerRows + notificationRows;
|
|
45
217
|
return Math.max(1, terminal().rows - reserved);
|
|
46
218
|
});
|
|
47
219
|
|
|
48
220
|
const list = useScrollableList({
|
|
49
|
-
items:
|
|
221
|
+
items: filteredEntries,
|
|
50
222
|
visibleCount: listHeight,
|
|
51
223
|
});
|
|
52
224
|
|
|
53
|
-
const currentEntry = createMemo(
|
|
225
|
+
const currentEntry = createMemo(
|
|
226
|
+
() => filteredEntries()[list.selectedIndex()],
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
createEffect(() => {
|
|
230
|
+
filterQuery();
|
|
231
|
+
levelMode();
|
|
232
|
+
list.setSelectedIndex(0);
|
|
233
|
+
list.setScrollOffset(0);
|
|
234
|
+
});
|
|
54
235
|
|
|
55
236
|
const updateSelectedIndex = (value: number | ((prev: number) => number)) => {
|
|
56
237
|
list.setSelectedIndex(value);
|
|
57
238
|
};
|
|
58
239
|
|
|
59
240
|
useKeyboard((key) => {
|
|
60
|
-
if (helpVisible) {
|
|
241
|
+
if (merged.helpVisible) {
|
|
61
242
|
return;
|
|
62
243
|
}
|
|
244
|
+
|
|
245
|
+
if (filterMode()) {
|
|
246
|
+
if (key.name === "down") {
|
|
247
|
+
updateSelectedIndex((prev) => prev + 1);
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
if (key.name === "up") {
|
|
251
|
+
updateSelectedIndex((prev) => prev - 1);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (key.name === "escape") {
|
|
255
|
+
if (filterQuery()) {
|
|
256
|
+
setFilterQuery("");
|
|
257
|
+
} else {
|
|
258
|
+
setFilterMode(false);
|
|
259
|
+
}
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
if (key.name === "backspace") {
|
|
263
|
+
setFilterQuery((prev) => prev.slice(0, -1));
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
if (key.name === "return" || key.name === "linefeed") {
|
|
267
|
+
setFilterMode(false);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
if (
|
|
271
|
+
key.sequence &&
|
|
272
|
+
key.sequence.length === 1 &&
|
|
273
|
+
!key.ctrl &&
|
|
274
|
+
!key.meta &&
|
|
275
|
+
!key.super &&
|
|
276
|
+
!key.hyper
|
|
277
|
+
) {
|
|
278
|
+
setFilterQuery((prev) => prev + key.sequence);
|
|
279
|
+
}
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
63
283
|
if (key.name === "escape" || key.name === "q") {
|
|
64
|
-
onBack();
|
|
284
|
+
merged.onBack();
|
|
65
285
|
return;
|
|
66
286
|
}
|
|
67
287
|
|
|
68
288
|
if (key.name === "c") {
|
|
69
289
|
const entry = currentEntry();
|
|
70
290
|
if (entry) {
|
|
71
|
-
onCopy(entry);
|
|
291
|
+
merged.onCopy(entry);
|
|
72
292
|
}
|
|
73
293
|
return;
|
|
74
294
|
}
|
|
75
295
|
|
|
76
296
|
if (key.name === "d") {
|
|
77
|
-
onPickDate?.();
|
|
297
|
+
merged.onPickDate?.();
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (key.name === "f") {
|
|
302
|
+
setFilterMode(true);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (key.name === "v") {
|
|
307
|
+
setLevelMode((prev) => {
|
|
308
|
+
const index = LEVEL_ORDER.indexOf(prev);
|
|
309
|
+
const nextIndex = (index + 1) % LEVEL_ORDER.length;
|
|
310
|
+
return LEVEL_ORDER[nextIndex] ?? "ALL";
|
|
311
|
+
});
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (key.name === "r") {
|
|
316
|
+
merged.onReload?.();
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (key.name === "t") {
|
|
321
|
+
merged.onToggleTail?.();
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (key.name === "x") {
|
|
326
|
+
merged.onReset?.();
|
|
78
327
|
return;
|
|
79
328
|
}
|
|
80
329
|
|
|
@@ -104,14 +353,14 @@ export function LogScreen({
|
|
|
104
353
|
}
|
|
105
354
|
|
|
106
355
|
if (key.name === "end") {
|
|
107
|
-
updateSelectedIndex(
|
|
356
|
+
updateSelectedIndex(filteredEntries().length - 1);
|
|
108
357
|
return;
|
|
109
358
|
}
|
|
110
359
|
|
|
111
360
|
if (key.name === "return" || key.name === "linefeed") {
|
|
112
361
|
const entry = currentEntry();
|
|
113
362
|
if (entry) {
|
|
114
|
-
onSelect(entry);
|
|
363
|
+
merged.onSelect(entry);
|
|
115
364
|
}
|
|
116
365
|
}
|
|
117
366
|
});
|
|
@@ -121,52 +370,132 @@ export function LogScreen({
|
|
|
121
370
|
{ key: "enter", description: "Detail" },
|
|
122
371
|
{ key: "c", description: "Copy" },
|
|
123
372
|
{ key: "d", description: "Date" },
|
|
373
|
+
{ key: "f", description: "Filter" },
|
|
374
|
+
{ key: "v", description: "Level" },
|
|
375
|
+
{ key: "r", description: "Reload" },
|
|
376
|
+
{ key: "t", description: "Tail" },
|
|
377
|
+
{ key: "x", description: "Reset" },
|
|
124
378
|
{ key: "esc", description: "Back" },
|
|
125
379
|
];
|
|
126
380
|
return actions;
|
|
127
381
|
});
|
|
128
382
|
|
|
383
|
+
const formatEntrySegments = (entry: FormattedLogEntry): TextSegment[] => {
|
|
384
|
+
const levelText = padToWidth(entry.levelLabel, levelWidth());
|
|
385
|
+
const categoryText = padToWidth(entry.category, categoryWidth());
|
|
386
|
+
const segments: TextSegment[] = [];
|
|
387
|
+
appendSegment(segments, { text: `[${entry.timeLabel}] ` });
|
|
388
|
+
appendSegment(segments, { text: "[" });
|
|
389
|
+
appendSegment(segments, {
|
|
390
|
+
text: levelText,
|
|
391
|
+
fg: getLogLevelColor(entry.levelLabel),
|
|
392
|
+
});
|
|
393
|
+
appendSegment(segments, { text: "] " });
|
|
394
|
+
appendSegment(segments, { text: "[" });
|
|
395
|
+
appendSegment(segments, { text: categoryText });
|
|
396
|
+
appendSegment(segments, { text: "] " });
|
|
397
|
+
appendSegment(segments, { text: entry.message });
|
|
398
|
+
const maxWidth = terminal().columns;
|
|
399
|
+
if (measureSegmentsWidth(segments) <= maxWidth) {
|
|
400
|
+
return segments;
|
|
401
|
+
}
|
|
402
|
+
return truncateSegmentsToWidth(segments, maxWidth);
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
const filterLabel = createMemo(() => {
|
|
406
|
+
if (filterQuery()) {
|
|
407
|
+
return filterQuery();
|
|
408
|
+
}
|
|
409
|
+
return filterMode() ? "(type to filter)" : "(press f to filter)";
|
|
410
|
+
});
|
|
411
|
+
|
|
129
412
|
return (
|
|
130
413
|
<box flexDirection="column" height={terminal().rows}>
|
|
131
|
-
<Header
|
|
414
|
+
<Header
|
|
415
|
+
title="gwt - Log Viewer"
|
|
416
|
+
titleColor="cyan"
|
|
417
|
+
version={merged.version}
|
|
418
|
+
/>
|
|
132
419
|
|
|
133
|
-
{notification ? (
|
|
134
|
-
<text fg={notification.tone === "error" ? "red" : "green"}>
|
|
135
|
-
{notification.message}
|
|
420
|
+
{merged.notification ? (
|
|
421
|
+
<text fg={merged.notification.tone === "error" ? "red" : "green"}>
|
|
422
|
+
{merged.notification.message}
|
|
136
423
|
</text>
|
|
137
424
|
) : null}
|
|
138
425
|
|
|
426
|
+
<box flexDirection="row">
|
|
427
|
+
<text attributes={TextAttributes.DIM}>Branch: </text>
|
|
428
|
+
<text attributes={TextAttributes.BOLD}>
|
|
429
|
+
{merged.branchLabel ?? "(none)"}
|
|
430
|
+
</text>
|
|
431
|
+
<text attributes={TextAttributes.DIM}> Source: </text>
|
|
432
|
+
<text attributes={TextAttributes.BOLD}>
|
|
433
|
+
{merged.sourceLabel ?? "(none)"}
|
|
434
|
+
</text>
|
|
435
|
+
</box>
|
|
436
|
+
|
|
139
437
|
<box flexDirection="row">
|
|
140
438
|
<text attributes={TextAttributes.DIM}>Date: </text>
|
|
141
|
-
<text attributes={TextAttributes.BOLD}>
|
|
439
|
+
<text attributes={TextAttributes.BOLD}>
|
|
440
|
+
{merged.selectedDate ?? "---"}
|
|
441
|
+
</text>
|
|
142
442
|
<text attributes={TextAttributes.DIM}> Total: </text>
|
|
143
|
-
<text attributes={TextAttributes.BOLD}>{
|
|
443
|
+
<text attributes={TextAttributes.BOLD}>{totalCount()}</text>
|
|
444
|
+
<text attributes={TextAttributes.DIM}> Level: </text>
|
|
445
|
+
<text attributes={TextAttributes.BOLD}>{levelMode()}</text>
|
|
446
|
+
<text attributes={TextAttributes.DIM}> Tail: </text>
|
|
447
|
+
<text attributes={TextAttributes.BOLD}>
|
|
448
|
+
{merged.tailing ? "ON" : "OFF"}
|
|
449
|
+
</text>
|
|
450
|
+
</box>
|
|
451
|
+
|
|
452
|
+
<box flexDirection="row">
|
|
453
|
+
<text attributes={TextAttributes.DIM}>Filter: </text>
|
|
454
|
+
<text attributes={TextAttributes.BOLD}>{filterLabel()}</text>
|
|
455
|
+
{showFilterCount() ? (
|
|
456
|
+
<text attributes={TextAttributes.DIM}>
|
|
457
|
+
{` (Showing ${filteredCount()} of ${totalCount()})`}
|
|
458
|
+
</text>
|
|
459
|
+
) : null}
|
|
144
460
|
</box>
|
|
145
461
|
|
|
146
462
|
<box flexDirection="column" flexGrow={1}>
|
|
147
|
-
{loading ? (
|
|
463
|
+
{merged.loading ? (
|
|
148
464
|
<text fg="gray">Loading logs...</text>
|
|
149
|
-
) :
|
|
465
|
+
) : filteredEntries().length === 0 ? (
|
|
150
466
|
<text fg="gray">No logs available.</text>
|
|
151
467
|
) : (
|
|
152
468
|
<box flexDirection="column">
|
|
153
469
|
{list.visibleItems().map((entry, index) => {
|
|
154
470
|
const absoluteIndex = list.scrollOffset() + index;
|
|
155
471
|
const isSelected = absoluteIndex === list.selectedIndex();
|
|
472
|
+
const segments = formatEntrySegments(entry);
|
|
473
|
+
const lineText = segmentsToText(segments);
|
|
474
|
+
const selectedContent = padLine(lineText, terminal().columns);
|
|
156
475
|
return (
|
|
157
476
|
<text
|
|
158
477
|
{...(isSelected
|
|
159
|
-
? { fg:
|
|
478
|
+
? { fg: selectionStyle.fg, bg: selectionStyle.bg }
|
|
160
479
|
: {})}
|
|
480
|
+
wrapMode="none"
|
|
481
|
+
width={terminal().columns}
|
|
161
482
|
>
|
|
162
|
-
{
|
|
483
|
+
{isSelected
|
|
484
|
+
? selectedContent
|
|
485
|
+
: segments.map((segment) =>
|
|
486
|
+
segment.fg ? (
|
|
487
|
+
<span style={{ fg: segment.fg }}>{segment.text}</span>
|
|
488
|
+
) : (
|
|
489
|
+
segment.text
|
|
490
|
+
),
|
|
491
|
+
)}
|
|
163
492
|
</text>
|
|
164
493
|
);
|
|
165
494
|
})}
|
|
166
495
|
</box>
|
|
167
496
|
)}
|
|
168
497
|
|
|
169
|
-
{error ? <text fg="red">{error}</text> : null}
|
|
498
|
+
{merged.error ? <text fg="red">{merged.error}</text> : null}
|
|
170
499
|
</box>
|
|
171
500
|
|
|
172
501
|
<Footer actions={footerActions()} />
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/** @jsxImportSource @opentui/solid */
|
|
2
|
-
import { TextAttributes } from "@opentui/core";
|
|
3
2
|
import { useKeyboard } from "@opentui/solid";
|
|
4
3
|
import { createMemo } from "solid-js";
|
|
5
4
|
import { Header } from "../../components/solid/Header.js";
|
|
6
5
|
import { Footer } from "../../components/solid/Footer.js";
|
|
7
6
|
import { useTerminalSize } from "../../hooks/solid/useTerminalSize.js";
|
|
8
7
|
import { useScrollableList } from "../../hooks/solid/useScrollableList.js";
|
|
8
|
+
import stringWidth from "string-width";
|
|
9
|
+
import { selectionStyle } from "../../core/theme.js";
|
|
9
10
|
|
|
10
11
|
export interface ProfileEnvVariable {
|
|
11
12
|
key: string;
|
|
@@ -39,6 +40,10 @@ export function ProfileEnvScreen({
|
|
|
39
40
|
helpVisible = false,
|
|
40
41
|
}: ProfileEnvScreenProps) {
|
|
41
42
|
const terminal = useTerminalSize();
|
|
43
|
+
const padLine = (value: string, width: number) => {
|
|
44
|
+
const padding = Math.max(0, width - stringWidth(value));
|
|
45
|
+
return padding > 0 ? `${value}${" ".repeat(padding)}` : value;
|
|
46
|
+
};
|
|
42
47
|
const listHeight = createMemo(() => {
|
|
43
48
|
const headerRows = 2;
|
|
44
49
|
const footerRows = 1;
|
|
@@ -163,22 +168,21 @@ export function ProfileEnvScreen({
|
|
|
163
168
|
{list.visibleItems().map((variable, index) => {
|
|
164
169
|
const absoluteIndex = list.scrollOffset() + index;
|
|
165
170
|
const isSelected = absoluteIndex === list.selectedIndex();
|
|
166
|
-
const attributes = isSelected ? TextAttributes.BOLD : undefined;
|
|
167
|
-
const color = isSelected ? "cyan" : undefined;
|
|
168
171
|
return (
|
|
169
172
|
<box flexDirection="row">
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
173
|
+
{isSelected ? (
|
|
174
|
+
<text fg={selectionStyle.fg} bg={selectionStyle.bg}>
|
|
175
|
+
{padLine(
|
|
176
|
+
`${variable.key}=${variable.value}`,
|
|
177
|
+
terminal().columns,
|
|
178
|
+
)}
|
|
179
|
+
</text>
|
|
180
|
+
) : (
|
|
181
|
+
<>
|
|
182
|
+
<text>{variable.key}</text>
|
|
183
|
+
<text>={variable.value}</text>
|
|
184
|
+
</>
|
|
185
|
+
)}
|
|
182
186
|
</box>
|
|
183
187
|
);
|
|
184
188
|
})}
|
|
@@ -6,6 +6,8 @@ import { Header } from "../../components/solid/Header.js";
|
|
|
6
6
|
import { Footer } from "../../components/solid/Footer.js";
|
|
7
7
|
import { useTerminalSize } from "../../hooks/solid/useTerminalSize.js";
|
|
8
8
|
import { useScrollableList } from "../../hooks/solid/useScrollableList.js";
|
|
9
|
+
import stringWidth from "string-width";
|
|
10
|
+
import { selectionStyle } from "../../core/theme.js";
|
|
9
11
|
|
|
10
12
|
export interface SelectorItem {
|
|
11
13
|
label: string;
|
|
@@ -30,6 +32,11 @@ export interface SelectorScreenProps {
|
|
|
30
32
|
const clamp = (value: number, min: number, max: number) =>
|
|
31
33
|
Math.min(Math.max(value, min), max);
|
|
32
34
|
|
|
35
|
+
const padLine = (value: string, width: number): string => {
|
|
36
|
+
const padding = Math.max(0, width - stringWidth(value));
|
|
37
|
+
return padding > 0 ? `${value}${" ".repeat(padding)}` : value;
|
|
38
|
+
};
|
|
39
|
+
|
|
33
40
|
export function SelectorScreen({
|
|
34
41
|
title,
|
|
35
42
|
description,
|
|
@@ -140,23 +147,27 @@ export function SelectorScreen({
|
|
|
140
147
|
{list.visibleItems().map((item, index) => {
|
|
141
148
|
const absoluteIndex = list.scrollOffset() + index;
|
|
142
149
|
const isSelected = absoluteIndex === selectedIndex();
|
|
150
|
+
const descriptionText =
|
|
151
|
+
showDescription && item.description
|
|
152
|
+
? ` - ${item.description}`
|
|
153
|
+
: "";
|
|
154
|
+
const fullLine = `${item.label}${descriptionText}`;
|
|
143
155
|
return (
|
|
144
156
|
<box flexDirection="row">
|
|
145
|
-
|
|
146
|
-
{
|
|
147
|
-
|
|
148
|
-
: {})}
|
|
149
|
-
>
|
|
150
|
-
{item.label}
|
|
151
|
-
</text>
|
|
152
|
-
{showDescription && item.description ? (
|
|
153
|
-
<text
|
|
154
|
-
attributes={TextAttributes.DIM}
|
|
155
|
-
{...(isSelected ? { fg: "cyan" } : {})}
|
|
156
|
-
>
|
|
157
|
-
{` - ${item.description}`}
|
|
157
|
+
{isSelected ? (
|
|
158
|
+
<text fg={selectionStyle.fg} bg={selectionStyle.bg}>
|
|
159
|
+
{padLine(fullLine, terminal().columns)}
|
|
158
160
|
</text>
|
|
159
|
-
) :
|
|
161
|
+
) : (
|
|
162
|
+
<>
|
|
163
|
+
<text>{item.label}</text>
|
|
164
|
+
{showDescription && item.description ? (
|
|
165
|
+
<text attributes={TextAttributes.DIM}>
|
|
166
|
+
{` - ${item.description}`}
|
|
167
|
+
</text>
|
|
168
|
+
) : null}
|
|
169
|
+
</>
|
|
170
|
+
)}
|
|
160
171
|
</box>
|
|
161
172
|
);
|
|
162
173
|
})}
|
|
@@ -25,7 +25,9 @@ export function SettingsScreen({
|
|
|
25
25
|
const items = settings.map((setting) => ({
|
|
26
26
|
label: setting.label,
|
|
27
27
|
value: setting.value,
|
|
28
|
-
|
|
28
|
+
...(setting.description !== undefined
|
|
29
|
+
? { description: setting.description }
|
|
30
|
+
: {}),
|
|
29
31
|
}));
|
|
30
32
|
|
|
31
33
|
const handleSelect = (item: { label: string; value: string }) => {
|
|
@@ -40,8 +42,8 @@ export function SettingsScreen({
|
|
|
40
42
|
title="gwt - Settings"
|
|
41
43
|
items={items}
|
|
42
44
|
onSelect={handleSelect}
|
|
43
|
-
onBack
|
|
44
|
-
version
|
|
45
|
+
{...(onBack ? { onBack } : {})}
|
|
46
|
+
{...(version !== undefined ? { version } : {})}
|
|
45
47
|
emptyMessage="No settings available."
|
|
46
48
|
showDescription
|
|
47
49
|
helpVisible={helpVisible}
|
package/src/cli/ui/types.ts
CHANGED