@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.
Files changed (226) hide show
  1. package/README.md +20 -28
  2. package/THIRD_PARTY_NOTICES.md +1 -1
  3. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +76 -59
  4. package/node_modules/@herbertgao/pi-cc-extensions/README.md +76 -59
  5. package/node_modules/@herbertgao/pi-cc-extensions/assets/readme/preview.webp +0 -0
  6. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +93 -34
  7. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +346 -117
  8. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/agent-summary/core.ts +6 -36
  9. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/agent-summary/index.ts +2 -2
  10. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +24 -29
  11. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/context.ts +330 -360
  12. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/reference/index.ts +56 -108
  13. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/reference/session.ts +97 -15
  14. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/reference/subagent.ts +80 -45
  15. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/flush-docked-bash.ts +36 -0
  16. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/footer-layout.ts +229 -0
  17. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/footer.ts +479 -0
  18. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/startup-header.ts +92 -61
  19. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/working-message.ts +75 -40
  20. package/node_modules/@herbertgao/pi-cc-extensions/extensions/index.ts +19 -3
  21. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +1080 -229
  22. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +232 -259
  23. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/index.ts +42 -41
  24. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/markdown-enhance.ts +8 -2
  25. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/hover.ts +2 -7
  26. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +277 -113
  27. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/layout.ts +15 -6
  28. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/packets.ts +18 -72
  29. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/scroll.ts +61 -26
  30. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/ansi-utils.ts +25 -57
  31. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-component.ts +125 -0
  32. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-edit-render.ts +227 -0
  33. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-header.ts +136 -0
  34. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-highlight.ts +115 -0
  35. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-inline.ts +324 -0
  36. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-layout.ts +762 -0
  37. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-limits.ts +152 -0
  38. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-palette.ts +410 -0
  39. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-parse.ts +535 -0
  40. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-presentation.ts +2 -2
  41. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-renderer.ts +21 -3219
  42. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-text.ts +44 -0
  43. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-write-render.ts +568 -0
  44. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/index.ts +47 -10
  45. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/line-width-safety.ts +2 -2
  46. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/write-execution.ts +1 -0
  47. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +145 -190
  48. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/mcp-title.ts +39 -0
  49. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/{message-display.ts → tool/message-display.ts} +24 -90
  50. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/names.ts +426 -0
  51. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/result.ts +90 -132
  52. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/{show-more-hint.ts → tool/show-more-hint.ts} +10 -0
  53. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/transcript-refresh.ts +8 -26
  54. package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/ansi-color.ts +44 -0
  55. package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/ansi-text.ts +76 -0
  56. package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/component-tree.ts +34 -0
  57. package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/format.ts +28 -0
  58. package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/fullscreen-detect.ts +51 -0
  59. package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/patch-keys.ts +124 -0
  60. package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/sgr-mouse.ts +70 -0
  61. package/node_modules/@herbertgao/pi-cc-extensions/package.json +8 -8
  62. package/node_modules/pi-antigravity/README.md +29 -6
  63. package/node_modules/pi-antigravity/package.json +12 -7
  64. package/node_modules/pi-antigravity/src/auth/accounts.ts +327 -0
  65. package/node_modules/pi-antigravity/src/auth/index.ts +9 -0
  66. package/node_modules/pi-antigravity/src/client/client.ts +1 -1
  67. package/node_modules/pi-antigravity/src/image/image.ts +4 -4
  68. package/node_modules/pi-antigravity/src/index.ts +203 -13
  69. package/node_modules/pi-antigravity/src/search/index.ts +1 -0
  70. package/node_modules/pi-antigravity/src/search/search.ts +325 -0
  71. package/node_modules/pi-antigravity/src/stream/stream.ts +191 -14
  72. package/node_modules/pi-antigravity/src/types/enums.ts +45 -36
  73. package/node_modules/pi-antigravity/src/types/types.ts +5 -12
  74. package/node_modules/pi-antigravity/src/utils/http.ts +7 -2
  75. package/node_modules/pi-antigravity/src/utils/util.ts +31 -3
  76. package/node_modules/pi-typesafe/README.md +2 -0
  77. package/node_modules/pi-typesafe/package.json +2 -1
  78. package/node_modules/pi-web-access/CHANGELOG.md +72 -0
  79. package/node_modules/pi-web-access/README.md +34 -13
  80. package/node_modules/pi-web-access/chrome-cookies.ts +79 -25
  81. package/node_modules/pi-web-access/curator-page.ts +5 -1
  82. package/node_modules/pi-web-access/curator-server.ts +2 -0
  83. package/node_modules/pi-web-access/dist/index.js +1337 -1009
  84. package/node_modules/pi-web-access/exa.ts +20 -52
  85. package/node_modules/pi-web-access/extract.ts +41 -4
  86. package/node_modules/pi-web-access/gemini-search.ts +12 -4
  87. package/node_modules/pi-web-access/index.ts +29 -37
  88. package/node_modules/pi-web-access/openai-search.ts +35 -7
  89. package/node_modules/pi-web-access/package.json +6 -5
  90. package/node_modules/pi-web-access/page-query.ts +2 -2
  91. package/node_modules/pi-web-access/query-rewrite.ts +2 -3
  92. package/node_modules/pi-web-access/ssrf-protection.ts +27 -10
  93. package/node_modules/pi-web-access/storage.ts +48 -77
  94. package/node_modules/pi-web-access/summary-model-scope.ts +6 -77
  95. package/node_modules/pi-web-access/summary-review.ts +3 -4
  96. package/node_modules/pi-web-access/tavily.ts +67 -20
  97. package/node_modules/pi-web-access/tool-activation.ts +53 -33
  98. package/node_modules/pi-web-access/utils.ts +1 -1
  99. package/node_modules/pi-web-access/you.ts +164 -0
  100. package/node_modules/pi-web-access/zai.ts +161 -0
  101. package/package.json +7 -11
  102. package/node_modules/@herbertgao/pi-cc-extensions/assets/readme/hero.en.svg +0 -101
  103. package/node_modules/@herbertgao/pi-cc-extensions/assets/readme/hero.svg +0 -101
  104. package/node_modules/@herbertgao/pi-cc-extensions/assets/readme/welcome.webp +0 -0
  105. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/render-utils.ts +0 -5
  106. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/types.ts +0 -30
  107. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/write-display-utils.ts +0 -12
  108. package/node_modules/pi-footer/CHANGELOG.md +0 -145
  109. package/node_modules/pi-footer/LICENSE +0 -21
  110. package/node_modules/pi-footer/README.md +0 -354
  111. package/node_modules/pi-footer/package.json +0 -68
  112. package/node_modules/pi-footer/src/cache.ts +0 -106
  113. package/node_modules/pi-footer/src/colors.ts +0 -237
  114. package/node_modules/pi-footer/src/config.ts +0 -202
  115. package/node_modules/pi-footer/src/event-widgets.ts +0 -41
  116. package/node_modules/pi-footer/src/extension-statuses.ts +0 -102
  117. package/node_modules/pi-footer/src/git.ts +0 -135
  118. package/node_modules/pi-footer/src/index.ts +0 -268
  119. package/node_modules/pi-footer/src/metrics.ts +0 -141
  120. package/node_modules/pi-footer/src/presets.ts +0 -383
  121. package/node_modules/pi-footer/src/render.ts +0 -114
  122. package/node_modules/pi-footer/src/separators.ts +0 -52
  123. package/node_modules/pi-footer/src/types.ts +0 -122
  124. package/node_modules/pi-footer/src/ui/color-level-confirm.ts +0 -22
  125. package/node_modules/pi-footer/src/ui/color-options.ts +0 -195
  126. package/node_modules/pi-footer/src/ui/config-lifecycle.ts +0 -61
  127. package/node_modules/pi-footer/src/ui/edit-colors.ts +0 -13
  128. package/node_modules/pi-footer/src/ui/events.ts +0 -35
  129. package/node_modules/pi-footer/src/ui/extension-status-picker.ts +0 -60
  130. package/node_modules/pi-footer/src/ui/extension-statuses.ts +0 -73
  131. package/node_modules/pi-footer/src/ui/fields.ts +0 -252
  132. package/node_modules/pi-footer/src/ui/global-menu.ts +0 -126
  133. package/node_modules/pi-footer/src/ui/helpers.ts +0 -44
  134. package/node_modules/pi-footer/src/ui/layout.ts +0 -17
  135. package/node_modules/pi-footer/src/ui/line-list.ts +0 -19
  136. package/node_modules/pi-footer/src/ui/model.ts +0 -64
  137. package/node_modules/pi-footer/src/ui/navigation.ts +0 -29
  138. package/node_modules/pi-footer/src/ui/option-edit.ts +0 -93
  139. package/node_modules/pi-footer/src/ui/overlay-render.ts +0 -103
  140. package/node_modules/pi-footer/src/ui/screen-context.ts +0 -18
  141. package/node_modules/pi-footer/src/ui/screen-controller.ts +0 -27
  142. package/node_modules/pi-footer/src/ui/screen-render.ts +0 -32
  143. package/node_modules/pi-footer/src/ui/screen-state.ts +0 -20
  144. package/node_modules/pi-footer/src/ui/screen.ts +0 -243
  145. package/node_modules/pi-footer/src/ui/screens/add-widget.ts +0 -92
  146. package/node_modules/pi-footer/src/ui/screens/confirm-exit.ts +0 -52
  147. package/node_modules/pi-footer/src/ui/screens/controller.ts +0 -12
  148. package/node_modules/pi-footer/src/ui/screens/edit-colors.ts +0 -68
  149. package/node_modules/pi-footer/src/ui/screens/edit-widget.ts +0 -115
  150. package/node_modules/pi-footer/src/ui/screens/extension-status-row.ts +0 -61
  151. package/node_modules/pi-footer/src/ui/screens/global.ts +0 -75
  152. package/node_modules/pi-footer/src/ui/screens/line-list.ts +0 -108
  153. package/node_modules/pi-footer/src/ui/screens/main.ts +0 -99
  154. package/node_modules/pi-footer/src/ui/screens/terminal.ts +0 -121
  155. package/node_modules/pi-footer/src/ui/screens/widget-list.ts +0 -154
  156. package/node_modules/pi-footer/src/ui/terminal-menu.ts +0 -32
  157. package/node_modules/pi-footer/src/ui/theme.ts +0 -48
  158. package/node_modules/pi-footer/src/ui/title-bar.ts +0 -100
  159. package/node_modules/pi-footer/src/ui/widget-actions.ts +0 -76
  160. package/node_modules/pi-footer/src/ui.ts +0 -66
  161. package/node_modules/pi-footer/src/widgets/context.ts +0 -28
  162. package/node_modules/pi-footer/src/widgets/core/active-tools.ts +0 -17
  163. package/node_modules/pi-footer/src/widgets/core/context-window.ts +0 -24
  164. package/node_modules/pi-footer/src/widgets/core/cwd-basename.ts +0 -19
  165. package/node_modules/pi-footer/src/widgets/core/cwd.ts +0 -108
  166. package/node_modules/pi-footer/src/widgets/core/event.ts +0 -34
  167. package/node_modules/pi-footer/src/widgets/core/external-status.ts +0 -102
  168. package/node_modules/pi-footer/src/widgets/core/model-provider.ts +0 -20
  169. package/node_modules/pi-footer/src/widgets/core/model.ts +0 -35
  170. package/node_modules/pi-footer/src/widgets/core/provider.ts +0 -17
  171. package/node_modules/pi-footer/src/widgets/core/session-name.ts +0 -17
  172. package/node_modules/pi-footer/src/widgets/core/text-verbosity.ts +0 -17
  173. package/node_modules/pi-footer/src/widgets/core/thinking-level.ts +0 -17
  174. package/node_modules/pi-footer/src/widgets/git/ahead-behind.ts +0 -17
  175. package/node_modules/pi-footer/src/widgets/git/branch.ts +0 -81
  176. package/node_modules/pi-footer/src/widgets/git/clean.ts +0 -19
  177. package/node_modules/pi-footer/src/widgets/git/deletions.ts +0 -17
  178. package/node_modules/pi-footer/src/widgets/git/diff.ts +0 -38
  179. package/node_modules/pi-footer/src/widgets/git/insertions.ts +0 -17
  180. package/node_modules/pi-footer/src/widgets/git/remote.ts +0 -17
  181. package/node_modules/pi-footer/src/widgets/git/root.ts +0 -17
  182. package/node_modules/pi-footer/src/widgets/git/sha.ts +0 -17
  183. package/node_modules/pi-footer/src/widgets/git/staged.ts +0 -17
  184. package/node_modules/pi-footer/src/widgets/git/status.ts +0 -19
  185. package/node_modules/pi-footer/src/widgets/git/unstaged.ts +0 -17
  186. package/node_modules/pi-footer/src/widgets/git/untracked.ts +0 -17
  187. package/node_modules/pi-footer/src/widgets/instance.ts +0 -118
  188. package/node_modules/pi-footer/src/widgets/layout/custom-text.ts +0 -18
  189. package/node_modules/pi-footer/src/widgets/layout/flex-separator.ts +0 -32
  190. package/node_modules/pi-footer/src/widgets/layout/separator.ts +0 -57
  191. package/node_modules/pi-footer/src/widgets/layout/spacer.ts +0 -31
  192. package/node_modules/pi-footer/src/widgets/options.ts +0 -122
  193. package/node_modules/pi-footer/src/widgets/project/runtime.ts +0 -361
  194. package/node_modules/pi-footer/src/widgets/registry.ts +0 -223
  195. package/node_modules/pi-footer/src/widgets/session/assistant-messages.ts +0 -17
  196. package/node_modules/pi-footer/src/widgets/session/compactions.ts +0 -17
  197. package/node_modules/pi-footer/src/widgets/session/elapsed.ts +0 -18
  198. package/node_modules/pi-footer/src/widgets/session/last-activity.ts +0 -18
  199. package/node_modules/pi-footer/src/widgets/session/messages.ts +0 -19
  200. package/node_modules/pi-footer/src/widgets/session/session-id.ts +0 -17
  201. package/node_modules/pi-footer/src/widgets/session/session-start.ts +0 -18
  202. package/node_modules/pi-footer/src/widgets/session/tool-results.ts +0 -17
  203. package/node_modules/pi-footer/src/widgets/session/total-messages.ts +0 -19
  204. package/node_modules/pi-footer/src/widgets/session/total-time.ts +0 -18
  205. package/node_modules/pi-footer/src/widgets/session/user-messages.ts +0 -17
  206. package/node_modules/pi-footer/src/widgets/store.ts +0 -26
  207. package/node_modules/pi-footer/src/widgets/tokens/cache-hit-rate.ts +0 -55
  208. package/node_modules/pi-footer/src/widgets/tokens/cache-read.ts +0 -18
  209. package/node_modules/pi-footer/src/widgets/tokens/cache-write.ts +0 -18
  210. package/node_modules/pi-footer/src/widgets/tokens/context-bar.ts +0 -108
  211. package/node_modules/pi-footer/src/widgets/tokens/context-length.ts +0 -26
  212. package/node_modules/pi-footer/src/widgets/tokens/context-remaining.ts +0 -29
  213. package/node_modules/pi-footer/src/widgets/tokens/context.ts +0 -29
  214. package/node_modules/pi-footer/src/widgets/tokens/cost.ts +0 -51
  215. package/node_modules/pi-footer/src/widgets/tokens/input-speed.ts +0 -25
  216. package/node_modules/pi-footer/src/widgets/tokens/input-tokens.ts +0 -18
  217. package/node_modules/pi-footer/src/widgets/tokens/output-speed.ts +0 -25
  218. package/node_modules/pi-footer/src/widgets/tokens/output-tokens.ts +0 -18
  219. package/node_modules/pi-footer/src/widgets/tokens/tokens.ts +0 -20
  220. package/node_modules/pi-footer/src/widgets/tokens/total-speed.ts +0 -25
  221. package/node_modules/pi-footer/src/widgets/tokens/total-tokens.ts +0 -18
  222. package/node_modules/pi-footer/src/widgets/types.ts +0 -181
  223. package/node_modules/pi-footer/src/widgets/utils/colors.ts +0 -11
  224. package/node_modules/pi-footer/src/widgets/utils/context.ts +0 -112
  225. package/node_modules/pi-footer/src/widgets/utils/session.ts +0 -19
  226. package/node_modules/pi-footer/src/widgets/utils/token-format.ts +0 -78
@@ -0,0 +1,762 @@
1
+ import { visibleWidth } from "@earendil-works/pi-tui";
2
+ import { fitToWidth, normalizeCodeWhitespace, wrapToWidth } from "./diff-text.ts";
3
+ import {
4
+ ANSI_BG_RESET,
5
+ applyLineBackgroundToWrappedRows,
6
+ colorizeSegment,
7
+ readThemeAnsi,
8
+ stabilizeBackgroundResets,
9
+ type DiffPalette,
10
+ type DiffTheme,
11
+ } from "./diff-palette.ts";
12
+ import { highlightDiffLine, type CodeLineHighlighter } from "./diff-highlight.ts";
13
+ import {
14
+ formatLineNumberLabel,
15
+ getCompactLineRenderContent,
16
+ type DiffLineEntry,
17
+ type DiffLineKind,
18
+ type DiffMetaEntry,
19
+ type DiffOmissionEntry,
20
+ type ParsedDiffEntry,
21
+ } from "./diff-parse.ts";
22
+ import { getCellLineNumber, type DiffSpan, type SplitDiffRow } from "./diff-inline.ts";
23
+ import { formatMetaEntryRows } from "./diff-header.ts";
24
+ import type { DiffIndicatorMode } from "../../../config/config.ts";
25
+
26
+ export interface RenderedRow {
27
+ text: string;
28
+ hunkIndex: number | null;
29
+ }
30
+
31
+ type MaterializedDiffEntry = DiffLineEntry | DiffMetaEntry;
32
+
33
+ interface LineCellRenderParams {
34
+ kind: DiffLineKind;
35
+ code: string;
36
+ width: number;
37
+ rowBg: string | undefined;
38
+ restoreBgAnsi: string | undefined;
39
+ theme: DiffTheme;
40
+ wordWrap: boolean;
41
+ indicatorMode: DiffIndicatorMode;
42
+ }
43
+
44
+ export interface DiffRenderContext {
45
+ width: number;
46
+ theme: DiffTheme;
47
+ inlineHighlights: WeakMap<DiffLineEntry, DiffSpan[]>;
48
+ palette: DiffPalette;
49
+ highlightLine: CodeLineHighlighter;
50
+ containerBgAnsi: string | undefined;
51
+ wordWrap: boolean;
52
+ indicatorMode: DiffIndicatorMode;
53
+ showHashlineAnchors: boolean;
54
+ }
55
+
56
+ const SPLIT_SEPARATOR = " │ ";
57
+ const OMISSION_GLYPH = "⋮";
58
+ const MIN_SPLIT_COLUMN_WIDTH = 24;
59
+
60
+ function resolveIndicatorGlyph(
61
+ kind: DiffLineKind,
62
+ indicatorMode: DiffIndicatorMode,
63
+ continuation: boolean,
64
+ ): string {
65
+ if (kind === "context") {
66
+ return " ";
67
+ }
68
+
69
+ switch (indicatorMode) {
70
+ case "bars":
71
+ return "▌";
72
+ case "classic":
73
+ if (continuation) {
74
+ return " ";
75
+ }
76
+ return kind === "add" ? "+" : "-";
77
+ case "none":
78
+ default:
79
+ return " ";
80
+ }
81
+ }
82
+
83
+ function renderChangeMarker(
84
+ kind: DiffLineKind,
85
+ theme: DiffTheme,
86
+ rowBg: string | undefined,
87
+ indicatorMode: DiffIndicatorMode,
88
+ continuation = false,
89
+ ): string {
90
+ const glyph = resolveIndicatorGlyph(kind, indicatorMode, continuation);
91
+ if (glyph === " ") {
92
+ return rowBg ? `${rowBg} ${rowBg}` : " ";
93
+ }
94
+ if (kind === "add") {
95
+ return colorizeSegment(theme, "toolDiffAdded", glyph, rowBg);
96
+ }
97
+ if (kind === "remove") {
98
+ return colorizeSegment(theme, "toolDiffRemoved", glyph, rowBg);
99
+ }
100
+ return colorizeSegment(theme, "dim", glyph, rowBg);
101
+ }
102
+
103
+ function usesHashlineGutter(showHashlineAnchors: boolean): boolean {
104
+ return showHashlineAnchors;
105
+ }
106
+
107
+ function getHashlineGutterMarkerWidth(_indicatorMode: DiffIndicatorMode): number {
108
+ return 0;
109
+ }
110
+
111
+ function getLineDividerPlainWidth(
112
+ indicatorMode: DiffIndicatorMode,
113
+ hashlineGutter = false,
114
+ ): number {
115
+ if (hashlineGutter) {
116
+ return 2;
117
+ }
118
+ return indicatorMode === "classic" ? 1 : 2;
119
+ }
120
+
121
+ function renderCodeDivider(
122
+ theme: DiffTheme,
123
+ rowBg: string | undefined,
124
+ indicatorMode: DiffIndicatorMode,
125
+ hashlineGutter = false,
126
+ ): string {
127
+ return colorizeSegment(
128
+ theme,
129
+ "dim",
130
+ hashlineGutter || indicatorMode !== "classic" ? "│ " : "│",
131
+ rowBg,
132
+ );
133
+ }
134
+
135
+ function getLineNumberColor(kind: DiffLineKind): "dim" | "toolDiffAdded" | "toolDiffRemoved" {
136
+ if (kind === "add") {
137
+ return "toolDiffAdded";
138
+ }
139
+ if (kind === "remove") {
140
+ return "toolDiffRemoved";
141
+ }
142
+ return "dim";
143
+ }
144
+
145
+ function renderLineNumberSegment(
146
+ kind: DiffLineKind,
147
+ lineNumber: string,
148
+ theme: DiffTheme,
149
+ rowBg: string | undefined,
150
+ ): string {
151
+ return colorizeSegment(theme, getLineNumberColor(kind), lineNumber, rowBg);
152
+ }
153
+
154
+ function getLinePrefixPlainWidth(
155
+ lineNumberWidth: number,
156
+ indicatorMode: DiffIndicatorMode,
157
+ hashlineGutter = false,
158
+ ): number {
159
+ if (hashlineGutter) {
160
+ return getHashlineGutterMarkerWidth(indicatorMode) + lineNumberWidth;
161
+ }
162
+ return indicatorMode === "bars"
163
+ ? visibleWidth(`▌ ${" ".repeat(lineNumberWidth)} `)
164
+ : visibleWidth(`${" ".repeat(lineNumberWidth)} `);
165
+ }
166
+
167
+ function getLineContentIndicatorPrefixPlainWidth(indicatorMode: DiffIndicatorMode): number {
168
+ return indicatorMode === "classic" ? 2 : 0;
169
+ }
170
+
171
+ function renderClassicContentPrefix(
172
+ kind: DiffLineKind,
173
+ theme: DiffTheme,
174
+ rowBg: string | undefined,
175
+ continuation = false,
176
+ ): string {
177
+ if (kind === "context" || continuation) {
178
+ return rowBg ? `${rowBg} ${rowBg}` : " ";
179
+ }
180
+
181
+ const glyph = kind === "add" ? "+" : "-";
182
+ const glyphColor = kind === "add" ? "toolDiffAdded" : "toolDiffRemoved";
183
+ const spacer = rowBg ? `${rowBg} ` : " ";
184
+ return `${colorizeSegment(theme, glyphColor, glyph, rowBg)}${spacer}`;
185
+ }
186
+
187
+ function renderLinePrefix(
188
+ kind: DiffLineKind,
189
+ lineNumber: string,
190
+ theme: DiffTheme,
191
+ rowBg: string | undefined,
192
+ indicatorMode: DiffIndicatorMode,
193
+ continuation = false,
194
+ hashlineGutter = false,
195
+ ): string {
196
+ const number = renderLineNumberSegment(kind, lineNumber, theme, rowBg);
197
+ if (hashlineGutter) {
198
+ return number;
199
+ }
200
+ const spacer = rowBg ? `${rowBg} ` : " ";
201
+ if (indicatorMode !== "bars") {
202
+ return `${number}${spacer}`;
203
+ }
204
+ const marker = renderChangeMarker(kind, theme, rowBg, indicatorMode, continuation);
205
+ return `${marker}${spacer}${number}${spacer}`;
206
+ }
207
+
208
+ function renderLineContinuationPrefix(
209
+ kind: DiffLineKind,
210
+ lineNumberWidth: number,
211
+ rowBg: string | undefined,
212
+ theme: DiffTheme,
213
+ indicatorMode: DiffIndicatorMode,
214
+ hashlineGutter = false,
215
+ ): string {
216
+ const blankLineNumber = " ".repeat(lineNumberWidth);
217
+ return renderLinePrefix(kind, blankLineNumber, theme, rowBg, indicatorMode, true, hashlineGutter);
218
+ }
219
+
220
+ function renderLineContentIndicatorPrefix(
221
+ kind: DiffLineKind,
222
+ theme: DiffTheme,
223
+ rowBg: string | undefined,
224
+ indicatorMode: DiffIndicatorMode,
225
+ continuation = false,
226
+ ): string {
227
+ return indicatorMode === "classic"
228
+ ? renderClassicContentPrefix(kind, theme, rowBg, continuation)
229
+ : "";
230
+ }
231
+
232
+ function renderCompactLinePrefix(
233
+ kind: DiffLineKind,
234
+ theme: DiffTheme,
235
+ rowBg: string | undefined,
236
+ indicatorMode: DiffIndicatorMode,
237
+ continuation = false,
238
+ ): string {
239
+ const marker = renderChangeMarker(kind, theme, rowBg, indicatorMode, continuation);
240
+ const spacer = rowBg ? `${rowBg} ` : " ";
241
+ return `${marker}${spacer}`;
242
+ }
243
+
244
+ function renderWrappedRowsWithOptionalBackground(
245
+ wrappedCodeLines: string[],
246
+ buildRow: (index: number, wrappedCodeLine: string) => string,
247
+ width: number,
248
+ rowBg: string | undefined,
249
+ restoreBgAnsi: string | undefined,
250
+ ): string[] {
251
+ if (!rowBg) {
252
+ return wrappedCodeLines.map((wrappedCodeLine, index) =>
253
+ stabilizeBackgroundResets(buildRow(index, wrappedCodeLine)),
254
+ );
255
+ }
256
+ const safeRestoreBgAnsi = restoreBgAnsi ?? rowBg ?? ANSI_BG_RESET;
257
+ const visualRows = wrappedCodeLines.map((wrappedCodeLine, index) =>
258
+ buildRow(index, wrappedCodeLine),
259
+ );
260
+ return applyLineBackgroundToWrappedRows(visualRows, width, rowBg, safeRestoreBgAnsi);
261
+ }
262
+
263
+ function computeLineCellCodeWidth(
264
+ width: number,
265
+ lineNumberWidth: number,
266
+ indicatorMode: DiffIndicatorMode,
267
+ hashlineGutter: boolean,
268
+ ): number {
269
+ const prefixPlainWidth = getLinePrefixPlainWidth(lineNumberWidth, indicatorMode, hashlineGutter);
270
+ const dividerPlainWidth = getLineDividerPlainWidth(indicatorMode, hashlineGutter);
271
+ const contentIndicatorWidth = hashlineGutter
272
+ ? 0
273
+ : getLineContentIndicatorPrefixPlainWidth(indicatorMode);
274
+ return Math.max(0, width - prefixPlainWidth - dividerPlainWidth - contentIndicatorWidth);
275
+ }
276
+
277
+ function buildLineCellParams(
278
+ kind: DiffLineKind,
279
+ code: string,
280
+ width: number,
281
+ rowBg: string | undefined,
282
+ restoreBgAnsi: string | undefined,
283
+ theme: DiffTheme,
284
+ wordWrap: boolean,
285
+ indicatorMode: DiffIndicatorMode,
286
+ ): LineCellRenderParams {
287
+ return { kind, code, width, rowBg, restoreBgAnsi, theme, wordWrap, indicatorMode };
288
+ }
289
+
290
+ function renderCompactLineCell({
291
+ kind,
292
+ code,
293
+ width,
294
+ rowBg,
295
+ restoreBgAnsi,
296
+ theme,
297
+ wordWrap,
298
+ indicatorMode,
299
+ }: LineCellRenderParams): string[] {
300
+ if (width <= 0) {
301
+ return [""];
302
+ }
303
+
304
+ const prefix = renderCompactLinePrefix(kind, theme, undefined, indicatorMode);
305
+ const continuationPrefix = renderCompactLinePrefix(kind, theme, undefined, indicatorMode, true);
306
+ const prefixPlainWidth = 2;
307
+ const codeWidth = Math.max(0, width - prefixPlainWidth);
308
+ const wrappedCodeLines = wrapToWidth(code, codeWidth, wordWrap);
309
+ return renderWrappedRowsWithOptionalBackground(
310
+ wrappedCodeLines,
311
+ (index, line) => `${index === 0 ? prefix : continuationPrefix}${line}`,
312
+ width,
313
+ rowBg,
314
+ restoreBgAnsi,
315
+ );
316
+ }
317
+
318
+ function renderLineCell(
319
+ { kind, code, width, rowBg, restoreBgAnsi, theme, wordWrap, indicatorMode }: LineCellRenderParams,
320
+ lineNumber: string,
321
+ hashlineGutter = false,
322
+ ): string[] {
323
+ if (width <= 0) {
324
+ return [""];
325
+ }
326
+
327
+ const codeWidth = computeLineCellCodeWidth(
328
+ width,
329
+ lineNumber.length,
330
+ indicatorMode,
331
+ hashlineGutter,
332
+ );
333
+ const prefix = renderLinePrefix(
334
+ kind,
335
+ lineNumber,
336
+ theme,
337
+ undefined,
338
+ indicatorMode,
339
+ false,
340
+ hashlineGutter,
341
+ );
342
+ const continuationPrefix = renderLineContinuationPrefix(
343
+ kind,
344
+ lineNumber.length,
345
+ undefined,
346
+ theme,
347
+ indicatorMode,
348
+ hashlineGutter,
349
+ );
350
+ const divider = renderCodeDivider(theme, undefined, indicatorMode, hashlineGutter);
351
+ const firstContentPrefix = hashlineGutter
352
+ ? ""
353
+ : renderLineContentIndicatorPrefix(kind, theme, undefined, indicatorMode);
354
+ const continuationContentPrefix = hashlineGutter
355
+ ? ""
356
+ : renderLineContentIndicatorPrefix(kind, theme, undefined, indicatorMode, true);
357
+ const wrappedCodeLines = wrapToWidth(code, codeWidth, wordWrap);
358
+ return renderWrappedRowsWithOptionalBackground(
359
+ wrappedCodeLines,
360
+ (index, line) => {
361
+ const linePrefix = index === 0 ? prefix : continuationPrefix;
362
+ const contentPrefix = index === 0 ? firstContentPrefix : continuationContentPrefix;
363
+ return `${linePrefix}${divider}${contentPrefix}${line}`;
364
+ },
365
+ width,
366
+ rowBg,
367
+ restoreBgAnsi,
368
+ );
369
+ }
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
+
394
+ function pushDiffLineRows(rows: RenderedRow[], lines: string[], entry: DiffLineEntry): void {
395
+ rows.push(
396
+ ...lines.map((text) => ({
397
+ text,
398
+ hunkIndex: entry.hunkIndex || null,
399
+ })),
400
+ );
401
+ }
402
+
403
+ function processDiffEntries(
404
+ entries: MaterializedDiffEntry[],
405
+ ctx: DiffRenderContext,
406
+ processLine: (entry: DiffLineEntry) => string[],
407
+ ): RenderedRow[] {
408
+ const { width, theme, wordWrap } = ctx;
409
+ const rows: RenderedRow[] = [];
410
+ for (const entry of entries) {
411
+ if (entry.kind !== "line") {
412
+ rows.push(...formatMetaEntryRows(entry, width, theme, wordWrap));
413
+ continue;
414
+ }
415
+ pushDiffLineRows(rows, processLine(entry), entry);
416
+ }
417
+ return rows;
418
+ }
419
+
420
+ export function renderUnified(
421
+ entries: ParsedDiffEntry[],
422
+ ctx: DiffRenderContext,
423
+ lineNumberWidth: number,
424
+ ): RenderedRow[] {
425
+ return processDiffEntries(materializeOmissionEntries(entries), ctx, (entry) => {
426
+ const lineNumber =
427
+ entry.lineKind === "add"
428
+ ? formatLineNumberLabel(
429
+ entry,
430
+ entry.newLineNumber,
431
+ entry.fallbackLineNumber,
432
+ lineNumberWidth,
433
+ ctx.showHashlineAnchors,
434
+ )
435
+ : formatLineNumberLabel(
436
+ entry,
437
+ entry.oldLineNumber,
438
+ entry.fallbackLineNumber,
439
+ lineNumberWidth,
440
+ ctx.showHashlineAnchors,
441
+ );
442
+ const codeText = normalizeCodeWhitespace(entry.content);
443
+ const { highlighted, rowBg } = highlightDiffLine(
444
+ codeText,
445
+ entry,
446
+ ctx.inlineHighlights,
447
+ ctx.palette,
448
+ ctx.highlightLine,
449
+ ctx.containerBgAnsi,
450
+ );
451
+ return renderLineCell(
452
+ buildLineCellParams(
453
+ entry.lineKind,
454
+ highlighted,
455
+ ctx.width,
456
+ rowBg,
457
+ ctx.containerBgAnsi,
458
+ ctx.theme,
459
+ ctx.wordWrap,
460
+ ctx.indicatorMode,
461
+ ),
462
+ lineNumber,
463
+ usesHashlineGutter(ctx.showHashlineAnchors),
464
+ );
465
+ });
466
+ }
467
+
468
+ function toUnifiedFallbackRows(
469
+ rows: SplitDiffRow[],
470
+ ctx: DiffRenderContext,
471
+ lineNumberWidth: number,
472
+ ): RenderedRow[] {
473
+ const flattened: ParsedDiffEntry[] = [];
474
+ for (const row of rows) {
475
+ if (row.omission) {
476
+ flattened.push(row.omission);
477
+ continue;
478
+ }
479
+ if (row.meta) {
480
+ flattened.push(row.meta);
481
+ continue;
482
+ }
483
+ if (row.left) {
484
+ flattened.push(row.left);
485
+ }
486
+ if (row.right && row.right !== row.left) {
487
+ flattened.push(row.right);
488
+ }
489
+ }
490
+ return renderUnified(flattened, ctx, lineNumberWidth);
491
+ }
492
+
493
+ export function renderCompact(entries: ParsedDiffEntry[], ctx: DiffRenderContext): RenderedRow[] {
494
+ return processDiffEntries(materializeOmissionEntries(entries, true), ctx, (entry) => {
495
+ const codeText = normalizeCodeWhitespace(
496
+ getCompactLineRenderContent(entry, ctx.showHashlineAnchors),
497
+ );
498
+ const { highlighted, rowBg } = highlightDiffLine(
499
+ codeText,
500
+ entry,
501
+ ctx.inlineHighlights,
502
+ ctx.palette,
503
+ ctx.highlightLine,
504
+ ctx.containerBgAnsi,
505
+ );
506
+ return renderCompactLineCell(
507
+ buildLineCellParams(
508
+ entry.lineKind,
509
+ highlighted,
510
+ ctx.width,
511
+ rowBg,
512
+ ctx.containerBgAnsi,
513
+ ctx.theme,
514
+ ctx.wordWrap,
515
+ ctx.indicatorMode,
516
+ ),
517
+ );
518
+ });
519
+ }
520
+
521
+ function renderSplitBlankCell(
522
+ columnWidth: number,
523
+ lineNumberWidth: number,
524
+ theme: DiffTheme,
525
+ indicatorMode: DiffIndicatorMode,
526
+ hashlineGutter = false,
527
+ ): string {
528
+ const codeWidth = computeLineCellCodeWidth(
529
+ columnWidth,
530
+ lineNumberWidth,
531
+ indicatorMode,
532
+ hashlineGutter,
533
+ );
534
+ const prefix = renderLinePrefix(
535
+ "context",
536
+ " ".repeat(lineNumberWidth),
537
+ theme,
538
+ undefined,
539
+ indicatorMode,
540
+ true,
541
+ hashlineGutter,
542
+ );
543
+ const divider = renderCodeDivider(theme, undefined, indicatorMode, hashlineGutter);
544
+ const contentPrefix = hashlineGutter
545
+ ? ""
546
+ : renderLineContentIndicatorPrefix("context", theme, undefined, indicatorMode, true);
547
+ return stabilizeBackgroundResets(`${prefix}${divider}${contentPrefix}${" ".repeat(codeWidth)}`);
548
+ }
549
+
550
+ function renderSplitCell(
551
+ line: DiffLineEntry | undefined,
552
+ side: "left" | "right",
553
+ columnWidth: number,
554
+ lineNumberWidth: number,
555
+ theme: DiffTheme,
556
+ inlineHighlights: WeakMap<DiffLineEntry, DiffSpan[]>,
557
+ palette: DiffPalette,
558
+ highlightLine: CodeLineHighlighter,
559
+ containerBgAnsi: string | undefined,
560
+ wordWrap: boolean,
561
+ indicatorMode: DiffIndicatorMode,
562
+ showHashlineAnchors: boolean,
563
+ ): string[] {
564
+ const hashlineGutter = usesHashlineGutter(showHashlineAnchors);
565
+ if (!line) {
566
+ return [
567
+ renderSplitBlankCell(columnWidth, lineNumberWidth, theme, indicatorMode, hashlineGutter),
568
+ ];
569
+ }
570
+
571
+ const lineNumber = formatLineNumberLabel(
572
+ line,
573
+ getCellLineNumber(line, side),
574
+ line.fallbackLineNumber,
575
+ lineNumberWidth,
576
+ showHashlineAnchors,
577
+ );
578
+ const codeText = normalizeCodeWhitespace(line.content);
579
+ const { highlighted, rowBg } = highlightDiffLine(
580
+ codeText,
581
+ line,
582
+ inlineHighlights,
583
+ palette,
584
+ highlightLine,
585
+ containerBgAnsi,
586
+ );
587
+ return renderLineCell(
588
+ buildLineCellParams(
589
+ line.lineKind,
590
+ highlighted,
591
+ columnWidth,
592
+ rowBg,
593
+ containerBgAnsi,
594
+ theme,
595
+ wordWrap,
596
+ indicatorMode,
597
+ ),
598
+ lineNumber,
599
+ hashlineGutter,
600
+ );
601
+ }
602
+
603
+ function renderSplitDivider(
604
+ theme: DiffTheme,
605
+ containerBgAnsi: string | undefined,
606
+ separatorText: string = SPLIT_SEPARATOR,
607
+ ): string {
608
+ const dimAnsi = readThemeAnsi(theme, "fg", "dim");
609
+ if (!containerBgAnsi) {
610
+ return stabilizeBackgroundResets(theme.fg("dim", separatorText));
611
+ }
612
+ if (!dimAnsi) {
613
+ return stabilizeBackgroundResets(
614
+ `${containerBgAnsi}${theme.fg("dim", separatorText)}${containerBgAnsi}`,
615
+ );
616
+ }
617
+ return stabilizeBackgroundResets(
618
+ `${containerBgAnsi}${dimAnsi}${separatorText}\x1b[39m${containerBgAnsi}`,
619
+ );
620
+ }
621
+
622
+ function renderSplitTopBorderCell(
623
+ columnWidth: number,
624
+ lineNumberWidth: number,
625
+ theme: DiffTheme,
626
+ indicatorMode: DiffIndicatorMode,
627
+ hashlineGutter = false,
628
+ ): string {
629
+ const safeColumnWidth = Math.max(1, columnWidth);
630
+ const chars = "─".repeat(safeColumnWidth).split("");
631
+ const dividerIndex = getLinePrefixPlainWidth(lineNumberWidth, indicatorMode, hashlineGutter);
632
+ if (dividerIndex >= 0 && dividerIndex < chars.length) {
633
+ chars[dividerIndex] = "┬";
634
+ }
635
+ return stabilizeBackgroundResets(theme.fg("dim", chars.join("")));
636
+ }
637
+
638
+ function renderSplitHeaderCell(
639
+ label: string,
640
+ columnWidth: number,
641
+ lineNumberWidth: number,
642
+ theme: DiffTheme,
643
+ indicatorMode: DiffIndicatorMode,
644
+ hashlineGutter = false,
645
+ ): string {
646
+ const markerPad = hashlineGutter
647
+ ? " ".repeat(getHashlineGutterMarkerWidth(indicatorMode))
648
+ : indicatorMode === "bars"
649
+ ? " "
650
+ : "";
651
+ const lineNumberLabel = fitToWidth(label, lineNumberWidth);
652
+ const lineNumberSpacer = hashlineGutter ? "" : " ";
653
+ const divider = hashlineGutter || indicatorMode !== "classic" ? "│ " : "│";
654
+ const prefix = `${theme.fg("dim", markerPad)}${theme.fg("muted", lineNumberLabel)}${theme.fg("dim", lineNumberSpacer)}${theme.fg("dim", divider)}`;
655
+ const prefixWidth = visibleWidth(`${markerPad}${lineNumberLabel}${lineNumberSpacer}${divider}`);
656
+ const contentIndicatorWidth = hashlineGutter
657
+ ? 0
658
+ : getLineContentIndicatorPrefixPlainWidth(indicatorMode);
659
+ const codeWidth = Math.max(0, columnWidth - prefixWidth - contentIndicatorWidth);
660
+ const contentPad = !hashlineGutter && indicatorMode === "classic" ? " " : "";
661
+ return stabilizeBackgroundResets(`${prefix}${contentPad}${" ".repeat(codeWidth)}`);
662
+ }
663
+
664
+ export function canRenderSplitLayout(width: number): boolean {
665
+ const separatorWidth = visibleWidth(SPLIT_SEPARATOR);
666
+ const minimumSplitWidth = MIN_SPLIT_COLUMN_WIDTH * 2 + separatorWidth;
667
+ return width >= minimumSplitWidth;
668
+ }
669
+
670
+ export function renderSplit(
671
+ rows: SplitDiffRow[],
672
+ ctx: DiffRenderContext,
673
+ lineNumberWidth: number,
674
+ ): RenderedRow[] {
675
+ const {
676
+ width,
677
+ theme,
678
+ inlineHighlights,
679
+ palette,
680
+ highlightLine,
681
+ containerBgAnsi,
682
+ wordWrap,
683
+ indicatorMode,
684
+ showHashlineAnchors,
685
+ } = ctx;
686
+ if (!canRenderSplitLayout(width)) {
687
+ return toUnifiedFallbackRows(rows, ctx, lineNumberWidth);
688
+ }
689
+
690
+ const separatorWidth = visibleWidth(SPLIT_SEPARATOR);
691
+ const leftWidth = Math.max(MIN_SPLIT_COLUMN_WIDTH, Math.floor((width - separatorWidth) / 2));
692
+ const rightWidth = Math.max(MIN_SPLIT_COLUMN_WIDTH, width - separatorWidth - leftWidth);
693
+ const splitLineNumberWidth = Math.max(3, lineNumberWidth);
694
+ const hashlineGutter = usesHashlineGutter(showHashlineAnchors);
695
+ const separator = renderSplitDivider(theme, containerBgAnsi);
696
+ const topSeparator = renderSplitDivider(theme, containerBgAnsi, "─┬─");
697
+ const output: RenderedRow[] = [];
698
+ output.push({
699
+ text: `${renderSplitTopBorderCell(leftWidth, splitLineNumberWidth, theme, indicatorMode, hashlineGutter)}${topSeparator}${renderSplitTopBorderCell(rightWidth, splitLineNumberWidth, theme, indicatorMode, hashlineGutter)}`,
700
+ hunkIndex: null,
701
+ });
702
+ output.push({
703
+ text: `${renderSplitHeaderCell("old", leftWidth, splitLineNumberWidth, theme, indicatorMode, hashlineGutter)}${separator}${renderSplitHeaderCell("new", rightWidth, splitLineNumberWidth, theme, indicatorMode, hashlineGutter)}`,
704
+ hunkIndex: null,
705
+ });
706
+
707
+ for (const row of rows) {
708
+ if (row.meta) {
709
+ output.push(...formatMetaEntryRows(row.meta, width, theme, wordWrap));
710
+ continue;
711
+ }
712
+
713
+ const omissionLine = row.omission ? createOmissionRenderLine(row.omission) : undefined;
714
+ const leftCells = renderSplitCell(
715
+ omissionLine ?? row.left,
716
+ "left",
717
+ leftWidth,
718
+ splitLineNumberWidth,
719
+ theme,
720
+ inlineHighlights,
721
+ palette,
722
+ highlightLine,
723
+ containerBgAnsi,
724
+ wordWrap,
725
+ indicatorMode,
726
+ showHashlineAnchors,
727
+ );
728
+ const rightCells = renderSplitCell(
729
+ omissionLine ?? row.right,
730
+ "right",
731
+ rightWidth,
732
+ splitLineNumberWidth,
733
+ theme,
734
+ inlineHighlights,
735
+ palette,
736
+ highlightLine,
737
+ containerBgAnsi,
738
+ wordWrap,
739
+ indicatorMode,
740
+ showHashlineAnchors,
741
+ );
742
+
743
+ const rowCount = Math.max(leftCells.length, rightCells.length);
744
+ for (let index = 0; index < rowCount; index++) {
745
+ const leftCell =
746
+ leftCells[index] ??
747
+ renderSplitBlankCell(leftWidth, splitLineNumberWidth, theme, indicatorMode, hashlineGutter);
748
+ const rightCell =
749
+ rightCells[index] ??
750
+ renderSplitBlankCell(
751
+ rightWidth,
752
+ splitLineNumberWidth,
753
+ theme,
754
+ indicatorMode,
755
+ hashlineGutter,
756
+ );
757
+ output.push({ text: `${leftCell}${separator}${rightCell}`, hunkIndex: row.hunkIndex });
758
+ }
759
+ }
760
+
761
+ return output;
762
+ }