archymedes-cli 2.0.0 → 2.2.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/README.md +18 -1
- package/dist/archymedes-5zn4zp84.mjs +646 -0
- package/dist/{archymedes-h2x8ct7s.mjs → archymedes-76z6n643.mjs} +1 -1
- package/dist/{archymedes-44c1mqas.mjs → archymedes-cdgejs3p.mjs} +1 -1
- package/dist/archymedes-main.mjs +2159 -992
- package/dist/{archymedes-k1bx08z9.mjs → archymedes-npcrm9a0.mjs} +3 -1
- package/dist/{archymedes-2tp22t9a.mjs → archymedes-pg3pvh9r.mjs} +6 -4
- package/dist/{archymedes-dwvn4c51.mjs → archymedes-r54qthg5.mjs} +47 -378
- package/dist/archymedes-sc3mwttb.mjs +284 -0
- package/dist/{archymedes-brkqgrxv.mjs → archymedes-wjaqydyg.mjs} +44 -32
- package/dist/{editor-screen-51z7z11j.mjs → editor-screen-1b3z96tn.mjs} +259 -11
- package/dist/{factory-0z4he0ev.mjs → factory-96yy6mxr.mjs} +1 -1
- package/dist/{file-screen-eg28a3ky.mjs → file-screen-m7q1xcer.mjs} +5 -5
- package/dist/{guide-screen-mfvwkdhb.mjs → guide-screen-aj9n9v80.mjs} +15 -11
- package/dist/{workspace-screen-3zztn4by.mjs → workspace-screen-w88mcw4w.mjs} +7 -6
- package/package.json +1 -1
- package/dist/archymedes-jecyxv5c.mjs +0 -249
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CYAN,
|
|
3
|
+
DIM,
|
|
4
|
+
GREEN,
|
|
5
|
+
GUTTER,
|
|
6
|
+
MAGENTA,
|
|
7
|
+
RED,
|
|
8
|
+
clip,
|
|
9
|
+
paint
|
|
10
|
+
} from "./archymedes-cdgejs3p.mjs";
|
|
11
|
+
import {
|
|
12
|
+
UNICODE_GLYPHS
|
|
13
|
+
} from "./archymedes-5zn4zp84.mjs";
|
|
14
|
+
|
|
15
|
+
// packages/archymedes-cli/src/code-view.ts
|
|
16
|
+
var MAX_DIFF_CELLS = 250000;
|
|
17
|
+
function diffLines(before, after) {
|
|
18
|
+
const left = before === "" ? [] : before.split(`
|
|
19
|
+
`);
|
|
20
|
+
const right = after === "" ? [] : after.split(`
|
|
21
|
+
`);
|
|
22
|
+
if (left.length * right.length > MAX_DIFF_CELLS) {
|
|
23
|
+
return [
|
|
24
|
+
...left.map((text) => ({ kind: "remove", text })),
|
|
25
|
+
...right.map((text) => ({ kind: "add", text }))
|
|
26
|
+
];
|
|
27
|
+
}
|
|
28
|
+
const lengths = Array.from({ length: left.length + 1 }, () => new Array(right.length + 1).fill(0));
|
|
29
|
+
for (let i2 = left.length - 1;i2 >= 0; i2 -= 1) {
|
|
30
|
+
for (let j2 = right.length - 1;j2 >= 0; j2 -= 1) {
|
|
31
|
+
lengths[i2][j2] = left[i2] === right[j2] ? lengths[i2 + 1][j2 + 1] + 1 : Math.max(lengths[i2 + 1][j2], lengths[i2][j2 + 1]);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const result = [];
|
|
35
|
+
let i = 0;
|
|
36
|
+
let j = 0;
|
|
37
|
+
while (i < left.length && j < right.length) {
|
|
38
|
+
if (left[i] === right[j]) {
|
|
39
|
+
result.push({ kind: "context", text: left[i] });
|
|
40
|
+
i += 1;
|
|
41
|
+
j += 1;
|
|
42
|
+
} else if (lengths[i + 1][j] >= lengths[i][j + 1]) {
|
|
43
|
+
result.push({ kind: "remove", text: left[i] });
|
|
44
|
+
i += 1;
|
|
45
|
+
} else {
|
|
46
|
+
result.push({ kind: "add", text: right[j] });
|
|
47
|
+
j += 1;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
while (i < left.length) {
|
|
51
|
+
result.push({ kind: "remove", text: left[i] });
|
|
52
|
+
i += 1;
|
|
53
|
+
}
|
|
54
|
+
while (j < right.length) {
|
|
55
|
+
result.push({ kind: "add", text: right[j] });
|
|
56
|
+
j += 1;
|
|
57
|
+
}
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
function condenseDiff(diff, context = 2) {
|
|
61
|
+
const keep = new Array(diff.length).fill(false);
|
|
62
|
+
diff.forEach((line, index) => {
|
|
63
|
+
if (line.kind === "context")
|
|
64
|
+
return;
|
|
65
|
+
for (let offset = -context;offset <= context; offset += 1) {
|
|
66
|
+
const target = index + offset;
|
|
67
|
+
if (target >= 0 && target < diff.length)
|
|
68
|
+
keep[target] = true;
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
const result = [];
|
|
72
|
+
let skipped = 0;
|
|
73
|
+
diff.forEach((line, index) => {
|
|
74
|
+
if (keep[index]) {
|
|
75
|
+
if (skipped > 0) {
|
|
76
|
+
result.push({ kind: "gap", text: `${skipped} unchanged line${skipped === 1 ? "" : "s"}` });
|
|
77
|
+
skipped = 0;
|
|
78
|
+
}
|
|
79
|
+
result.push(line);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
skipped += 1;
|
|
83
|
+
});
|
|
84
|
+
if (skipped > 0)
|
|
85
|
+
result.push({ kind: "gap", text: `${skipped} unchanged line${skipped === 1 ? "" : "s"}` });
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
function languageOf(filePath) {
|
|
89
|
+
const name = filePath.split(/[\\/]/).pop() ?? "";
|
|
90
|
+
const extension = name.includes(".") ? name.split(".").pop().toLowerCase() : "";
|
|
91
|
+
const byExtension = {
|
|
92
|
+
ts: "typescript",
|
|
93
|
+
tsx: "tsx",
|
|
94
|
+
js: "javascript",
|
|
95
|
+
jsx: "jsx",
|
|
96
|
+
mjs: "javascript",
|
|
97
|
+
cjs: "javascript",
|
|
98
|
+
py: "python",
|
|
99
|
+
rb: "ruby",
|
|
100
|
+
go: "go",
|
|
101
|
+
rs: "rust",
|
|
102
|
+
java: "java",
|
|
103
|
+
kt: "kotlin",
|
|
104
|
+
swift: "swift",
|
|
105
|
+
c: "c",
|
|
106
|
+
h: "c",
|
|
107
|
+
cc: "cpp",
|
|
108
|
+
cpp: "cpp",
|
|
109
|
+
hpp: "cpp",
|
|
110
|
+
cs: "csharp",
|
|
111
|
+
php: "php",
|
|
112
|
+
sh: "shell",
|
|
113
|
+
bash: "shell",
|
|
114
|
+
zsh: "shell",
|
|
115
|
+
fish: "shell",
|
|
116
|
+
json: "json",
|
|
117
|
+
yml: "yaml",
|
|
118
|
+
yaml: "yaml",
|
|
119
|
+
toml: "toml",
|
|
120
|
+
ini: "ini",
|
|
121
|
+
xml: "xml",
|
|
122
|
+
html: "html",
|
|
123
|
+
css: "css",
|
|
124
|
+
scss: "scss",
|
|
125
|
+
sql: "sql",
|
|
126
|
+
md: "markdown",
|
|
127
|
+
mdx: "markdown",
|
|
128
|
+
txt: "text"
|
|
129
|
+
};
|
|
130
|
+
if (byExtension[extension])
|
|
131
|
+
return byExtension[extension];
|
|
132
|
+
if (/^(?:dockerfile|makefile|justfile)$/i.test(name))
|
|
133
|
+
return name.toLowerCase();
|
|
134
|
+
return extension || "text";
|
|
135
|
+
}
|
|
136
|
+
var KEYWORDS = new Set([
|
|
137
|
+
"async",
|
|
138
|
+
"await",
|
|
139
|
+
"break",
|
|
140
|
+
"case",
|
|
141
|
+
"catch",
|
|
142
|
+
"class",
|
|
143
|
+
"const",
|
|
144
|
+
"continue",
|
|
145
|
+
"def",
|
|
146
|
+
"default",
|
|
147
|
+
"elif",
|
|
148
|
+
"else",
|
|
149
|
+
"end",
|
|
150
|
+
"enum",
|
|
151
|
+
"export",
|
|
152
|
+
"extends",
|
|
153
|
+
"finally",
|
|
154
|
+
"fn",
|
|
155
|
+
"for",
|
|
156
|
+
"from",
|
|
157
|
+
"func",
|
|
158
|
+
"function",
|
|
159
|
+
"if",
|
|
160
|
+
"impl",
|
|
161
|
+
"import",
|
|
162
|
+
"in",
|
|
163
|
+
"interface",
|
|
164
|
+
"let",
|
|
165
|
+
"match",
|
|
166
|
+
"mod",
|
|
167
|
+
"new",
|
|
168
|
+
"package",
|
|
169
|
+
"private",
|
|
170
|
+
"public",
|
|
171
|
+
"pub",
|
|
172
|
+
"raise",
|
|
173
|
+
"return",
|
|
174
|
+
"static",
|
|
175
|
+
"struct",
|
|
176
|
+
"switch",
|
|
177
|
+
"throw",
|
|
178
|
+
"trait",
|
|
179
|
+
"try",
|
|
180
|
+
"type",
|
|
181
|
+
"use",
|
|
182
|
+
"var",
|
|
183
|
+
"void",
|
|
184
|
+
"while",
|
|
185
|
+
"with",
|
|
186
|
+
"yield"
|
|
187
|
+
]);
|
|
188
|
+
var LITERALS = new Set(["true", "false", "null", "nil", "None", "True", "False", "undefined", "self", "this"]);
|
|
189
|
+
function highlightCode(line, depth) {
|
|
190
|
+
if (depth === "none")
|
|
191
|
+
return line;
|
|
192
|
+
const commentAt = findCommentStart(line);
|
|
193
|
+
const code = commentAt === -1 ? line : line.slice(0, commentAt);
|
|
194
|
+
const comment = commentAt === -1 ? "" : paint(line.slice(commentAt), DIM, depth);
|
|
195
|
+
const painted = code.replace(/("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|`(?:[^`\\]|\\.)*`)|\b(\d+(?:\.\d+)?)\b|\b([A-Za-z_$][\w$]*)\b/g, (whole, text, numeric, word) => {
|
|
196
|
+
if (text !== undefined)
|
|
197
|
+
return paint(text, GREEN, depth);
|
|
198
|
+
if (numeric !== undefined)
|
|
199
|
+
return paint(numeric, MAGENTA, depth);
|
|
200
|
+
if (word !== undefined && KEYWORDS.has(word))
|
|
201
|
+
return paint(word, CYAN, depth);
|
|
202
|
+
if (word !== undefined && LITERALS.has(word))
|
|
203
|
+
return paint(word, MAGENTA, depth);
|
|
204
|
+
return whole;
|
|
205
|
+
});
|
|
206
|
+
return `${painted}${comment}`;
|
|
207
|
+
}
|
|
208
|
+
function findCommentStart(line) {
|
|
209
|
+
let quote;
|
|
210
|
+
for (let index = 0;index < line.length; index += 1) {
|
|
211
|
+
const character = line[index];
|
|
212
|
+
if (quote) {
|
|
213
|
+
if (character === "\\") {
|
|
214
|
+
index += 1;
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
if (character === quote)
|
|
218
|
+
quote = undefined;
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if (character === '"' || character === "'" || character === "`") {
|
|
222
|
+
quote = character;
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
if (character === "#")
|
|
226
|
+
return index;
|
|
227
|
+
if (character === "/" && line[index + 1] === "/")
|
|
228
|
+
return index;
|
|
229
|
+
if (character === "-" && line[index + 1] === "-")
|
|
230
|
+
return index;
|
|
231
|
+
}
|
|
232
|
+
return -1;
|
|
233
|
+
}
|
|
234
|
+
function renderCode(content, style, options = {}) {
|
|
235
|
+
const glyphs = style.glyphs ?? UNICODE_GLYPHS;
|
|
236
|
+
const lines = content.replace(/\n$/, "").split(`
|
|
237
|
+
`);
|
|
238
|
+
const maxLines = options.maxLines ?? 16;
|
|
239
|
+
const start = options.startLine ?? 1;
|
|
240
|
+
const numberWidth = String(start + lines.length - 1).length;
|
|
241
|
+
const bodyWidth = Math.max(8, style.width - GUTTER.length - numberWidth - 4);
|
|
242
|
+
const render = (from, to) => lines.slice(from, to).map((line, index) => {
|
|
243
|
+
const number = paint(String(start + from + index).padStart(numberWidth), DIM, style.depth);
|
|
244
|
+
const painted = options.highlight === false ? line : highlightCode(line, style.depth);
|
|
245
|
+
return `${number} ${clip(painted, bodyWidth, glyphs)}`;
|
|
246
|
+
});
|
|
247
|
+
const hidden = Math.max(0, lines.length - maxLines);
|
|
248
|
+
const shown = render(0, hidden > 0 ? maxLines : lines.length);
|
|
249
|
+
return {
|
|
250
|
+
text: shown.join(`
|
|
251
|
+
`),
|
|
252
|
+
hidden,
|
|
253
|
+
full: render(0, lines.length).join(`
|
|
254
|
+
`)
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
function renderDiff(diff, style, options = {}) {
|
|
258
|
+
const glyphs = style.glyphs ?? UNICODE_GLYPHS;
|
|
259
|
+
const maxLines = options.maxLines ?? 16;
|
|
260
|
+
const condensed = condenseDiff(diff);
|
|
261
|
+
const bodyWidth = Math.max(8, style.width - GUTTER.length - 4);
|
|
262
|
+
const row = (line) => {
|
|
263
|
+
if (line.kind === "gap")
|
|
264
|
+
return paint(`${glyphs.middot.repeat(3)} ${line.text}`, DIM, style.depth);
|
|
265
|
+
const mark = line.kind === "add" ? glyphs.plus : line.kind === "remove" ? glyphs.minus : " ";
|
|
266
|
+
const code = line.kind === "add" ? GREEN : line.kind === "remove" ? RED : DIM;
|
|
267
|
+
const body = line.kind === "context" ? paint(line.text, DIM, style.depth) : highlightCode(line.text, style.depth);
|
|
268
|
+
return `${paint(mark, code, style.depth)} ${clip(body, bodyWidth, glyphs)}`;
|
|
269
|
+
};
|
|
270
|
+
const rows = condensed.map(row);
|
|
271
|
+
const hidden = Math.max(0, rows.length - maxLines);
|
|
272
|
+
return {
|
|
273
|
+
text: (hidden > 0 ? rows.slice(0, maxLines) : rows).join(`
|
|
274
|
+
`),
|
|
275
|
+
hidden,
|
|
276
|
+
full: diff.map(row).join(`
|
|
277
|
+
`)
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
function describeChange(stat, depth) {
|
|
281
|
+
return `${paint(`+${stat.added}`, GREEN, depth)} ${paint(`-${stat.removed}`, RED, depth)}`;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export { diffLines, languageOf, highlightCode, renderCode, renderDiff, describeChange };
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
SpringAnimator,
|
|
3
|
+
paginator
|
|
4
|
+
} from "./archymedes-r54qthg5.mjs";
|
|
5
|
+
import {
|
|
3
6
|
UNICODE_GLYPHS,
|
|
4
|
-
paginator,
|
|
5
7
|
visibleWidth
|
|
6
|
-
} from "./archymedes-
|
|
8
|
+
} from "./archymedes-5zn4zp84.mjs";
|
|
7
9
|
|
|
8
10
|
// packages/archymedes-cli/src/chooser.ts
|
|
9
11
|
var ALL_CHROME = { title: true, status: true, pagination: true, help: true };
|
|
@@ -129,7 +131,11 @@ function renderChooser(state, items, options) {
|
|
|
129
131
|
const shownLabel = clip(item.label, labelBudget);
|
|
130
132
|
const padded = shownLabel + " ".repeat(Math.max(0, labelBudget - visibleWidth(shownLabel)));
|
|
131
133
|
const marker = active ? paint.green(glyphs.prompt) : fadingOut ? paint.dim(glyphs.prompt) : " ";
|
|
132
|
-
const
|
|
134
|
+
const focus = Math.max(0, Math.min(1, options.focusProgress ?? 1));
|
|
135
|
+
const graphemes = [...new Intl.Segmenter(undefined, { granularity: "grapheme" }).segment(padded)].map((part) => part.segment);
|
|
136
|
+
const illuminated = Math.round(graphemes.length * focus);
|
|
137
|
+
const label = active ? paint.cyan(graphemes.slice(0, illuminated).join("")) + graphemes.slice(illuminated).join("") : padded;
|
|
138
|
+
const head = `${marker} ${paint.dim(number)} ${label} `;
|
|
133
139
|
const tail = `${item.hint ? ` ${item.hint}` : ""}${item.description ? ` ${item.description}` : ""}`;
|
|
134
140
|
lines.push(` ${head}${paint.dim(clip(tail, Math.max(0, room - visibleWidth(padded) - 2)))}`);
|
|
135
141
|
}
|
|
@@ -173,39 +179,45 @@ async function runChooser(keys, items, paint, options) {
|
|
|
173
179
|
glide?.stop();
|
|
174
180
|
glide = undefined;
|
|
175
181
|
};
|
|
176
|
-
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
182
|
+
try {
|
|
183
|
+
for await (const input of keys) {
|
|
184
|
+
const current = liveOptions();
|
|
185
|
+
const previousSelected = state.selected;
|
|
186
|
+
const step = advanceChooser(state, items, input, {
|
|
187
|
+
filter: options.filter ?? false,
|
|
188
|
+
page: options.page ?? current.height ?? 8,
|
|
189
|
+
...current.height === undefined ? {} : { height: current.height }
|
|
190
|
+
});
|
|
191
|
+
state = step.state;
|
|
192
|
+
if (step.done) {
|
|
193
|
+
settleGlide();
|
|
194
|
+
if (step.done.index === undefined)
|
|
195
|
+
return;
|
|
196
|
+
return filterItems(items, state.query)[step.done.index]?.value;
|
|
197
|
+
}
|
|
186
198
|
settleGlide();
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
199
|
+
const isSingleStep = (input.key.name === "up" || input.key.name === "down") && Math.abs(state.selected - previousSelected) === 1;
|
|
200
|
+
const motion = options.motion ?? (process.env.ARCHYMEDES_NO_MOTION !== "1" && process.env.NO_COLOR === undefined && process.env.TERM !== "dumb");
|
|
201
|
+
if (!motion || !isSingleStep || previousSelected === state.selected) {
|
|
202
|
+
paint(renderChooser(state, items, liveOptions()));
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
paint(renderChooser(state, items, { ...liveOptions(), transitionFrom: previousSelected, focusProgress: 0 }));
|
|
206
|
+
const animator = new SpringAnimator(0, (value) => {
|
|
207
|
+
if (value < 0.95) {
|
|
208
|
+
paint(renderChooser(state, items, { ...liveOptions(), transitionFrom: value < 0.5 ? previousSelected : undefined, focusProgress: value }));
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
animator.stop();
|
|
212
|
+
glide = undefined;
|
|
213
|
+
paint(renderChooser(state, items, liveOptions()));
|
|
214
|
+
}, { intervalMs: 30 });
|
|
215
|
+
glide = animator;
|
|
216
|
+
animator.retarget(1);
|
|
190
217
|
}
|
|
218
|
+
} finally {
|
|
191
219
|
settleGlide();
|
|
192
|
-
const isSingleStep = (input.key.name === "up" || input.key.name === "down") && Math.abs(state.selected - previousSelected) === 1;
|
|
193
|
-
if (!isSingleStep || previousSelected === state.selected) {
|
|
194
|
-
paint(renderChooser(state, items, liveOptions()));
|
|
195
|
-
continue;
|
|
196
|
-
}
|
|
197
|
-
paint(renderChooser(state, items, { ...liveOptions(), transitionFrom: previousSelected }));
|
|
198
|
-
const animator = new SpringAnimator(0, (value) => {
|
|
199
|
-
if (value < 0.6)
|
|
200
|
-
return;
|
|
201
|
-
animator.stop();
|
|
202
|
-
glide = undefined;
|
|
203
|
-
paint(renderChooser(state, items, liveOptions()));
|
|
204
|
-
}, { intervalMs: 30 });
|
|
205
|
-
glide = animator;
|
|
206
|
-
animator.retarget(1);
|
|
207
220
|
}
|
|
208
|
-
settleGlide();
|
|
209
221
|
return;
|
|
210
222
|
}
|
|
211
223
|
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
+
import {
|
|
2
|
+
diffLines,
|
|
3
|
+
languageOf
|
|
4
|
+
} from "./archymedes-sc3mwttb.mjs";
|
|
5
|
+
import"./archymedes-cdgejs3p.mjs";
|
|
1
6
|
import {
|
|
2
7
|
NO_COLOR_PALETTE
|
|
3
|
-
} from "./archymedes-
|
|
8
|
+
} from "./archymedes-5zn4zp84.mjs";
|
|
4
9
|
import {
|
|
5
10
|
__require
|
|
6
11
|
} from "./archymedes-x67v3n58.mjs";
|
|
@@ -342,14 +347,233 @@ function caretInto(text, cursorCol) {
|
|
|
342
347
|
return `${padded.slice(0, cursorCol)}█${padded.slice(cursorCol + 1)}`;
|
|
343
348
|
}
|
|
344
349
|
|
|
350
|
+
// packages/archymedes-cli/src/explain-view.ts
|
|
351
|
+
var PATTERNS = {
|
|
352
|
+
typescript: {
|
|
353
|
+
imports: /^\s*import\b.*from\s+["'](.+)["']/,
|
|
354
|
+
exports: /^\s*export\s+(?:default\s+)?(?:const|function|class|interface|type|enum)\s+([\w$]+)/,
|
|
355
|
+
functions: /^\s*(?:export\s+)?(?:default\s+)?(?:async\s+)?function\s*\*?\s*([\w$]+)/,
|
|
356
|
+
classes: /^\s*(?:export\s+)?(?:default\s+)?(?:abstract\s+)?class\s+([\w$]+)/
|
|
357
|
+
},
|
|
358
|
+
javascript: {},
|
|
359
|
+
jsx: {},
|
|
360
|
+
tsx: {},
|
|
361
|
+
python: {
|
|
362
|
+
imports: /^\s*(?:import\s+([\w.]+)|from\s+([\w.]+)\s+import\b)/,
|
|
363
|
+
functions: /^\s*(?:async\s+)?def\s+([\w]+)/,
|
|
364
|
+
classes: /^\s*class\s+([\w]+)/
|
|
365
|
+
},
|
|
366
|
+
go: {
|
|
367
|
+
imports: /^\s*import\s+["'](.+)["']/,
|
|
368
|
+
functions: /^\s*func\s+(?:\([^)]*\)\s*)?([\w]+)/
|
|
369
|
+
},
|
|
370
|
+
rust: {
|
|
371
|
+
imports: /^\s*use\s+([\w:]+)/,
|
|
372
|
+
functions: /^\s*(?:pub\s+)?(?:async\s+)?fn\s+([\w]+)/,
|
|
373
|
+
classes: /^\s*(?:pub\s+)?struct\s+([\w]+)/
|
|
374
|
+
},
|
|
375
|
+
ruby: {
|
|
376
|
+
imports: /^\s*require\s+["'](.+)["']/,
|
|
377
|
+
functions: /^\s*def\s+([\w?!]+)/,
|
|
378
|
+
classes: /^\s*class\s+([\w:]+)/
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
PATTERNS.javascript = PATTERNS.typescript;
|
|
382
|
+
PATTERNS.jsx = PATTERNS.typescript;
|
|
383
|
+
PATTERNS.tsx = PATTERNS.typescript;
|
|
384
|
+
function structuralFacts(content, path) {
|
|
385
|
+
const language = languageOf(path);
|
|
386
|
+
const rules = PATTERNS[language] ?? {};
|
|
387
|
+
const facts = { imports: [], exports: [], functions: [], classes: [] };
|
|
388
|
+
const lines = content.split(`
|
|
389
|
+
`);
|
|
390
|
+
lines.forEach((raw, index) => {
|
|
391
|
+
for (const key of Object.keys(rules)) {
|
|
392
|
+
const pattern = rules[key];
|
|
393
|
+
if (!pattern)
|
|
394
|
+
continue;
|
|
395
|
+
const match = pattern.exec(raw);
|
|
396
|
+
if (!match)
|
|
397
|
+
continue;
|
|
398
|
+
const name = match.slice(1).find((group) => group !== undefined);
|
|
399
|
+
facts[key].push({ line: index + 1, text: name ?? raw.trim() });
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
return facts;
|
|
403
|
+
}
|
|
404
|
+
var MAX_LINE_LENGTH = 120;
|
|
405
|
+
var MAGIC_NUMBER = /(?<![\w.])(?!0\b|1\b)\d{2,}(?![\w.])/;
|
|
406
|
+
function annotateLines(content) {
|
|
407
|
+
const lines = content.split(`
|
|
408
|
+
`);
|
|
409
|
+
const notes = [];
|
|
410
|
+
lines.forEach((raw, index) => {
|
|
411
|
+
const line = index + 1;
|
|
412
|
+
if (/\b(TODO|FIXME|HACK|XXX)\b/.test(raw)) {
|
|
413
|
+
notes.push({ line, note: "unfinished work flagged here", severity: "warn" });
|
|
414
|
+
}
|
|
415
|
+
if (raw.length > MAX_LINE_LENGTH) {
|
|
416
|
+
notes.push({ line, note: `long line (${raw.length} chars)`, severity: "info" });
|
|
417
|
+
}
|
|
418
|
+
if (MAGIC_NUMBER.test(raw) && !/^\s*(?:\/\/|#|\*)/.test(raw)) {
|
|
419
|
+
notes.push({ line, note: "unexplained numeric literal", severity: "info" });
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
return notes;
|
|
423
|
+
}
|
|
424
|
+
var EXPLAIN_TABS = ["facts", "notes", "diff", "ai"];
|
|
425
|
+
function initialExplainPanelState() {
|
|
426
|
+
return { open: false, tab: "facts", ai: { status: "idle" } };
|
|
427
|
+
}
|
|
428
|
+
function toggleExplainPanel(state) {
|
|
429
|
+
return { ...state, open: !state.open };
|
|
430
|
+
}
|
|
431
|
+
function cycleExplainTab(state, step = 1) {
|
|
432
|
+
const index = EXPLAIN_TABS.indexOf(state.tab);
|
|
433
|
+
const next = ((index + step) % EXPLAIN_TABS.length + EXPLAIN_TABS.length) % EXPLAIN_TABS.length;
|
|
434
|
+
return { ...state, tab: EXPLAIN_TABS[next] };
|
|
435
|
+
}
|
|
436
|
+
function withAiExplanation(state, ai) {
|
|
437
|
+
return { ...state, ai };
|
|
438
|
+
}
|
|
439
|
+
var TAB_LABELS = { facts: "Structure", notes: "Notes", diff: "Diff", ai: "AI" };
|
|
440
|
+
function tabStrip(active) {
|
|
441
|
+
const text = EXPLAIN_TABS.map((tab) => tab === active ? `[${TAB_LABELS[tab]}]` : ` ${TAB_LABELS[tab]} `).join(" ");
|
|
442
|
+
return { text, bold: true };
|
|
443
|
+
}
|
|
444
|
+
function hintRow(active) {
|
|
445
|
+
const ask = active === "ai" ? " ? ask" : "";
|
|
446
|
+
return { text: `[ ] tabs ^E close${ask}`, dim: true };
|
|
447
|
+
}
|
|
448
|
+
function wrap(text, width) {
|
|
449
|
+
const words = text.split(/\s+/).filter(Boolean);
|
|
450
|
+
const rows = [];
|
|
451
|
+
let line = "";
|
|
452
|
+
for (const word of words) {
|
|
453
|
+
const candidate = line ? `${line} ${word}` : word;
|
|
454
|
+
if (candidate.length > width && line) {
|
|
455
|
+
rows.push(line);
|
|
456
|
+
line = word;
|
|
457
|
+
} else {
|
|
458
|
+
line = candidate;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
if (line)
|
|
462
|
+
rows.push(line);
|
|
463
|
+
return rows.length > 0 ? rows : [""];
|
|
464
|
+
}
|
|
465
|
+
function factsRows(facts, width) {
|
|
466
|
+
const section = (title, entries) => {
|
|
467
|
+
if (entries.length === 0)
|
|
468
|
+
return [];
|
|
469
|
+
return [
|
|
470
|
+
{ text: title, bold: true },
|
|
471
|
+
...entries.slice(0, 40).map((entry) => ({ text: ` ${String(entry.line).padStart(4)} ${entry.text}`.slice(0, width) })),
|
|
472
|
+
{ text: "" }
|
|
473
|
+
];
|
|
474
|
+
};
|
|
475
|
+
const rows = [
|
|
476
|
+
...section("Imports", facts.imports),
|
|
477
|
+
...section("Exports", facts.exports),
|
|
478
|
+
...section("Classes", facts.classes),
|
|
479
|
+
...section("Functions", facts.functions)
|
|
480
|
+
];
|
|
481
|
+
if (rows.length === 0)
|
|
482
|
+
return [{ text: "No imports, exports, functions or classes recognised.", dim: true }];
|
|
483
|
+
return rows.slice(0, -1);
|
|
484
|
+
}
|
|
485
|
+
function notesRows(notes, width) {
|
|
486
|
+
if (notes.length === 0)
|
|
487
|
+
return [{ text: "Nothing flagged.", dim: true }];
|
|
488
|
+
return notes.slice(0, 60).map((entry) => ({
|
|
489
|
+
text: `${String(entry.line).padStart(4)} ${entry.note}`.slice(0, width),
|
|
490
|
+
color: entry.severity === "warn" ? "yellow" : undefined
|
|
491
|
+
}));
|
|
492
|
+
}
|
|
493
|
+
function diffRows(diff, width) {
|
|
494
|
+
if (diff.length === 0 || diff.every((line) => line.kind === "context")) {
|
|
495
|
+
return [{ text: "No changes yet.", dim: true }];
|
|
496
|
+
}
|
|
497
|
+
return diff.filter((line) => line.kind !== "context").slice(0, 200).map((line) => ({
|
|
498
|
+
text: `${line.kind === "add" ? "+" : "-"} ${line.text}`.slice(0, width),
|
|
499
|
+
color: line.kind === "add" ? "green" : "red"
|
|
500
|
+
}));
|
|
501
|
+
}
|
|
502
|
+
function aiRows(ai, width) {
|
|
503
|
+
switch (ai.status) {
|
|
504
|
+
case "idle":
|
|
505
|
+
return [{ text: "Press ? to ask the model to explain this file.", dim: true }];
|
|
506
|
+
case "loading":
|
|
507
|
+
return [{ text: "Thinking…", dim: true }];
|
|
508
|
+
case "error":
|
|
509
|
+
return [{ text: `Could not get an explanation: ${ai.message}`, color: "red" }];
|
|
510
|
+
case "ready":
|
|
511
|
+
return ai.text.split(`
|
|
512
|
+
`).flatMap((line) => wrap(line, width)).map((text) => ({ text }));
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
function composeExplainPanel(input, width, height) {
|
|
516
|
+
const body = (() => {
|
|
517
|
+
switch (input.panel.tab) {
|
|
518
|
+
case "facts":
|
|
519
|
+
return factsRows(structuralFacts(input.after, input.path), width);
|
|
520
|
+
case "notes":
|
|
521
|
+
return notesRows(annotateLines(input.after), width);
|
|
522
|
+
case "diff":
|
|
523
|
+
return diffRows(diffLines(input.before, input.after), width);
|
|
524
|
+
case "ai":
|
|
525
|
+
return aiRows(input.panel.ai, width);
|
|
526
|
+
}
|
|
527
|
+
})();
|
|
528
|
+
const rows = [tabStrip(input.panel.tab), hintRow(input.panel.tab), { text: "" }, ...body];
|
|
529
|
+
const padded = rows.length >= height ? rows.slice(0, height) : [...rows, ...Array.from({ length: height - rows.length }, () => ({ text: "" }))];
|
|
530
|
+
return padded;
|
|
531
|
+
}
|
|
532
|
+
|
|
345
533
|
// packages/archymedes-cli/src/editor-screen.tsx
|
|
346
534
|
import { jsxDEV } from "@termuijs/jsx/jsx-dev-runtime";
|
|
347
535
|
var Panel = Box;
|
|
348
536
|
var Line = Text;
|
|
349
537
|
var CHROME_ROWS = 2;
|
|
350
|
-
|
|
538
|
+
var MIN_CODE_COLUMNS = 30;
|
|
539
|
+
var MAX_PANEL_COLUMNS = 52;
|
|
540
|
+
function panelWidthFor(columns) {
|
|
541
|
+
if (columns - MIN_CODE_COLUMNS < 20)
|
|
542
|
+
return 0;
|
|
543
|
+
return Math.min(MAX_PANEL_COLUMNS, Math.floor(columns * 0.38), columns - MIN_CODE_COLUMNS);
|
|
544
|
+
}
|
|
545
|
+
function EditorScreen({ columns, rows, path, content, onExit, palette = NO_COLOR_PALETTE, explain }) {
|
|
351
546
|
const [state, setState] = useState(() => initialEditorState(path, content, Math.max(1, rows - CHROME_ROWS)));
|
|
547
|
+
const [panel, setPanel] = useState(() => initialExplainPanelState());
|
|
548
|
+
const panelWidth = panel.open ? panelWidthFor(columns) : 0;
|
|
549
|
+
const codeWidth = panelWidth > 0 ? columns - panelWidth - 1 : columns;
|
|
550
|
+
const askAi = (currentContent) => {
|
|
551
|
+
if (!explain) {
|
|
552
|
+
setPanel((current) => withAiExplanation(current, { status: "error", message: "no model available in this session" }));
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
setPanel((current) => withAiExplanation(current, { status: "loading" }));
|
|
556
|
+
explain(currentContent, path).then((text) => setPanel((current) => withAiExplanation(current, { status: "ready", text })), (error) => setPanel((current) => withAiExplanation(current, { status: "error", message: error instanceof Error ? error.message : String(error) })));
|
|
557
|
+
};
|
|
352
558
|
useInput((input, key) => {
|
|
559
|
+
if (key?.ctrl && key?.key === "e") {
|
|
560
|
+
setPanel((current) => toggleExplainPanel(current));
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
if (panel.open && state.mode === "normal" && !state.search.typing) {
|
|
564
|
+
if (input === "]") {
|
|
565
|
+
setPanel((current) => cycleExplainTab(current, 1));
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
if (input === "[") {
|
|
569
|
+
setPanel((current) => cycleExplainTab(current, -1));
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
if (input === "?" && panel.tab === "ai" && panel.ai.status !== "loading") {
|
|
573
|
+
askAi(editorContent(state));
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
353
577
|
setState((current) => {
|
|
354
578
|
const action = keyToEditorAction({ name: key?.key, ctrl: key?.ctrl, shift: key?.shift }, input, current);
|
|
355
579
|
const { state: next, effect } = applyEditorAction(current, action);
|
|
@@ -360,18 +584,41 @@ function EditorScreen({ columns, rows, path, content, onExit, palette = NO_COLOR
|
|
|
360
584
|
return next;
|
|
361
585
|
});
|
|
362
586
|
});
|
|
363
|
-
const frame = composeEditorFrame(state,
|
|
587
|
+
const frame = composeEditorFrame(state, codeWidth, palette.accent);
|
|
588
|
+
const lastRow = frame.length - 1;
|
|
589
|
+
if (!panel.open && lastRow >= 0 && frame[lastRow].text.length + " ^E explain".length <= codeWidth) {
|
|
590
|
+
frame[lastRow] = { ...frame[lastRow], text: `${frame[lastRow].text} ^E explain` };
|
|
591
|
+
}
|
|
592
|
+
const panelRows = panelWidth > 0 ? composeExplainPanel({ path, before: content, after: editorContent(state), panel }, panelWidth - 2, rows) : [];
|
|
364
593
|
return /* @__PURE__ */ jsxDEV(Panel, {
|
|
365
|
-
flexDirection: "
|
|
594
|
+
flexDirection: "row",
|
|
366
595
|
width: columns,
|
|
367
596
|
height: rows,
|
|
368
|
-
children:
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
597
|
+
children: [
|
|
598
|
+
/* @__PURE__ */ jsxDEV(Panel, {
|
|
599
|
+
flexDirection: "column",
|
|
600
|
+
width: codeWidth,
|
|
601
|
+
height: rows,
|
|
602
|
+
children: frame.map((line, index) => /* @__PURE__ */ jsxDEV(Line, {
|
|
603
|
+
bold: line.bold,
|
|
604
|
+
dimColor: line.dim,
|
|
605
|
+
color: line.color,
|
|
606
|
+
children: line.text
|
|
607
|
+
}, `row-${index}`, false, undefined, this))
|
|
608
|
+
}, undefined, false, undefined, this),
|
|
609
|
+
panelWidth > 0 && /* @__PURE__ */ jsxDEV(Panel, {
|
|
610
|
+
flexDirection: "column",
|
|
611
|
+
width: panelWidth,
|
|
612
|
+
height: rows,
|
|
613
|
+
children: panelRows.map((line, index) => /* @__PURE__ */ jsxDEV(Line, {
|
|
614
|
+
bold: line.bold,
|
|
615
|
+
dimColor: line.dim,
|
|
616
|
+
color: line.color,
|
|
617
|
+
children: line.text
|
|
618
|
+
}, `explain-${index}`, false, undefined, this))
|
|
619
|
+
}, undefined, false, undefined, this)
|
|
620
|
+
]
|
|
621
|
+
}, undefined, true, undefined, this);
|
|
375
622
|
}
|
|
376
623
|
async function runEditorScreen(options) {
|
|
377
624
|
const { renderApp } = await import("@termuijs/jsx");
|
|
@@ -389,6 +636,7 @@ async function runEditorScreen(options) {
|
|
|
389
636
|
path: options.path,
|
|
390
637
|
content: options.content,
|
|
391
638
|
palette: options.palette,
|
|
639
|
+
explain: options.explain,
|
|
392
640
|
onExit: finish,
|
|
393
641
|
fullscreen: true
|
|
394
642
|
}).catch(() => finish(undefined));
|