spacehub

surshar / claude-code

Claude Code is an agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster by executing routine tasks, explaining complex code, and handling git workflows - all through natural language commands.

Built by Anthropic · github.com/anthropics

mods/diff: the closing empty row is the git backend's to add after parsing, so hunks parsed for another backend stay as printedBrowse files
poteat authored on Sep 13, 2026 parent 778a2eb · commit ddb122f0b875
11 changed files+49 -31compared with 778a2eb
Mmods/diff/hooks/git/fetch-file-hunks/fetch-file-hunks.ts
Mmods/diff/hooks/git/fetch-file-hunks/index.ts
Mmods/diff/hooks/git/fetch-file-hunks/is-last-of-diff/is-last-of-diff.ts
Amods/diff/hooks/git/fetch-file-hunks/with-closing-line/index.ts
Amods/diff/hooks/git/fetch-file-hunks/with-closing-line/with-closing-line.ts
Dmods/diff/hooks/git/parse/body-line-of/body-line-of.ts
Dmods/diff/hooks/git/parse/body-line-of/index.ts
Mmods/diff/hooks/git/parse/index.ts
Mmods/diff/hooks/git/parse/is-body-line/is-body-line.ts
Mmods/diff/hooks/git/parse/parse-file-diff.ts
Mmods/diff/tests/views.test.ts
@@ -3,6 +3,7 @@
33 import Parse from '../parse'
44 import type Types from '../types'
55 import { isLastOfDiff } from './is-last-of-diff'
6+import { withClosingLine } from './with-closing-line'
67
78 /**
89 * One file's hunks against the base its row was read against, so body and
@@ -10,7 +11,7 @@
1011 *
1112 * An untracked, binary or renamed row (the built-in shows a rename's
1213 * counts, never its body), or on an unborn HEAD a file edited after
13- * staging, answers no hunks; the diff's last file, no closing empty line.
14+ * staging, answers no hunks; each file but the last, a closing empty row.
1415 *
1516 * @param run runs git against the pinned repository
1617 * @param data the fetch the row belongs to
@@ -40,7 +41,11 @@
4041 file.path,
4142 ])
4243
43- const printed = isLastOfDiff(data, file) ? stdout.replace(/\n$/, '') : stdout
44+ if (exitCode !== 0) {
45+ return null
46+ }
4447
45- return exitCode === 0 ? Parse.parseFileDiff(printed) : null
48+ const body = Parse.parseFileDiff(stdout)
49+
50+ return isLastOfDiff(data, file) ? body : withClosingLine(body)
4651 }
@@ -1,4 +1,5 @@
11 export * from './fetch-file-hunks.js'
22 export * from './is-last-of-diff'
3+export * from './with-closing-line'
34
45 export * as default from '.'
@@ -5,7 +5,7 @@
55 * fetch: the last tracked row, the untracked ones being listed apart.
66 *
77 * The built-in reads that diff in one piece and trims its end, so the last
8- * file alone loses git's closing empty line and the context row it becomes.
8+ * file alone has no closing empty line to draw (withClosingLine).
99 *
1010 * @param data the fetch the row belongs to
1111 * @param file the row
@@ -1,0 +1,3 @@
1+export * from './with-closing-line.js'
2+
3+export * as default from '.'
@@ -1,0 +1,27 @@
1+import type Types from '../../types'
2+
3+/**
4+ * A parsed body with one empty context row after its last hunk's lines, as
5+ * the built-in panel draws the empty line that closes git's output.
6+ *
7+ * The built-in reads every file's diff in one piece: the newline before the
8+ * next file's header parses as a context line of the file before it. A
9+ * body with no hunk, or one already cut, gains nothing.
10+ *
11+ * @param body the parsed body
12+ * @returns the body, its last hunk one row longer
13+ */
14+export function withClosingLine(body: Types.FileHunks): Types.FileHunks {
15+ const last = body.hunks.at(-1)
16+ const isClosed = last !== undefined && !body.isTruncated
17+
18+ return isClosed
19+ ? {
20+ ...body,
21+ hunks: [
22+ ...body.hunks.slice(0, -1),
23+ { ...last, lines: [...last.lines, ' '] },
24+ ],
25+ }
26+ : body
27+}
Dmods/diff/hooks/git/parse/body-line-of/body-line-of.ts+0 -8
@@ -1,8 +1,0 @@
1-/**
2- * A body line as a hunk keeps it: git's closing empty line becomes the
3- * empty context row the built-in panel draws for it; the rest stand.
4- *
5- * @param line a line isBodyLine passed
6- * @returns the line, a lone space for the empty one
7- */
8-export const bodyLineOf = (line: string) => (line === '' ? ' ' : line)
Dmods/diff/hooks/git/parse/body-line-of/index.ts+0 -3
@@ -1,3 +1,0 @@
1-export * from './body-line-of.js'
2-
3-export * as default from '.'
@@ -1,4 +1,3 @@
1-export * from './body-line-of'
21 export * from './hunk-header'
32 export * from './is-body-line'
43 export * from './is-whole-answer'
@@ -2,14 +2,8 @@
22 * Whether a line after a hunk header is a body line (added, removed or
33 * context) rather than a note such as `\ No newline at end of file`.
44 *
5- * The empty line that ends git's output counts, as the built-in panel
6- * counts it: the last hunk of a file closes on one more context row.
7- *
85 * @param line one line of `git diff` output
9- * @returns whether it starts with `+`, `-` or a space, or is empty
6+ * @returns whether it starts with `+`, `-` or a space
107 */
118 export const isBodyLine = (line: string) =>
12- line === '' ||
13- line.startsWith('+') ||
14- line.startsWith('-') ||
15- line.startsWith(' ')
9+ line.startsWith('+') || line.startsWith('-') || line.startsWith(' ')
@@ -3,7 +3,6 @@
33 import { hunkOf } from '../hunk-of'
44 import { LARGE_FILE_HUNKS } from '../large-file-hunks'
55 import type Types from '../types'
6-import { bodyLineOf } from './body-line-of'
76 import { HUNK_HEADER } from './hunk-header'
87 import { isBodyLine } from './is-body-line'
98
@@ -33,10 +32,7 @@
3332 headerRows.map((at, ordinal) =>
3433 hunkOf(
3534 HUNK_HEADER.exec(rows[at] ?? '')?.groups,
36- rows
37- .slice(at + 1, headerRows[ordinal + 1])
38- .filter(isBodyLine)
39- .map(bodyLineOf),
35+ rows.slice(at + 1, headerRows[ordinal + 1]).filter(isBodyLine),
4036 ),
4137 ),
4238 ),
@@ -15,8 +15,12 @@
1515 const drawn = Fixtures.textOf(await $.ui.render(Fixtures.PANE))
1616
1717 expect(drawn).toContain('2 files changed +3 -1')
18- expect(drawn).toContain('+const a = 2')
19- expect(drawn).toContain('+export const c = 2')
18+
19+ expect(drawn, "a closing empty row, as the built-in's").toContain(
20+ '+const a = 2\n ',
21+ )
22+
23+ expect(drawn, 'none after the last file').toMatch(/\+export const c = 2$/)
2024 expect(drawn).not.toContain('❯')
2125 })
2226