pi-cc-extensions 0.8.70 → 0.8.72
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.en.md +3 -1
- package/README.md +3 -1
- package/extensions/config/config.ts +4 -0
- package/extensions/config/panel.ts +16 -0
- package/extensions/feature/shell/footer.ts +413 -0
- package/extensions/index.ts +2 -0
- package/extensions/renderer/compact-mode.ts +154 -9
- package/extensions/renderer/default-mode.ts +82 -0
- package/extensions/renderer/mouse/interaction.ts +11 -6
- package/extensions/renderer/mouse/packets.ts +1 -0
- package/extensions/renderer/mouse/scroll.ts +23 -1
- package/extensions/renderer/tool/diff/diff-edit-render.ts +27 -7
- package/extensions/renderer/tool/diff/diff-inline.ts +13 -1
- package/extensions/renderer/tool/diff/diff-layout.ts +37 -5
- package/extensions/renderer/tool/diff/diff-parse.ts +18 -4
- package/extensions/renderer/tool/grouping.ts +49 -8
- package/extensions/renderer/tool/result.ts +6 -2
- package/extensions/utils/patch-keys.ts +1 -0
- package/extensions/utils/sgr-mouse.ts +5 -0
- package/package.json +1 -1
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
type DiffLineEntry,
|
|
17
17
|
type DiffLineKind,
|
|
18
18
|
type DiffMetaEntry,
|
|
19
|
+
type DiffOmissionEntry,
|
|
19
20
|
type ParsedDiffEntry,
|
|
20
21
|
} from "./diff-parse.ts";
|
|
21
22
|
import { getCellLineNumber, type DiffSpan, type SplitDiffRow } from "./diff-inline.ts";
|
|
@@ -27,6 +28,8 @@ export interface RenderedRow {
|
|
|
27
28
|
hunkIndex: number | null;
|
|
28
29
|
}
|
|
29
30
|
|
|
31
|
+
type MaterializedDiffEntry = DiffLineEntry | DiffMetaEntry;
|
|
32
|
+
|
|
30
33
|
interface LineCellRenderParams {
|
|
31
34
|
kind: DiffLineKind;
|
|
32
35
|
code: string;
|
|
@@ -51,6 +54,7 @@ export interface DiffRenderContext {
|
|
|
51
54
|
}
|
|
52
55
|
|
|
53
56
|
const SPLIT_SEPARATOR = " │ ";
|
|
57
|
+
const OMISSION_GLYPH = "⋮";
|
|
54
58
|
const MIN_SPLIT_COLUMN_WIDTH = 24;
|
|
55
59
|
|
|
56
60
|
function resolveIndicatorGlyph(
|
|
@@ -364,6 +368,29 @@ function renderLineCell(
|
|
|
364
368
|
);
|
|
365
369
|
}
|
|
366
370
|
|
|
371
|
+
// Reuse line-cell layout without treating omissions as parsed source lines.
|
|
372
|
+
function createOmissionRenderLine(entry: DiffOmissionEntry, compact = false): DiffLineEntry {
|
|
373
|
+
return {
|
|
374
|
+
kind: "line",
|
|
375
|
+
lineKind: "context",
|
|
376
|
+
oldLineNumber: null,
|
|
377
|
+
newLineNumber: null,
|
|
378
|
+
fallbackLineNumber: compact ? "" : OMISSION_GLYPH,
|
|
379
|
+
content: compact ? OMISSION_GLYPH : "",
|
|
380
|
+
raw: entry.raw,
|
|
381
|
+
hunkIndex: entry.hunkIndex,
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function materializeOmissionEntries(
|
|
386
|
+
entries: ParsedDiffEntry[],
|
|
387
|
+
compact = false,
|
|
388
|
+
): MaterializedDiffEntry[] {
|
|
389
|
+
return entries.map((entry) =>
|
|
390
|
+
entry.kind === "omission" ? createOmissionRenderLine(entry, compact) : entry,
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
|
|
367
394
|
function pushDiffLineRows(rows: RenderedRow[], lines: string[], entry: DiffLineEntry): void {
|
|
368
395
|
rows.push(
|
|
369
396
|
...lines.map((text) => ({
|
|
@@ -374,7 +401,7 @@ function pushDiffLineRows(rows: RenderedRow[], lines: string[], entry: DiffLineE
|
|
|
374
401
|
}
|
|
375
402
|
|
|
376
403
|
function processDiffEntries(
|
|
377
|
-
entries:
|
|
404
|
+
entries: MaterializedDiffEntry[],
|
|
378
405
|
ctx: DiffRenderContext,
|
|
379
406
|
processLine: (entry: DiffLineEntry) => string[],
|
|
380
407
|
): RenderedRow[] {
|
|
@@ -395,7 +422,7 @@ export function renderUnified(
|
|
|
395
422
|
ctx: DiffRenderContext,
|
|
396
423
|
lineNumberWidth: number,
|
|
397
424
|
): RenderedRow[] {
|
|
398
|
-
return processDiffEntries(entries, ctx, (entry) => {
|
|
425
|
+
return processDiffEntries(materializeOmissionEntries(entries), ctx, (entry) => {
|
|
399
426
|
const lineNumber =
|
|
400
427
|
entry.lineKind === "add"
|
|
401
428
|
? formatLineNumberLabel(
|
|
@@ -445,6 +472,10 @@ function toUnifiedFallbackRows(
|
|
|
445
472
|
): RenderedRow[] {
|
|
446
473
|
const flattened: ParsedDiffEntry[] = [];
|
|
447
474
|
for (const row of rows) {
|
|
475
|
+
if (row.omission) {
|
|
476
|
+
flattened.push(row.omission);
|
|
477
|
+
continue;
|
|
478
|
+
}
|
|
448
479
|
if (row.meta) {
|
|
449
480
|
flattened.push(row.meta);
|
|
450
481
|
continue;
|
|
@@ -460,7 +491,7 @@ function toUnifiedFallbackRows(
|
|
|
460
491
|
}
|
|
461
492
|
|
|
462
493
|
export function renderCompact(entries: ParsedDiffEntry[], ctx: DiffRenderContext): RenderedRow[] {
|
|
463
|
-
return processDiffEntries(entries, ctx, (entry) => {
|
|
494
|
+
return processDiffEntries(materializeOmissionEntries(entries, true), ctx, (entry) => {
|
|
464
495
|
const codeText = normalizeCodeWhitespace(
|
|
465
496
|
getCompactLineRenderContent(entry, ctx.showHashlineAnchors),
|
|
466
497
|
);
|
|
@@ -679,8 +710,9 @@ export function renderSplit(
|
|
|
679
710
|
continue;
|
|
680
711
|
}
|
|
681
712
|
|
|
713
|
+
const omissionLine = row.omission ? createOmissionRenderLine(row.omission) : undefined;
|
|
682
714
|
const leftCells = renderSplitCell(
|
|
683
|
-
row.left,
|
|
715
|
+
omissionLine ?? row.left,
|
|
684
716
|
"left",
|
|
685
717
|
leftWidth,
|
|
686
718
|
splitLineNumberWidth,
|
|
@@ -694,7 +726,7 @@ export function renderSplit(
|
|
|
694
726
|
showHashlineAnchors,
|
|
695
727
|
);
|
|
696
728
|
const rightCells = renderSplitCell(
|
|
697
|
-
row.right,
|
|
729
|
+
omissionLine ?? row.right,
|
|
698
730
|
"right",
|
|
699
731
|
rightWidth,
|
|
700
732
|
splitLineNumberWidth,
|
|
@@ -2,7 +2,7 @@ import { visibleWidth } from "@earendil-works/pi-tui";
|
|
|
2
2
|
import { fitToWidth } from "./diff-text.ts";
|
|
3
3
|
|
|
4
4
|
export type DiffLineKind = "add" | "remove" | "context";
|
|
5
|
-
export type DiffEntryKind = "line" | "meta" | "hunk" | "file";
|
|
5
|
+
export type DiffEntryKind = "line" | "omission" | "meta" | "hunk" | "file";
|
|
6
6
|
|
|
7
7
|
export interface DiffLineEntry {
|
|
8
8
|
kind: "line";
|
|
@@ -16,13 +16,19 @@ export interface DiffLineEntry {
|
|
|
16
16
|
hunkIndex: number;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
export interface DiffOmissionEntry {
|
|
20
|
+
kind: "omission";
|
|
21
|
+
raw: string;
|
|
22
|
+
hunkIndex: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
19
25
|
export interface DiffMetaEntry {
|
|
20
|
-
kind: Exclude<DiffEntryKind, "line">;
|
|
26
|
+
kind: Exclude<DiffEntryKind, "line" | "omission">;
|
|
21
27
|
raw: string;
|
|
22
28
|
hunkIndex: number;
|
|
23
29
|
}
|
|
24
30
|
|
|
25
|
-
export type ParsedDiffEntry = DiffLineEntry | DiffMetaEntry;
|
|
31
|
+
export type ParsedDiffEntry = DiffLineEntry | DiffOmissionEntry | DiffMetaEntry;
|
|
26
32
|
|
|
27
33
|
export interface ParsedDiff {
|
|
28
34
|
entries: ParsedDiffEntry[];
|
|
@@ -163,6 +169,14 @@ function pushParsedLineEntry(
|
|
|
163
169
|
});
|
|
164
170
|
}
|
|
165
171
|
|
|
172
|
+
function createOmissionEntry(raw: string, hunkIndex: number): DiffOmissionEntry {
|
|
173
|
+
return {
|
|
174
|
+
kind: "omission",
|
|
175
|
+
raw,
|
|
176
|
+
hunkIndex,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
166
180
|
function createMetaEntry(raw: string, hunkIndex: number): DiffMetaEntry {
|
|
167
181
|
return {
|
|
168
182
|
kind: classifyMetaLine(raw),
|
|
@@ -228,7 +242,7 @@ export function parseDiff(diffText: string): ParsedDiff {
|
|
|
228
242
|
|
|
229
243
|
// Pi pads omitted context with a blank line number; it is not a source row.
|
|
230
244
|
if (!hasHunkHeader && PI_OMISSION_LINE_PATTERN.test(rawLine)) {
|
|
231
|
-
entries.push(
|
|
245
|
+
entries.push(createOmissionEntry(rawLine, hunkIndex));
|
|
232
246
|
continue;
|
|
233
247
|
}
|
|
234
248
|
|
|
@@ -173,6 +173,15 @@ type SettledGroupCache = {
|
|
|
173
173
|
lines: string[];
|
|
174
174
|
};
|
|
175
175
|
|
|
176
|
+
type ExpandedGroupCache = {
|
|
177
|
+
width: number;
|
|
178
|
+
hover: boolean;
|
|
179
|
+
theme: unknown;
|
|
180
|
+
fullscreen: boolean;
|
|
181
|
+
paints: readonly unknown[];
|
|
182
|
+
lines: string[];
|
|
183
|
+
};
|
|
184
|
+
|
|
176
185
|
export class ToolGroupComponent extends Container {
|
|
177
186
|
readonly toolCallId = `ccstyle-tool-group-${nextGroupId++}`;
|
|
178
187
|
readonly toolName = "Tool group";
|
|
@@ -183,8 +192,10 @@ export class ToolGroupComponent extends Container {
|
|
|
183
192
|
}
|
|
184
193
|
private hintHovered = false;
|
|
185
194
|
private readonly patch: Patch;
|
|
186
|
-
/** 仅缓存已完成且折叠的分组;pending
|
|
195
|
+
/** 仅缓存已完成且折叠的分组;pending 每帧现算。 */
|
|
187
196
|
private settledCache: SettledGroupCache | undefined;
|
|
197
|
+
/** 展开分组:子工具 paint 引用未变则复用整卡行。 */
|
|
198
|
+
private expandedPaintCache: ExpandedGroupCache | undefined;
|
|
188
199
|
|
|
189
200
|
constructor(patch: Patch) {
|
|
190
201
|
super();
|
|
@@ -192,14 +203,19 @@ export class ToolGroupComponent extends Container {
|
|
|
192
203
|
patch.groups.add(this);
|
|
193
204
|
}
|
|
194
205
|
|
|
195
|
-
|
|
206
|
+
private clearPaintCache(): void {
|
|
196
207
|
this.settledCache = undefined;
|
|
208
|
+
this.expandedPaintCache = undefined;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
addTool(tool: any): void {
|
|
212
|
+
this.clearPaintCache();
|
|
197
213
|
this.children.push(tool);
|
|
198
214
|
tool[PARENT_KEY] = this;
|
|
199
215
|
}
|
|
200
216
|
|
|
201
217
|
releaseTools(): any[] {
|
|
202
|
-
this.
|
|
218
|
+
this.clearPaintCache();
|
|
203
219
|
const tools = [...this.children];
|
|
204
220
|
this.children.length = 0;
|
|
205
221
|
this.patch.groups.delete(this);
|
|
@@ -207,21 +223,21 @@ export class ToolGroupComponent extends Container {
|
|
|
207
223
|
}
|
|
208
224
|
|
|
209
225
|
removeTool(tool: any): void {
|
|
210
|
-
this.
|
|
226
|
+
this.clearPaintCache();
|
|
211
227
|
const index = this.children.indexOf(tool);
|
|
212
228
|
if (index >= 0) this.children.splice(index, 1);
|
|
213
229
|
if (tool?.[PARENT_KEY] === this) delete tool[PARENT_KEY];
|
|
214
230
|
}
|
|
215
231
|
|
|
216
232
|
setExpanded(expanded: boolean): void {
|
|
217
|
-
if (this._expanded !== expanded) this.
|
|
233
|
+
if (this._expanded !== expanded) this.clearPaintCache();
|
|
218
234
|
this._expanded = expanded;
|
|
219
235
|
for (const tool of this.children)
|
|
220
236
|
(tool as Component & { setExpanded?: (expanded: boolean) => void }).setExpanded?.(expanded);
|
|
221
237
|
}
|
|
222
238
|
|
|
223
239
|
setHintHovered(hovered: boolean): void {
|
|
224
|
-
if (this.hintHovered !== hovered) this.
|
|
240
|
+
if (this.hintHovered !== hovered) this.clearPaintCache();
|
|
225
241
|
this.hintHovered = hovered;
|
|
226
242
|
}
|
|
227
243
|
|
|
@@ -250,7 +266,7 @@ export class ToolGroupComponent extends Container {
|
|
|
250
266
|
}
|
|
251
267
|
|
|
252
268
|
invalidate(): void {
|
|
253
|
-
this.
|
|
269
|
+
this.clearPaintCache();
|
|
254
270
|
for (const tool of this.children) tool.invalidate?.();
|
|
255
271
|
}
|
|
256
272
|
|
|
@@ -330,6 +346,23 @@ export class ToolGroupComponent extends Container {
|
|
|
330
346
|
),
|
|
331
347
|
];
|
|
332
348
|
const total = this.children.length;
|
|
349
|
+
const childPaints = this._expanded
|
|
350
|
+
? (this.children as any[]).map((tool) => tool.render?.(Math.max(1, width - 2)))
|
|
351
|
+
: undefined;
|
|
352
|
+
if (this._expanded && childPaints) {
|
|
353
|
+
const expandedHit = this.expandedPaintCache;
|
|
354
|
+
if (
|
|
355
|
+
expandedHit &&
|
|
356
|
+
expandedHit.width === width &&
|
|
357
|
+
expandedHit.hover === this.hintHovered &&
|
|
358
|
+
expandedHit.theme === this.patch.theme &&
|
|
359
|
+
expandedHit.fullscreen === isToolTuiFullscreen() &&
|
|
360
|
+
expandedHit.paints.length === childPaints.length &&
|
|
361
|
+
expandedHit.paints.every((paint, index) => paint === childPaints[index])
|
|
362
|
+
) {
|
|
363
|
+
return expandedHit.lines;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
333
366
|
const expandedLines: string[] = [];
|
|
334
367
|
for (let index = 0; index < total; index++) {
|
|
335
368
|
const tool = this.children[index];
|
|
@@ -351,7 +384,7 @@ export class ToolGroupComponent extends Container {
|
|
|
351
384
|
);
|
|
352
385
|
continue;
|
|
353
386
|
}
|
|
354
|
-
const rendered = visibleLines(
|
|
387
|
+
const rendered = visibleLines(Array.isArray(childPaints?.[index]) ? childPaints[index] : []);
|
|
355
388
|
if (rendered.length) {
|
|
356
389
|
rendered[0] = stripLeadingStatusIcon(rendered[0])
|
|
357
390
|
.replace(/^ +/, "")
|
|
@@ -376,6 +409,14 @@ export class ToolGroupComponent extends Container {
|
|
|
376
409
|
lines.push(paddedBackgroundRow(theme, backgroundSlot, line, width));
|
|
377
410
|
}
|
|
378
411
|
lines.push(paddedBackgroundRow(theme, backgroundSlot, "", width));
|
|
412
|
+
this.expandedPaintCache = {
|
|
413
|
+
width,
|
|
414
|
+
hover: this.hintHovered,
|
|
415
|
+
theme: this.patch.theme,
|
|
416
|
+
fullscreen: isToolTuiFullscreen(),
|
|
417
|
+
paints: childPaints ?? [],
|
|
418
|
+
lines,
|
|
419
|
+
};
|
|
379
420
|
} else if (counts.pending === 0) {
|
|
380
421
|
this.storeSettledCache(width, lines);
|
|
381
422
|
}
|
|
@@ -15,9 +15,9 @@ export function toolViewportWidth(width: number): number {
|
|
|
15
15
|
export function insetComponent(component: any): any {
|
|
16
16
|
return {
|
|
17
17
|
render: (width: number) =>
|
|
18
|
-
component.render(Math.max(1, width -
|
|
18
|
+
component.render(Math.max(1, width - 3)).map((line: string) => {
|
|
19
19
|
const nestedMarker = line.replace(/^((?:\x1b\[[0-?]*[ -/]*[@-~])*)↳/, "$1 ↳");
|
|
20
|
-
return ` ${nestedMarker}
|
|
20
|
+
return truncateToWidth(` ${nestedMarker}`, Math.max(0, width), "");
|
|
21
21
|
}),
|
|
22
22
|
invalidate: () => component.invalidate?.(),
|
|
23
23
|
};
|
|
@@ -309,6 +309,10 @@ export class ExpandedToolIoView {
|
|
|
309
309
|
this.invalidate();
|
|
310
310
|
}
|
|
311
311
|
|
|
312
|
+
getHoveredSection(): ToolIoSection | null {
|
|
313
|
+
return this.hoveredSection;
|
|
314
|
+
}
|
|
315
|
+
|
|
312
316
|
/** True when the plain truncation footer carries show-more. Input 续行带 │,Output 不带。 */
|
|
313
317
|
matchShowMoreLine(plainLine: string): ToolIoSection | null {
|
|
314
318
|
const line = plainLine.replace(/\x1b\[[0-9;]*m/g, "");
|
|
@@ -42,6 +42,7 @@ export const TOOL_GROUPING_GENERATION_KEY = Symbol.for("pi.ccstyle.tool-grouping
|
|
|
42
42
|
export const TOOL_MOUSE_OWNER_KEY = Symbol.for("pi.ccstyle.tool-mouse-owner");
|
|
43
43
|
export const TOOL_MOUSE_TUI_SLOT = Symbol.for("pi.ccstyle.tool-mouse-tui");
|
|
44
44
|
export const SCROLL_BUTTON_STATE_SLOT = Symbol.for("pi.ccstyle.scroll-button-state");
|
|
45
|
+
export const OFFICIAL_SCROLL_TO_END_KEY = Symbol.for("pi.ccstyle.official-scroll-to-end");
|
|
45
46
|
export const FLUSH_DOCKED_BASH_PATCH = Symbol.for("pi.ccstyle.flush-docked-bash-patch");
|
|
46
47
|
export const TOOL_HOVER_STATE_KEY = Symbol.for("pi.ccstyle.tool-hover-state");
|
|
47
48
|
|
|
@@ -59,6 +59,11 @@ export function isSgrLeftRelease(packet: SgrMousePacket): boolean {
|
|
|
59
59
|
return packet.final === "m" && baseButton === 0 && (packet.code & 32) === 0;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
/** 无按键按住的指针移动(DECSET 1003 的 35)。左键拖动(32,文本选区)不是 hover。 */
|
|
63
|
+
export function isSgrIdleMotion(packet: SgrMousePacket): boolean {
|
|
64
|
+
return packet.final === "M" && (packet.code & 32) !== 0 && mouseBaseButton(packet.code) === 3;
|
|
65
|
+
}
|
|
66
|
+
|
|
62
67
|
/** 剥离修饰键位(4/8/16/32:shift/meta/ctrl/motion),得到基础按键码。 */
|
|
63
68
|
export function mouseBaseButton(code: number): number {
|
|
64
69
|
return code & ~(4 | 8 | 16 | 32);
|