@quandev104/pi-style 0.2.2 → 0.2.3
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/CHANGELOG.md +11 -0
- package/README.md +8 -3
- package/dist/extensions/pi-style.js +674 -49
- package/dist/extensions/pi-style.js.map +1 -1
- package/extension-src/pi-style/app/command-service.ts +8 -0
- package/extension-src/pi-style/app/runtime.ts +4 -0
- package/extension-src/pi-style/domain/config-normalization.ts +11 -1
- package/extension-src/pi-style/domain/config-types.ts +12 -0
- package/extension-src/pi-style/domain/status-renderer.ts +1 -1
- package/extension-src/pi-style/domain/status.ts +13 -7
- package/extension-src/pi-style/features/editor/index.ts +127 -0
- package/extension-src/pi-style/features/messages/image-input.ts +205 -0
- package/extension-src/pi-style/features/messages/image-preview.ts +288 -0
- package/extension-src/pi-style/features/messages/render-config.ts +36 -0
- package/extension-src/pi-style/pi/index.ts +53 -2
- package/extension-src/pi-style/pi/session-coordinator.ts +13 -0
- package/extension-src/pi-style/shared/clipboard-path.ts +98 -0
- package/extension-src/pi-style/shared/clipboard-presence.ts +112 -0
- package/extension-src/pi-style/shared/pending-images.ts +199 -0
- package/package.json +1 -1
- package/extension-src/pi-style/features/.gitkeep +0 -0
|
@@ -1,3 +1,352 @@
|
|
|
1
|
+
// extension-src/pi-style/features/messages/image-input.ts
|
|
2
|
+
import { readFile as readFile2, stat as stat2 } from "fs/promises";
|
|
3
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
4
|
+
|
|
5
|
+
// extension-src/pi-style/shared/clipboard-path.ts
|
|
6
|
+
import { tmpdir } from "os";
|
|
7
|
+
var UUID_PATTERN = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}";
|
|
8
|
+
var CLIPBOARD_IMAGE_EXTENSIONS = ["png", "jpg", "jpeg", "webp", "gif"];
|
|
9
|
+
function clipboardImageMarker(index) {
|
|
10
|
+
return `[Image #${index}]`;
|
|
11
|
+
}
|
|
12
|
+
function escapeRegExp(text) {
|
|
13
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
14
|
+
}
|
|
15
|
+
var clipboardPathRegexCache = /* @__PURE__ */ new Map();
|
|
16
|
+
var CLIPBOARD_PATH_REGEX_CACHE_MAX = 16;
|
|
17
|
+
function clipboardPathRegex(tmpRoot = tmpdir()) {
|
|
18
|
+
const cached = clipboardPathRegexCache.get(tmpRoot);
|
|
19
|
+
if (cached) return cached;
|
|
20
|
+
const exts = CLIPBOARD_IMAGE_EXTENSIONS.join("|");
|
|
21
|
+
const regex = new RegExp(
|
|
22
|
+
`(?<![a-zA-Z0-9])(${escapeRegExp(tmpRoot)}/pi-clipboard-${UUID_PATTERN}\\.(${exts}))(?![a-zA-Z0-9])`,
|
|
23
|
+
"g"
|
|
24
|
+
);
|
|
25
|
+
if (clipboardPathRegexCache.size >= CLIPBOARD_PATH_REGEX_CACHE_MAX) {
|
|
26
|
+
const oldest = clipboardPathRegexCache.keys().next().value;
|
|
27
|
+
if (oldest !== void 0) clipboardPathRegexCache.delete(oldest);
|
|
28
|
+
}
|
|
29
|
+
clipboardPathRegexCache.set(tmpRoot, regex);
|
|
30
|
+
return regex;
|
|
31
|
+
}
|
|
32
|
+
function isSingleClipboardImagePath(text, tmpRoot) {
|
|
33
|
+
if (!text || /\s/.test(text)) return false;
|
|
34
|
+
const match = text.match(clipboardPathRegex(tmpRoot ?? tmpdir()));
|
|
35
|
+
return match !== null && match.length === 1 && match[0] === text;
|
|
36
|
+
}
|
|
37
|
+
function extractClipboardImageTokens(text, tmpRoot) {
|
|
38
|
+
const tokens = /* @__PURE__ */ new Set();
|
|
39
|
+
for (const match of text.matchAll(clipboardPathRegex(tmpRoot ?? tmpdir()))) {
|
|
40
|
+
const token = match[1];
|
|
41
|
+
if (token) tokens.add(token);
|
|
42
|
+
}
|
|
43
|
+
return [...tokens];
|
|
44
|
+
}
|
|
45
|
+
var MARKER_AT_END_PATTERN = /\[Image #([0-9]+)\]( ?)$/;
|
|
46
|
+
function clipboardMarkerAtEnd(text) {
|
|
47
|
+
const match = MARKER_AT_END_PATTERN.exec(text);
|
|
48
|
+
if (!match) return void 0;
|
|
49
|
+
return { index: Number(match[1]), length: match[0].length };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// extension-src/pi-style/shared/clipboard-presence.ts
|
|
53
|
+
import { realpathSync } from "fs";
|
|
54
|
+
import { createRequire } from "module";
|
|
55
|
+
import { dirname } from "path";
|
|
56
|
+
var cachedModule;
|
|
57
|
+
function clipboardResolutionRoots() {
|
|
58
|
+
const roots = [];
|
|
59
|
+
const argvMain = process.argv[1];
|
|
60
|
+
if (typeof argvMain === "string" && argvMain.startsWith("/")) {
|
|
61
|
+
try {
|
|
62
|
+
roots.push(dirname(realpathSync(argvMain)));
|
|
63
|
+
} catch {
|
|
64
|
+
roots.push(dirname(argvMain));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
roots.push(import.meta.url);
|
|
68
|
+
return roots;
|
|
69
|
+
}
|
|
70
|
+
function loadClipboardModule() {
|
|
71
|
+
if (cachedModule !== void 0) return cachedModule;
|
|
72
|
+
if (process.env.TERMUX_VERSION) {
|
|
73
|
+
cachedModule = null;
|
|
74
|
+
return cachedModule;
|
|
75
|
+
}
|
|
76
|
+
for (const root of clipboardResolutionRoots()) {
|
|
77
|
+
try {
|
|
78
|
+
const require2 = createRequire(root);
|
|
79
|
+
const resolved = require2("@mariozechner/clipboard");
|
|
80
|
+
if (resolved && typeof resolved.hasImage === "function") {
|
|
81
|
+
cachedModule = resolved;
|
|
82
|
+
return cachedModule;
|
|
83
|
+
}
|
|
84
|
+
} catch {
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
cachedModule = null;
|
|
88
|
+
return cachedModule;
|
|
89
|
+
}
|
|
90
|
+
function clipboardHasImageSync() {
|
|
91
|
+
const clipboard = loadClipboardModule();
|
|
92
|
+
if (!clipboard || typeof clipboard.hasImage !== "function") return null;
|
|
93
|
+
try {
|
|
94
|
+
return clipboard.hasImage() === true;
|
|
95
|
+
} catch {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
async function readClipboardImageBinary(options = {}) {
|
|
100
|
+
const clipboard = loadClipboardModule();
|
|
101
|
+
if (!clipboard || typeof clipboard.hasImage !== "function" || typeof clipboard.getImageBinary !== "function") {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
if (!options.skipPresenceProbe && !clipboard.hasImage()) return null;
|
|
106
|
+
const imageData = await clipboard.getImageBinary();
|
|
107
|
+
if (!imageData || imageData.length === 0) return null;
|
|
108
|
+
const bytes = imageData instanceof Uint8Array ? imageData : Uint8Array.from(imageData);
|
|
109
|
+
return bytes.length > 0 ? { bytes } : null;
|
|
110
|
+
} catch {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// extension-src/pi-style/shared/pending-images.ts
|
|
116
|
+
import { readFile, stat } from "fs/promises";
|
|
117
|
+
var MAX_ATTACHABLE_BYTES = 20 * 1024 * 1024;
|
|
118
|
+
var MIME_BY_EXTENSION = Object.freeze({
|
|
119
|
+
png: "image/png",
|
|
120
|
+
jpg: "image/jpeg",
|
|
121
|
+
jpeg: "image/jpeg",
|
|
122
|
+
webp: "image/webp",
|
|
123
|
+
gif: "image/gif"
|
|
124
|
+
});
|
|
125
|
+
var pendingImages = /* @__PURE__ */ new Map();
|
|
126
|
+
var nextPendingIndex = 1;
|
|
127
|
+
function resetPendingImageRegistry() {
|
|
128
|
+
pendingImages.clear();
|
|
129
|
+
nextPendingIndex = 1;
|
|
130
|
+
}
|
|
131
|
+
function allocatePendingImage() {
|
|
132
|
+
const index = nextPendingIndex++;
|
|
133
|
+
let resolveFill = () => {
|
|
134
|
+
};
|
|
135
|
+
const entry = {
|
|
136
|
+
fill: new Promise((resolve3) => {
|
|
137
|
+
resolveFill = resolve3;
|
|
138
|
+
}),
|
|
139
|
+
settled: false
|
|
140
|
+
};
|
|
141
|
+
pendingImages.set(index, entry);
|
|
142
|
+
entry.resolveFill = resolveFill;
|
|
143
|
+
return { index, marker: `${clipboardImageMarker(index)} ` };
|
|
144
|
+
}
|
|
145
|
+
async function fillPendingImageFromPath(index, path, deps = {}) {
|
|
146
|
+
const entry = pendingImages.get(index);
|
|
147
|
+
if (!entry || entry.settled) return;
|
|
148
|
+
const extension = path.split(".").pop() ?? "";
|
|
149
|
+
const mimeType = MIME_BY_EXTENSION[extension];
|
|
150
|
+
if (!mimeType) {
|
|
151
|
+
settle(entry);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
const readFileImpl = deps.readFile ?? ((p) => readFile(p));
|
|
155
|
+
const statSizeImpl = deps.statSize ?? (async (p) => {
|
|
156
|
+
try {
|
|
157
|
+
return (await stat(p)).size;
|
|
158
|
+
} catch {
|
|
159
|
+
return void 0;
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
try {
|
|
163
|
+
const size = await statSizeImpl(path);
|
|
164
|
+
if (size === void 0 || size <= 0 || size > MAX_ATTACHABLE_BYTES) {
|
|
165
|
+
settle(entry);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const bytes = await readFileImpl(path);
|
|
169
|
+
if (bytes.length === 0 || bytes.length > MAX_ATTACHABLE_BYTES) {
|
|
170
|
+
settle(entry);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
entry.data = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength).toString("base64");
|
|
174
|
+
entry.mimeType = mimeType;
|
|
175
|
+
} catch {
|
|
176
|
+
}
|
|
177
|
+
settle(entry);
|
|
178
|
+
}
|
|
179
|
+
function fillPendingImageBytes(index, data, mimeType) {
|
|
180
|
+
const entry = pendingImages.get(index);
|
|
181
|
+
if (!entry || entry.settled) return;
|
|
182
|
+
if (data.length > 0) {
|
|
183
|
+
entry.data = data;
|
|
184
|
+
entry.mimeType = mimeType;
|
|
185
|
+
}
|
|
186
|
+
settle(entry);
|
|
187
|
+
}
|
|
188
|
+
function discardPendingImage(index) {
|
|
189
|
+
const entry = pendingImages.get(index);
|
|
190
|
+
if (!entry) return;
|
|
191
|
+
settle(entry);
|
|
192
|
+
pendingImages.delete(index);
|
|
193
|
+
}
|
|
194
|
+
function isPendingMarkerIndex(index) {
|
|
195
|
+
return pendingImages.has(index);
|
|
196
|
+
}
|
|
197
|
+
function discardPendingMarkerIndex(index) {
|
|
198
|
+
discardPendingImage(index);
|
|
199
|
+
}
|
|
200
|
+
function isPendingImageFilled(index) {
|
|
201
|
+
const entry = pendingImages.get(index);
|
|
202
|
+
return entry !== void 0 && entry.data !== void 0 && entry.mimeType !== void 0;
|
|
203
|
+
}
|
|
204
|
+
function settle(entry) {
|
|
205
|
+
entry.settled = true;
|
|
206
|
+
entry.resolveFill?.();
|
|
207
|
+
}
|
|
208
|
+
async function resolvePendingImagesForSubmit(text) {
|
|
209
|
+
if (pendingImages.size === 0) return void 0;
|
|
210
|
+
const markerRe = /\[Image #([0-9]+)\]/g;
|
|
211
|
+
const found = [];
|
|
212
|
+
for (const match of text.matchAll(markerRe)) {
|
|
213
|
+
const index = Number(match[1]);
|
|
214
|
+
if (Number.isInteger(index) && pendingImages.has(index)) found.push(index);
|
|
215
|
+
}
|
|
216
|
+
const matched = new Set(found);
|
|
217
|
+
await Promise.all(
|
|
218
|
+
[...pendingImages.entries()].filter(([index]) => matched.has(index)).map(([, entry]) => entry.fill)
|
|
219
|
+
);
|
|
220
|
+
const images = [];
|
|
221
|
+
for (const index of [...matched].sort((a, b) => a - b)) {
|
|
222
|
+
const entry = pendingImages.get(index);
|
|
223
|
+
if (entry?.data && entry.mimeType) {
|
|
224
|
+
images.push({ type: "image", data: entry.data, mimeType: entry.mimeType });
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
for (const index of [...pendingImages.keys()]) discardPendingImage(index);
|
|
228
|
+
return images.length > 0 ? { images } : void 0;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// extension-src/pi-style/features/messages/render-config.ts
|
|
232
|
+
var sessionMessagesConfig = {
|
|
233
|
+
showImagePreviews: true,
|
|
234
|
+
clipboardImages: true,
|
|
235
|
+
previewMaxWidth: 30
|
|
236
|
+
};
|
|
237
|
+
function setMessagesRenderConfig(config) {
|
|
238
|
+
sessionMessagesConfig = { ...sessionMessagesConfig, ...config };
|
|
239
|
+
}
|
|
240
|
+
function getMessagesRenderConfig() {
|
|
241
|
+
return sessionMessagesConfig;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// extension-src/pi-style/features/messages/image-input.ts
|
|
245
|
+
var IMAGE_TOKEN = "[image]";
|
|
246
|
+
var MAX_ATTACHABLE_BYTES2 = 20 * 1024 * 1024;
|
|
247
|
+
var MIME_BY_EXTENSION2 = Object.freeze({
|
|
248
|
+
png: "image/png",
|
|
249
|
+
jpg: "image/jpeg",
|
|
250
|
+
jpeg: "image/jpeg",
|
|
251
|
+
webp: "image/webp",
|
|
252
|
+
gif: "image/gif"
|
|
253
|
+
});
|
|
254
|
+
function createClipboardImagePasteSurface(deps = {}) {
|
|
255
|
+
const hasImage = deps.hasImage ?? clipboardHasImageSync;
|
|
256
|
+
const readBinary = deps.readBinary ?? (() => readClipboardImageBinary({ skipPresenceProbe: true }));
|
|
257
|
+
return {
|
|
258
|
+
enabled: () => getMessagesRenderConfig().clipboardImages,
|
|
259
|
+
clipboardHasImage: () => {
|
|
260
|
+
try {
|
|
261
|
+
return hasImage();
|
|
262
|
+
} catch {
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
markerFromClipboard() {
|
|
267
|
+
const { index, marker: marker2 } = allocatePendingImage();
|
|
268
|
+
void (async () => {
|
|
269
|
+
try {
|
|
270
|
+
const image = await readBinary();
|
|
271
|
+
if (image) {
|
|
272
|
+
fillPendingImageBytes(
|
|
273
|
+
index,
|
|
274
|
+
Buffer.from(image.bytes.buffer, image.bytes.byteOffset, image.bytes.byteLength).toString("base64"),
|
|
275
|
+
"image/png"
|
|
276
|
+
);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
} catch {
|
|
280
|
+
}
|
|
281
|
+
discardPendingMarkerIndex(index);
|
|
282
|
+
})();
|
|
283
|
+
return marker2;
|
|
284
|
+
},
|
|
285
|
+
async markerFromArtifact(path) {
|
|
286
|
+
const { index, marker: marker2 } = allocatePendingImage();
|
|
287
|
+
await fillPendingImageFromPath(index, path);
|
|
288
|
+
return isPendingImageFilled(index) ? marker2 : path;
|
|
289
|
+
},
|
|
290
|
+
isMarkerIndexRegistered: (index) => isPendingMarkerIndex(index),
|
|
291
|
+
discardMarkerIndex: (index) => discardPendingMarkerIndex(index)
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
async function resolvePendingImageMarkers(text) {
|
|
295
|
+
if (!getMessagesRenderConfig().clipboardImages) return void 0;
|
|
296
|
+
return resolvePendingImagesForSubmit(text);
|
|
297
|
+
}
|
|
298
|
+
async function transformClipboardImages(text, deps = {}) {
|
|
299
|
+
if (!getMessagesRenderConfig().clipboardImages) return void 0;
|
|
300
|
+
const tokens = extractClipboardImageTokens(text, deps.tmpRoot);
|
|
301
|
+
if (tokens.length === 0) return void 0;
|
|
302
|
+
const attached = [];
|
|
303
|
+
const rewritten = /* @__PURE__ */ new Map();
|
|
304
|
+
for (const token of tokens) {
|
|
305
|
+
const image = await readAttachable(token, deps);
|
|
306
|
+
if (!image) continue;
|
|
307
|
+
attached.push({ type: "image", data: image.data, mimeType: image.mimeType });
|
|
308
|
+
rewritten.set(token, IMAGE_TOKEN);
|
|
309
|
+
}
|
|
310
|
+
if (attached.length === 0) return void 0;
|
|
311
|
+
return { text: rewriteTokens(text, rewritten, deps.tmpRoot), images: attached };
|
|
312
|
+
}
|
|
313
|
+
async function readAttachable(path, deps) {
|
|
314
|
+
const extension = path.split(".").pop() ?? "";
|
|
315
|
+
const mimeType = MIME_BY_EXTENSION2[extension];
|
|
316
|
+
if (!mimeType) return void 0;
|
|
317
|
+
const readFileImpl = deps?.readFile ?? ((p) => readFile2(p));
|
|
318
|
+
const statSizeImpl = deps?.statSize ?? (async (p) => {
|
|
319
|
+
try {
|
|
320
|
+
return (await stat2(p)).size;
|
|
321
|
+
} catch {
|
|
322
|
+
return void 0;
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
try {
|
|
326
|
+
const size = await statSizeImpl(path);
|
|
327
|
+
if (size === void 0 || size <= 0 || size > MAX_ATTACHABLE_BYTES2) return void 0;
|
|
328
|
+
const bytes = await readFileImpl(path);
|
|
329
|
+
if (bytes.length === 0 || bytes.length > MAX_ATTACHABLE_BYTES2) return void 0;
|
|
330
|
+
return { data: Buffer.from(bytes).toString("base64"), mimeType };
|
|
331
|
+
} catch {
|
|
332
|
+
return void 0;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
function rewriteTokens(text, rewritten, tmpRoot) {
|
|
336
|
+
if (rewritten.size === 0) return text;
|
|
337
|
+
return text.replace(
|
|
338
|
+
clipboardPathRegex(tmpRoot ?? tmpdir2()),
|
|
339
|
+
(match, token) => rewritten.has(token) ? IMAGE_TOKEN : match
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// extension-src/pi-style/features/messages/image-preview.ts
|
|
344
|
+
import {
|
|
345
|
+
getCapabilities,
|
|
346
|
+
getImageDimensions,
|
|
347
|
+
Image
|
|
348
|
+
} from "@earendil-works/pi-tui";
|
|
349
|
+
|
|
1
350
|
// extension-src/pi-style/shared/ansi.ts
|
|
2
351
|
import { visibleWidth as tuiVisibleWidth } from "@earendil-works/pi-tui";
|
|
3
352
|
function isFinal(byte) {
|
|
@@ -214,6 +563,147 @@ function fgHex(theme, hex, text) {
|
|
|
214
563
|
return `${escapes.prefix}${text}${escapes.suffix}`;
|
|
215
564
|
}
|
|
216
565
|
|
|
566
|
+
// extension-src/pi-style/features/messages/image-preview.ts
|
|
567
|
+
var IMAGE_PREVIEW_ENTRY_TYPE = "pi-style-image-preview";
|
|
568
|
+
var IMAGE_PREVIEW_EXPANDED_MAX_WIDTH_CELLS = 60;
|
|
569
|
+
var IMAGE_PREVIEW_MAX_WIDTH_CELLS = 60;
|
|
570
|
+
var GRID_GAP = 2;
|
|
571
|
+
var GRID_MIN_COLUMN = 14;
|
|
572
|
+
function isPreviewableImage(value) {
|
|
573
|
+
if (!value || typeof value !== "object") return false;
|
|
574
|
+
const data = value.data;
|
|
575
|
+
const mimeType = value.mimeType;
|
|
576
|
+
return typeof data === "string" && data.length > 0 && typeof mimeType === "string" && mimeType.length > 0;
|
|
577
|
+
}
|
|
578
|
+
function stageImagePreviewData(images) {
|
|
579
|
+
if (!getMessagesRenderConfig().showImagePreviews) return void 0;
|
|
580
|
+
const valid = images.filter(isPreviewableImage).map((image) => ({ data: image.data, mimeType: image.mimeType }));
|
|
581
|
+
return valid.length > 0 ? { images: valid } : void 0;
|
|
582
|
+
}
|
|
583
|
+
function flushImagePreviewEntry(pi, data) {
|
|
584
|
+
if (!getMessagesRenderConfig().showImagePreviews) return;
|
|
585
|
+
pi.appendEntry(IMAGE_PREVIEW_ENTRY_TYPE, data);
|
|
586
|
+
}
|
|
587
|
+
function previewImagesFromEntryData(data) {
|
|
588
|
+
if (!data || typeof data !== "object") return void 0;
|
|
589
|
+
const images = data.images;
|
|
590
|
+
if (!Array.isArray(images) || images.length === 0) return void 0;
|
|
591
|
+
if (!images.every(isPreviewableImage)) return void 0;
|
|
592
|
+
return images;
|
|
593
|
+
}
|
|
594
|
+
function imageLabel(theme, index, dims) {
|
|
595
|
+
const dimsPart = dims ? ` \xB7 ${dims.widthPx}\xD7${dims.heightPx}` : "";
|
|
596
|
+
return theme.fg("dim", `#${index}${dimsPart}`);
|
|
597
|
+
}
|
|
598
|
+
var __dimensionParsesForTest = { count: 0 };
|
|
599
|
+
function parseImageDimensions(image) {
|
|
600
|
+
__dimensionParsesForTest.count++;
|
|
601
|
+
const fromPrefix = getImageDimensions(image.data.slice(0, 256), image.mimeType);
|
|
602
|
+
return fromPrefix ?? getImageDimensions(image.data, image.mimeType) ?? void 0;
|
|
603
|
+
}
|
|
604
|
+
var previewCache = /* @__PURE__ */ new WeakMap();
|
|
605
|
+
function renderImagePreviewEntry(entry, options, theme) {
|
|
606
|
+
if (!getMessagesRenderConfig().showImagePreviews) return void 0;
|
|
607
|
+
const images = previewImagesFromEntryData(entry?.data);
|
|
608
|
+
if (!images) return void 0;
|
|
609
|
+
const fg = theme?.fg;
|
|
610
|
+
if (typeof fg !== "function") return void 0;
|
|
611
|
+
const expanded = options?.expanded === true;
|
|
612
|
+
try {
|
|
613
|
+
const themed = { fg: fg.bind(theme) };
|
|
614
|
+
let cached = previewCache.get(entry);
|
|
615
|
+
if (!cached) {
|
|
616
|
+
const dims = images.map(parseImageDimensions);
|
|
617
|
+
const components2 = images.map(
|
|
618
|
+
(image, index) => new Image(
|
|
619
|
+
image.data,
|
|
620
|
+
image.mimeType,
|
|
621
|
+
{ fallbackColor: (text) => themed.fg("toolOutput", text) },
|
|
622
|
+
{},
|
|
623
|
+
dims[index]
|
|
624
|
+
)
|
|
625
|
+
);
|
|
626
|
+
const labels2 = images.map((_image, index) => imageLabel(themed, index + 1, dims[index]));
|
|
627
|
+
cached = { images, components: components2, labels: labels2 };
|
|
628
|
+
previewCache.set(entry, cached);
|
|
629
|
+
}
|
|
630
|
+
const { components, labels } = cached;
|
|
631
|
+
let memo;
|
|
632
|
+
return {
|
|
633
|
+
invalidate() {
|
|
634
|
+
memo = void 0;
|
|
635
|
+
for (const component of components) component.invalidate();
|
|
636
|
+
},
|
|
637
|
+
render(width) {
|
|
638
|
+
const config = getMessagesRenderConfig();
|
|
639
|
+
const kitty = getCapabilities().images === "kitty";
|
|
640
|
+
const key = `${width}|${expanded}|${config.previewMaxWidth}|${config.showImagePreviews}|${kitty}`;
|
|
641
|
+
if (memo && memo.key === key) return memo.lines;
|
|
642
|
+
const maxWidth = expanded ? IMAGE_PREVIEW_EXPANDED_MAX_WIDTH_CELLS : Math.min(config.previewMaxWidth, IMAGE_PREVIEW_MAX_WIDTH_CELLS);
|
|
643
|
+
let lines;
|
|
644
|
+
if (width <= 0) lines = [];
|
|
645
|
+
else if (images.length === 1 || !kitty) lines = renderStacked(width, maxWidth, labels, components);
|
|
646
|
+
else lines = renderGrid(width, maxWidth, labels, components);
|
|
647
|
+
memo = { key, lines };
|
|
648
|
+
return lines;
|
|
649
|
+
}
|
|
650
|
+
};
|
|
651
|
+
} catch {
|
|
652
|
+
return void 0;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
function renderStacked(width, maxWidth, labels, components) {
|
|
656
|
+
const lines = [];
|
|
657
|
+
for (const [index, component] of components.entries()) {
|
|
658
|
+
if (index > 0) lines.push("");
|
|
659
|
+
lines.push(labels[index] ?? "");
|
|
660
|
+
lines.push(...component.render(Math.min(width, Math.max(1, maxWidth + 2))));
|
|
661
|
+
}
|
|
662
|
+
return lines;
|
|
663
|
+
}
|
|
664
|
+
function stripTrailingSpaces(line) {
|
|
665
|
+
if (!line.endsWith(" ")) return line;
|
|
666
|
+
let end = line.length;
|
|
667
|
+
while (end > 0 && line.charCodeAt(end - 1) === 32) end--;
|
|
668
|
+
return line.slice(0, end);
|
|
669
|
+
}
|
|
670
|
+
function renderGrid(width, maxWidth, labels, components) {
|
|
671
|
+
const usable = Math.max(1, width - 2);
|
|
672
|
+
const maxColumns = Math.floor((usable + GRID_GAP) / (GRID_MIN_COLUMN + GRID_GAP));
|
|
673
|
+
const columns = Math.max(1, Math.min(components.length, maxColumns, 3));
|
|
674
|
+
if (columns < 2) return renderStacked(width, maxWidth, labels, components);
|
|
675
|
+
const columnWidth = Math.min(maxWidth, Math.floor((usable - (columns - 1) * GRID_GAP) / columns));
|
|
676
|
+
if (columnWidth < GRID_MIN_COLUMN) return renderStacked(width, maxWidth, labels, components);
|
|
677
|
+
const lines = [];
|
|
678
|
+
for (let start = 0; start < components.length; start += columns) {
|
|
679
|
+
const group = components.slice(start, start + columns);
|
|
680
|
+
const groupLabels = labels.slice(start, start + columns);
|
|
681
|
+
if (start > 0) lines.push("");
|
|
682
|
+
const rendered = group.map((component) => component.render(columnWidth));
|
|
683
|
+
const labelRow = groupLabels.map((label, _index) => {
|
|
684
|
+
const pad = Math.max(0, columnWidth - visibleWidth(stripAnsi(label)));
|
|
685
|
+
return label + " ".repeat(pad);
|
|
686
|
+
}).join(" ".repeat(GRID_GAP)).trimEnd();
|
|
687
|
+
lines.push(labelRow);
|
|
688
|
+
const rows = Math.max(...rendered.map((column) => column.length));
|
|
689
|
+
for (let row = 0; row < rows; row++) {
|
|
690
|
+
let line = "";
|
|
691
|
+
for (let column = 0; column < rendered.length; column++) {
|
|
692
|
+
if (column > 0) {
|
|
693
|
+
const startCol = column * (columnWidth + GRID_GAP);
|
|
694
|
+
line += `\x1B[${startCol + 1}G`;
|
|
695
|
+
}
|
|
696
|
+
line += rendered[column]?.[row] ?? "";
|
|
697
|
+
}
|
|
698
|
+
lines.push(stripTrailingSpaces(line));
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
return lines;
|
|
702
|
+
}
|
|
703
|
+
function registerImagePreviewSurface(pi) {
|
|
704
|
+
pi.registerEntryRenderer(IMAGE_PREVIEW_ENTRY_TYPE, renderImagePreviewEntry);
|
|
705
|
+
}
|
|
706
|
+
|
|
217
707
|
// extension-src/pi-style/shared/box.ts
|
|
218
708
|
import { homedir as homedir2 } from "os";
|
|
219
709
|
import { relative, resolve as resolve2 } from "path";
|
|
@@ -511,9 +1001,9 @@ function boxedResultRenderBudget(rawLineBudget = DEFAULT_COLLAPSED_RENDER_LINES)
|
|
|
511
1001
|
// extension-src/pi-style/shared/theme-extras.ts
|
|
512
1002
|
import { existsSync, readdirSync, readFileSync } from "fs";
|
|
513
1003
|
import { homedir } from "os";
|
|
514
|
-
import { dirname, join, resolve } from "path";
|
|
1004
|
+
import { dirname as dirname2, join, resolve } from "path";
|
|
515
1005
|
import { fileURLToPath } from "url";
|
|
516
|
-
var extensionDir =
|
|
1006
|
+
var extensionDir = dirname2(fileURLToPath(import.meta.url));
|
|
517
1007
|
var THEME_EXTRA_DEFAULTS = Object.freeze({
|
|
518
1008
|
assistantPrefix: "\u2022",
|
|
519
1009
|
assistantPrefixColor: "",
|
|
@@ -4212,16 +4702,16 @@ function parseGitShowStat(text) {
|
|
|
4212
4702
|
}
|
|
4213
4703
|
break;
|
|
4214
4704
|
}
|
|
4215
|
-
const
|
|
4216
|
-
if (!
|
|
4705
|
+
const stat3 = parseGitDiffStat(lines.slice(i).join("\n"));
|
|
4706
|
+
if (!stat3) return null;
|
|
4217
4707
|
return {
|
|
4218
4708
|
kind: "show-stat",
|
|
4219
4709
|
hash,
|
|
4220
4710
|
subject,
|
|
4221
|
-
files:
|
|
4222
|
-
...
|
|
4223
|
-
...
|
|
4224
|
-
...
|
|
4711
|
+
files: stat3.files,
|
|
4712
|
+
...stat3.filesChanged !== void 0 ? { filesChanged: stat3.filesChanged } : {},
|
|
4713
|
+
...stat3.insertions !== void 0 ? { insertions: stat3.insertions } : {},
|
|
4714
|
+
...stat3.deletions !== void 0 ? { deletions: stat3.deletions } : {}
|
|
4225
4715
|
};
|
|
4226
4716
|
}
|
|
4227
4717
|
var DIFF_GIT_HEADER = /^diff --git a\/(.*) b\/(.*)$/;
|
|
@@ -4548,17 +5038,17 @@ function parseGitPull(text) {
|
|
|
4548
5038
|
return null;
|
|
4549
5039
|
}
|
|
4550
5040
|
if (!range || ffIndex < 0) return null;
|
|
4551
|
-
const
|
|
4552
|
-
if (!
|
|
5041
|
+
const stat3 = parseGitDiffStat(lines.slice(ffIndex + 1).join("\n"));
|
|
5042
|
+
if (!stat3) return null;
|
|
4553
5043
|
return {
|
|
4554
5044
|
kind: "action",
|
|
4555
5045
|
command: "pull",
|
|
4556
|
-
files:
|
|
5046
|
+
files: stat3.files,
|
|
4557
5047
|
status: "Fast-forward",
|
|
4558
5048
|
range,
|
|
4559
|
-
...
|
|
4560
|
-
...
|
|
4561
|
-
...
|
|
5049
|
+
...stat3.filesChanged !== void 0 ? { filesChanged: stat3.filesChanged } : {},
|
|
5050
|
+
...stat3.insertions !== void 0 ? { insertions: stat3.insertions } : {},
|
|
5051
|
+
...stat3.deletions !== void 0 ? { deletions: stat3.deletions } : {}
|
|
4562
5052
|
};
|
|
4563
5053
|
}
|
|
4564
5054
|
function parseGitFetch(text) {
|
|
@@ -4700,30 +5190,30 @@ function parseGitMerge(text) {
|
|
|
4700
5190
|
}
|
|
4701
5191
|
const marker2 = body[idx] ?? "";
|
|
4702
5192
|
if (range && marker2 === "Fast-forward") {
|
|
4703
|
-
const
|
|
4704
|
-
if (!
|
|
5193
|
+
const stat3 = parseGitDiffStatTolerant(body.slice(idx + 1).join("\n"));
|
|
5194
|
+
if (!stat3) return null;
|
|
4705
5195
|
return {
|
|
4706
5196
|
kind: "action",
|
|
4707
5197
|
command: "merge",
|
|
4708
|
-
files:
|
|
5198
|
+
files: stat3.files,
|
|
4709
5199
|
status: "Fast-forward",
|
|
4710
5200
|
range,
|
|
4711
|
-
...
|
|
4712
|
-
...
|
|
4713
|
-
...
|
|
5201
|
+
...stat3.filesChanged !== void 0 ? { filesChanged: stat3.filesChanged } : {},
|
|
5202
|
+
...stat3.insertions !== void 0 ? { insertions: stat3.insertions } : {},
|
|
5203
|
+
...stat3.deletions !== void 0 ? { deletions: stat3.deletions } : {}
|
|
4714
5204
|
};
|
|
4715
5205
|
}
|
|
4716
5206
|
if (MERGE_MADE.exec(marker2)) {
|
|
4717
|
-
const
|
|
4718
|
-
if (!
|
|
5207
|
+
const stat3 = parseGitDiffStatTolerant(body.slice(idx + 1).join("\n"));
|
|
5208
|
+
if (!stat3) return null;
|
|
4719
5209
|
return {
|
|
4720
5210
|
kind: "action",
|
|
4721
5211
|
command: "merge",
|
|
4722
|
-
files:
|
|
5212
|
+
files: stat3.files,
|
|
4723
5213
|
status: marker2,
|
|
4724
|
-
...
|
|
4725
|
-
...
|
|
4726
|
-
...
|
|
5214
|
+
...stat3.filesChanged !== void 0 ? { filesChanged: stat3.filesChanged } : {},
|
|
5215
|
+
...stat3.insertions !== void 0 ? { insertions: stat3.insertions } : {},
|
|
5216
|
+
...stat3.deletions !== void 0 ? { deletions: stat3.deletions } : {}
|
|
4727
5217
|
};
|
|
4728
5218
|
}
|
|
4729
5219
|
return null;
|
|
@@ -7095,7 +7585,7 @@ var DEFAULT_CONFIG = Object.freeze({
|
|
|
7095
7585
|
startup: Object.freeze({ mode: "compact", showResources: false, alwaysExpanded: false }),
|
|
7096
7586
|
statusLine: Object.freeze({
|
|
7097
7587
|
enabled: true,
|
|
7098
|
-
separator: "
|
|
7588
|
+
separator: "|",
|
|
7099
7589
|
layout: Object.freeze({
|
|
7100
7590
|
left: ["path", "git", "context_bar", "cost"],
|
|
7101
7591
|
right: ["model_effort"],
|
|
@@ -7112,7 +7602,10 @@ var DEFAULT_CONFIG = Object.freeze({
|
|
|
7112
7602
|
assistantPrefix: true,
|
|
7113
7603
|
specialBlocks: true,
|
|
7114
7604
|
hideThinkingLabel: true,
|
|
7115
|
-
hideInterimText: true
|
|
7605
|
+
hideInterimText: true,
|
|
7606
|
+
showImagePreviews: true,
|
|
7607
|
+
clipboardImages: true,
|
|
7608
|
+
previewMaxWidth: 30
|
|
7116
7609
|
}),
|
|
7117
7610
|
tools: Object.freeze({
|
|
7118
7611
|
enabled: true,
|
|
@@ -7229,7 +7722,10 @@ function normalizeConfig(input, defaults = DEFAULT_CONFIG) {
|
|
|
7229
7722
|
assistantPrefix: bool(messages.assistantPrefix, defaults.messages.assistantPrefix),
|
|
7230
7723
|
specialBlocks: bool(messages.specialBlocks, defaults.messages.specialBlocks),
|
|
7231
7724
|
hideThinkingLabel: bool(messages.hideThinkingLabel, defaults.messages.hideThinkingLabel),
|
|
7232
|
-
hideInterimText: bool(messages.hideInterimText, defaults.messages.hideInterimText)
|
|
7725
|
+
hideInterimText: bool(messages.hideInterimText, defaults.messages.hideInterimText),
|
|
7726
|
+
showImagePreviews: bool(messages.showImagePreviews, defaults.messages.showImagePreviews),
|
|
7727
|
+
clipboardImages: bool(messages.clipboardImages, defaults.messages.clipboardImages),
|
|
7728
|
+
previewMaxWidth: boundedInt(messages.previewMaxWidth, defaults.messages.previewMaxWidth, 8, 60)
|
|
7233
7729
|
}),
|
|
7234
7730
|
tools: Object.freeze({
|
|
7235
7731
|
enabled: bool(tools.enabled, defaults.tools.enabled),
|
|
@@ -7283,6 +7779,8 @@ var BOOL_PATHS = /* @__PURE__ */ new Set([
|
|
|
7283
7779
|
"messages.specialBlocks",
|
|
7284
7780
|
"messages.hideThinkingLabel",
|
|
7285
7781
|
"messages.hideInterimText",
|
|
7782
|
+
"messages.showImagePreviews",
|
|
7783
|
+
"messages.clipboardImages",
|
|
7286
7784
|
"tools.enabled",
|
|
7287
7785
|
"tools.showElapsed",
|
|
7288
7786
|
"tools.dimOutput",
|
|
@@ -7328,6 +7826,8 @@ function validLeaf(path, value) {
|
|
|
7328
7826
|
return typeof value === "string";
|
|
7329
7827
|
if (path === "tools.maxCollapsedLines" || path === "tools.maxExpandedLines")
|
|
7330
7828
|
return typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
7829
|
+
if (path === "messages.previewMaxWidth")
|
|
7830
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 8 && value <= 60;
|
|
7331
7831
|
if (path === "statusLine.bottomMargin" || path === "statusLine.contextBarWidth")
|
|
7332
7832
|
return typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
7333
7833
|
if (STRING_ARRAY_PATHS.has(path)) return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
@@ -7751,6 +8251,9 @@ var allowedPaths = /* @__PURE__ */ new Set([
|
|
|
7751
8251
|
"messages.specialBlocks",
|
|
7752
8252
|
"messages.hideThinkingLabel",
|
|
7753
8253
|
"messages.hideInterimText",
|
|
8254
|
+
"messages.showImagePreviews",
|
|
8255
|
+
"messages.clipboardImages",
|
|
8256
|
+
"messages.previewMaxWidth",
|
|
7754
8257
|
"tools.enabled",
|
|
7755
8258
|
"tools.style",
|
|
7756
8259
|
"tools.maxCollapsedLines",
|
|
@@ -7783,6 +8286,9 @@ function validatePathValue(path, value) {
|
|
|
7783
8286
|
"messages.specialBlocks",
|
|
7784
8287
|
"messages.hideThinkingLabel",
|
|
7785
8288
|
"messages.hideInterimText",
|
|
8289
|
+
"messages.showImagePreviews",
|
|
8290
|
+
"messages.clipboardImages",
|
|
8291
|
+
"messages.previewMaxWidth",
|
|
7786
8292
|
"tools.enabled",
|
|
7787
8293
|
"tools.showElapsed",
|
|
7788
8294
|
"tools.dimOutput",
|
|
@@ -7805,6 +8311,8 @@ function validatePathValue(path, value) {
|
|
|
7805
8311
|
return typeof value === "string";
|
|
7806
8312
|
if (path === "tools.maxCollapsedLines" || path === "tools.maxExpandedLines")
|
|
7807
8313
|
return typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
8314
|
+
if (path === "messages.previewMaxWidth")
|
|
8315
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 8 && value <= 60;
|
|
7808
8316
|
if (path === "statusLine.bottomMargin" || path === "statusLine.contextBarWidth")
|
|
7809
8317
|
return typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
7810
8318
|
if (path.endsWith(".colors") || path.endsWith(".glyphs"))
|
|
@@ -7937,8 +8445,8 @@ function runCommand(args, host, app, storage) {
|
|
|
7937
8445
|
}
|
|
7938
8446
|
|
|
7939
8447
|
// extension-src/pi-style/pi/config-host.ts
|
|
7940
|
-
import { mkdir, readFile, rename, unlink, writeFile } from "fs/promises";
|
|
7941
|
-
import { dirname as
|
|
8448
|
+
import { mkdir, readFile as readFile3, rename, unlink, writeFile } from "fs/promises";
|
|
8449
|
+
import { dirname as dirname3, join as join2 } from "path";
|
|
7942
8450
|
import { CONFIG_DIR_NAME, getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
7943
8451
|
var locks = /* @__PURE__ */ new Map();
|
|
7944
8452
|
function serialize(path, operation) {
|
|
@@ -7951,14 +8459,14 @@ function serialize(path, operation) {
|
|
|
7951
8459
|
}
|
|
7952
8460
|
function createPiConfigFilePort(fs = {}) {
|
|
7953
8461
|
const fsMkdir = fs.mkdir ?? mkdir;
|
|
7954
|
-
const fsReadFile = fs.readFile ??
|
|
8462
|
+
const fsReadFile = fs.readFile ?? readFile3;
|
|
7955
8463
|
const fsRename = fs.rename ?? rename;
|
|
7956
8464
|
const fsUnlink = fs.unlink ?? unlink;
|
|
7957
8465
|
const fsWriteFile = fs.writeFile ?? writeFile;
|
|
7958
8466
|
return {
|
|
7959
8467
|
read: (path) => fsReadFile(path, "utf8"),
|
|
7960
8468
|
writeAtomic: async (path, content) => serialize(path, async () => {
|
|
7961
|
-
await fsMkdir(
|
|
8469
|
+
await fsMkdir(dirname3(path), { recursive: true });
|
|
7962
8470
|
const temporary = `${path}.pi-style-${process.pid}-${Date.now()}.tmp`;
|
|
7963
8471
|
try {
|
|
7964
8472
|
await fsWriteFile(temporary, content, { mode: 384 });
|
|
@@ -8147,12 +8655,17 @@ function createBuiltinSegments() {
|
|
|
8147
8655
|
const percent = contextPercent(snapshot.context ?? {});
|
|
8148
8656
|
if (percent === void 0) return { visible: false, content: "" };
|
|
8149
8657
|
const token = contextBarToken(percent);
|
|
8150
|
-
const window = snapshot.context?.windowTokens;
|
|
8151
|
-
const label = `ctx${window !== void 0 ? ` (${formatTokens(window)})` : ""}:`;
|
|
8152
8658
|
const width = options.context_bar?.width ?? CONTEXT_BAR_WIDTH;
|
|
8659
|
+
const pipe = theme.apply("separator", "|");
|
|
8660
|
+
const bar = `${theme.apply("muted", "[")}${theme.apply(token, contextBar(percent, width))}${theme.apply("muted", "]")}`;
|
|
8661
|
+
const parts = [bar, `${theme.apply(token, `${Math.round(percent)}%`)}${theme.apply("muted", " used")}`];
|
|
8662
|
+
const current = snapshot.context?.currentTokens;
|
|
8663
|
+
const window = snapshot.context?.windowTokens;
|
|
8664
|
+
if (current !== void 0 && window !== void 0)
|
|
8665
|
+
parts.push(theme.apply("muted", `${formatTokens(current)}/${formatTokens(window)}`));
|
|
8153
8666
|
return {
|
|
8154
8667
|
visible: true,
|
|
8155
|
-
content:
|
|
8668
|
+
content: parts.join(` ${pipe} `),
|
|
8156
8669
|
compactContent: theme.apply(token, `${Math.round(percent)}%`)
|
|
8157
8670
|
};
|
|
8158
8671
|
}),
|
|
@@ -8235,10 +8748,7 @@ function contextBarToken(percent) {
|
|
|
8235
8748
|
return "success";
|
|
8236
8749
|
}
|
|
8237
8750
|
function formatTokens(value) {
|
|
8238
|
-
if (value >= 1e6) {
|
|
8239
|
-
const millions = value / 1e6;
|
|
8240
|
-
return `${Number.isInteger(millions) ? millions : millions.toFixed(1)}M`;
|
|
8241
|
-
}
|
|
8751
|
+
if (value >= 1e6) return `${(value / 1e6).toFixed(1)}M`;
|
|
8242
8752
|
if (value >= 1e3) {
|
|
8243
8753
|
const thousands = value / 1e3;
|
|
8244
8754
|
return `${Number.isInteger(thousands) ? thousands : thousands.toFixed(1)}K`;
|
|
@@ -8546,6 +9056,10 @@ var StyledEditor = class extends CustomEditor {
|
|
|
8546
9056
|
semanticDirty = false;
|
|
8547
9057
|
disposed = false;
|
|
8548
9058
|
renderPlanCache;
|
|
9059
|
+
clipboardImagePaste;
|
|
9060
|
+
/** Keybinding matcher (the factory's KeybindingsManager) for keystroke-level
|
|
9061
|
+
* interception: the paste keybinding and backspace checks below. */
|
|
9062
|
+
editorKeybindings;
|
|
8549
9063
|
constructor(tui, theme, keybindings, options) {
|
|
8550
9064
|
super(tui, theme, keybindings);
|
|
8551
9065
|
this.config = options.config;
|
|
@@ -8553,6 +9067,8 @@ var StyledEditor = class extends CustomEditor {
|
|
|
8553
9067
|
this.piTheme = theme;
|
|
8554
9068
|
this.fullTheme = options.fullTheme;
|
|
8555
9069
|
this.onSnapshot = options.onSnapshot;
|
|
9070
|
+
this.clipboardImagePaste = options.clipboardImagePaste;
|
|
9071
|
+
this.editorKeybindings = keybindings;
|
|
8556
9072
|
this.semantic = semanticTheme(theme, options.config);
|
|
8557
9073
|
this.setPaddingX(0);
|
|
8558
9074
|
}
|
|
@@ -8569,9 +9085,88 @@ var StyledEditor = class extends CustomEditor {
|
|
|
8569
9085
|
}
|
|
8570
9086
|
handleInput(data) {
|
|
8571
9087
|
if (this.disposed) return;
|
|
9088
|
+
if (this.handleClipboardImagePasteKeystroke(data)) return;
|
|
9089
|
+
if (this.handleAtomicMarkerBackspace(data)) return;
|
|
8572
9090
|
super.handleInput(data);
|
|
8573
9091
|
this.onSnapshot({ ...this.snapshot });
|
|
8574
9092
|
}
|
|
9093
|
+
/**
|
|
9094
|
+
* Instant marker (ADR 0009): when the built-in paste keybinding fires, the
|
|
9095
|
+
* clipboard holds an image (sync native probe), and the surface is enabled,
|
|
9096
|
+
* the keystroke is owned here — an `[Image #N] ` marker is inserted
|
|
9097
|
+
* immediately (zero-latency feedback) and the bytes fill the registry
|
|
9098
|
+
* entry asynchronously. Pi's own paste handler never runs for this
|
|
9099
|
+
* keystroke, so no temp artifact is written. Text pastes (no image) and
|
|
9100
|
+
* probe-unavailable hosts fall through to the native path untouched.
|
|
9101
|
+
*/
|
|
9102
|
+
handleClipboardImagePasteKeystroke(data) {
|
|
9103
|
+
const surface = this.clipboardImagePaste;
|
|
9104
|
+
if (!surface?.enabled()) return false;
|
|
9105
|
+
if (!this.editorKeybindings.matches(data, "app.clipboard.pasteImage")) return false;
|
|
9106
|
+
if (surface.clipboardHasImage() !== true) return false;
|
|
9107
|
+
super.insertTextAtCursor(surface.markerFromClipboard());
|
|
9108
|
+
this.onSnapshot({ ...this.snapshot });
|
|
9109
|
+
return true;
|
|
9110
|
+
}
|
|
9111
|
+
/**
|
|
9112
|
+
* Atomic marker backspace (ADR 0009): backspacing directly after a
|
|
9113
|
+
* registered `[Image #N] ` marker deletes the whole marker as one unit —
|
|
9114
|
+
* the image is discarded (image-paste semantics). Direct state surgery
|
|
9115
|
+
* (O(1)) mirroring pi-tui's handleBackspace mutation protocol: no setText
|
|
9116
|
+
* round-trip (it clears the pastes registry, and undo snapshots store that
|
|
9117
|
+
* Map by reference — silently breaking `[paste #N]` atomicity on undo) and
|
|
9118
|
+
* no per-column left-arrow inputs (O(K·L) through the keybinding chain and
|
|
9119
|
+
* visual line maps). No undo snapshot is pushed: the discarded image
|
|
9120
|
+
* cannot be restored, so the deletion is intentionally non-undoable.
|
|
9121
|
+
*/
|
|
9122
|
+
handleAtomicMarkerBackspace(data) {
|
|
9123
|
+
const surface = this.clipboardImagePaste;
|
|
9124
|
+
if (!surface?.enabled()) return false;
|
|
9125
|
+
const isBackspace = this.editorKeybindings.matches(data, "tui.editor.deleteCharBackward") || data === "\x7F";
|
|
9126
|
+
if (!isBackspace) return false;
|
|
9127
|
+
const lines = this.getLines();
|
|
9128
|
+
const cursor = this.getCursor();
|
|
9129
|
+
const current = lines[cursor.line];
|
|
9130
|
+
if (current === void 0) return false;
|
|
9131
|
+
const before = current.slice(0, cursor.col);
|
|
9132
|
+
const hit = clipboardMarkerAtEnd(before);
|
|
9133
|
+
if (!hit || !surface.isMarkerIndexRegistered(hit.index)) return false;
|
|
9134
|
+
const editor = this;
|
|
9135
|
+
const targetCol = cursor.col - hit.length;
|
|
9136
|
+
editor.exitHistoryBrowsing();
|
|
9137
|
+
editor.lastAction = null;
|
|
9138
|
+
editor.cancelAutocomplete();
|
|
9139
|
+
editor.state.lines[cursor.line] = current.slice(0, targetCol) + current.slice(cursor.col);
|
|
9140
|
+
editor.setCursorCol(targetCol);
|
|
9141
|
+
editor.onChange?.(this.getText());
|
|
9142
|
+
surface.discardMarkerIndex(hit.index);
|
|
9143
|
+
this.invalidate();
|
|
9144
|
+
this.onSnapshot({ ...this.snapshot });
|
|
9145
|
+
return true;
|
|
9146
|
+
}
|
|
9147
|
+
/**
|
|
9148
|
+
* Artifact-path fallback (ADR 0009): when the keystroke was not owned
|
|
9149
|
+
* (probe unavailable, native editor path, config toggled) Pi's built-in
|
|
9150
|
+
* paste still inserts a `<tmpdir>/pi-clipboard-<uuid>.<ext>` path through
|
|
9151
|
+
* this method. Convert it to a marker when the artifact reads successfully;
|
|
9152
|
+
* every failure inserts the original text verbatim (exact native behavior).
|
|
9153
|
+
*/
|
|
9154
|
+
insertTextAtCursor(text) {
|
|
9155
|
+
const surface = this.clipboardImagePaste;
|
|
9156
|
+
if (!surface?.enabled() || !isSingleClipboardImagePath(text)) {
|
|
9157
|
+
super.insertTextAtCursor(text);
|
|
9158
|
+
return;
|
|
9159
|
+
}
|
|
9160
|
+
void (async () => {
|
|
9161
|
+
let replacement = text;
|
|
9162
|
+
try {
|
|
9163
|
+
replacement = await surface.markerFromArtifact(text);
|
|
9164
|
+
} catch {
|
|
9165
|
+
}
|
|
9166
|
+
super.insertTextAtCursor(replacement);
|
|
9167
|
+
this.tui.requestRender();
|
|
9168
|
+
})();
|
|
9169
|
+
}
|
|
8575
9170
|
invalidate() {
|
|
8576
9171
|
super.invalidate();
|
|
8577
9172
|
if (this.semanticDirty) {
|
|
@@ -8787,6 +9382,7 @@ function installEditor(options) {
|
|
|
8787
9382
|
snapshot,
|
|
8788
9383
|
theme,
|
|
8789
9384
|
...options.host.theme ? { fullTheme: options.host.theme } : {},
|
|
9385
|
+
...options.clipboardImagePaste ? { clipboardImagePaste: options.clipboardImagePaste } : {},
|
|
8790
9386
|
onSnapshot: (next) => {
|
|
8791
9387
|
if (!disposed && options.isCurrent?.() !== false) snapshot = next;
|
|
8792
9388
|
}
|
|
@@ -9363,7 +9959,7 @@ function renderGroup(items, separator, padding) {
|
|
|
9363
9959
|
}
|
|
9364
9960
|
function renderStatus(layout, snapshot, width, options) {
|
|
9365
9961
|
if (width <= 0) return { primary: "", lines: [], visibleSegments: [] };
|
|
9366
|
-
const separator = options.separator ?? "
|
|
9962
|
+
const separator = options.separator ?? "|";
|
|
9367
9963
|
const padding = options.padding ?? " ";
|
|
9368
9964
|
const normalized = uniqueLayout(layout);
|
|
9369
9965
|
const context = { snapshot, theme: options.theme, options: options.options ?? {}, width };
|
|
@@ -10180,7 +10776,10 @@ function createPiStyleRuntime(host, generation2, requestRender = host.requestRen
|
|
|
10180
10776
|
config: currentConfig,
|
|
10181
10777
|
generation: generation2,
|
|
10182
10778
|
initialSnapshot: currentSnapshot,
|
|
10183
|
-
isCurrent: () => !disposed
|
|
10779
|
+
isCurrent: () => !disposed,
|
|
10780
|
+
// Clipboard image paste surface (ADR 0009): instant `[Image #N] `
|
|
10781
|
+
// markers at keystroke time, artifact fallback, atomic backspace.
|
|
10782
|
+
clipboardImagePaste: createClipboardImagePasteSurface()
|
|
10184
10783
|
});
|
|
10185
10784
|
if (editor) {
|
|
10186
10785
|
disposables.add(editor);
|
|
@@ -10878,7 +11477,7 @@ function isTierCAuthorized(input) {
|
|
|
10878
11477
|
|
|
10879
11478
|
// extension-src/pi-style/pi/compatibility-probe.ts
|
|
10880
11479
|
import { readFileSync as readFileSync2 } from "fs";
|
|
10881
|
-
import { dirname as
|
|
11480
|
+
import { dirname as dirname4, join as join3 } from "path";
|
|
10882
11481
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
10883
11482
|
import {
|
|
10884
11483
|
AssistantMessageComponent,
|
|
@@ -11675,19 +12274,19 @@ function detectPiVersion(resolution = {}) {
|
|
|
11675
12274
|
);
|
|
11676
12275
|
}
|
|
11677
12276
|
});
|
|
11678
|
-
const
|
|
12277
|
+
const readFile4 = resolution.readFile ?? ((path) => readFileSync2(path, "utf8"));
|
|
11679
12278
|
try {
|
|
11680
12279
|
const entry = resolvePackageEntry("@earendil-works/pi-coding-agent");
|
|
11681
|
-
let directory =
|
|
12280
|
+
let directory = dirname4(entry);
|
|
11682
12281
|
for (; ; ) {
|
|
11683
12282
|
const packagePath = join3(directory, "package.json");
|
|
11684
12283
|
try {
|
|
11685
|
-
const packageJson = JSON.parse(
|
|
12284
|
+
const packageJson = JSON.parse(readFile4(packagePath));
|
|
11686
12285
|
if (packageJson.name === "@earendil-works/pi-coding-agent" && typeof packageJson.version === "string")
|
|
11687
12286
|
return { version: packageJson.version };
|
|
11688
12287
|
} catch {
|
|
11689
12288
|
}
|
|
11690
|
-
const parent =
|
|
12289
|
+
const parent = dirname4(directory);
|
|
11691
12290
|
if (parent === directory) break;
|
|
11692
12291
|
directory = parent;
|
|
11693
12292
|
}
|
|
@@ -12350,6 +12949,11 @@ function createPiStyleSessionCoordinator(pi, hooks = {}) {
|
|
|
12350
12949
|
};
|
|
12351
12950
|
const applyMessagesConfig = (config) => {
|
|
12352
12951
|
sessionUi?.setHiddenThinkingLabel?.(config.messages.hideThinkingLabel ? "" : void 0);
|
|
12952
|
+
setMessagesRenderConfig({
|
|
12953
|
+
showImagePreviews: config.messages.showImagePreviews,
|
|
12954
|
+
clipboardImages: config.messages.clipboardImages,
|
|
12955
|
+
previewMaxWidth: config.messages.previewMaxWidth
|
|
12956
|
+
});
|
|
12353
12957
|
};
|
|
12354
12958
|
const applyAutoTheme = (config, ctx) => {
|
|
12355
12959
|
const target = config.theme.autoApply;
|
|
@@ -12409,6 +13013,7 @@ function createPiStyleSessionCoordinator(pi, hooks = {}) {
|
|
|
12409
13013
|
resetBatchRegistry();
|
|
12410
13014
|
resetGrepRegistry();
|
|
12411
13015
|
resetBashTreeRegistry();
|
|
13016
|
+
resetPendingImageRegistry();
|
|
12412
13017
|
resetTurnRegistry();
|
|
12413
13018
|
rebuildTurnRegistryFromEntries(ctx.sessionManager.getEntries());
|
|
12414
13019
|
stopAllElapsedTickers();
|
|
@@ -12474,6 +13079,7 @@ function createPiStyleSessionCoordinator(pi, hooks = {}) {
|
|
|
12474
13079
|
resetBatchRegistry();
|
|
12475
13080
|
resetGrepRegistry();
|
|
12476
13081
|
resetBashTreeRegistry();
|
|
13082
|
+
resetPendingImageRegistry();
|
|
12477
13083
|
resetTurnRegistry();
|
|
12478
13084
|
stopAllElapsedTickers();
|
|
12479
13085
|
app.sessionShutdown();
|
|
@@ -12677,6 +13283,12 @@ function piStyleExtension(pi) {
|
|
|
12677
13283
|
pi.registerFlag("pi-style-ascii", { type: "boolean", description: "Use ASCII pi-style markers" });
|
|
12678
13284
|
const coordinator = createPiStyleSessionCoordinator(pi, compatibilityTestHooks);
|
|
12679
13285
|
registerPiStyleCommand(pi, coordinator.app);
|
|
13286
|
+
registerImagePreviewSurface(pi);
|
|
13287
|
+
let stagedImagePreview;
|
|
13288
|
+
pi.on("before_agent_start", (event) => {
|
|
13289
|
+
const staged = stageImagePreviewData(event.images ?? []);
|
|
13290
|
+
if (staged) stagedImagePreview = staged;
|
|
13291
|
+
});
|
|
12680
13292
|
pi.on("session_start", async (event, ctx) => {
|
|
12681
13293
|
resetUsageFromSessionCache(ctx.sessionManager);
|
|
12682
13294
|
if (pi.getFlag("pi-style-readonly-tools") === true) {
|
|
@@ -12688,7 +13300,7 @@ function piStyleExtension(pi) {
|
|
|
12688
13300
|
coordinator.app.runtime.current?.dismissStartup();
|
|
12689
13301
|
beginAgentRun();
|
|
12690
13302
|
});
|
|
12691
|
-
pi.on("input", (event, _ctx) => {
|
|
13303
|
+
pi.on("input", async (event, _ctx) => {
|
|
12692
13304
|
coordinator.app.runtime.current?.dismissStartup();
|
|
12693
13305
|
if (event.source === "interactive") {
|
|
12694
13306
|
const trimmed = event.text.trimStart();
|
|
@@ -12697,6 +13309,15 @@ function piStyleExtension(pi) {
|
|
|
12697
13309
|
if (trimmed.slice(bangLength).trim() === "") return { action: "handled" };
|
|
12698
13310
|
}
|
|
12699
13311
|
}
|
|
13312
|
+
const markerResult = await resolvePendingImageMarkers(event.text);
|
|
13313
|
+
const pathResult = event.source === "interactive" ? await transformClipboardImages(event.text) : void 0;
|
|
13314
|
+
if (markerResult || pathResult) {
|
|
13315
|
+
return {
|
|
13316
|
+
action: "transform",
|
|
13317
|
+
text: pathResult?.text ?? event.text,
|
|
13318
|
+
images: [...markerResult?.images ?? [], ...pathResult?.images ?? []]
|
|
13319
|
+
};
|
|
13320
|
+
}
|
|
12700
13321
|
return void 0;
|
|
12701
13322
|
});
|
|
12702
13323
|
pi.on("tool_execution_start", () => coordinator.app.runtime.current?.dismissStartup());
|
|
@@ -12716,8 +13337,12 @@ function piStyleExtension(pi) {
|
|
|
12716
13337
|
"session_info_changed",
|
|
12717
13338
|
(event) => coordinator.app.update({ sessionName: event.name }, "coalesced", { refreshExtensionStatuses: true })
|
|
12718
13339
|
);
|
|
12719
|
-
pi.on("message_start", () => {
|
|
13340
|
+
pi.on("message_start", (event) => {
|
|
12720
13341
|
closeActiveBatch();
|
|
13342
|
+
if (stagedImagePreview && event.message?.role === "assistant") {
|
|
13343
|
+
flushImagePreviewEntry(pi, stagedImagePreview);
|
|
13344
|
+
stagedImagePreview = void 0;
|
|
13345
|
+
}
|
|
12721
13346
|
});
|
|
12722
13347
|
pi.on("message_update", () => coordinator.app.update({}, "coalesced"));
|
|
12723
13348
|
pi.on(
|