@herbertgao/pi-extensions 2026.9.18 → 2026.9.20
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 +20 -28
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +76 -59
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +76 -59
- package/node_modules/@herbertgao/pi-cc-extensions/assets/readme/preview.webp +0 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +93 -34
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +346 -117
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/agent-summary/core.ts +6 -36
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/agent-summary/index.ts +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +24 -29
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/context.ts +330 -360
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/reference/index.ts +56 -108
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/reference/session.ts +97 -15
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/reference/subagent.ts +80 -45
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/flush-docked-bash.ts +36 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/footer-layout.ts +229 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/footer.ts +479 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/startup-header.ts +92 -61
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/working-message.ts +75 -40
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/index.ts +19 -3
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +1080 -229
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +232 -259
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/index.ts +42 -41
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/markdown-enhance.ts +8 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/hover.ts +2 -7
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +277 -113
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/layout.ts +15 -6
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/packets.ts +18 -72
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/scroll.ts +61 -26
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/ansi-utils.ts +25 -57
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-component.ts +125 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-edit-render.ts +227 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-header.ts +136 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-highlight.ts +115 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-inline.ts +324 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-layout.ts +762 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-limits.ts +152 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-palette.ts +410 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-parse.ts +535 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-presentation.ts +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-renderer.ts +21 -3219
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-text.ts +44 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-write-render.ts +568 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/index.ts +47 -10
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/line-width-safety.ts +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/write-execution.ts +1 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +145 -190
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/mcp-title.ts +39 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/{message-display.ts → tool/message-display.ts} +24 -90
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/names.ts +426 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/result.ts +90 -132
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/{show-more-hint.ts → tool/show-more-hint.ts} +10 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/transcript-refresh.ts +8 -26
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/ansi-color.ts +44 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/ansi-text.ts +76 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/component-tree.ts +34 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/format.ts +28 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/fullscreen-detect.ts +51 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/patch-keys.ts +124 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/sgr-mouse.ts +70 -0
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +8 -8
- package/node_modules/pi-antigravity/README.md +29 -6
- package/node_modules/pi-antigravity/package.json +12 -7
- package/node_modules/pi-antigravity/src/auth/accounts.ts +327 -0
- package/node_modules/pi-antigravity/src/auth/index.ts +9 -0
- package/node_modules/pi-antigravity/src/client/client.ts +1 -1
- package/node_modules/pi-antigravity/src/image/image.ts +4 -4
- package/node_modules/pi-antigravity/src/index.ts +203 -13
- package/node_modules/pi-antigravity/src/search/index.ts +1 -0
- package/node_modules/pi-antigravity/src/search/search.ts +325 -0
- package/node_modules/pi-antigravity/src/stream/stream.ts +191 -14
- package/node_modules/pi-antigravity/src/types/enums.ts +45 -36
- package/node_modules/pi-antigravity/src/types/types.ts +5 -12
- package/node_modules/pi-antigravity/src/utils/http.ts +7 -2
- package/node_modules/pi-antigravity/src/utils/util.ts +31 -3
- package/node_modules/pi-typesafe/README.md +2 -0
- package/node_modules/pi-typesafe/package.json +2 -1
- package/node_modules/pi-web-access/CHANGELOG.md +72 -0
- package/node_modules/pi-web-access/README.md +34 -13
- package/node_modules/pi-web-access/chrome-cookies.ts +79 -25
- package/node_modules/pi-web-access/curator-page.ts +5 -1
- package/node_modules/pi-web-access/curator-server.ts +2 -0
- package/node_modules/pi-web-access/dist/index.js +1337 -1009
- package/node_modules/pi-web-access/exa.ts +20 -52
- package/node_modules/pi-web-access/extract.ts +41 -4
- package/node_modules/pi-web-access/gemini-search.ts +12 -4
- package/node_modules/pi-web-access/index.ts +29 -37
- package/node_modules/pi-web-access/openai-search.ts +35 -7
- package/node_modules/pi-web-access/package.json +6 -5
- package/node_modules/pi-web-access/page-query.ts +2 -2
- package/node_modules/pi-web-access/query-rewrite.ts +2 -3
- package/node_modules/pi-web-access/ssrf-protection.ts +27 -10
- package/node_modules/pi-web-access/storage.ts +48 -77
- package/node_modules/pi-web-access/summary-model-scope.ts +6 -77
- package/node_modules/pi-web-access/summary-review.ts +3 -4
- package/node_modules/pi-web-access/tavily.ts +67 -20
- package/node_modules/pi-web-access/tool-activation.ts +53 -33
- package/node_modules/pi-web-access/utils.ts +1 -1
- package/node_modules/pi-web-access/you.ts +164 -0
- package/node_modules/pi-web-access/zai.ts +161 -0
- package/package.json +7 -11
- package/node_modules/@herbertgao/pi-cc-extensions/assets/readme/hero.en.svg +0 -101
- package/node_modules/@herbertgao/pi-cc-extensions/assets/readme/hero.svg +0 -101
- package/node_modules/@herbertgao/pi-cc-extensions/assets/readme/welcome.webp +0 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/render-utils.ts +0 -5
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/types.ts +0 -30
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/write-display-utils.ts +0 -12
- package/node_modules/pi-footer/CHANGELOG.md +0 -145
- package/node_modules/pi-footer/LICENSE +0 -21
- package/node_modules/pi-footer/README.md +0 -354
- package/node_modules/pi-footer/package.json +0 -68
- package/node_modules/pi-footer/src/cache.ts +0 -106
- package/node_modules/pi-footer/src/colors.ts +0 -237
- package/node_modules/pi-footer/src/config.ts +0 -202
- package/node_modules/pi-footer/src/event-widgets.ts +0 -41
- package/node_modules/pi-footer/src/extension-statuses.ts +0 -102
- package/node_modules/pi-footer/src/git.ts +0 -135
- package/node_modules/pi-footer/src/index.ts +0 -268
- package/node_modules/pi-footer/src/metrics.ts +0 -141
- package/node_modules/pi-footer/src/presets.ts +0 -383
- package/node_modules/pi-footer/src/render.ts +0 -114
- package/node_modules/pi-footer/src/separators.ts +0 -52
- package/node_modules/pi-footer/src/types.ts +0 -122
- package/node_modules/pi-footer/src/ui/color-level-confirm.ts +0 -22
- package/node_modules/pi-footer/src/ui/color-options.ts +0 -195
- package/node_modules/pi-footer/src/ui/config-lifecycle.ts +0 -61
- package/node_modules/pi-footer/src/ui/edit-colors.ts +0 -13
- package/node_modules/pi-footer/src/ui/events.ts +0 -35
- package/node_modules/pi-footer/src/ui/extension-status-picker.ts +0 -60
- package/node_modules/pi-footer/src/ui/extension-statuses.ts +0 -73
- package/node_modules/pi-footer/src/ui/fields.ts +0 -252
- package/node_modules/pi-footer/src/ui/global-menu.ts +0 -126
- package/node_modules/pi-footer/src/ui/helpers.ts +0 -44
- package/node_modules/pi-footer/src/ui/layout.ts +0 -17
- package/node_modules/pi-footer/src/ui/line-list.ts +0 -19
- package/node_modules/pi-footer/src/ui/model.ts +0 -64
- package/node_modules/pi-footer/src/ui/navigation.ts +0 -29
- package/node_modules/pi-footer/src/ui/option-edit.ts +0 -93
- package/node_modules/pi-footer/src/ui/overlay-render.ts +0 -103
- package/node_modules/pi-footer/src/ui/screen-context.ts +0 -18
- package/node_modules/pi-footer/src/ui/screen-controller.ts +0 -27
- package/node_modules/pi-footer/src/ui/screen-render.ts +0 -32
- package/node_modules/pi-footer/src/ui/screen-state.ts +0 -20
- package/node_modules/pi-footer/src/ui/screen.ts +0 -243
- package/node_modules/pi-footer/src/ui/screens/add-widget.ts +0 -92
- package/node_modules/pi-footer/src/ui/screens/confirm-exit.ts +0 -52
- package/node_modules/pi-footer/src/ui/screens/controller.ts +0 -12
- package/node_modules/pi-footer/src/ui/screens/edit-colors.ts +0 -68
- package/node_modules/pi-footer/src/ui/screens/edit-widget.ts +0 -115
- package/node_modules/pi-footer/src/ui/screens/extension-status-row.ts +0 -61
- package/node_modules/pi-footer/src/ui/screens/global.ts +0 -75
- package/node_modules/pi-footer/src/ui/screens/line-list.ts +0 -108
- package/node_modules/pi-footer/src/ui/screens/main.ts +0 -99
- package/node_modules/pi-footer/src/ui/screens/terminal.ts +0 -121
- package/node_modules/pi-footer/src/ui/screens/widget-list.ts +0 -154
- package/node_modules/pi-footer/src/ui/terminal-menu.ts +0 -32
- package/node_modules/pi-footer/src/ui/theme.ts +0 -48
- package/node_modules/pi-footer/src/ui/title-bar.ts +0 -100
- package/node_modules/pi-footer/src/ui/widget-actions.ts +0 -76
- package/node_modules/pi-footer/src/ui.ts +0 -66
- package/node_modules/pi-footer/src/widgets/context.ts +0 -28
- package/node_modules/pi-footer/src/widgets/core/active-tools.ts +0 -17
- package/node_modules/pi-footer/src/widgets/core/context-window.ts +0 -24
- package/node_modules/pi-footer/src/widgets/core/cwd-basename.ts +0 -19
- package/node_modules/pi-footer/src/widgets/core/cwd.ts +0 -108
- package/node_modules/pi-footer/src/widgets/core/event.ts +0 -34
- package/node_modules/pi-footer/src/widgets/core/external-status.ts +0 -102
- package/node_modules/pi-footer/src/widgets/core/model-provider.ts +0 -20
- package/node_modules/pi-footer/src/widgets/core/model.ts +0 -35
- package/node_modules/pi-footer/src/widgets/core/provider.ts +0 -17
- package/node_modules/pi-footer/src/widgets/core/session-name.ts +0 -17
- package/node_modules/pi-footer/src/widgets/core/text-verbosity.ts +0 -17
- package/node_modules/pi-footer/src/widgets/core/thinking-level.ts +0 -17
- package/node_modules/pi-footer/src/widgets/git/ahead-behind.ts +0 -17
- package/node_modules/pi-footer/src/widgets/git/branch.ts +0 -81
- package/node_modules/pi-footer/src/widgets/git/clean.ts +0 -19
- package/node_modules/pi-footer/src/widgets/git/deletions.ts +0 -17
- package/node_modules/pi-footer/src/widgets/git/diff.ts +0 -38
- package/node_modules/pi-footer/src/widgets/git/insertions.ts +0 -17
- package/node_modules/pi-footer/src/widgets/git/remote.ts +0 -17
- package/node_modules/pi-footer/src/widgets/git/root.ts +0 -17
- package/node_modules/pi-footer/src/widgets/git/sha.ts +0 -17
- package/node_modules/pi-footer/src/widgets/git/staged.ts +0 -17
- package/node_modules/pi-footer/src/widgets/git/status.ts +0 -19
- package/node_modules/pi-footer/src/widgets/git/unstaged.ts +0 -17
- package/node_modules/pi-footer/src/widgets/git/untracked.ts +0 -17
- package/node_modules/pi-footer/src/widgets/instance.ts +0 -118
- package/node_modules/pi-footer/src/widgets/layout/custom-text.ts +0 -18
- package/node_modules/pi-footer/src/widgets/layout/flex-separator.ts +0 -32
- package/node_modules/pi-footer/src/widgets/layout/separator.ts +0 -57
- package/node_modules/pi-footer/src/widgets/layout/spacer.ts +0 -31
- package/node_modules/pi-footer/src/widgets/options.ts +0 -122
- package/node_modules/pi-footer/src/widgets/project/runtime.ts +0 -361
- package/node_modules/pi-footer/src/widgets/registry.ts +0 -223
- package/node_modules/pi-footer/src/widgets/session/assistant-messages.ts +0 -17
- package/node_modules/pi-footer/src/widgets/session/compactions.ts +0 -17
- package/node_modules/pi-footer/src/widgets/session/elapsed.ts +0 -18
- package/node_modules/pi-footer/src/widgets/session/last-activity.ts +0 -18
- package/node_modules/pi-footer/src/widgets/session/messages.ts +0 -19
- package/node_modules/pi-footer/src/widgets/session/session-id.ts +0 -17
- package/node_modules/pi-footer/src/widgets/session/session-start.ts +0 -18
- package/node_modules/pi-footer/src/widgets/session/tool-results.ts +0 -17
- package/node_modules/pi-footer/src/widgets/session/total-messages.ts +0 -19
- package/node_modules/pi-footer/src/widgets/session/total-time.ts +0 -18
- package/node_modules/pi-footer/src/widgets/session/user-messages.ts +0 -17
- package/node_modules/pi-footer/src/widgets/store.ts +0 -26
- package/node_modules/pi-footer/src/widgets/tokens/cache-hit-rate.ts +0 -55
- package/node_modules/pi-footer/src/widgets/tokens/cache-read.ts +0 -18
- package/node_modules/pi-footer/src/widgets/tokens/cache-write.ts +0 -18
- package/node_modules/pi-footer/src/widgets/tokens/context-bar.ts +0 -108
- package/node_modules/pi-footer/src/widgets/tokens/context-length.ts +0 -26
- package/node_modules/pi-footer/src/widgets/tokens/context-remaining.ts +0 -29
- package/node_modules/pi-footer/src/widgets/tokens/context.ts +0 -29
- package/node_modules/pi-footer/src/widgets/tokens/cost.ts +0 -51
- package/node_modules/pi-footer/src/widgets/tokens/input-speed.ts +0 -25
- package/node_modules/pi-footer/src/widgets/tokens/input-tokens.ts +0 -18
- package/node_modules/pi-footer/src/widgets/tokens/output-speed.ts +0 -25
- package/node_modules/pi-footer/src/widgets/tokens/output-tokens.ts +0 -18
- package/node_modules/pi-footer/src/widgets/tokens/tokens.ts +0 -20
- package/node_modules/pi-footer/src/widgets/tokens/total-speed.ts +0 -25
- package/node_modules/pi-footer/src/widgets/tokens/total-tokens.ts +0 -18
- package/node_modules/pi-footer/src/widgets/types.ts +0 -181
- package/node_modules/pi-footer/src/widgets/utils/colors.ts +0 -11
- package/node_modules/pi-footer/src/widgets/utils/context.ts +0 -112
- package/node_modules/pi-footer/src/widgets/utils/session.ts +0 -19
- package/node_modules/pi-footer/src/widgets/utils/token-format.ts +0 -78
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
|
|
2
|
+
|
|
3
|
+
export function pluralize(count: number, singular: string, plural = `${singular}s`): string {
|
|
4
|
+
return count === 1 ? singular : plural;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function splitWriteContentLines(content: string): string[] {
|
|
8
|
+
if (!content) {
|
|
9
|
+
return [];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const lines = content.split(/\r\n|\n|\r/);
|
|
13
|
+
if (lines.length > 0 && lines[lines.length - 1] === "") {
|
|
14
|
+
lines.pop();
|
|
15
|
+
}
|
|
16
|
+
return lines;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function normalizeCodeWhitespace(text: string): string {
|
|
20
|
+
return text.replace(/\t/g, " ");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function fitToWidth(text: string, width: number): string {
|
|
24
|
+
const trimmed = truncateToWidth(text, width, "");
|
|
25
|
+
const gap = Math.max(0, width - visibleWidth(trimmed));
|
|
26
|
+
return gap > 0 ? `${trimmed}${" ".repeat(gap)}` : trimmed;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function wrapToWidth(text: string, width: number, wordWrap: boolean): string[] {
|
|
30
|
+
if (width <= 0) {
|
|
31
|
+
return [""];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!wordWrap) {
|
|
35
|
+
return [fitToWidth(text, width)];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const wrapped = wrapTextWithAnsi(text, width);
|
|
39
|
+
if (wrapped.length === 0) {
|
|
40
|
+
return [fitToWidth("", width)];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return wrapped.map((line) => fitToWidth(line, width));
|
|
44
|
+
}
|
package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-write-render.ts
ADDED
|
@@ -0,0 +1,568 @@
|
|
|
1
|
+
import { Text, truncateToWidth, visibleWidth, type Component } from "@earendil-works/pi-tui";
|
|
2
|
+
import { sanitizeToolResultText } from "../../../utils/tool-result-sanitize.ts";
|
|
3
|
+
import { showMoreHintText } from "../show-more-hint.ts";
|
|
4
|
+
import { splitWriteContentLines } from "./diff-text.ts";
|
|
5
|
+
import {
|
|
6
|
+
collectDiffStats,
|
|
7
|
+
getLineNumberWidth,
|
|
8
|
+
type DiffLineEntry,
|
|
9
|
+
type DiffStats,
|
|
10
|
+
type ParsedDiffEntry,
|
|
11
|
+
} from "./diff-parse.ts";
|
|
12
|
+
import {
|
|
13
|
+
buildInlineHighlightMap,
|
|
14
|
+
buildSplitRows,
|
|
15
|
+
type DiffSpan,
|
|
16
|
+
type SplitDiffRow,
|
|
17
|
+
} from "./diff-inline.ts";
|
|
18
|
+
import { resolveDiffPalette, type DiffTheme } from "./diff-palette.ts";
|
|
19
|
+
import {
|
|
20
|
+
createCodeLineHighlighter,
|
|
21
|
+
resolveLanguageFromPath,
|
|
22
|
+
type CodeLineHighlighter,
|
|
23
|
+
} from "./diff-highlight.ts";
|
|
24
|
+
import {
|
|
25
|
+
canRenderSplitLayout,
|
|
26
|
+
renderCompact,
|
|
27
|
+
renderSplit,
|
|
28
|
+
renderUnified,
|
|
29
|
+
type DiffRenderContext,
|
|
30
|
+
type RenderedRow,
|
|
31
|
+
} from "./diff-layout.ts";
|
|
32
|
+
import { renderDiffFrameLine, renderSingleDiffRow, renderWriteHeader } from "./diff-header.ts";
|
|
33
|
+
import {
|
|
34
|
+
applyLineLimit,
|
|
35
|
+
clampDiffLineToWidth,
|
|
36
|
+
clampDiffLinesToWidth,
|
|
37
|
+
resolveDiffDisplayLimit,
|
|
38
|
+
resolveDiffProcessBudget,
|
|
39
|
+
resolveWriteCollapsedLimit,
|
|
40
|
+
takeEntriesForLineBudget,
|
|
41
|
+
takeSplitRowsForBudget,
|
|
42
|
+
} from "./diff-limits.ts";
|
|
43
|
+
import {
|
|
44
|
+
RICH_DIFF_COMPONENT,
|
|
45
|
+
createDiffRenderCache,
|
|
46
|
+
displayConfigCacheKey,
|
|
47
|
+
resolveDiffIndicatorMode,
|
|
48
|
+
resolveLiveDisplayConfig,
|
|
49
|
+
normalizeCollapsedHintLine,
|
|
50
|
+
type DiffRenderOptions,
|
|
51
|
+
type DisplayConfigInput,
|
|
52
|
+
} from "./diff-component.ts";
|
|
53
|
+
import {
|
|
54
|
+
buildDiffSummaryText,
|
|
55
|
+
normalizeDiffRenderWidth,
|
|
56
|
+
resolveDiffPresentationMode,
|
|
57
|
+
type DiffPresentationMode,
|
|
58
|
+
} from "./diff-presentation.ts";
|
|
59
|
+
|
|
60
|
+
type WriteDiffOperationKind = "context" | "remove" | "add";
|
|
61
|
+
|
|
62
|
+
interface WriteDiffOperation {
|
|
63
|
+
kind: WriteDiffOperationKind;
|
|
64
|
+
content: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function buildWriteDiffOperations(oldLines: string[], newLines: string[]): WriteDiffOperation[] {
|
|
68
|
+
const oldLength = oldLines.length;
|
|
69
|
+
const newLength = newLines.length;
|
|
70
|
+
const table: number[][] = Array.from({ length: oldLength + 1 }, () =>
|
|
71
|
+
Array<number>(newLength + 1).fill(0),
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
for (let oldIndex = 1; oldIndex <= oldLength; oldIndex++) {
|
|
75
|
+
for (let newIndex = 1; newIndex <= newLength; newIndex++) {
|
|
76
|
+
if ((oldLines[oldIndex - 1] ?? "") === (newLines[newIndex - 1] ?? "")) {
|
|
77
|
+
table[oldIndex]![newIndex] = (table[oldIndex - 1]?.[newIndex - 1] ?? 0) + 1;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const top = table[oldIndex - 1]?.[newIndex] ?? 0;
|
|
81
|
+
const left = table[oldIndex]?.[newIndex - 1] ?? 0;
|
|
82
|
+
table[oldIndex]![newIndex] = Math.max(top, left);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const operations: WriteDiffOperation[] = [];
|
|
87
|
+
let oldCursor = oldLength;
|
|
88
|
+
let newCursor = newLength;
|
|
89
|
+
|
|
90
|
+
while (oldCursor > 0 || newCursor > 0) {
|
|
91
|
+
const oldLine = oldCursor > 0 ? (oldLines[oldCursor - 1] ?? "") : undefined;
|
|
92
|
+
const newLine = newCursor > 0 ? (newLines[newCursor - 1] ?? "") : undefined;
|
|
93
|
+
|
|
94
|
+
if (oldCursor > 0 && newCursor > 0 && oldLine === newLine) {
|
|
95
|
+
operations.push({ kind: "context", content: oldLine ?? "" });
|
|
96
|
+
oldCursor--;
|
|
97
|
+
newCursor--;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const top = oldCursor > 0 ? (table[oldCursor - 1]?.[newCursor] ?? 0) : -1;
|
|
102
|
+
const left = newCursor > 0 ? (table[oldCursor]?.[newCursor - 1] ?? 0) : -1;
|
|
103
|
+
|
|
104
|
+
if (newCursor > 0 && left >= top) {
|
|
105
|
+
operations.push({ kind: "add", content: newLine ?? "" });
|
|
106
|
+
newCursor--;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (oldCursor > 0) {
|
|
111
|
+
operations.push({ kind: "remove", content: oldLine ?? "" });
|
|
112
|
+
oldCursor--;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
operations.reverse();
|
|
117
|
+
return operations;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function buildWriteEntries(lines: string[]): ParsedDiffEntry[] {
|
|
121
|
+
return lines.map((line, index) => ({
|
|
122
|
+
kind: "line",
|
|
123
|
+
lineKind: "add",
|
|
124
|
+
oldLineNumber: null,
|
|
125
|
+
newLineNumber: index + 1,
|
|
126
|
+
fallbackLineNumber: `${index + 1}`,
|
|
127
|
+
content: line,
|
|
128
|
+
raw: `+${line}`,
|
|
129
|
+
hunkIndex: 1,
|
|
130
|
+
}));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function buildWriteOverwriteEntries(oldLines: string[], newLines: string[]): ParsedDiffEntry[] {
|
|
134
|
+
const operations = buildWriteDiffOperations(oldLines, newLines);
|
|
135
|
+
const entries: ParsedDiffEntry[] = [];
|
|
136
|
+
let oldLineNumber = 1;
|
|
137
|
+
let newLineNumber = 1;
|
|
138
|
+
|
|
139
|
+
for (const operation of operations) {
|
|
140
|
+
if (operation.kind === "context") {
|
|
141
|
+
entries.push({
|
|
142
|
+
kind: "line",
|
|
143
|
+
lineKind: "context",
|
|
144
|
+
oldLineNumber,
|
|
145
|
+
newLineNumber,
|
|
146
|
+
fallbackLineNumber: `${newLineNumber}`,
|
|
147
|
+
content: operation.content,
|
|
148
|
+
raw: ` ${operation.content}`,
|
|
149
|
+
hunkIndex: 1,
|
|
150
|
+
});
|
|
151
|
+
oldLineNumber++;
|
|
152
|
+
newLineNumber++;
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (operation.kind === "remove") {
|
|
157
|
+
entries.push({
|
|
158
|
+
kind: "line",
|
|
159
|
+
lineKind: "remove",
|
|
160
|
+
oldLineNumber,
|
|
161
|
+
newLineNumber: null,
|
|
162
|
+
fallbackLineNumber: `${oldLineNumber}`,
|
|
163
|
+
content: operation.content,
|
|
164
|
+
raw: `-${operation.content}`,
|
|
165
|
+
hunkIndex: 1,
|
|
166
|
+
});
|
|
167
|
+
oldLineNumber++;
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
entries.push({
|
|
172
|
+
kind: "line",
|
|
173
|
+
lineKind: "add",
|
|
174
|
+
oldLineNumber: null,
|
|
175
|
+
newLineNumber,
|
|
176
|
+
fallbackLineNumber: `${newLineNumber}`,
|
|
177
|
+
content: operation.content,
|
|
178
|
+
raw: `+${operation.content}`,
|
|
179
|
+
hunkIndex: 1,
|
|
180
|
+
});
|
|
181
|
+
newLineNumber++;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return entries;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
interface WriteDiffData {
|
|
188
|
+
entries: ParsedDiffEntry[];
|
|
189
|
+
splitRows: SplitDiffRow[];
|
|
190
|
+
inlineHighlights: WeakMap<DiffLineEntry, DiffSpan[]>;
|
|
191
|
+
lineNumberWidth: number;
|
|
192
|
+
stats: DiffStats;
|
|
193
|
+
hunkCount: number;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
interface WriteOverwriteGuard {
|
|
197
|
+
previousLineCount: number;
|
|
198
|
+
nextLineCount: number;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const MAX_WRITE_OVERWRITE_DIFF_LINES = 4000;
|
|
202
|
+
const MAX_WRITE_OVERWRITE_DIFF_MATRIX_CELLS = 1_000_000;
|
|
203
|
+
/** Collapsed overwrite previews refuse larger LCS matrices to avoid TUI freezes. */
|
|
204
|
+
const MAX_COLLAPSED_WRITE_OVERWRITE_DIFF_MATRIX_CELLS = 200_000;
|
|
205
|
+
|
|
206
|
+
function buildApproximateWriteStats(
|
|
207
|
+
lineCount: number,
|
|
208
|
+
previousLineCount: number,
|
|
209
|
+
hasComparablePrevious: boolean,
|
|
210
|
+
): DiffStats {
|
|
211
|
+
const removed = hasComparablePrevious ? previousLineCount : 0;
|
|
212
|
+
const added = lineCount;
|
|
213
|
+
const hasContent = lineCount > 0 || removed > 0;
|
|
214
|
+
return {
|
|
215
|
+
added,
|
|
216
|
+
removed,
|
|
217
|
+
context: 0,
|
|
218
|
+
hunks: hasContent ? 1 : 0,
|
|
219
|
+
files: 1,
|
|
220
|
+
lines: added + removed,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function buildWriteDiffData(entries: ParsedDiffEntry[]): WriteDiffData {
|
|
225
|
+
const splitRows = buildSplitRows(entries);
|
|
226
|
+
const inlineHighlights = buildInlineHighlightMap(splitRows);
|
|
227
|
+
const lineNumberWidth = getLineNumberWidth(entries);
|
|
228
|
+
const hunkCount = entries.length > 0 ? 1 : 0;
|
|
229
|
+
const stats = collectDiffStats(entries, hunkCount, 1);
|
|
230
|
+
return {
|
|
231
|
+
entries,
|
|
232
|
+
splitRows,
|
|
233
|
+
inlineHighlights,
|
|
234
|
+
lineNumberWidth,
|
|
235
|
+
stats,
|
|
236
|
+
hunkCount,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function resolveWriteOverwriteGuard(
|
|
241
|
+
previousLines: string[],
|
|
242
|
+
nextLines: string[],
|
|
243
|
+
expanded = true,
|
|
244
|
+
): WriteOverwriteGuard | undefined {
|
|
245
|
+
const previousLineCount = previousLines.length;
|
|
246
|
+
const nextLineCount = nextLines.length;
|
|
247
|
+
if (
|
|
248
|
+
previousLineCount > MAX_WRITE_OVERWRITE_DIFF_LINES ||
|
|
249
|
+
nextLineCount > MAX_WRITE_OVERWRITE_DIFF_LINES
|
|
250
|
+
) {
|
|
251
|
+
return { previousLineCount, nextLineCount };
|
|
252
|
+
}
|
|
253
|
+
if (previousLineCount === 0 || nextLineCount === 0) {
|
|
254
|
+
return undefined;
|
|
255
|
+
}
|
|
256
|
+
const cellLimit = expanded
|
|
257
|
+
? MAX_WRITE_OVERWRITE_DIFF_MATRIX_CELLS
|
|
258
|
+
: MAX_COLLAPSED_WRITE_OVERWRITE_DIFF_MATRIX_CELLS;
|
|
259
|
+
return previousLineCount * nextLineCount > cellLimit
|
|
260
|
+
? { previousLineCount, nextLineCount }
|
|
261
|
+
: undefined;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function buildWriteOverwriteGuardText(guard: WriteOverwriteGuard, width: number): string {
|
|
265
|
+
const safeWidth = normalizeDiffRenderWidth(width);
|
|
266
|
+
if (safeWidth === 0) {
|
|
267
|
+
return "";
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const candidates = [
|
|
271
|
+
`↳ overwrite diff omitted (${guard.previousLineCount} → ${guard.nextLineCount} lines)`,
|
|
272
|
+
`↳ overwrite diff omitted (${guard.previousLineCount}→${guard.nextLineCount})`,
|
|
273
|
+
"↳ overwrite diff omitted",
|
|
274
|
+
"diff omitted",
|
|
275
|
+
"…",
|
|
276
|
+
];
|
|
277
|
+
for (const candidate of candidates) {
|
|
278
|
+
if (visibleWidth(candidate) <= safeWidth) {
|
|
279
|
+
return candidate;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return truncateToWidth(candidates[candidates.length - 1] ?? "", safeWidth, "");
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function renderWriteOverwriteGuardRows(
|
|
286
|
+
guard: WriteOverwriteGuard,
|
|
287
|
+
width: number,
|
|
288
|
+
theme: DiffTheme,
|
|
289
|
+
): string[] {
|
|
290
|
+
return renderSingleDiffRow(buildWriteOverwriteGuardText(guard, width), "warning", width, theme);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/** Folded write with `writeDiffCollapsedLines: 0`: `↳ created • click to show more`. */
|
|
294
|
+
function renderWriteCollapsedHintLine(
|
|
295
|
+
wasOverwrite: boolean,
|
|
296
|
+
width: number,
|
|
297
|
+
theme: DiffTheme,
|
|
298
|
+
hovered: boolean,
|
|
299
|
+
headerLabel?: string,
|
|
300
|
+
): string[] {
|
|
301
|
+
const safeWidth = normalizeDiffRenderWidth(width);
|
|
302
|
+
if (safeWidth === 0) {
|
|
303
|
+
return [""];
|
|
304
|
+
}
|
|
305
|
+
const actionLabel = headerLabel?.trim() || (wasOverwrite ? "overwritten" : "created");
|
|
306
|
+
const clickLabel = showMoreHintText();
|
|
307
|
+
const candidates = [`↳ ${actionLabel} • ${clickLabel}`, `↳ ${actionLabel}`, actionLabel, "…"];
|
|
308
|
+
let text = candidates[candidates.length - 1] ?? "…";
|
|
309
|
+
for (const candidate of candidates) {
|
|
310
|
+
if (visibleWidth(candidate) <= safeWidth) {
|
|
311
|
+
text = candidate;
|
|
312
|
+
break;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
if (visibleWidth(text) > safeWidth) {
|
|
316
|
+
text = truncateToWidth(text, safeWidth, "");
|
|
317
|
+
}
|
|
318
|
+
const clickIndex = text.lastIndexOf(clickLabel);
|
|
319
|
+
const styled =
|
|
320
|
+
hovered && clickIndex >= 0
|
|
321
|
+
? theme.fg("muted", text.slice(0, clickIndex)) +
|
|
322
|
+
theme.fg("text", clickLabel) +
|
|
323
|
+
theme.fg("muted", text.slice(clickIndex + clickLabel.length))
|
|
324
|
+
: theme.fg("muted", text);
|
|
325
|
+
return [clampDiffLineToWidth(styled, safeWidth)];
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* write 变更行统计(compact 单行用):复用当前 write diff 算法。
|
|
330
|
+
* 新文件 = 全部新内容为 added;覆盖已有文件用 LCS 精确统计。
|
|
331
|
+
* content 非字符串、元数据缺失或超过矩阵预算时返回 undefined(不显示计数)。
|
|
332
|
+
*/
|
|
333
|
+
export function countWriteDiffStats(
|
|
334
|
+
content: string | undefined,
|
|
335
|
+
previousContent: string | undefined,
|
|
336
|
+
fileExistedBeforeWrite: boolean | undefined,
|
|
337
|
+
): { added: number; removed: number } | undefined {
|
|
338
|
+
if (typeof content !== "string" || fileExistedBeforeWrite === undefined) return undefined;
|
|
339
|
+
const lines = splitWriteContentLines(sanitizeToolResultText(content));
|
|
340
|
+
if (fileExistedBeforeWrite === false) return { added: lines.length, removed: 0 };
|
|
341
|
+
if (typeof previousContent !== "string") return undefined;
|
|
342
|
+
const previousLines = splitWriteContentLines(sanitizeToolResultText(previousContent));
|
|
343
|
+
if (previousLines.length === 0 || lines.length === 0) {
|
|
344
|
+
return { added: lines.length, removed: previousLines.length };
|
|
345
|
+
}
|
|
346
|
+
if (previousLines.length * lines.length > MAX_COLLAPSED_WRITE_OVERWRITE_DIFF_MATRIX_CELLS) {
|
|
347
|
+
return undefined;
|
|
348
|
+
}
|
|
349
|
+
let added = 0;
|
|
350
|
+
let removed = 0;
|
|
351
|
+
for (const operation of buildWriteDiffOperations(previousLines, lines)) {
|
|
352
|
+
if (operation.kind === "add") added++;
|
|
353
|
+
else if (operation.kind === "remove") removed++;
|
|
354
|
+
}
|
|
355
|
+
return { added, removed };
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
export function renderWriteDiffResult(
|
|
359
|
+
content: string | undefined,
|
|
360
|
+
options: DiffRenderOptions,
|
|
361
|
+
config: DisplayConfigInput,
|
|
362
|
+
theme: DiffTheme,
|
|
363
|
+
fallbackText: string,
|
|
364
|
+
): Component {
|
|
365
|
+
const safeFallbackText = sanitizeToolResultText(fallbackText);
|
|
366
|
+
if (typeof content !== "string") {
|
|
367
|
+
if (!safeFallbackText.trim()) {
|
|
368
|
+
return new Text(theme.fg("muted", "↳ write completed"), 0, 0);
|
|
369
|
+
}
|
|
370
|
+
return new Text(theme.fg("toolOutput", safeFallbackText), 0, 0);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const filePath = options.filePath?.trim() || "(unknown path)";
|
|
374
|
+
const lines = splitWriteContentLines(sanitizeToolResultText(content));
|
|
375
|
+
const previousLines =
|
|
376
|
+
typeof options.previousContent === "string"
|
|
377
|
+
? splitWriteContentLines(sanitizeToolResultText(options.previousContent))
|
|
378
|
+
: [];
|
|
379
|
+
const hasComparablePrevious =
|
|
380
|
+
options.fileExistedBeforeWrite === true && typeof options.previousContent === "string";
|
|
381
|
+
const approximateStats = buildApproximateWriteStats(
|
|
382
|
+
lines.length,
|
|
383
|
+
previousLines.length,
|
|
384
|
+
hasComparablePrevious,
|
|
385
|
+
);
|
|
386
|
+
const palette = resolveDiffPalette(theme);
|
|
387
|
+
// Keep the panel transparent; changed rows and inline spans retain their own highlights.
|
|
388
|
+
const containerBgAnsi = undefined;
|
|
389
|
+
const language = resolveLanguageFromPath(filePath);
|
|
390
|
+
const cache = createDiffRenderCache();
|
|
391
|
+
let highlightLine: CodeLineHighlighter | undefined;
|
|
392
|
+
let detailedData: WriteDiffData | undefined;
|
|
393
|
+
|
|
394
|
+
function getDetailedData(): WriteDiffData {
|
|
395
|
+
if (detailedData) {
|
|
396
|
+
return detailedData;
|
|
397
|
+
}
|
|
398
|
+
const entries = hasComparablePrevious
|
|
399
|
+
? buildWriteOverwriteEntries(previousLines, lines)
|
|
400
|
+
: buildWriteEntries(lines);
|
|
401
|
+
detailedData = buildWriteDiffData(entries);
|
|
402
|
+
highlightLine = createCodeLineHighlighter(language, theme, entries, () => {
|
|
403
|
+
cache.invalidate();
|
|
404
|
+
options.invalidate?.();
|
|
405
|
+
});
|
|
406
|
+
return detailedData;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
return {
|
|
410
|
+
[RICH_DIFF_COMPONENT]: true,
|
|
411
|
+
/** 鼠标层只在真正的 remainder 行上提供展开入口。 */
|
|
412
|
+
isCollapsedHintLine(plainLine: string): boolean {
|
|
413
|
+
const hint = cache.getHintLine();
|
|
414
|
+
return hint !== undefined && normalizeCollapsedHintLine(plainLine) === hint;
|
|
415
|
+
},
|
|
416
|
+
render(width: number): string[] {
|
|
417
|
+
// Live config: panel can change indicator/wrap/limits after this component is created.
|
|
418
|
+
const live = resolveLiveDisplayConfig(config);
|
|
419
|
+
const wordWrap = live.diffWordWrap;
|
|
420
|
+
const indicatorMode = resolveDiffIndicatorMode(live);
|
|
421
|
+
const configKey = displayConfigCacheKey(live);
|
|
422
|
+
const safeWidth = normalizeDiffRenderWidth(width);
|
|
423
|
+
const resolvedMode = resolveDiffPresentationMode(
|
|
424
|
+
live,
|
|
425
|
+
safeWidth,
|
|
426
|
+
canRenderSplitLayout(safeWidth),
|
|
427
|
+
);
|
|
428
|
+
const mode: DiffPresentationMode = hasComparablePrevious
|
|
429
|
+
? resolvedMode
|
|
430
|
+
: resolvedMode === "split"
|
|
431
|
+
? "unified"
|
|
432
|
+
: resolvedMode;
|
|
433
|
+
const hovered = options.isHovered?.() ?? false;
|
|
434
|
+
const cached = cache.get(safeWidth, options.expanded, mode, configKey, hovered);
|
|
435
|
+
if (cached) {
|
|
436
|
+
return cached;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const writeCollapsedLimit = resolveWriteCollapsedLimit(live);
|
|
440
|
+
const statsOnlyCollapsed = !options.expanded && writeCollapsedLimit === 0;
|
|
441
|
+
if (statsOnlyCollapsed) {
|
|
442
|
+
const hintLines = renderWriteCollapsedHintLine(
|
|
443
|
+
options.fileExistedBeforeWrite === true,
|
|
444
|
+
safeWidth,
|
|
445
|
+
theme,
|
|
446
|
+
hovered,
|
|
447
|
+
options.headerLabel,
|
|
448
|
+
);
|
|
449
|
+
return cache.set(
|
|
450
|
+
safeWidth,
|
|
451
|
+
options.expanded,
|
|
452
|
+
mode,
|
|
453
|
+
configKey,
|
|
454
|
+
hovered,
|
|
455
|
+
clampDiffLinesToWidth(hintLines, safeWidth),
|
|
456
|
+
normalizeCollapsedHintLine(hintLines[0]),
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const header = renderWriteHeader(
|
|
461
|
+
options.fileExistedBeforeWrite === true,
|
|
462
|
+
safeWidth,
|
|
463
|
+
theme,
|
|
464
|
+
options.headerLabel,
|
|
465
|
+
);
|
|
466
|
+
// Guard depends on expanded: collapsed previews use a stricter LCS cell budget.
|
|
467
|
+
const overwriteGuard = hasComparablePrevious
|
|
468
|
+
? resolveWriteOverwriteGuard(previousLines, lines, options.expanded)
|
|
469
|
+
: undefined;
|
|
470
|
+
if (overwriteGuard) {
|
|
471
|
+
return cache.set(
|
|
472
|
+
safeWidth,
|
|
473
|
+
options.expanded,
|
|
474
|
+
mode,
|
|
475
|
+
configKey,
|
|
476
|
+
hovered,
|
|
477
|
+
clampDiffLinesToWidth(
|
|
478
|
+
[header, ...renderWriteOverwriteGuardRows(overwriteGuard, safeWidth, theme)],
|
|
479
|
+
safeWidth,
|
|
480
|
+
),
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
if (mode === "summary") {
|
|
485
|
+
const summaryRows =
|
|
486
|
+
approximateStats.lines === 0
|
|
487
|
+
? [header]
|
|
488
|
+
: [
|
|
489
|
+
header,
|
|
490
|
+
...renderSingleDiffRow(
|
|
491
|
+
buildDiffSummaryText(approximateStats, safeWidth),
|
|
492
|
+
"toolOutput",
|
|
493
|
+
safeWidth,
|
|
494
|
+
theme,
|
|
495
|
+
),
|
|
496
|
+
];
|
|
497
|
+
return cache.set(
|
|
498
|
+
safeWidth,
|
|
499
|
+
options.expanded,
|
|
500
|
+
mode,
|
|
501
|
+
configKey,
|
|
502
|
+
hovered,
|
|
503
|
+
clampDiffLinesToWidth(summaryRows, safeWidth),
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
const data = getDetailedData();
|
|
508
|
+
const displayLimit = resolveDiffDisplayLimit(options.expanded, writeCollapsedLimit);
|
|
509
|
+
const processBudget = resolveDiffProcessBudget(displayLimit, wordWrap);
|
|
510
|
+
const entryBudget = takeEntriesForLineBudget(data.entries, processBudget);
|
|
511
|
+
const splitBudget = takeSplitRowsForBudget(data.splitRows, processBudget);
|
|
512
|
+
const inlineHighlights =
|
|
513
|
+
splitBudget.processedRows < splitBudget.totalRows
|
|
514
|
+
? buildInlineHighlightMap(splitBudget.rows)
|
|
515
|
+
: data.inlineHighlights;
|
|
516
|
+
const renderCtx: DiffRenderContext = {
|
|
517
|
+
width: safeWidth,
|
|
518
|
+
theme,
|
|
519
|
+
inlineHighlights,
|
|
520
|
+
palette,
|
|
521
|
+
highlightLine: highlightLine!,
|
|
522
|
+
containerBgAnsi,
|
|
523
|
+
wordWrap,
|
|
524
|
+
indicatorMode,
|
|
525
|
+
showHashlineAnchors: false,
|
|
526
|
+
};
|
|
527
|
+
const bodyRows: RenderedRow[] =
|
|
528
|
+
entryBudget.entries.length === 0
|
|
529
|
+
? [{ text: theme.fg("muted", "(empty file)"), hunkIndex: null }]
|
|
530
|
+
: mode === "split"
|
|
531
|
+
? renderSplit(splitBudget.rows, renderCtx, data.lineNumberWidth)
|
|
532
|
+
: mode === "compact"
|
|
533
|
+
? renderCompact(entryBudget.entries, renderCtx)
|
|
534
|
+
: renderUnified(entryBudget.entries, renderCtx, data.lineNumberWidth);
|
|
535
|
+
const unprocessedLogicalRows =
|
|
536
|
+
mode === "split"
|
|
537
|
+
? Math.max(0, splitBudget.totalRows - splitBudget.processedRows)
|
|
538
|
+
: Math.max(0, entryBudget.totalLineEntries - entryBudget.processedLineEntries);
|
|
539
|
+
|
|
540
|
+
const bodyWithLimit = applyLineLimit(
|
|
541
|
+
bodyRows,
|
|
542
|
+
safeWidth,
|
|
543
|
+
options.expanded,
|
|
544
|
+
writeCollapsedLimit,
|
|
545
|
+
data.hunkCount,
|
|
546
|
+
theme,
|
|
547
|
+
unprocessedLogicalRows,
|
|
548
|
+
hovered,
|
|
549
|
+
);
|
|
550
|
+
const frame = renderDiffFrameLine(safeWidth, theme);
|
|
551
|
+
const renderedLines =
|
|
552
|
+
mode === "unified"
|
|
553
|
+
? [header, frame, ...bodyWithLimit.lines, frame]
|
|
554
|
+
: [header, ...bodyWithLimit.lines];
|
|
555
|
+
const finalLines = clampDiffLinesToWidth(renderedLines, safeWidth);
|
|
556
|
+
return cache.set(
|
|
557
|
+
safeWidth,
|
|
558
|
+
options.expanded,
|
|
559
|
+
mode,
|
|
560
|
+
configKey,
|
|
561
|
+
hovered,
|
|
562
|
+
finalLines,
|
|
563
|
+
normalizeCollapsedHintLine(bodyWithLimit.hintLine),
|
|
564
|
+
);
|
|
565
|
+
},
|
|
566
|
+
invalidate: cache.invalidate,
|
|
567
|
+
} as Component;
|
|
568
|
+
}
|
|
@@ -5,7 +5,8 @@ import {
|
|
|
5
5
|
renderWriteDiffResult,
|
|
6
6
|
type DisplayConfigInput,
|
|
7
7
|
} from "./diff-renderer.ts";
|
|
8
|
-
import { DEFAULT_TOOL_DISPLAY_CONFIG } from "
|
|
8
|
+
import { DEFAULT_TOOL_DISPLAY_CONFIG } from "../../../config/config.ts";
|
|
9
|
+
import { patchRegistry, WRITE_OWNERSHIP_SLOT } from "../../../utils/patch-keys.ts";
|
|
9
10
|
import { executeWriteWithMetadata, WriteExecutionMetadataStore } from "./write-execution.ts";
|
|
10
11
|
|
|
11
12
|
function resultText(result: any): string {
|
|
@@ -59,6 +60,8 @@ export function renderRichToolResult(
|
|
|
59
60
|
);
|
|
60
61
|
}
|
|
61
62
|
if (toolName !== "write") return undefined;
|
|
63
|
+
// 让位点:write 归其他扩展时不提供富 diff,交回普通结果行。
|
|
64
|
+
if (!ownsWriteTool()) return undefined;
|
|
62
65
|
|
|
63
66
|
const metadata = writeMetadata.get(context?.toolCallId);
|
|
64
67
|
if (!metadata) {
|
|
@@ -83,23 +86,57 @@ export function renderRichToolResult(
|
|
|
83
86
|
);
|
|
84
87
|
}
|
|
85
88
|
|
|
86
|
-
|
|
89
|
+
/** write 被其他扩展占用时的来源,用于提示冲突。 */
|
|
90
|
+
export type ExternalWriteOwner = { source: string; path: string };
|
|
91
|
+
|
|
92
|
+
type WriteOwnershipState = {
|
|
93
|
+
/** write 是否由本插件执行;undefined 表示尚未确认,按拥有处理以保持既有行为。 */
|
|
94
|
+
owned?: boolean;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
function writeOwnership(): WriteOwnershipState {
|
|
98
|
+
return patchRegistry.ensure<WriteOwnershipState>(WRITE_OWNERSHIP_SLOT, () => ({}));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* write 是否由本插件执行。只有注册过 write override 才有执行元数据,
|
|
103
|
+
* 否则渲染层要放行给普通结果行,避免每张卡降级成 "diff unavailable"。
|
|
104
|
+
*/
|
|
105
|
+
export function ownsWriteTool(): boolean {
|
|
106
|
+
return writeOwnership().owned !== false;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** 当前占用 write 的其他扩展;builtin 或未注册时返回 undefined。 */
|
|
110
|
+
function findExternalWriteOwner(pi: ExtensionAPI): ExternalWriteOwner | undefined {
|
|
87
111
|
try {
|
|
88
|
-
const tools = pi.getAllTools();
|
|
89
|
-
const write = tools
|
|
90
|
-
const
|
|
91
|
-
|
|
112
|
+
const tools = pi.getAllTools() as any[];
|
|
113
|
+
const write = tools?.find((tool: any) => tool?.name === "write") as any;
|
|
114
|
+
const sourceInfo = write?.sourceInfo;
|
|
115
|
+
const source = sourceInfo?.source;
|
|
116
|
+
if (!write || typeof source !== "string" || source === "builtin") return undefined;
|
|
117
|
+
return { source, path: typeof sourceInfo?.path === "string" ? sourceInfo.path : "" };
|
|
92
118
|
} catch {
|
|
93
|
-
// getAllTools
|
|
94
|
-
return
|
|
119
|
+
// getAllTools 在扩展运行时绑定前不可用。
|
|
120
|
+
return undefined;
|
|
95
121
|
}
|
|
96
122
|
}
|
|
97
123
|
|
|
98
124
|
export function installWriteOverride(
|
|
99
125
|
pi: ExtensionAPI,
|
|
100
126
|
store = new WriteExecutionMetadataStore(),
|
|
127
|
+
/** write 已被其他扩展占用时回调一次,调用方据此提示冲突。 */
|
|
128
|
+
onExternalOwner?: (owner: ExternalWriteOwner) => void,
|
|
101
129
|
): WriteExecutionMetadataStore {
|
|
102
|
-
if (typeof (pi as any).registerTool !== "function"
|
|
130
|
+
if (typeof (pi as any).registerTool !== "function") return store;
|
|
131
|
+
const state = writeOwnership();
|
|
132
|
+
const external = findExternalWriteOwner(pi);
|
|
133
|
+
if (external) {
|
|
134
|
+
// 让位:执行与 diff 都归对方,渲染层据 owned=false 走普通结果行。
|
|
135
|
+
state.owned = false;
|
|
136
|
+
onExternalOwner?.(external);
|
|
137
|
+
return store;
|
|
138
|
+
}
|
|
139
|
+
state.owned = true;
|
|
103
140
|
const nativeWrite = createWriteToolDefinition(process.cwd()) as any;
|
|
104
141
|
pi.registerTool({
|
|
105
142
|
...nativeWrite,
|
|
@@ -121,6 +158,6 @@ export {
|
|
|
121
158
|
type ToolDisplayConfig,
|
|
122
159
|
type DiffViewMode,
|
|
123
160
|
type DiffIndicatorMode,
|
|
124
|
-
} from "
|
|
161
|
+
} from "../../../config/config.ts";
|
|
125
162
|
export type { DisplayConfigInput } from "./diff-renderer.ts";
|
|
126
163
|
export { WriteExecutionMetadataStore } from "./write-execution.ts";
|