toolcraft-openapi 0.0.17 → 0.0.19
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/bin/generate.js +7 -0
- package/dist/define-client.js +2 -2
- package/dist/generate.js +2 -2
- package/dist/http.d.ts +21 -2
- package/dist/http.js +147 -22
- package/dist/index.d.ts +1 -1
- package/dist/lock.d.ts +1 -1
- package/dist/lock.js +109 -5
- package/dist/mock/fetch.js +1 -1
- package/dist/network-error.d.ts +2 -0
- package/dist/network-error.js +83 -0
- package/dist/spec-source.js +103 -3
- package/node_modules/@poe-code/design-system/dist/acp/components.js +15 -13
- package/node_modules/@poe-code/design-system/dist/components/color.d.ts +31 -0
- package/node_modules/@poe-code/design-system/dist/components/color.js +101 -0
- package/node_modules/@poe-code/design-system/dist/components/help-formatter-plain.d.ts +1 -0
- package/node_modules/@poe-code/design-system/dist/components/help-formatter-plain.js +1 -1
- package/node_modules/@poe-code/design-system/dist/components/index.d.ts +4 -0
- package/node_modules/@poe-code/design-system/dist/components/index.js +2 -0
- package/node_modules/@poe-code/design-system/dist/components/logger.js +2 -2
- package/node_modules/@poe-code/design-system/dist/components/symbols.js +3 -3
- package/node_modules/@poe-code/design-system/dist/components/table.js +191 -40
- package/node_modules/@poe-code/design-system/dist/components/template.d.ts +6 -0
- package/node_modules/@poe-code/design-system/dist/components/template.js +271 -0
- package/node_modules/@poe-code/design-system/dist/components/text.d.ts +1 -0
- package/node_modules/@poe-code/design-system/dist/components/text.js +11 -3
- package/node_modules/@poe-code/design-system/dist/dashboard/buffer.js +20 -13
- package/node_modules/@poe-code/design-system/dist/dashboard/keymap.d.ts +5 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/keymap.js +146 -12
- package/node_modules/@poe-code/design-system/dist/dashboard/terminal.js +31 -0
- package/node_modules/@poe-code/design-system/dist/dashboard/types.d.ts +1 -0
- package/node_modules/@poe-code/design-system/dist/explorer/actions.d.ts +16 -0
- package/node_modules/@poe-code/design-system/dist/explorer/actions.js +39 -0
- package/node_modules/@poe-code/design-system/dist/explorer/demo.d.ts +13 -0
- package/node_modules/@poe-code/design-system/dist/explorer/demo.js +297 -0
- package/node_modules/@poe-code/design-system/dist/explorer/events.d.ts +61 -0
- package/node_modules/@poe-code/design-system/dist/explorer/events.js +1 -0
- package/node_modules/@poe-code/design-system/dist/explorer/filter.d.ts +10 -0
- package/node_modules/@poe-code/design-system/dist/explorer/filter.js +95 -0
- package/node_modules/@poe-code/design-system/dist/explorer/index.d.ts +8 -0
- package/node_modules/@poe-code/design-system/dist/explorer/index.js +8 -0
- package/node_modules/@poe-code/design-system/dist/explorer/jobs.d.ts +7 -0
- package/node_modules/@poe-code/design-system/dist/explorer/jobs.js +59 -0
- package/node_modules/@poe-code/design-system/dist/explorer/keymap.d.ts +21 -0
- package/node_modules/@poe-code/design-system/dist/explorer/keymap.js +363 -0
- package/node_modules/@poe-code/design-system/dist/explorer/layout.d.ts +20 -0
- package/node_modules/@poe-code/design-system/dist/explorer/layout.js +73 -0
- package/node_modules/@poe-code/design-system/dist/explorer/reducer.d.ts +9 -0
- package/node_modules/@poe-code/design-system/dist/explorer/reducer.js +704 -0
- package/node_modules/@poe-code/design-system/dist/explorer/render/detail.d.ts +4 -0
- package/node_modules/@poe-code/design-system/dist/explorer/render/detail.js +96 -0
- package/node_modules/@poe-code/design-system/dist/explorer/render/footer.d.ts +4 -0
- package/node_modules/@poe-code/design-system/dist/explorer/render/footer.js +49 -0
- package/node_modules/@poe-code/design-system/dist/explorer/render/header.d.ts +4 -0
- package/node_modules/@poe-code/design-system/dist/explorer/render/header.js +56 -0
- package/node_modules/@poe-code/design-system/dist/explorer/render/index.d.ts +8 -0
- package/node_modules/@poe-code/design-system/dist/explorer/render/index.js +61 -0
- package/node_modules/@poe-code/design-system/dist/explorer/render/list.d.ts +4 -0
- package/node_modules/@poe-code/design-system/dist/explorer/render/list.js +106 -0
- package/node_modules/@poe-code/design-system/dist/explorer/render/modal.d.ts +3 -0
- package/node_modules/@poe-code/design-system/dist/explorer/render/modal.js +91 -0
- package/node_modules/@poe-code/design-system/dist/explorer/render/test-fixtures.d.ts +8 -0
- package/node_modules/@poe-code/design-system/dist/explorer/render/test-fixtures.js +156 -0
- package/node_modules/@poe-code/design-system/dist/explorer/runtime.d.ts +2 -0
- package/node_modules/@poe-code/design-system/dist/explorer/runtime.js +282 -0
- package/node_modules/@poe-code/design-system/dist/explorer/runtime.test-helpers.d.ts +50 -0
- package/node_modules/@poe-code/design-system/dist/explorer/runtime.test-helpers.js +101 -0
- package/node_modules/@poe-code/design-system/dist/explorer/state.d.ts +130 -0
- package/node_modules/@poe-code/design-system/dist/explorer/state.js +87 -0
- package/node_modules/@poe-code/design-system/dist/explorer/theme.d.ts +27 -0
- package/node_modules/@poe-code/design-system/dist/explorer/theme.js +97 -0
- package/node_modules/@poe-code/design-system/dist/index.d.ts +7 -0
- package/node_modules/@poe-code/design-system/dist/index.js +5 -0
- package/node_modules/@poe-code/design-system/dist/internal/color-support.d.ts +9 -0
- package/node_modules/@poe-code/design-system/dist/internal/color-support.js +12 -0
- package/node_modules/@poe-code/design-system/dist/prompts/index.js +2 -2
- package/node_modules/@poe-code/design-system/dist/prompts/primitives/cancel.js +2 -2
- package/node_modules/@poe-code/design-system/dist/prompts/primitives/intro.js +2 -2
- package/node_modules/@poe-code/design-system/dist/prompts/primitives/log.js +4 -4
- package/node_modules/@poe-code/design-system/dist/prompts/primitives/note.js +5 -5
- package/node_modules/@poe-code/design-system/dist/prompts/primitives/outro.js +2 -2
- package/node_modules/@poe-code/design-system/dist/prompts/primitives/spinner.js +3 -3
- package/node_modules/@poe-code/design-system/dist/static/menu.js +5 -5
- package/node_modules/@poe-code/design-system/dist/static/spinner.js +8 -8
- package/node_modules/@poe-code/design-system/dist/tokens/colors.js +29 -29
- package/node_modules/@poe-code/design-system/dist/tokens/typography.js +6 -6
- package/node_modules/@poe-code/design-system/package.json +6 -3
- package/package.json +2 -4
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
const HTML_ESCAPE = {
|
|
2
|
+
"&": "&",
|
|
3
|
+
"<": "<",
|
|
4
|
+
">": ">",
|
|
5
|
+
'"': """,
|
|
6
|
+
"'": "'",
|
|
7
|
+
"/": "/",
|
|
8
|
+
"`": "`",
|
|
9
|
+
"=": "="
|
|
10
|
+
};
|
|
11
|
+
export function renderTemplate(template, view, options = {}) {
|
|
12
|
+
const prepared = options.yield === undefined
|
|
13
|
+
? template
|
|
14
|
+
: template.split("{{yield}}").join(options.yield);
|
|
15
|
+
const tokens = parseTemplate(prepared);
|
|
16
|
+
const escape = options.escape === "none" ? String : escapeHtml;
|
|
17
|
+
const preserveMissing = options.yield !== undefined && options.escape === "none";
|
|
18
|
+
return renderTokens(tokens, { view }, prepared, escape, preserveMissing);
|
|
19
|
+
}
|
|
20
|
+
function renderTemplateInContext(template, context, escape, preserveMissing) {
|
|
21
|
+
return renderTokens(parseTemplate(template), context, template, escape, preserveMissing);
|
|
22
|
+
}
|
|
23
|
+
function parseTemplate(template) {
|
|
24
|
+
const root = [];
|
|
25
|
+
const stack = [];
|
|
26
|
+
let tokens = root;
|
|
27
|
+
let index = 0;
|
|
28
|
+
while (index < template.length) {
|
|
29
|
+
const open = template.indexOf("{{", index);
|
|
30
|
+
if (open === -1) {
|
|
31
|
+
appendText(tokens, template.slice(index), index);
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
appendText(tokens, template.slice(index, open), index);
|
|
35
|
+
const parsed = parseTag(template, open);
|
|
36
|
+
const standalone = getStandalone(template, open, parsed.end, parsed.kind);
|
|
37
|
+
if (standalone !== undefined) {
|
|
38
|
+
trimTextAfter(tokens, standalone.lineStart);
|
|
39
|
+
}
|
|
40
|
+
if (parsed.kind === "comment") {
|
|
41
|
+
index = standalone?.nextIndex ?? parsed.end;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (parsed.kind === "partial") {
|
|
45
|
+
throw new Error(`Partials are not supported: "${parsed.name}"`);
|
|
46
|
+
}
|
|
47
|
+
if (parsed.kind === "delimiter") {
|
|
48
|
+
throw new Error("Custom delimiters are not supported");
|
|
49
|
+
}
|
|
50
|
+
if (parsed.kind === "section" || parsed.kind === "inverted") {
|
|
51
|
+
const token = {
|
|
52
|
+
type: parsed.kind,
|
|
53
|
+
name: parsed.name,
|
|
54
|
+
children: [],
|
|
55
|
+
rawStart: parsed.end,
|
|
56
|
+
rawEnd: standalone?.lineStart ?? open
|
|
57
|
+
};
|
|
58
|
+
tokens.push(token);
|
|
59
|
+
stack.push({ token, parent: tokens });
|
|
60
|
+
tokens = token.children;
|
|
61
|
+
index = standalone?.nextIndex ?? parsed.end;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (parsed.kind === "close") {
|
|
65
|
+
const frame = stack.pop();
|
|
66
|
+
if (frame === undefined) {
|
|
67
|
+
throw new Error(`Closing unopened section "${parsed.name}"`);
|
|
68
|
+
}
|
|
69
|
+
if (frame.token.name !== parsed.name) {
|
|
70
|
+
throw new Error(`Unclosed section "${frame.token.name}" before closing "${parsed.name}"`);
|
|
71
|
+
}
|
|
72
|
+
frame.token.rawEnd = open;
|
|
73
|
+
tokens = frame.parent;
|
|
74
|
+
index = standalone?.nextIndex ?? parsed.end;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
tokens.push({ type: parsed.kind, name: parsed.name, raw: template.slice(open, parsed.end) });
|
|
78
|
+
index = parsed.end;
|
|
79
|
+
}
|
|
80
|
+
const frame = stack.pop();
|
|
81
|
+
if (frame !== undefined) {
|
|
82
|
+
throw new Error(`Unclosed section "${frame.token.name}"`);
|
|
83
|
+
}
|
|
84
|
+
return root;
|
|
85
|
+
}
|
|
86
|
+
function parseTag(template, open) {
|
|
87
|
+
if (template.startsWith("{{{", open)) {
|
|
88
|
+
const close = template.indexOf("}}}", open + 3);
|
|
89
|
+
if (close === -1) {
|
|
90
|
+
throw new Error("Unclosed unescaped tag");
|
|
91
|
+
}
|
|
92
|
+
return { kind: "unescaped", name: template.slice(open + 3, close).trim(), end: close + 3 };
|
|
93
|
+
}
|
|
94
|
+
const close = template.indexOf("}}", open + 2);
|
|
95
|
+
if (close === -1) {
|
|
96
|
+
throw new Error("Unclosed tag");
|
|
97
|
+
}
|
|
98
|
+
const raw = template.slice(open + 2, close).trim();
|
|
99
|
+
const sigil = raw[0];
|
|
100
|
+
const name = sigil === undefined ? "" : raw.slice(1).trim();
|
|
101
|
+
const end = close + 2;
|
|
102
|
+
if (sigil === "#")
|
|
103
|
+
return { kind: "section", name, end };
|
|
104
|
+
if (sigil === "^")
|
|
105
|
+
return { kind: "inverted", name, end };
|
|
106
|
+
if (sigil === "/")
|
|
107
|
+
return { kind: "close", name, end };
|
|
108
|
+
if (sigil === "!")
|
|
109
|
+
return { kind: "comment", name, end };
|
|
110
|
+
if (sigil === "&")
|
|
111
|
+
return { kind: "unescaped", name, end };
|
|
112
|
+
if (sigil === ">")
|
|
113
|
+
return { kind: "partial", name, end };
|
|
114
|
+
if (sigil === "=" && raw.endsWith("="))
|
|
115
|
+
return { kind: "delimiter", name, end };
|
|
116
|
+
return { kind: "name", name: raw, end };
|
|
117
|
+
}
|
|
118
|
+
function getStandalone(template, tagStart, tagEnd, kind) {
|
|
119
|
+
if (!["section", "inverted", "close", "comment", "partial", "delimiter"].includes(kind)) {
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
const lineStart = template.lastIndexOf("\n", tagStart - 1) + 1;
|
|
123
|
+
if (!isWhitespace(template.slice(lineStart, tagStart))) {
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
let cursor = tagEnd;
|
|
127
|
+
while (cursor < template.length && (template[cursor] === " " || template[cursor] === "\t")) {
|
|
128
|
+
cursor += 1;
|
|
129
|
+
}
|
|
130
|
+
if (template.startsWith("\r\n", cursor)) {
|
|
131
|
+
return { lineStart, nextIndex: cursor + 2 };
|
|
132
|
+
}
|
|
133
|
+
if (template[cursor] === "\n") {
|
|
134
|
+
return { lineStart, nextIndex: cursor + 1 };
|
|
135
|
+
}
|
|
136
|
+
if (cursor === template.length) {
|
|
137
|
+
return { lineStart, nextIndex: cursor };
|
|
138
|
+
}
|
|
139
|
+
return undefined;
|
|
140
|
+
}
|
|
141
|
+
function renderTokens(tokens, context, template, escape, preserveMissing) {
|
|
142
|
+
let output = "";
|
|
143
|
+
for (const token of tokens) {
|
|
144
|
+
switch (token.type) {
|
|
145
|
+
case "text":
|
|
146
|
+
output += token.value;
|
|
147
|
+
continue;
|
|
148
|
+
case "name":
|
|
149
|
+
case "unescaped": {
|
|
150
|
+
const value = lookup(context, token.name);
|
|
151
|
+
if (value == null) {
|
|
152
|
+
if (preserveMissing) {
|
|
153
|
+
output += token.raw;
|
|
154
|
+
}
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
const rendered = String(value);
|
|
158
|
+
output += token.type === "name" ? escape(rendered) : rendered;
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
case "inverted": {
|
|
162
|
+
const value = lookup(context, token.name);
|
|
163
|
+
if (!value || (Array.isArray(value) && value.length === 0)) {
|
|
164
|
+
output += renderTokens(token.children, context, template, escape, preserveMissing);
|
|
165
|
+
}
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
case "section": {
|
|
169
|
+
const value = lookup(context, token.name);
|
|
170
|
+
if (!value) {
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
if (Array.isArray(value)) {
|
|
174
|
+
for (const item of value) {
|
|
175
|
+
output += renderTokens(token.children, pushContext(context, item), template, escape, preserveMissing);
|
|
176
|
+
}
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
if (typeof value === "function") {
|
|
180
|
+
const raw = template.slice(token.rawStart, token.rawEnd);
|
|
181
|
+
const rendered = value.call(context.view, raw, (nextTemplate) => renderTemplateInContext(nextTemplate, context, escape, preserveMissing));
|
|
182
|
+
if (rendered != null) {
|
|
183
|
+
output += String(rendered);
|
|
184
|
+
}
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
if (typeof value === "object" || typeof value === "string" || typeof value === "number") {
|
|
188
|
+
output += renderTokens(token.children, pushContext(context, value), template, escape, preserveMissing);
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
output += renderTokens(token.children, context, template, escape, preserveMissing);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return output;
|
|
196
|
+
}
|
|
197
|
+
function lookup(context, name) {
|
|
198
|
+
if (name === ".") {
|
|
199
|
+
return callLambda(context.view, context.view);
|
|
200
|
+
}
|
|
201
|
+
let cursor = context;
|
|
202
|
+
while (cursor !== undefined) {
|
|
203
|
+
const result = name.includes(".")
|
|
204
|
+
? lookupDotted(cursor.view, name)
|
|
205
|
+
: lookupName(cursor.view, name);
|
|
206
|
+
if (result.hit) {
|
|
207
|
+
return callLambda(result.value, cursor.view);
|
|
208
|
+
}
|
|
209
|
+
cursor = cursor.parent;
|
|
210
|
+
}
|
|
211
|
+
return undefined;
|
|
212
|
+
}
|
|
213
|
+
function lookupName(view, name) {
|
|
214
|
+
if (!isPropertyContainer(view) || !hasProperty(view, name)) {
|
|
215
|
+
return { hit: false, value: undefined };
|
|
216
|
+
}
|
|
217
|
+
return { hit: true, value: view[name] };
|
|
218
|
+
}
|
|
219
|
+
function lookupDotted(view, name) {
|
|
220
|
+
const parts = name.split(".");
|
|
221
|
+
let value = view;
|
|
222
|
+
let hit = false;
|
|
223
|
+
for (let index = 0; value != null && index < parts.length; index += 1) {
|
|
224
|
+
const part = parts[index] ?? "";
|
|
225
|
+
if (index === parts.length - 1) {
|
|
226
|
+
hit = hasProperty(Object(value), part);
|
|
227
|
+
}
|
|
228
|
+
value = Object(value)[part];
|
|
229
|
+
}
|
|
230
|
+
return { hit, value };
|
|
231
|
+
}
|
|
232
|
+
function callLambda(value, view) {
|
|
233
|
+
return typeof value === "function" ? value.call(view) : value;
|
|
234
|
+
}
|
|
235
|
+
function pushContext(parent, view) {
|
|
236
|
+
return { view, parent };
|
|
237
|
+
}
|
|
238
|
+
function appendText(tokens, value, start) {
|
|
239
|
+
if (value === "") {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
const previous = tokens[tokens.length - 1];
|
|
243
|
+
if (previous?.type === "text") {
|
|
244
|
+
previous.value += value;
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
tokens.push({ type: "text", value, start });
|
|
248
|
+
}
|
|
249
|
+
function trimTextAfter(tokens, lineStart) {
|
|
250
|
+
const previous = tokens[tokens.length - 1];
|
|
251
|
+
if (previous?.type !== "text") {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
const keep = Math.max(0, lineStart - previous.start);
|
|
255
|
+
previous.value = previous.value.slice(0, keep);
|
|
256
|
+
if (previous.value === "") {
|
|
257
|
+
tokens.pop();
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
function escapeHtml(value) {
|
|
261
|
+
return value.replace(/[&<>"'`=/]/g, (char) => HTML_ESCAPE[char] ?? char);
|
|
262
|
+
}
|
|
263
|
+
function isWhitespace(value) {
|
|
264
|
+
return value.trim() === "";
|
|
265
|
+
}
|
|
266
|
+
function isPropertyContainer(value) {
|
|
267
|
+
return (typeof value === "object" && value !== null) || typeof value === "function";
|
|
268
|
+
}
|
|
269
|
+
function hasProperty(value, key) {
|
|
270
|
+
return key in value;
|
|
271
|
+
}
|
|
@@ -10,6 +10,7 @@ export declare const text: {
|
|
|
10
10
|
readonly usageCommand: (content: string) => string;
|
|
11
11
|
readonly link: (content: string) => string;
|
|
12
12
|
readonly muted: (content: string) => string;
|
|
13
|
+
readonly error: (content: string) => string;
|
|
13
14
|
readonly badge: (content: string) => string;
|
|
14
15
|
readonly selectLabel: (label: string, detail?: string) => string;
|
|
15
16
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { color } from "./color.js";
|
|
2
2
|
import { resolveOutputFormat } from "../internal/output-format.js";
|
|
3
3
|
import { getTheme } from "../internal/theme-detect.js";
|
|
4
4
|
import { typography } from "../tokens/typography.js";
|
|
@@ -57,7 +57,7 @@ export const text = {
|
|
|
57
57
|
return content;
|
|
58
58
|
if (format === "markdown")
|
|
59
59
|
return `\`${content}\``;
|
|
60
|
-
return
|
|
60
|
+
return color.yellow(content);
|
|
61
61
|
},
|
|
62
62
|
example(content) {
|
|
63
63
|
const format = resolveOutputFormat();
|
|
@@ -73,7 +73,7 @@ export const text = {
|
|
|
73
73
|
return content;
|
|
74
74
|
if (format === "markdown")
|
|
75
75
|
return `\`${content}\``;
|
|
76
|
-
return
|
|
76
|
+
return color.green(content);
|
|
77
77
|
},
|
|
78
78
|
link(content) {
|
|
79
79
|
const format = resolveOutputFormat();
|
|
@@ -91,6 +91,14 @@ export const text = {
|
|
|
91
91
|
return `*${content}*`;
|
|
92
92
|
return getTheme().muted(content);
|
|
93
93
|
},
|
|
94
|
+
error(content) {
|
|
95
|
+
const format = resolveOutputFormat();
|
|
96
|
+
if (format === "json")
|
|
97
|
+
return content;
|
|
98
|
+
if (format === "markdown")
|
|
99
|
+
return `**${content}**`;
|
|
100
|
+
return getTheme().error(content);
|
|
101
|
+
},
|
|
94
102
|
badge(content) {
|
|
95
103
|
const format = resolveOutputFormat();
|
|
96
104
|
if (format === "json")
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { color } from "../components/color.js";
|
|
2
2
|
const EMPTY_CELL = { ch: " ", style: {} };
|
|
3
3
|
export class ScreenBuffer {
|
|
4
4
|
_width;
|
|
@@ -119,13 +119,16 @@ export function diff(prev, next) {
|
|
|
119
119
|
}
|
|
120
120
|
export function cellToAnsi(cell) {
|
|
121
121
|
const style = cell.style ?? {};
|
|
122
|
-
let painter =
|
|
122
|
+
let painter = color;
|
|
123
123
|
if (style.bold) {
|
|
124
124
|
painter = painter.bold;
|
|
125
125
|
}
|
|
126
126
|
if (style.dim) {
|
|
127
127
|
painter = painter.dim;
|
|
128
128
|
}
|
|
129
|
+
if (style.underline) {
|
|
130
|
+
painter = painter.underline;
|
|
131
|
+
}
|
|
129
132
|
if (style.fg) {
|
|
130
133
|
painter = applyForegroundColor(painter, style.fg);
|
|
131
134
|
}
|
|
@@ -158,6 +161,9 @@ function normalizeStyle(style) {
|
|
|
158
161
|
if (style?.dim !== undefined) {
|
|
159
162
|
next.dim = style.dim;
|
|
160
163
|
}
|
|
164
|
+
if (style?.underline !== undefined) {
|
|
165
|
+
next.underline = style.underline;
|
|
166
|
+
}
|
|
161
167
|
return next;
|
|
162
168
|
}
|
|
163
169
|
function normalizeSize(value) {
|
|
@@ -168,22 +174,23 @@ function cellsEqual(left, right) {
|
|
|
168
174
|
&& left.style.fg === right.style.fg
|
|
169
175
|
&& left.style.bg === right.style.bg
|
|
170
176
|
&& left.style.bold === right.style.bold
|
|
171
|
-
&& left.style.dim === right.style.dim
|
|
177
|
+
&& left.style.dim === right.style.dim
|
|
178
|
+
&& left.style.underline === right.style.underline;
|
|
172
179
|
}
|
|
173
|
-
function applyForegroundColor(instance,
|
|
174
|
-
if (
|
|
175
|
-
return instance.hex(
|
|
180
|
+
function applyForegroundColor(instance, ansiColor) {
|
|
181
|
+
if (ansiColor.startsWith("#")) {
|
|
182
|
+
return instance.hex(ansiColor);
|
|
176
183
|
}
|
|
177
|
-
const painter = instance[
|
|
184
|
+
const painter = instance[ansiColor];
|
|
178
185
|
return typeof painter === "function" ? painter : instance;
|
|
179
186
|
}
|
|
180
|
-
function applyBackgroundColor(instance,
|
|
181
|
-
if (
|
|
182
|
-
return instance.bgHex(
|
|
187
|
+
function applyBackgroundColor(instance, ansiColor) {
|
|
188
|
+
if (ansiColor.startsWith("#")) {
|
|
189
|
+
return instance.bgHex(ansiColor);
|
|
183
190
|
}
|
|
184
|
-
const methodName =
|
|
185
|
-
?
|
|
186
|
-
: `bg${
|
|
191
|
+
const methodName = ansiColor.startsWith("bg")
|
|
192
|
+
? ansiColor
|
|
193
|
+
: `bg${ansiColor.charAt(0).toUpperCase()}${ansiColor.slice(1)}`;
|
|
187
194
|
const painter = instance[methodName];
|
|
188
195
|
return typeof painter === "function" ? painter : instance;
|
|
189
196
|
}
|
|
@@ -1,3 +1,8 @@
|
|
|
1
1
|
import type { KeypressEvent } from "./terminal.js";
|
|
2
2
|
import type { Command } from "./types.js";
|
|
3
3
|
export declare function createKeymap(overrides?: Partial<Record<Command, string[]>>): (event: KeypressEvent) => Command | undefined;
|
|
4
|
+
export declare function createKeymap<TCommand extends string>(overrides: Partial<Record<TCommand, string[]>> | undefined, options: {
|
|
5
|
+
commands: readonly TCommand[];
|
|
6
|
+
defaultBindings: Record<TCommand, readonly string[]>;
|
|
7
|
+
}): (event: KeypressEvent) => TCommand | undefined;
|
|
8
|
+
export declare function canonicalizeBinding(binding: string): string | undefined;
|
|
@@ -7,23 +7,78 @@ const defaultBindings = {
|
|
|
7
7
|
retry: ["r"],
|
|
8
8
|
"view-log": ["l"]
|
|
9
9
|
};
|
|
10
|
-
export function createKeymap(overrides) {
|
|
10
|
+
export function createKeymap(overrides, options) {
|
|
11
|
+
const resolvedCommands = options?.commands ?? commands;
|
|
12
|
+
const resolvedDefaults = options?.defaultBindings ?? defaultBindings;
|
|
11
13
|
const bindings = new Map();
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
const sequences = new Set();
|
|
15
|
+
let pendingSequence = "";
|
|
16
|
+
for (const command of resolvedCommands) {
|
|
17
|
+
const keys = overrides?.[command] ?? resolvedDefaults[command];
|
|
18
|
+
const commandBindings = keys
|
|
15
19
|
.map(parseBinding)
|
|
16
|
-
.filter((binding) => binding !== undefined)
|
|
20
|
+
.filter((binding) => binding !== undefined);
|
|
21
|
+
for (const binding of commandBindings) {
|
|
22
|
+
if (binding.sequence !== undefined) {
|
|
23
|
+
sequences.add(binding.sequence);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
bindings.set(command, commandBindings);
|
|
17
27
|
}
|
|
18
28
|
return (event) => {
|
|
19
|
-
for (const command of
|
|
29
|
+
for (const command of resolvedCommands) {
|
|
20
30
|
const commandBindings = bindings.get(command);
|
|
21
|
-
if (commandBindings?.some((binding) =>
|
|
31
|
+
if (commandBindings?.some((binding) => matchesSingleKey(binding, event))) {
|
|
32
|
+
pendingSequence = "";
|
|
22
33
|
return command;
|
|
23
34
|
}
|
|
24
35
|
}
|
|
36
|
+
const sequenceCommand = resolveSequence(event);
|
|
37
|
+
if (sequenceCommand !== undefined) {
|
|
38
|
+
return sequenceCommand;
|
|
39
|
+
}
|
|
25
40
|
return undefined;
|
|
26
41
|
};
|
|
42
|
+
function resolveSequence(event) {
|
|
43
|
+
const token = eventToSequenceToken(event);
|
|
44
|
+
if (token === undefined) {
|
|
45
|
+
pendingSequence = "";
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
pendingSequence = `${pendingSequence}${token}`;
|
|
49
|
+
for (const command of resolvedCommands) {
|
|
50
|
+
const commandBindings = bindings.get(command);
|
|
51
|
+
if (commandBindings?.some((binding) => binding.sequence === pendingSequence)) {
|
|
52
|
+
pendingSequence = "";
|
|
53
|
+
return command;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (hasSequencePrefix(sequences, pendingSequence)) {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
pendingSequence = token;
|
|
60
|
+
if (hasSequencePrefix(sequences, pendingSequence)) {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
pendingSequence = "";
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
export function canonicalizeBinding(binding) {
|
|
68
|
+
const parsed = parseBinding(binding);
|
|
69
|
+
if (parsed === undefined) {
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
const modifiers = [
|
|
73
|
+
parsed.ctrl ? "ctrl" : undefined,
|
|
74
|
+
parsed.meta ? "meta" : undefined,
|
|
75
|
+
parsed.shift ? "shift" : undefined
|
|
76
|
+
].filter((modifier) => modifier !== undefined);
|
|
77
|
+
const key = parsed.name ?? parsed.ch;
|
|
78
|
+
if (parsed.sequence !== undefined) {
|
|
79
|
+
return parsed.sequence.toLowerCase();
|
|
80
|
+
}
|
|
81
|
+
return key === undefined ? undefined : [...modifiers, key.toLowerCase()].join("+");
|
|
27
82
|
}
|
|
28
83
|
function parseBinding(binding) {
|
|
29
84
|
const value = binding.trim();
|
|
@@ -56,25 +111,41 @@ function parseBinding(binding) {
|
|
|
56
111
|
continue;
|
|
57
112
|
}
|
|
58
113
|
}
|
|
59
|
-
|
|
114
|
+
const normalizedKey = normalizeKeyName(key);
|
|
115
|
+
if (parts.length === 1 && isShiftedCharacter(normalizedKey)) {
|
|
60
116
|
shift = true;
|
|
61
117
|
}
|
|
62
|
-
if (
|
|
118
|
+
if (normalizedKey.length === 1) {
|
|
119
|
+
return {
|
|
120
|
+
ch: normalizeBindingCharacter(normalizedKey, shift),
|
|
121
|
+
ctrl,
|
|
122
|
+
meta,
|
|
123
|
+
shift
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
if (!ctrl &&
|
|
127
|
+
!meta &&
|
|
128
|
+
!shift &&
|
|
129
|
+
!isNamedKey(normalizedKey) &&
|
|
130
|
+
isPrintableSequence(normalizedKey)) {
|
|
63
131
|
return {
|
|
64
|
-
|
|
132
|
+
sequence: normalizedKey,
|
|
65
133
|
ctrl,
|
|
66
134
|
meta,
|
|
67
135
|
shift
|
|
68
136
|
};
|
|
69
137
|
}
|
|
70
138
|
return {
|
|
71
|
-
name:
|
|
139
|
+
name: normalizedKey.toLowerCase(),
|
|
72
140
|
ctrl,
|
|
73
141
|
meta,
|
|
74
142
|
shift
|
|
75
143
|
};
|
|
76
144
|
}
|
|
77
|
-
function
|
|
145
|
+
function matchesSingleKey(binding, event) {
|
|
146
|
+
if (binding.sequence !== undefined) {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
78
149
|
if (binding.ctrl !== event.ctrl ||
|
|
79
150
|
binding.meta !== event.meta ||
|
|
80
151
|
binding.shift !== event.shift) {
|
|
@@ -88,6 +159,20 @@ function matches(binding, event) {
|
|
|
88
159
|
}
|
|
89
160
|
return false;
|
|
90
161
|
}
|
|
162
|
+
function eventToSequenceToken(event) {
|
|
163
|
+
if (event.ctrl || event.meta || event.ch === undefined) {
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
return event.ch;
|
|
167
|
+
}
|
|
168
|
+
function hasSequencePrefix(sequences, prefix) {
|
|
169
|
+
for (const sequence of sequences) {
|
|
170
|
+
if (sequence.startsWith(prefix)) {
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
91
176
|
function isShiftedCharacter(value) {
|
|
92
177
|
return value.length === 1 && value.toLowerCase() !== value && value.toUpperCase() === value;
|
|
93
178
|
}
|
|
@@ -97,3 +182,52 @@ function normalizeBindingCharacter(value, shift) {
|
|
|
97
182
|
}
|
|
98
183
|
return value.toUpperCase();
|
|
99
184
|
}
|
|
185
|
+
function normalizeKeyName(value) {
|
|
186
|
+
if (value.toLowerCase() === "space") {
|
|
187
|
+
return " ";
|
|
188
|
+
}
|
|
189
|
+
if (value === "↑") {
|
|
190
|
+
return "up";
|
|
191
|
+
}
|
|
192
|
+
if (value === "↓") {
|
|
193
|
+
return "down";
|
|
194
|
+
}
|
|
195
|
+
if (value === "←") {
|
|
196
|
+
return "left";
|
|
197
|
+
}
|
|
198
|
+
if (value === "→") {
|
|
199
|
+
return "right";
|
|
200
|
+
}
|
|
201
|
+
return value;
|
|
202
|
+
}
|
|
203
|
+
function isNamedKey(value) {
|
|
204
|
+
return namedKeys.has(value.toLowerCase());
|
|
205
|
+
}
|
|
206
|
+
function isPrintableSequence(value) {
|
|
207
|
+
if (Array.from(value).length <= 1) {
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
for (const char of value) {
|
|
211
|
+
const codePoint = char.codePointAt(0);
|
|
212
|
+
if (codePoint === undefined || codePoint < 0x20 || codePoint === 0x7f) {
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
const namedKeys = new Set([
|
|
219
|
+
"backspace",
|
|
220
|
+
"delete",
|
|
221
|
+
"down",
|
|
222
|
+
"end",
|
|
223
|
+
"enter",
|
|
224
|
+
"escape",
|
|
225
|
+
"home",
|
|
226
|
+
"left",
|
|
227
|
+
"pagedown",
|
|
228
|
+
"pageup",
|
|
229
|
+
"return",
|
|
230
|
+
"right",
|
|
231
|
+
"tab",
|
|
232
|
+
"up"
|
|
233
|
+
]);
|
|
@@ -185,6 +185,10 @@ export function parseKeypress(data) {
|
|
|
185
185
|
return event;
|
|
186
186
|
}
|
|
187
187
|
function toKeypressEvent(str, key) {
|
|
188
|
+
const controlCharacter = controlCharacterToKeypress(key?.sequence);
|
|
189
|
+
if (controlCharacter !== undefined) {
|
|
190
|
+
return controlCharacter;
|
|
191
|
+
}
|
|
188
192
|
const ctrl = key?.ctrl ?? false;
|
|
189
193
|
const meta = key?.meta ?? false;
|
|
190
194
|
const shift = key?.shift ?? false;
|
|
@@ -206,12 +210,22 @@ function extractPrintableCharacter(str, sequence) {
|
|
|
206
210
|
if (isPrintableCharacter(str)) {
|
|
207
211
|
return str;
|
|
208
212
|
}
|
|
213
|
+
if (isSinglePrintableSequence(sequence)) {
|
|
214
|
+
return sequence;
|
|
215
|
+
}
|
|
209
216
|
if (sequence === undefined || sequence.length <= 1 || sequence[0] !== "\u001b") {
|
|
210
217
|
return undefined;
|
|
211
218
|
}
|
|
212
219
|
const candidate = sequence.slice(1);
|
|
213
220
|
return isPrintableCharacter(candidate) ? candidate : undefined;
|
|
214
221
|
}
|
|
222
|
+
function isSinglePrintableSequence(value) {
|
|
223
|
+
if (value === undefined || Array.from(value).length !== 1) {
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
const codePoint = value.codePointAt(0);
|
|
227
|
+
return codePoint !== undefined && codePoint >= 0x20 && codePoint !== 0x7f;
|
|
228
|
+
}
|
|
215
229
|
function isPrintableCharacter(value) {
|
|
216
230
|
if (value === undefined || Array.from(value).length !== 1) {
|
|
217
231
|
return false;
|
|
@@ -219,6 +233,23 @@ function isPrintableCharacter(value) {
|
|
|
219
233
|
const codePoint = value.codePointAt(0);
|
|
220
234
|
return codePoint !== undefined && codePoint >= 0x20 && codePoint !== 0x7f;
|
|
221
235
|
}
|
|
236
|
+
function controlCharacterToKeypress(sequence) {
|
|
237
|
+
if (sequence === "\u001f") {
|
|
238
|
+
return { ch: "/", ctrl: true, meta: false, shift: false };
|
|
239
|
+
}
|
|
240
|
+
if (sequence !== undefined && sequence.length === 1) {
|
|
241
|
+
const code = sequence.charCodeAt(0);
|
|
242
|
+
if (code >= 1 && code <= 26 && code !== 9 && code !== 10 && code !== 13) {
|
|
243
|
+
return {
|
|
244
|
+
name: String.fromCharCode(code + 96),
|
|
245
|
+
ctrl: true,
|
|
246
|
+
meta: false,
|
|
247
|
+
shift: false
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return undefined;
|
|
252
|
+
}
|
|
222
253
|
function cursorPositionAnsi(x, y) {
|
|
223
254
|
return `\u001b[${normalizeCoordinate(y) + 1};${normalizeCoordinate(x) + 1}H`;
|
|
224
255
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ExplorerEvent } from "./events.js";
|
|
2
|
+
import type { Action, ActionContext, ExplorerState, Row, Tone } from "./state.js";
|
|
3
|
+
export type ActionSource = "row" | "detail";
|
|
4
|
+
type ExplorerKeypressEvent = Extract<ExplorerEvent, {
|
|
5
|
+
type: "key";
|
|
6
|
+
}>["key"];
|
|
7
|
+
export type ActionRuntimeHandles = {
|
|
8
|
+
refresh: () => Promise<void>;
|
|
9
|
+
suspendAnd: <T>(fn: () => Promise<T>) => Promise<T>;
|
|
10
|
+
toast: (msg: string, tone?: Tone) => void;
|
|
11
|
+
confirm: (prompt: string) => Promise<boolean>;
|
|
12
|
+
exit: (after?: () => void | Promise<void>) => void;
|
|
13
|
+
};
|
|
14
|
+
export declare function resolveAction<R>(state: ExplorerState, keyEvent: ExplorerKeypressEvent): Action<R> | null;
|
|
15
|
+
export declare function buildActionContext<R>(state: ExplorerState, _action: Action<R>, source: ActionSource, runtimeHandles: ActionRuntimeHandles, rowsOverride?: Row[]): ActionContext<R>;
|
|
16
|
+
export {};
|