pi-one-ui 0.1.1

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 (101) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +106 -0
  3. package/extensions/app/config/renderer.ts +452 -0
  4. package/extensions/app/config/shell.ts +2484 -0
  5. package/extensions/app/ownership.ts +18 -0
  6. package/extensions/app/panel.ts +533 -0
  7. package/extensions/app/presets.ts +36 -0
  8. package/extensions/features/agent-summary/core.ts +194 -0
  9. package/extensions/features/agent-summary/index.ts +37 -0
  10. package/extensions/features/compact-thinking.ts +1262 -0
  11. package/extensions/features/context.ts +857 -0
  12. package/extensions/features/reference/index.ts +643 -0
  13. package/extensions/features/reference/session.ts +278 -0
  14. package/extensions/features/reference/subagent.ts +218 -0
  15. package/extensions/features/shell/aliases.ts +28 -0
  16. package/extensions/features/shell/flush-docked-bash.ts +39 -0
  17. package/extensions/features/shell/startup-header.ts +357 -0
  18. package/extensions/features/shell/working-message.ts +226 -0
  19. package/extensions/index.ts +52 -0
  20. package/extensions/shell/accent-rail-editor.ts +112 -0
  21. package/extensions/shell/accent-rail-layout-patch.ts +530 -0
  22. package/extensions/shell/completion-menu.ts +186 -0
  23. package/extensions/shell/editor-metadata-format.ts +287 -0
  24. package/extensions/shell/editor-transfer.ts +73 -0
  25. package/extensions/shell/extension-status.ts +102 -0
  26. package/extensions/shell/footer-format.ts +388 -0
  27. package/extensions/shell/footer-layout.ts +144 -0
  28. package/extensions/shell/footer.ts +1035 -0
  29. package/extensions/shell/format.ts +577 -0
  30. package/extensions/shell/git.ts +434 -0
  31. package/extensions/shell/icons.ts +261 -0
  32. package/extensions/shell/index.ts +1659 -0
  33. package/extensions/shell/interaction-summary.ts +1194 -0
  34. package/extensions/shell/live-context.ts +82 -0
  35. package/extensions/shell/minimalist-editor.ts +548 -0
  36. package/extensions/shell/package-version.ts +675 -0
  37. package/extensions/shell/project-refresh.ts +130 -0
  38. package/extensions/shell/project-state.ts +60 -0
  39. package/extensions/shell/prototype-patch-registry.ts +183 -0
  40. package/extensions/shell/runtime.ts +915 -0
  41. package/extensions/shell/selector-border.ts +109 -0
  42. package/extensions/shell/session-lifecycle.ts +60 -0
  43. package/extensions/shell/settings-command.ts +2144 -0
  44. package/extensions/shell/settings-previews.ts +137 -0
  45. package/extensions/shell/state.ts +83 -0
  46. package/extensions/shell/style.ts +480 -0
  47. package/extensions/shell/telemetry.ts +90 -0
  48. package/extensions/shell/ui.ts +1326 -0
  49. package/extensions/shell/user-message-osc.ts +279 -0
  50. package/extensions/shell/user-message-styles.ts +266 -0
  51. package/extensions/shell/user-message.ts +226 -0
  52. package/extensions/shell/working-line-messages.ts +24 -0
  53. package/extensions/shell/working-line-spinners.ts +54 -0
  54. package/extensions/shell/working-line.ts +1603 -0
  55. package/extensions/tools/ansi-color.ts +44 -0
  56. package/extensions/tools/ansi-text.ts +52 -0
  57. package/extensions/tools/component-tree.ts +37 -0
  58. package/extensions/tools/format.ts +28 -0
  59. package/extensions/tools/fullscreen-detect.ts +11 -0
  60. package/extensions/tools/patch-keys.ts +158 -0
  61. package/extensions/tools/sgr-mouse.ts +59 -0
  62. package/extensions/tools/tool-loading-icon.ts +20 -0
  63. package/extensions/tools/tool-result-sanitize.ts +27 -0
  64. package/extensions/transcript/index.ts +65 -0
  65. package/extensions/transcript/renderer/compact-mode.ts +1443 -0
  66. package/extensions/transcript/renderer/default-mode.ts +735 -0
  67. package/extensions/transcript/renderer/index.ts +286 -0
  68. package/extensions/transcript/renderer/markdown-enhance.ts +325 -0
  69. package/extensions/transcript/renderer/mouse/hover.ts +175 -0
  70. package/extensions/transcript/renderer/mouse/interaction.ts +1016 -0
  71. package/extensions/transcript/renderer/mouse/layout.ts +207 -0
  72. package/extensions/transcript/renderer/mouse/packets.ts +96 -0
  73. package/extensions/transcript/renderer/mouse/scroll.ts +211 -0
  74. package/extensions/transcript/renderer/tool/diff/ATTRIBUTION.md +28 -0
  75. package/extensions/transcript/renderer/tool/diff/ansi-utils.ts +128 -0
  76. package/extensions/transcript/renderer/tool/diff/diff-component.ts +112 -0
  77. package/extensions/transcript/renderer/tool/diff/diff-edit-render.ts +255 -0
  78. package/extensions/transcript/renderer/tool/diff/diff-header.ts +184 -0
  79. package/extensions/transcript/renderer/tool/diff/diff-highlight.ts +131 -0
  80. package/extensions/transcript/renderer/tool/diff/diff-inline.ts +350 -0
  81. package/extensions/transcript/renderer/tool/diff/diff-layout.ts +870 -0
  82. package/extensions/transcript/renderer/tool/diff/diff-limits.ts +189 -0
  83. package/extensions/transcript/renderer/tool/diff/diff-palette.ts +436 -0
  84. package/extensions/transcript/renderer/tool/diff/diff-parse.ts +536 -0
  85. package/extensions/transcript/renderer/tool/diff/diff-presentation.ts +79 -0
  86. package/extensions/transcript/renderer/tool/diff/diff-renderer.ts +29 -0
  87. package/extensions/transcript/renderer/tool/diff/diff-text.ts +57 -0
  88. package/extensions/transcript/renderer/tool/diff/diff-write-render.ts +629 -0
  89. package/extensions/transcript/renderer/tool/diff/index.ts +147 -0
  90. package/extensions/transcript/renderer/tool/diff/line-width-safety.ts +100 -0
  91. package/extensions/transcript/renderer/tool/diff/shiki-highlight.ts +104 -0
  92. package/extensions/transcript/renderer/tool/diff/write-execution.ts +124 -0
  93. package/extensions/transcript/renderer/tool/grouping.ts +619 -0
  94. package/extensions/transcript/renderer/tool/message-display.ts +183 -0
  95. package/extensions/transcript/renderer/tool/names.ts +195 -0
  96. package/extensions/transcript/renderer/tool/result.ts +842 -0
  97. package/extensions/transcript/renderer/tool/show-more-hint.ts +42 -0
  98. package/extensions/transcript/renderer/transcript-refresh.ts +79 -0
  99. package/package.json +58 -0
  100. package/themes/cc-dark.json +91 -0
  101. package/themes/cc-light.json +91 -0
@@ -0,0 +1,870 @@
1
+ import { visibleWidth } from "@earendil-works/pi-tui";
2
+ import {
3
+ fitToWidth,
4
+ normalizeCodeWhitespace,
5
+ wrapToWidth,
6
+ } from "./diff-text.ts";
7
+ import {
8
+ ANSI_BG_RESET,
9
+ applyLineBackgroundToWrappedRows,
10
+ colorizeSegment,
11
+ readThemeAnsi,
12
+ stabilizeBackgroundResets,
13
+ type DiffPalette,
14
+ type DiffTheme,
15
+ } from "./diff-palette.ts";
16
+ import {
17
+ highlightDiffLine,
18
+ type CodeLineHighlighter,
19
+ } from "./diff-highlight.ts";
20
+ import {
21
+ formatLineNumberLabel,
22
+ getCompactLineRenderContent,
23
+ type DiffLineEntry,
24
+ type DiffLineKind,
25
+ type DiffMetaEntry,
26
+ type ParsedDiffEntry,
27
+ } from "./diff-parse.ts";
28
+ import {
29
+ getCellLineNumber,
30
+ type DiffSpan,
31
+ type SplitDiffRow,
32
+ } from "./diff-inline.ts";
33
+ import { formatMetaEntryRows } from "./diff-header.ts";
34
+ import type { DiffIndicatorMode } from "../../../../app/config/renderer.ts";
35
+
36
+ export interface RenderedRow {
37
+ text: string;
38
+ hunkIndex: number | null;
39
+ }
40
+
41
+ interface LineCellRenderParams {
42
+ kind: DiffLineKind;
43
+ code: string;
44
+ width: number;
45
+ rowBg: string | undefined;
46
+ restoreBgAnsi: string | undefined;
47
+ theme: DiffTheme;
48
+ wordWrap: boolean;
49
+ indicatorMode: DiffIndicatorMode;
50
+ }
51
+
52
+ export interface DiffRenderContext {
53
+ width: number;
54
+ theme: DiffTheme;
55
+ inlineHighlights: WeakMap<DiffLineEntry, DiffSpan[]>;
56
+ palette: DiffPalette;
57
+ highlightLine: CodeLineHighlighter;
58
+ containerBgAnsi: string | undefined;
59
+ wordWrap: boolean;
60
+ indicatorMode: DiffIndicatorMode;
61
+ showHashlineAnchors: boolean;
62
+ }
63
+
64
+ const SPLIT_SEPARATOR = " │ ";
65
+ const MIN_SPLIT_COLUMN_WIDTH = 24;
66
+
67
+ function resolveIndicatorGlyph(
68
+ kind: DiffLineKind,
69
+ indicatorMode: DiffIndicatorMode,
70
+ continuation: boolean,
71
+ ): string {
72
+ if (kind === "context") {
73
+ return " ";
74
+ }
75
+
76
+ switch (indicatorMode) {
77
+ case "bars":
78
+ return "▌";
79
+ case "classic":
80
+ if (continuation) {
81
+ return " ";
82
+ }
83
+ return kind === "add" ? "+" : "-";
84
+ case "none":
85
+ default:
86
+ return " ";
87
+ }
88
+ }
89
+
90
+ function renderChangeMarker(
91
+ kind: DiffLineKind,
92
+ theme: DiffTheme,
93
+ rowBg: string | undefined,
94
+ indicatorMode: DiffIndicatorMode,
95
+ continuation = false,
96
+ ): string {
97
+ const glyph = resolveIndicatorGlyph(kind, indicatorMode, continuation);
98
+ if (glyph === " ") {
99
+ return rowBg ? `${rowBg} ${rowBg}` : " ";
100
+ }
101
+ if (kind === "add") {
102
+ return colorizeSegment(theme, "toolDiffAdded", glyph, rowBg);
103
+ }
104
+ if (kind === "remove") {
105
+ return colorizeSegment(theme, "toolDiffRemoved", glyph, rowBg);
106
+ }
107
+ return colorizeSegment(theme, "dim", glyph, rowBg);
108
+ }
109
+
110
+ function usesHashlineGutter(showHashlineAnchors: boolean): boolean {
111
+ return showHashlineAnchors;
112
+ }
113
+
114
+ function getHashlineGutterMarkerWidth(
115
+ _indicatorMode: DiffIndicatorMode,
116
+ ): number {
117
+ return 0;
118
+ }
119
+
120
+ function getLineDividerPlainWidth(
121
+ indicatorMode: DiffIndicatorMode,
122
+ hashlineGutter = false,
123
+ ): number {
124
+ if (hashlineGutter) {
125
+ return 2;
126
+ }
127
+ return indicatorMode === "classic" ? 1 : 2;
128
+ }
129
+
130
+ function renderCodeDivider(
131
+ theme: DiffTheme,
132
+ rowBg: string | undefined,
133
+ indicatorMode: DiffIndicatorMode,
134
+ hashlineGutter = false,
135
+ ): string {
136
+ return colorizeSegment(
137
+ theme,
138
+ "dim",
139
+ hashlineGutter || indicatorMode !== "classic" ? "│ " : "│",
140
+ rowBg,
141
+ );
142
+ }
143
+
144
+ function getLineNumberColor(
145
+ kind: DiffLineKind,
146
+ ): "dim" | "toolDiffAdded" | "toolDiffRemoved" {
147
+ if (kind === "add") {
148
+ return "toolDiffAdded";
149
+ }
150
+ if (kind === "remove") {
151
+ return "toolDiffRemoved";
152
+ }
153
+ return "dim";
154
+ }
155
+
156
+ function renderLineNumberSegment(
157
+ kind: DiffLineKind,
158
+ lineNumber: string,
159
+ theme: DiffTheme,
160
+ rowBg: string | undefined,
161
+ ): string {
162
+ return colorizeSegment(theme, getLineNumberColor(kind), lineNumber, rowBg);
163
+ }
164
+
165
+ function getLinePrefixPlainWidth(
166
+ lineNumberWidth: number,
167
+ indicatorMode: DiffIndicatorMode,
168
+ hashlineGutter = false,
169
+ ): number {
170
+ if (hashlineGutter) {
171
+ return getHashlineGutterMarkerWidth(indicatorMode) + lineNumberWidth;
172
+ }
173
+ return indicatorMode === "bars"
174
+ ? visibleWidth(`▌ ${" ".repeat(lineNumberWidth)} `)
175
+ : visibleWidth(`${" ".repeat(lineNumberWidth)} `);
176
+ }
177
+
178
+ function getLineContentIndicatorPrefixPlainWidth(
179
+ indicatorMode: DiffIndicatorMode,
180
+ ): number {
181
+ return indicatorMode === "classic" ? 2 : 0;
182
+ }
183
+
184
+ function renderClassicContentPrefix(
185
+ kind: DiffLineKind,
186
+ theme: DiffTheme,
187
+ rowBg: string | undefined,
188
+ continuation = false,
189
+ ): string {
190
+ if (kind === "context" || continuation) {
191
+ return rowBg ? `${rowBg} ${rowBg}` : " ";
192
+ }
193
+
194
+ const glyph = kind === "add" ? "+" : "-";
195
+ const glyphColor = kind === "add" ? "toolDiffAdded" : "toolDiffRemoved";
196
+ const spacer = rowBg ? `${rowBg} ` : " ";
197
+ return `${colorizeSegment(theme, glyphColor, glyph, rowBg)}${spacer}`;
198
+ }
199
+
200
+ function renderLinePrefix(
201
+ kind: DiffLineKind,
202
+ lineNumber: string,
203
+ theme: DiffTheme,
204
+ rowBg: string | undefined,
205
+ indicatorMode: DiffIndicatorMode,
206
+ continuation = false,
207
+ hashlineGutter = false,
208
+ ): string {
209
+ const number = renderLineNumberSegment(kind, lineNumber, theme, rowBg);
210
+ if (hashlineGutter) {
211
+ return number;
212
+ }
213
+ const spacer = rowBg ? `${rowBg} ` : " ";
214
+ if (indicatorMode !== "bars") {
215
+ return `${number}${spacer}`;
216
+ }
217
+ const marker = renderChangeMarker(
218
+ kind,
219
+ theme,
220
+ rowBg,
221
+ indicatorMode,
222
+ continuation,
223
+ );
224
+ return `${marker}${spacer}${number}${spacer}`;
225
+ }
226
+
227
+ function renderLineContinuationPrefix(
228
+ kind: DiffLineKind,
229
+ lineNumberWidth: number,
230
+ rowBg: string | undefined,
231
+ theme: DiffTheme,
232
+ indicatorMode: DiffIndicatorMode,
233
+ hashlineGutter = false,
234
+ ): string {
235
+ const blankLineNumber = " ".repeat(lineNumberWidth);
236
+ return renderLinePrefix(
237
+ kind,
238
+ blankLineNumber,
239
+ theme,
240
+ rowBg,
241
+ indicatorMode,
242
+ true,
243
+ hashlineGutter,
244
+ );
245
+ }
246
+
247
+ function renderLineContentIndicatorPrefix(
248
+ kind: DiffLineKind,
249
+ theme: DiffTheme,
250
+ rowBg: string | undefined,
251
+ indicatorMode: DiffIndicatorMode,
252
+ continuation = false,
253
+ ): string {
254
+ return indicatorMode === "classic"
255
+ ? renderClassicContentPrefix(kind, theme, rowBg, continuation)
256
+ : "";
257
+ }
258
+
259
+ function renderCompactLinePrefix(
260
+ kind: DiffLineKind,
261
+ theme: DiffTheme,
262
+ rowBg: string | undefined,
263
+ indicatorMode: DiffIndicatorMode,
264
+ continuation = false,
265
+ ): string {
266
+ const marker = renderChangeMarker(
267
+ kind,
268
+ theme,
269
+ rowBg,
270
+ indicatorMode,
271
+ continuation,
272
+ );
273
+ const spacer = rowBg ? `${rowBg} ` : " ";
274
+ return `${marker}${spacer}`;
275
+ }
276
+
277
+ function renderWrappedRowsWithOptionalBackground(
278
+ wrappedCodeLines: string[],
279
+ buildRow: (index: number, wrappedCodeLine: string) => string,
280
+ width: number,
281
+ rowBg: string | undefined,
282
+ restoreBgAnsi: string | undefined,
283
+ ): string[] {
284
+ if (!rowBg) {
285
+ return wrappedCodeLines.map((wrappedCodeLine, index) =>
286
+ stabilizeBackgroundResets(buildRow(index, wrappedCodeLine)),
287
+ );
288
+ }
289
+ const safeRestoreBgAnsi = restoreBgAnsi ?? rowBg ?? ANSI_BG_RESET;
290
+ const visualRows = wrappedCodeLines.map((wrappedCodeLine, index) =>
291
+ buildRow(index, wrappedCodeLine),
292
+ );
293
+ return applyLineBackgroundToWrappedRows(
294
+ visualRows,
295
+ width,
296
+ rowBg,
297
+ safeRestoreBgAnsi,
298
+ );
299
+ }
300
+
301
+ function computeLineCellCodeWidth(
302
+ width: number,
303
+ lineNumberWidth: number,
304
+ indicatorMode: DiffIndicatorMode,
305
+ hashlineGutter: boolean,
306
+ ): number {
307
+ const prefixPlainWidth = getLinePrefixPlainWidth(
308
+ lineNumberWidth,
309
+ indicatorMode,
310
+ hashlineGutter,
311
+ );
312
+ const dividerPlainWidth = getLineDividerPlainWidth(
313
+ indicatorMode,
314
+ hashlineGutter,
315
+ );
316
+ const contentIndicatorWidth = hashlineGutter
317
+ ? 0
318
+ : getLineContentIndicatorPrefixPlainWidth(indicatorMode);
319
+ return Math.max(
320
+ 0,
321
+ width - prefixPlainWidth - dividerPlainWidth - contentIndicatorWidth,
322
+ );
323
+ }
324
+
325
+ function buildLineCellParams(
326
+ kind: DiffLineKind,
327
+ code: string,
328
+ width: number,
329
+ rowBg: string | undefined,
330
+ restoreBgAnsi: string | undefined,
331
+ theme: DiffTheme,
332
+ wordWrap: boolean,
333
+ indicatorMode: DiffIndicatorMode,
334
+ ): LineCellRenderParams {
335
+ return {
336
+ kind,
337
+ code,
338
+ width,
339
+ rowBg,
340
+ restoreBgAnsi,
341
+ theme,
342
+ wordWrap,
343
+ indicatorMode,
344
+ };
345
+ }
346
+
347
+ function renderCompactLineCell({
348
+ kind,
349
+ code,
350
+ width,
351
+ rowBg,
352
+ restoreBgAnsi,
353
+ theme,
354
+ wordWrap,
355
+ indicatorMode,
356
+ }: LineCellRenderParams): string[] {
357
+ if (width <= 0) {
358
+ return [""];
359
+ }
360
+
361
+ const prefix = renderCompactLinePrefix(kind, theme, undefined, indicatorMode);
362
+ const continuationPrefix = renderCompactLinePrefix(
363
+ kind,
364
+ theme,
365
+ undefined,
366
+ indicatorMode,
367
+ true,
368
+ );
369
+ const prefixPlainWidth = 2;
370
+ const codeWidth = Math.max(0, width - prefixPlainWidth);
371
+ const wrappedCodeLines = wrapToWidth(code, codeWidth, wordWrap);
372
+ return renderWrappedRowsWithOptionalBackground(
373
+ wrappedCodeLines,
374
+ (index, line) => `${index === 0 ? prefix : continuationPrefix}${line}`,
375
+ width,
376
+ rowBg,
377
+ restoreBgAnsi,
378
+ );
379
+ }
380
+
381
+ function renderLineCell(
382
+ {
383
+ kind,
384
+ code,
385
+ width,
386
+ rowBg,
387
+ restoreBgAnsi,
388
+ theme,
389
+ wordWrap,
390
+ indicatorMode,
391
+ }: LineCellRenderParams,
392
+ lineNumber: string,
393
+ hashlineGutter = false,
394
+ ): string[] {
395
+ if (width <= 0) {
396
+ return [""];
397
+ }
398
+
399
+ const codeWidth = computeLineCellCodeWidth(
400
+ width,
401
+ lineNumber.length,
402
+ indicatorMode,
403
+ hashlineGutter,
404
+ );
405
+ const prefix = renderLinePrefix(
406
+ kind,
407
+ lineNumber,
408
+ theme,
409
+ undefined,
410
+ indicatorMode,
411
+ false,
412
+ hashlineGutter,
413
+ );
414
+ const continuationPrefix = renderLineContinuationPrefix(
415
+ kind,
416
+ lineNumber.length,
417
+ undefined,
418
+ theme,
419
+ indicatorMode,
420
+ hashlineGutter,
421
+ );
422
+ const divider = renderCodeDivider(
423
+ theme,
424
+ undefined,
425
+ indicatorMode,
426
+ hashlineGutter,
427
+ );
428
+ const firstContentPrefix = hashlineGutter
429
+ ? ""
430
+ : renderLineContentIndicatorPrefix(kind, theme, undefined, indicatorMode);
431
+ const continuationContentPrefix = hashlineGutter
432
+ ? ""
433
+ : renderLineContentIndicatorPrefix(
434
+ kind,
435
+ theme,
436
+ undefined,
437
+ indicatorMode,
438
+ true,
439
+ );
440
+ const wrappedCodeLines = wrapToWidth(code, codeWidth, wordWrap);
441
+ return renderWrappedRowsWithOptionalBackground(
442
+ wrappedCodeLines,
443
+ (index, line) => {
444
+ const linePrefix = index === 0 ? prefix : continuationPrefix;
445
+ const contentPrefix =
446
+ index === 0 ? firstContentPrefix : continuationContentPrefix;
447
+ return `${linePrefix}${divider}${contentPrefix}${line}`;
448
+ },
449
+ width,
450
+ rowBg,
451
+ restoreBgAnsi,
452
+ );
453
+ }
454
+
455
+ function pushDiffLineRows(
456
+ rows: RenderedRow[],
457
+ lines: string[],
458
+ entry: DiffLineEntry,
459
+ ): void {
460
+ rows.push(
461
+ ...lines.map((text) => ({
462
+ text,
463
+ hunkIndex: entry.hunkIndex || null,
464
+ })),
465
+ );
466
+ }
467
+
468
+ function processDiffEntries(
469
+ entries: ParsedDiffEntry[],
470
+ ctx: DiffRenderContext,
471
+ processLine: (entry: DiffLineEntry) => string[],
472
+ ): RenderedRow[] {
473
+ const { width, theme, wordWrap } = ctx;
474
+ const rows: RenderedRow[] = [];
475
+ for (const entry of entries) {
476
+ if (entry.kind !== "line") {
477
+ rows.push(...formatMetaEntryRows(entry, width, theme, wordWrap));
478
+ continue;
479
+ }
480
+ pushDiffLineRows(rows, processLine(entry), entry);
481
+ }
482
+ return rows;
483
+ }
484
+
485
+ export function renderUnified(
486
+ entries: ParsedDiffEntry[],
487
+ ctx: DiffRenderContext,
488
+ lineNumberWidth: number,
489
+ ): RenderedRow[] {
490
+ return processDiffEntries(entries, ctx, (entry) => {
491
+ const lineNumber =
492
+ entry.lineKind === "add"
493
+ ? formatLineNumberLabel(
494
+ entry,
495
+ entry.newLineNumber,
496
+ entry.fallbackLineNumber,
497
+ lineNumberWidth,
498
+ ctx.showHashlineAnchors,
499
+ )
500
+ : formatLineNumberLabel(
501
+ entry,
502
+ entry.oldLineNumber,
503
+ entry.fallbackLineNumber,
504
+ lineNumberWidth,
505
+ ctx.showHashlineAnchors,
506
+ );
507
+ const codeText = normalizeCodeWhitespace(entry.content);
508
+ const { highlighted, rowBg } = highlightDiffLine(
509
+ codeText,
510
+ entry,
511
+ ctx.inlineHighlights,
512
+ ctx.palette,
513
+ ctx.highlightLine,
514
+ ctx.containerBgAnsi,
515
+ );
516
+ return renderLineCell(
517
+ buildLineCellParams(
518
+ entry.lineKind,
519
+ highlighted,
520
+ ctx.width,
521
+ rowBg,
522
+ ctx.containerBgAnsi,
523
+ ctx.theme,
524
+ ctx.wordWrap,
525
+ ctx.indicatorMode,
526
+ ),
527
+ lineNumber,
528
+ usesHashlineGutter(ctx.showHashlineAnchors),
529
+ );
530
+ });
531
+ }
532
+
533
+ function toUnifiedFallbackRows(
534
+ rows: SplitDiffRow[],
535
+ ctx: DiffRenderContext,
536
+ lineNumberWidth: number,
537
+ ): RenderedRow[] {
538
+ const flattened: ParsedDiffEntry[] = [];
539
+ for (const row of rows) {
540
+ if (row.meta) {
541
+ flattened.push(row.meta);
542
+ continue;
543
+ }
544
+ if (row.left) {
545
+ flattened.push(row.left);
546
+ }
547
+ if (row.right && row.right !== row.left) {
548
+ flattened.push(row.right);
549
+ }
550
+ }
551
+ return renderUnified(flattened, ctx, lineNumberWidth);
552
+ }
553
+
554
+ export function renderCompact(
555
+ entries: ParsedDiffEntry[],
556
+ ctx: DiffRenderContext,
557
+ ): RenderedRow[] {
558
+ return processDiffEntries(entries, ctx, (entry) => {
559
+ const codeText = normalizeCodeWhitespace(
560
+ getCompactLineRenderContent(entry, ctx.showHashlineAnchors),
561
+ );
562
+ const { highlighted, rowBg } = highlightDiffLine(
563
+ codeText,
564
+ entry,
565
+ ctx.inlineHighlights,
566
+ ctx.palette,
567
+ ctx.highlightLine,
568
+ ctx.containerBgAnsi,
569
+ );
570
+ return renderCompactLineCell(
571
+ buildLineCellParams(
572
+ entry.lineKind,
573
+ highlighted,
574
+ ctx.width,
575
+ rowBg,
576
+ ctx.containerBgAnsi,
577
+ ctx.theme,
578
+ ctx.wordWrap,
579
+ ctx.indicatorMode,
580
+ ),
581
+ );
582
+ });
583
+ }
584
+
585
+ function renderSplitBlankCell(
586
+ columnWidth: number,
587
+ lineNumberWidth: number,
588
+ theme: DiffTheme,
589
+ indicatorMode: DiffIndicatorMode,
590
+ hashlineGutter = false,
591
+ ): string {
592
+ const codeWidth = computeLineCellCodeWidth(
593
+ columnWidth,
594
+ lineNumberWidth,
595
+ indicatorMode,
596
+ hashlineGutter,
597
+ );
598
+ const prefix = renderLinePrefix(
599
+ "context",
600
+ " ".repeat(lineNumberWidth),
601
+ theme,
602
+ undefined,
603
+ indicatorMode,
604
+ true,
605
+ hashlineGutter,
606
+ );
607
+ const divider = renderCodeDivider(
608
+ theme,
609
+ undefined,
610
+ indicatorMode,
611
+ hashlineGutter,
612
+ );
613
+ const contentPrefix = hashlineGutter
614
+ ? ""
615
+ : renderLineContentIndicatorPrefix(
616
+ "context",
617
+ theme,
618
+ undefined,
619
+ indicatorMode,
620
+ true,
621
+ );
622
+ return stabilizeBackgroundResets(
623
+ `${prefix}${divider}${contentPrefix}${" ".repeat(codeWidth)}`,
624
+ );
625
+ }
626
+
627
+ function renderSplitCell(
628
+ line: DiffLineEntry | undefined,
629
+ side: "left" | "right",
630
+ columnWidth: number,
631
+ lineNumberWidth: number,
632
+ theme: DiffTheme,
633
+ inlineHighlights: WeakMap<DiffLineEntry, DiffSpan[]>,
634
+ palette: DiffPalette,
635
+ highlightLine: CodeLineHighlighter,
636
+ containerBgAnsi: string | undefined,
637
+ wordWrap: boolean,
638
+ indicatorMode: DiffIndicatorMode,
639
+ showHashlineAnchors: boolean,
640
+ ): string[] {
641
+ const hashlineGutter = usesHashlineGutter(showHashlineAnchors);
642
+ if (!line) {
643
+ return [
644
+ renderSplitBlankCell(
645
+ columnWidth,
646
+ lineNumberWidth,
647
+ theme,
648
+ indicatorMode,
649
+ hashlineGutter,
650
+ ),
651
+ ];
652
+ }
653
+
654
+ const lineNumber = formatLineNumberLabel(
655
+ line,
656
+ getCellLineNumber(line, side),
657
+ line.fallbackLineNumber,
658
+ lineNumberWidth,
659
+ showHashlineAnchors,
660
+ );
661
+ const codeText = normalizeCodeWhitespace(line.content);
662
+ const { highlighted, rowBg } = highlightDiffLine(
663
+ codeText,
664
+ line,
665
+ inlineHighlights,
666
+ palette,
667
+ highlightLine,
668
+ containerBgAnsi,
669
+ );
670
+ return renderLineCell(
671
+ buildLineCellParams(
672
+ line.lineKind,
673
+ highlighted,
674
+ columnWidth,
675
+ rowBg,
676
+ containerBgAnsi,
677
+ theme,
678
+ wordWrap,
679
+ indicatorMode,
680
+ ),
681
+ lineNumber,
682
+ hashlineGutter,
683
+ );
684
+ }
685
+
686
+ function renderSplitDivider(
687
+ theme: DiffTheme,
688
+ containerBgAnsi: string | undefined,
689
+ separatorText: string = SPLIT_SEPARATOR,
690
+ ): string {
691
+ const dimAnsi = readThemeAnsi(theme, "fg", "dim");
692
+ if (!containerBgAnsi) {
693
+ return stabilizeBackgroundResets(theme.fg("dim", separatorText));
694
+ }
695
+ if (!dimAnsi) {
696
+ return stabilizeBackgroundResets(
697
+ `${containerBgAnsi}${theme.fg("dim", separatorText)}${containerBgAnsi}`,
698
+ );
699
+ }
700
+ return stabilizeBackgroundResets(
701
+ `${containerBgAnsi}${dimAnsi}${separatorText}\x1b[39m${containerBgAnsi}`,
702
+ );
703
+ }
704
+
705
+ function renderSplitTopBorderCell(
706
+ columnWidth: number,
707
+ lineNumberWidth: number,
708
+ theme: DiffTheme,
709
+ indicatorMode: DiffIndicatorMode,
710
+ hashlineGutter = false,
711
+ ): string {
712
+ const safeColumnWidth = Math.max(1, columnWidth);
713
+ const chars = "─".repeat(safeColumnWidth).split("");
714
+ const dividerIndex = getLinePrefixPlainWidth(
715
+ lineNumberWidth,
716
+ indicatorMode,
717
+ hashlineGutter,
718
+ );
719
+ if (dividerIndex >= 0 && dividerIndex < chars.length) {
720
+ chars[dividerIndex] = "┬";
721
+ }
722
+ return stabilizeBackgroundResets(theme.fg("dim", chars.join("")));
723
+ }
724
+
725
+ function renderSplitHeaderCell(
726
+ label: string,
727
+ columnWidth: number,
728
+ lineNumberWidth: number,
729
+ theme: DiffTheme,
730
+ indicatorMode: DiffIndicatorMode,
731
+ hashlineGutter = false,
732
+ ): string {
733
+ const markerPad = hashlineGutter
734
+ ? " ".repeat(getHashlineGutterMarkerWidth(indicatorMode))
735
+ : indicatorMode === "bars"
736
+ ? " "
737
+ : "";
738
+ const lineNumberLabel = fitToWidth(label, lineNumberWidth);
739
+ const lineNumberSpacer = hashlineGutter ? "" : " ";
740
+ const divider = hashlineGutter || indicatorMode !== "classic" ? "│ " : "│";
741
+ const prefix = `${theme.fg("dim", markerPad)}${theme.fg("muted", lineNumberLabel)}${theme.fg("dim", lineNumberSpacer)}${theme.fg("dim", divider)}`;
742
+ const prefixWidth = visibleWidth(
743
+ `${markerPad}${lineNumberLabel}${lineNumberSpacer}${divider}`,
744
+ );
745
+ const contentIndicatorWidth = hashlineGutter
746
+ ? 0
747
+ : getLineContentIndicatorPrefixPlainWidth(indicatorMode);
748
+ const codeWidth = Math.max(
749
+ 0,
750
+ columnWidth - prefixWidth - contentIndicatorWidth,
751
+ );
752
+ const contentPad = !hashlineGutter && indicatorMode === "classic" ? " " : "";
753
+ return stabilizeBackgroundResets(
754
+ `${prefix}${contentPad}${" ".repeat(codeWidth)}`,
755
+ );
756
+ }
757
+
758
+ export function canRenderSplitLayout(width: number): boolean {
759
+ const separatorWidth = visibleWidth(SPLIT_SEPARATOR);
760
+ const minimumSplitWidth = MIN_SPLIT_COLUMN_WIDTH * 2 + separatorWidth;
761
+ return width >= minimumSplitWidth;
762
+ }
763
+
764
+ export function renderSplit(
765
+ rows: SplitDiffRow[],
766
+ ctx: DiffRenderContext,
767
+ lineNumberWidth: number,
768
+ ): RenderedRow[] {
769
+ const {
770
+ width,
771
+ theme,
772
+ inlineHighlights,
773
+ palette,
774
+ highlightLine,
775
+ containerBgAnsi,
776
+ wordWrap,
777
+ indicatorMode,
778
+ showHashlineAnchors,
779
+ } = ctx;
780
+ if (!canRenderSplitLayout(width)) {
781
+ return toUnifiedFallbackRows(rows, ctx, lineNumberWidth);
782
+ }
783
+
784
+ const separatorWidth = visibleWidth(SPLIT_SEPARATOR);
785
+ const leftWidth = Math.max(
786
+ MIN_SPLIT_COLUMN_WIDTH,
787
+ Math.floor((width - separatorWidth) / 2),
788
+ );
789
+ const rightWidth = Math.max(
790
+ MIN_SPLIT_COLUMN_WIDTH,
791
+ width - separatorWidth - leftWidth,
792
+ );
793
+ const splitLineNumberWidth = Math.max(3, lineNumberWidth);
794
+ const hashlineGutter = usesHashlineGutter(showHashlineAnchors);
795
+ const separator = renderSplitDivider(theme, containerBgAnsi);
796
+ const topSeparator = renderSplitDivider(theme, containerBgAnsi, "─┬─");
797
+ const output: RenderedRow[] = [];
798
+ output.push({
799
+ text: `${renderSplitTopBorderCell(leftWidth, splitLineNumberWidth, theme, indicatorMode, hashlineGutter)}${topSeparator}${renderSplitTopBorderCell(rightWidth, splitLineNumberWidth, theme, indicatorMode, hashlineGutter)}`,
800
+ hunkIndex: null,
801
+ });
802
+ output.push({
803
+ text: `${renderSplitHeaderCell("old", leftWidth, splitLineNumberWidth, theme, indicatorMode, hashlineGutter)}${separator}${renderSplitHeaderCell("new", rightWidth, splitLineNumberWidth, theme, indicatorMode, hashlineGutter)}`,
804
+ hunkIndex: null,
805
+ });
806
+
807
+ for (const row of rows) {
808
+ if (row.meta) {
809
+ output.push(...formatMetaEntryRows(row.meta, width, theme, wordWrap));
810
+ continue;
811
+ }
812
+
813
+ const leftCells = renderSplitCell(
814
+ row.left,
815
+ "left",
816
+ leftWidth,
817
+ splitLineNumberWidth,
818
+ theme,
819
+ inlineHighlights,
820
+ palette,
821
+ highlightLine,
822
+ containerBgAnsi,
823
+ wordWrap,
824
+ indicatorMode,
825
+ showHashlineAnchors,
826
+ );
827
+ const rightCells = renderSplitCell(
828
+ row.right,
829
+ "right",
830
+ rightWidth,
831
+ splitLineNumberWidth,
832
+ theme,
833
+ inlineHighlights,
834
+ palette,
835
+ highlightLine,
836
+ containerBgAnsi,
837
+ wordWrap,
838
+ indicatorMode,
839
+ showHashlineAnchors,
840
+ );
841
+
842
+ const rowCount = Math.max(leftCells.length, rightCells.length);
843
+ for (let index = 0; index < rowCount; index++) {
844
+ const leftCell =
845
+ leftCells[index] ??
846
+ renderSplitBlankCell(
847
+ leftWidth,
848
+ splitLineNumberWidth,
849
+ theme,
850
+ indicatorMode,
851
+ hashlineGutter,
852
+ );
853
+ const rightCell =
854
+ rightCells[index] ??
855
+ renderSplitBlankCell(
856
+ rightWidth,
857
+ splitLineNumberWidth,
858
+ theme,
859
+ indicatorMode,
860
+ hashlineGutter,
861
+ );
862
+ output.push({
863
+ text: `${leftCell}${separator}${rightCell}`,
864
+ hunkIndex: row.hunkIndex,
865
+ });
866
+ }
867
+ }
868
+
869
+ return output;
870
+ }