Skip to content

Commit 7d4f947

Browse files
committed
diff: the docked window's hunks are made printable and split under a leaf's cap, draw only their lines in view, and stop at the tree's budget
1 parent 9d302a8 commit 7d4f947

8 files changed

Lines changed: 90 additions & 61 deletions

File tree

‎mods/diff/hooks/views/body/draw-window.tsx‎

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,20 @@
44
import type { RenderElement } from 'claude-code'
55

66
import Limits from '../../limits'
7+
import Detail from '../detail'
78
import type { Kit } from '../kit'
9+
import Layout from '../layout'
810
import Sections from '../sections'
911
import Plan from './plan'
10-
import type { BodyLayout, Segment } from './types'
12+
import type { BodyLayout, HunkSegment, Segment, WindowCut } from './types'
1113

1214
/**
1315
* The rows of the docked body its window shows from a row down: each
1416
* segment drawn from the row the window cuts it at, until the window fills.
1517
*
16-
* A hunk cut by the window's top draws from the line holding that row on;
17-
* rows past its foot are the pane's to clip, so a few more are drawn than
18-
* fit (SCROLL_MARGIN_ROWS) lest a miscounted wrap leave it short.
18+
* A hunk draws only its lines in view, and past MAX_BODY_CHARS of hunk
19+
* text the rest waits for a scroll (the tree's cap); rows past the foot
20+
* are the pane's to clip, so SCROLL_MARGIN_ROWS more are drawn than fit.
1921
*
2022
* @param kit the elements, the handlers, the width
2123
* @param layout the body laid out
@@ -27,16 +29,33 @@ export function drawWindow(
2729
layout: BodyLayout,
2830
top: number,
2931
): RenderElement[] {
30-
const { Box, Text } = kit.ui
32+
const { Box, Text, Code } = kit.ui
3133
const drawn: RenderElement[] = []
3234
const wanted = layout.visibleRows + Limits.SCROLL_MARGIN_ROWS
3335
const at = Math.max(0, Math.min(top, layout.maxTop))
3436
const onPress = kit.actions.togglePreSession
3537

3638
let start = 0
3739
let filled = 0
40+
let chars = 0
3841

39-
function elementOf(segment: Segment, skip: number): RenderElement {
42+
function codeOf(segment: HunkSegment, cut: WindowCut): RenderElement {
43+
const source = Detail.hunkSourceOf(Plan.hunkWindowOf(segment, cut))
44+
const path = Layout.sanitizeName(segment.path)
45+
chars += source.length
46+
47+
const isOver = chars > Detail.MAX_BODY_CHARS
48+
49+
return isOver ? (
50+
<Text dimColor italic>
51+
…
52+
</Text>
53+
) : (
54+
<Code source={source} format="diff" path={path} />
55+
)
56+
}
57+
58+
function elementOf(segment: Segment, cut: WindowCut): RenderElement {
4059
switch (segment.kind) {
4160
case 'rule':
4261
return Sections.divider(kit)
@@ -45,7 +64,7 @@ export function drawWindow(
4564
case 'name':
4665
return Sections.nameRow(kit, segment.name)
4766
case 'hunk':
48-
return Plan.hunkCodeOf(kit, segment, skip)
67+
return codeOf(segment, cut)
4968
case 'note':
5069
return (
5170
<Text dimColor italic wrap="wrap">
@@ -74,7 +93,7 @@ export function drawWindow(
7493
if (isShown) {
7594
const skip = Math.max(0, at - start)
7695

77-
drawn.push(elementOf(segment, skip))
96+
drawn.push(elementOf(segment, { skip, take: wanted - filled }))
7897
filled += rows - skip
7998
}
8099

‎mods/diff/hooks/views/body/plan/hunk-code-of.tsx‎

Lines changed: 0 additions & 47 deletions
This file was deleted.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import Git from '../../../git'
2+
import type { HunkSegment, WindowCut } from '../types'
3+
4+
/**
5+
* The part of a hunk the window shows: from the line holding its first
6+
* shown row (hunkAfter renumbers) to the last that starts in the room left.
7+
*
8+
* @param segment the hunk and the rows each of its lines takes
9+
* @param cut the rows hidden above the window and the rows it has left
10+
* @returns the hunk's shown lines
11+
*/
12+
export function hunkWindowOf(segment: HunkSegment, cut: WindowCut): Git.Hunk {
13+
let passed = 0
14+
let first = 0
15+
let shown = 0
16+
let last = 0
17+
18+
for (const rows of segment.lineRows) {
19+
if (passed + rows <= cut.skip) {
20+
first += 1
21+
} else if (shown < cut.take) {
22+
shown += rows
23+
last += 1
24+
}
25+
26+
passed += rows
27+
}
28+
29+
const tail = Git.hunkAfter(segment.hunk, first)
30+
31+
return { ...tail, lines: tail.lines.slice(0, last) }
32+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
export * from './body-layout-of.js'
22
export * from './dock-plan-of.js'
3-
export * from './hunk-code-of.jsx'
3+
export * from './hunk-window-of.js'
44
export * from './segments'
55

66
export * as default from '.'

‎mods/diff/hooks/views/body/plan/segments/segments-of.ts‎

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { countOf } from '../../../../count-of'
2-
import type Git from '../../../../git'
2+
import Git from '../../../../git'
33
import { keptOf } from '../../../../kept-of'
44
import Limits from '../../../../limits'
55
import type PaneState from '../../../../pane-state'
@@ -13,8 +13,8 @@ import type { DockPlan, Segment } from '../../types'
1313
* box): each listed file's block, a blank between, the pre-session section.
1414
*
1515
* Under an empty state whose pre-session section is open, the empty lines
16-
* head the body instead. A hunk carries the rows its lines take wrapped
17-
* past the engine's gutter at the body's width; pre-session bodies stack.
16+
* head the body instead. Hunks are made printable and split under a leaf's
17+
* cap (subHunksOf), each carrying the rows its lines take wrapped.
1818
*
1919
* @param model the pane's state: which file is armed, which section is open
2020
* @param plan the docked pane's decisions over that state
@@ -64,14 +64,27 @@ export function segmentsOf(
6464
}
6565
}
6666

67+
const splitOf = (hunk: Git.Hunk) =>
68+
Detail.subHunksOf(
69+
{
70+
...hunk,
71+
lines: hunk.lines.filter(Git.isBodyLine).map(Detail.drawnLineOf),
72+
},
73+
Detail.MAX_CODE_CHARS,
74+
)
75+
6776
function fileOf(entry: Entries.BodyEntry): Segment[] {
6877
const detail = Entries.detailModelOf(entry, model)
6978
const notes = Detail.placeholderOf(detail)
70-
const isTruncated = entry.body?.isTruncated === true
79+
const splits = (entry.body?.hunks ?? []).map(splitOf)
80+
81+
const isTruncated =
82+
entry.body?.isTruncated === true ||
83+
splits.some(split => split.isTruncated)
7184

7285
const body: Segment[] = notes
7386
? notes.map(text => ({ kind: 'note', text }))
74-
: (entry.body?.hunks ?? []).map(hunk => hunkOf(entry, hunk))
87+
: splits.flatMap(split => split.hunks).map(hunk => hunkOf(entry, hunk))
7588

7689
return [
7790
{ kind: 'rule', path: entry.path },

‎mods/diff/hooks/views/body/types/index.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@ export type * from './body-layout.js'
22
export type * from './dock-plan.js'
33
export type * from './list-window.js'
44
export * from './segment'
5+
export * from './window-cut'
56

67
export * as default from '.'
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export type * from './window-cut.js'
2+
3+
export * as default from '.'
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/**
2+
* How the body's window cuts one hunk: the wrapped rows its top hides, and
3+
* the rows it still has room for from there.
4+
*/
5+
export type WindowCut = {
6+
skip: number
7+
take: number
8+
}

0 commit comments

Comments
 (0)