pi-better-background-tasks 0.2.13 → 0.2.15
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/package.json +1 -1
- package/src/shared-navigator.ts +31 -59
package/package.json
CHANGED
package/src/shared-navigator.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { truncateToWidth as piTruncateToWidth, visibleWidth as piVisibleWidth } from "@earendil-works/pi-tui";
|
|
2
3
|
import type { Component } from "@earendil-works/pi-tui";
|
|
3
4
|
import { createRenderScheduler, type RenderScheduler } from "./shared-render-scheduler.ts";
|
|
4
5
|
|
|
@@ -179,7 +180,10 @@ export function ensureBackgroundWorkNavigator(ctx: ExtensionContext, deps: HostD
|
|
|
179
180
|
if (!isNavigatorUiAvailable(ctx)) return;
|
|
180
181
|
const s = state();
|
|
181
182
|
s.uiCtx = ctx;
|
|
182
|
-
s.deps =
|
|
183
|
+
s.deps = {
|
|
184
|
+
...deps,
|
|
185
|
+
createTranscriptComponent: deps.createTranscriptComponent ?? s.deps?.createTranscriptComponent,
|
|
186
|
+
};
|
|
183
187
|
try { (ctx.ui as any).setWidget?.(MAIN_LIST_WIDGET_KEY, undefined); } catch { /* ignore */ }
|
|
184
188
|
s.mainListWidgetInstalled = false;
|
|
185
189
|
s.mainListRequestRender = undefined;
|
|
@@ -277,10 +281,10 @@ function refreshMainListWidget(): void {
|
|
|
277
281
|
}) : undefined;
|
|
278
282
|
const changed = nextSignature !== s.lastMainListSignature;
|
|
279
283
|
s.lastMainListSignature = nextSignature;
|
|
280
|
-
s.lastMainListLines = rows.length ? buildMainListLines(rows, MAIN_LIST_FALLBACK_WIDTH, deps.truncate, themeFg(ctx), {
|
|
284
|
+
s.lastMainListLines = rows.length ? ["", ...buildMainListLines(rows, MAIN_LIST_FALLBACK_WIDTH, deps.truncate, themeFg(ctx), {
|
|
281
285
|
selectedId: s.mainListFocused ? s.mainListSelectedId : undefined,
|
|
282
286
|
focused: s.mainListFocused === true,
|
|
283
|
-
}) : undefined;
|
|
287
|
+
})] : undefined;
|
|
284
288
|
if (!rows.length) {
|
|
285
289
|
if (!s.mainListWidgetInstalled) return;
|
|
286
290
|
try { (ctx.ui as any).setWidget?.(MAIN_LIST_WIDGET_KEY, undefined); } catch { /* ignore */ }
|
|
@@ -320,10 +324,10 @@ function createMainListWidget(tui: { requestRender?(): void }, theme: unknown, d
|
|
|
320
324
|
state().lastMainListLines = undefined;
|
|
321
325
|
return [];
|
|
322
326
|
}
|
|
323
|
-
const lines = buildMainListLines(rows, renderWidth(width), deps.truncate, themeFgFromTheme(theme), {
|
|
327
|
+
const lines = ["", ...buildMainListLines(rows, renderWidth(width), deps.truncate, themeFgFromTheme(theme), {
|
|
324
328
|
selectedId: state().mainListFocused ? state().mainListSelectedId : undefined,
|
|
325
329
|
focused: state().mainListFocused === true,
|
|
326
|
-
});
|
|
330
|
+
})];
|
|
327
331
|
state().lastMainListLines = lines;
|
|
328
332
|
return lines;
|
|
329
333
|
},
|
|
@@ -1266,7 +1270,8 @@ export function wrapLogText(text: string, width: number): string[] {
|
|
|
1266
1270
|
continue;
|
|
1267
1271
|
}
|
|
1268
1272
|
const sourceIndent = source.match(/^[ \t]*/)?.[0] ?? "";
|
|
1269
|
-
|
|
1273
|
+
// Leave room for a full-width grapheme even on deeply indented log lines.
|
|
1274
|
+
const indent = sourceIndent.replace(/\t/g, " ").slice(0, max - 2);
|
|
1270
1275
|
let remaining = source.slice(sourceIndent.length);
|
|
1271
1276
|
const contentWidth = Math.max(1, max - visibleWidth(indent));
|
|
1272
1277
|
if (!remaining) {
|
|
@@ -1299,33 +1304,25 @@ function lastDelimiterBreak(value: string): number {
|
|
|
1299
1304
|
return last;
|
|
1300
1305
|
}
|
|
1301
1306
|
|
|
1307
|
+
const GRAPHEME_SEGMENTER = new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
1308
|
+
|
|
1302
1309
|
function splitVisiblePrefix(value: string, width: number): [string, string] {
|
|
1303
1310
|
const max = Math.max(0, Math.floor(width));
|
|
1304
1311
|
let visible = 0;
|
|
1305
|
-
let
|
|
1306
|
-
|
|
1307
|
-
if (
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
}
|
|
1313
|
-
}
|
|
1314
|
-
if (value[index] === "<") {
|
|
1315
|
-
const close = value.indexOf(">", index);
|
|
1316
|
-
if (close !== -1) {
|
|
1317
|
-
const tag = value.slice(index, close + 1);
|
|
1318
|
-
if (/^<\/?[a-zA-Z][\w-]*>$/.test(tag) || tag === "</>") {
|
|
1319
|
-
index = close + 1;
|
|
1320
|
-
continue;
|
|
1321
|
-
}
|
|
1322
|
-
}
|
|
1312
|
+
let end = 0;
|
|
1313
|
+
for (const { segment, index } of GRAPHEME_SEGMENTER.segment(value)) {
|
|
1314
|
+
if (index < end) continue;
|
|
1315
|
+
const formatting = value.slice(index).match(FORMATTING_PREFIX_RE);
|
|
1316
|
+
if (formatting) {
|
|
1317
|
+
end = index + formatting[0].length;
|
|
1318
|
+
continue;
|
|
1323
1319
|
}
|
|
1324
|
-
const
|
|
1325
|
-
|
|
1326
|
-
visible +=
|
|
1320
|
+
const cells = piVisibleWidth(segment);
|
|
1321
|
+
if (visible + cells > max) break;
|
|
1322
|
+
visible += cells;
|
|
1323
|
+
end = index + segment.length;
|
|
1327
1324
|
}
|
|
1328
|
-
return [value.slice(0,
|
|
1325
|
+
return [value.slice(0, end), value.slice(end)];
|
|
1329
1326
|
}
|
|
1330
1327
|
|
|
1331
1328
|
function cycleLogTailRows(current: number): number {
|
|
@@ -1384,44 +1381,19 @@ function safeTruncate(line: string, width: number, truncate: (s: string, width:
|
|
|
1384
1381
|
|
|
1385
1382
|
const ANSI_RE = new RegExp("[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))", "g");
|
|
1386
1383
|
|
|
1384
|
+
const FORMATTING_PREFIX_RE = new RegExp(`^(?:${ANSI_RE.source}|<\\/?[a-zA-Z][\\w-]*>|</>)`);
|
|
1385
|
+
|
|
1387
1386
|
function visibleWidth(value: string): number {
|
|
1388
|
-
|
|
1387
|
+
const cleaned = String(value ?? "")
|
|
1389
1388
|
.replace(ANSI_RE, "")
|
|
1390
1389
|
.replace(/<\/?[a-zA-Z][\w-]*>/g, "")
|
|
1391
|
-
.replace(/<\/>/g, "")
|
|
1392
|
-
|
|
1390
|
+
.replace(/<\/>/g, "");
|
|
1391
|
+
return piVisibleWidth(cleaned);
|
|
1393
1392
|
}
|
|
1394
1393
|
|
|
1395
1394
|
function truncateVisible(value: string, width: number): string {
|
|
1396
1395
|
const str = String(value ?? "");
|
|
1397
1396
|
const max = Math.max(0, Math.floor(width || 0));
|
|
1398
1397
|
if (visibleWidth(str) <= max) return str;
|
|
1399
|
-
|
|
1400
|
-
let vis = 0;
|
|
1401
|
-
let i = 0;
|
|
1402
|
-
while (i < str.length && vis < max) {
|
|
1403
|
-
if (str[i] === "\u001b" || str[i] === "\u009b") {
|
|
1404
|
-
const match = str.slice(i).match(ANSI_RE);
|
|
1405
|
-
if (match && match.index === 0) {
|
|
1406
|
-
out += match[0];
|
|
1407
|
-
i += match[0].length;
|
|
1408
|
-
continue;
|
|
1409
|
-
}
|
|
1410
|
-
}
|
|
1411
|
-
if (str[i] === "<") {
|
|
1412
|
-
const close = str.indexOf(">", i);
|
|
1413
|
-
if (close !== -1) {
|
|
1414
|
-
const tag = str.slice(i, close + 1);
|
|
1415
|
-
if (/^<\/?[a-zA-Z][\w-]*>$/.test(tag) || tag === "</>") {
|
|
1416
|
-
out += tag;
|
|
1417
|
-
i = close + 1;
|
|
1418
|
-
continue;
|
|
1419
|
-
}
|
|
1420
|
-
}
|
|
1421
|
-
}
|
|
1422
|
-
out += str[i];
|
|
1423
|
-
vis += 1;
|
|
1424
|
-
i += 1;
|
|
1425
|
-
}
|
|
1426
|
-
return out;
|
|
1398
|
+
return piTruncateToWidth(str, max);
|
|
1427
1399
|
}
|