@kitlangton/ghui 0.1.17 → 0.1.19
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 +11 -4
- package/package.json +2 -1
- package/src/App.tsx +983 -238
- package/src/domain.ts +77 -10
- package/src/services/CommandRunner.ts +8 -1
- package/src/services/GitHubService.ts +291 -193
- package/src/ui/DetailsPane.tsx +26 -25
- package/src/ui/FooterHints.tsx +62 -6
- package/src/ui/PullRequestDiffPane.tsx +160 -49
- package/src/ui/PullRequestList.tsx +28 -10
- package/src/ui/colors.ts +41 -0
- package/src/ui/commentEditor.ts +126 -0
- package/src/ui/diff.ts +229 -16
- package/src/ui/modals.tsx +236 -9
- package/src/ui/primitives.tsx +2 -2
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
export interface CommentEditorValue {
|
|
2
|
+
readonly body: string
|
|
3
|
+
readonly cursor: number
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface CommentEditorLine {
|
|
7
|
+
readonly text: string
|
|
8
|
+
readonly start: number
|
|
9
|
+
readonly end: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const clampCursor = (body: string, cursor: number) => Math.max(0, Math.min(cursor, body.length))
|
|
13
|
+
|
|
14
|
+
export const commentEditorLines = (body: string): readonly CommentEditorLine[] => body.split("\n").reduce<CommentEditorLine[]>((ranges, text) => {
|
|
15
|
+
const start = ranges.length === 0 ? 0 : ranges[ranges.length - 1]!.end + 1
|
|
16
|
+
ranges.push({ text, start, end: start + text.length })
|
|
17
|
+
return ranges
|
|
18
|
+
}, [])
|
|
19
|
+
|
|
20
|
+
export const cursorLineIndexForLines = (lines: readonly CommentEditorLine[], cursor: number) =>
|
|
21
|
+
Math.max(0, lines.findIndex((line, index) => cursor <= line.end || index === lines.length - 1))
|
|
22
|
+
|
|
23
|
+
export const cursorLineIndex = (body: string, cursor: number) => {
|
|
24
|
+
const lines = commentEditorLines(body)
|
|
25
|
+
const safeCursor = clampCursor(body, cursor)
|
|
26
|
+
return cursorLineIndexForLines(lines, safeCursor)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const lineStartAt = (body: string, cursor: number) => body.lastIndexOf("\n", Math.max(0, clampCursor(body, cursor) - 1)) + 1
|
|
30
|
+
|
|
31
|
+
export const lineEndAt = (body: string, cursor: number) => {
|
|
32
|
+
const end = body.indexOf("\n", clampCursor(body, cursor))
|
|
33
|
+
return end === -1 ? body.length : end
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const previousWordStart = (body: string, cursor: number) => {
|
|
37
|
+
let index = clampCursor(body, cursor)
|
|
38
|
+
while (index > 0 && /\s/.test(body[index - 1]!)) index--
|
|
39
|
+
while (index > 0 && !/\s/.test(body[index - 1]!)) index--
|
|
40
|
+
return index
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const nextWordEnd = (body: string, cursor: number) => {
|
|
44
|
+
let index = clampCursor(body, cursor)
|
|
45
|
+
while (index < body.length && /\s/.test(body[index]!)) index++
|
|
46
|
+
while (index < body.length && !/\s/.test(body[index]!)) index++
|
|
47
|
+
return index
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const replaceRange = (state: CommentEditorValue, start: number, end: number, text = ""): CommentEditorValue => ({
|
|
51
|
+
body: `${state.body.slice(0, start)}${text}${state.body.slice(end)}`,
|
|
52
|
+
cursor: start + text.length,
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
export const insertText = (state: CommentEditorValue, text: string): CommentEditorValue =>
|
|
56
|
+
replaceRange(state, clampCursor(state.body, state.cursor), clampCursor(state.body, state.cursor), text)
|
|
57
|
+
|
|
58
|
+
export const moveLeft = (state: CommentEditorValue): CommentEditorValue => ({
|
|
59
|
+
...state,
|
|
60
|
+
cursor: Math.max(0, clampCursor(state.body, state.cursor) - 1),
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
export const moveRight = (state: CommentEditorValue): CommentEditorValue => ({
|
|
64
|
+
...state,
|
|
65
|
+
cursor: Math.min(state.body.length, clampCursor(state.body, state.cursor) + 1),
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
export const moveLineStart = (state: CommentEditorValue): CommentEditorValue => ({
|
|
69
|
+
...state,
|
|
70
|
+
cursor: lineStartAt(state.body, state.cursor),
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
export const moveLineEnd = (state: CommentEditorValue): CommentEditorValue => ({
|
|
74
|
+
...state,
|
|
75
|
+
cursor: lineEndAt(state.body, state.cursor),
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
export const moveWordBackward = (state: CommentEditorValue): CommentEditorValue => ({
|
|
79
|
+
...state,
|
|
80
|
+
cursor: previousWordStart(state.body, state.cursor),
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
export const moveWordForward = (state: CommentEditorValue): CommentEditorValue => ({
|
|
84
|
+
...state,
|
|
85
|
+
cursor: nextWordEnd(state.body, state.cursor),
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
export const moveVertically = (state: CommentEditorValue, delta: number): CommentEditorValue => {
|
|
89
|
+
const lines = commentEditorLines(state.body)
|
|
90
|
+
const safeCursor = clampCursor(state.body, state.cursor)
|
|
91
|
+
const currentLineIndex = cursorLineIndexForLines(lines, safeCursor)
|
|
92
|
+
const currentLine = lines[currentLineIndex] ?? { text: state.body, start: 0, end: state.body.length }
|
|
93
|
+
const targetLine = lines[Math.max(0, Math.min(lines.length - 1, currentLineIndex + delta))] ?? currentLine
|
|
94
|
+
const column = safeCursor - currentLine.start
|
|
95
|
+
return { ...state, cursor: Math.min(targetLine.end, targetLine.start + column) }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export const backspace = (state: CommentEditorValue): CommentEditorValue => {
|
|
99
|
+
const cursor = clampCursor(state.body, state.cursor)
|
|
100
|
+
return cursor === 0 ? { ...state, cursor } : replaceRange({ ...state, cursor }, cursor - 1, cursor)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export const deleteForward = (state: CommentEditorValue): CommentEditorValue => {
|
|
104
|
+
const cursor = clampCursor(state.body, state.cursor)
|
|
105
|
+
return cursor >= state.body.length ? { ...state, cursor } : replaceRange({ ...state, cursor }, cursor, cursor + 1)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export const deleteWordBackward = (state: CommentEditorValue): CommentEditorValue => {
|
|
109
|
+
const cursor = clampCursor(state.body, state.cursor)
|
|
110
|
+
return replaceRange({ ...state, cursor }, previousWordStart(state.body, cursor), cursor)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export const deleteWordForward = (state: CommentEditorValue): CommentEditorValue => {
|
|
114
|
+
const cursor = clampCursor(state.body, state.cursor)
|
|
115
|
+
return replaceRange({ ...state, cursor }, cursor, nextWordEnd(state.body, cursor))
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export const deleteToLineStart = (state: CommentEditorValue): CommentEditorValue => {
|
|
119
|
+
const cursor = clampCursor(state.body, state.cursor)
|
|
120
|
+
return replaceRange({ ...state, cursor }, lineStartAt(state.body, cursor), cursor)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export const deleteToLineEnd = (state: CommentEditorValue): CommentEditorValue => {
|
|
124
|
+
const cursor = clampCursor(state.body, state.cursor)
|
|
125
|
+
return replaceRange({ ...state, cursor }, cursor, lineEndAt(state.body, cursor))
|
|
126
|
+
}
|
package/src/ui/diff.ts
CHANGED
|
@@ -1,17 +1,57 @@
|
|
|
1
1
|
import { parseColor, SyntaxStyle } from "@opentui/core"
|
|
2
|
-
import
|
|
2
|
+
import { Data, Schema } from "effect"
|
|
3
|
+
import type { DiffCommentSide, PullRequestItem, PullRequestReviewComment } from "../domain.js"
|
|
3
4
|
import { colors } from "./colors.js"
|
|
4
5
|
|
|
6
|
+
export const DiffView = Schema.Literals(["unified", "split"])
|
|
7
|
+
export type DiffView = Schema.Schema.Type<typeof DiffView>
|
|
8
|
+
|
|
9
|
+
export const DiffWrapMode = Schema.Literals(["none", "word"])
|
|
10
|
+
export type DiffWrapMode = Schema.Schema.Type<typeof DiffWrapMode>
|
|
11
|
+
|
|
12
|
+
export const DiffCommentKind = Schema.Literals(["addition", "deletion", "context"])
|
|
13
|
+
export type DiffCommentKind = Schema.Schema.Type<typeof DiffCommentKind>
|
|
14
|
+
|
|
5
15
|
export interface DiffFilePatch {
|
|
6
16
|
readonly name: string
|
|
7
17
|
readonly filetype: string | undefined
|
|
8
18
|
readonly patch: string
|
|
9
19
|
}
|
|
10
20
|
|
|
11
|
-
export
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
21
|
+
export interface DiffFileStats {
|
|
22
|
+
readonly additions: number
|
|
23
|
+
readonly deletions: number
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface StackedDiffFilePatch {
|
|
27
|
+
readonly file: DiffFilePatch
|
|
28
|
+
readonly index: number
|
|
29
|
+
readonly headerLine: number
|
|
30
|
+
readonly diffStartLine: number
|
|
31
|
+
readonly diffHeight: number
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface DiffCommentAnchor {
|
|
35
|
+
readonly path: string
|
|
36
|
+
readonly line: number
|
|
37
|
+
readonly side: DiffCommentSide
|
|
38
|
+
readonly kind: DiffCommentKind
|
|
39
|
+
readonly renderLine: number
|
|
40
|
+
readonly text: string
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type StackedDiffCommentAnchor = DiffCommentAnchor & {
|
|
44
|
+
readonly fileIndex: number
|
|
45
|
+
readonly localRenderLine: number
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export type PullRequestDiffState = Data.TaggedEnum<{
|
|
49
|
+
Loading: {}
|
|
50
|
+
Ready: { readonly patch: string; readonly files: readonly DiffFilePatch[] }
|
|
51
|
+
Error: { readonly error: string }
|
|
52
|
+
}>
|
|
53
|
+
|
|
54
|
+
export const PullRequestDiffState = Data.taggedEnum<PullRequestDiffState>()
|
|
15
55
|
|
|
16
56
|
export const createDiffSyntaxStyle = () => SyntaxStyle.fromStyles({
|
|
17
57
|
keyword: { fg: parseColor(colors.accent), bold: true },
|
|
@@ -144,17 +184,193 @@ export const splitPatchFiles = (patch: string): readonly DiffFilePatch[] => {
|
|
|
144
184
|
|
|
145
185
|
export const pullRequestDiffKey = (pullRequest: PullRequestItem) => `${pullRequest.repository}#${pullRequest.number}`
|
|
146
186
|
|
|
187
|
+
export const safeDiffFileIndex = (files: readonly DiffFilePatch[], index: number) =>
|
|
188
|
+
files.length > 0 ? Math.max(0, Math.min(index, files.length - 1)) : 0
|
|
189
|
+
|
|
190
|
+
export const buildStackedDiffFiles = (
|
|
191
|
+
files: readonly DiffFilePatch[],
|
|
192
|
+
view: DiffView,
|
|
193
|
+
wrapMode: DiffWrapMode,
|
|
194
|
+
width: number,
|
|
195
|
+
): readonly StackedDiffFilePatch[] => {
|
|
196
|
+
let offset = 0
|
|
197
|
+
return files.map((file, index) => {
|
|
198
|
+
const diffHeight = patchRenderableLineCount(file.patch, view, wrapMode, width)
|
|
199
|
+
const separatorBefore = index === 0 ? 0 : 1
|
|
200
|
+
const headerLine = offset + separatorBefore
|
|
201
|
+
const stackedFile = {
|
|
202
|
+
file,
|
|
203
|
+
index,
|
|
204
|
+
headerLine,
|
|
205
|
+
diffStartLine: headerLine + 2,
|
|
206
|
+
diffHeight,
|
|
207
|
+
} satisfies StackedDiffFilePatch
|
|
208
|
+
offset += separatorBefore + 2 + diffHeight
|
|
209
|
+
return stackedFile
|
|
210
|
+
})
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export const stackedDiffFileAtLine = (stackedFiles: readonly StackedDiffFilePatch[], line: number) =>
|
|
214
|
+
stackedFiles.reduce<StackedDiffFilePatch | undefined>((current, file) => file.headerLine <= line ? file : current, undefined)
|
|
215
|
+
|
|
147
216
|
export const diffStatText = (pullRequest: PullRequestItem) => {
|
|
148
217
|
if (!pullRequest.detailLoaded) return "loading details"
|
|
149
218
|
const files = pullRequest.changedFiles === 1 ? "1 file" : `${pullRequest.changedFiles} files`
|
|
219
|
+
const stats = diffFileStatsText(pullRequest)
|
|
220
|
+
return stats ? `${stats} ${files}` : files
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export const diffCommentLocationKey = (location: Pick<PullRequestReviewComment, "path" | "side" | "line">) => `${location.path}:${location.side}:${location.line}`
|
|
224
|
+
|
|
225
|
+
export const diffCommentAnchorKey = diffCommentLocationKey
|
|
226
|
+
|
|
227
|
+
type PendingDiffCommentAnchor = Omit<DiffCommentAnchor, "renderLine">
|
|
228
|
+
|
|
229
|
+
const diffContentWidth = (lines: readonly string[], view: DiffView, width: number) => {
|
|
230
|
+
const lineNumberGutterWidth = patchLineNumberGutterWidth(lines)
|
|
231
|
+
return view === "split"
|
|
232
|
+
? Math.max(1, Math.floor(width / 2) - lineNumberGutterWidth)
|
|
233
|
+
: Math.max(1, width - lineNumberGutterWidth)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export const diffFileStats = (file: DiffFilePatch): DiffFileStats => {
|
|
237
|
+
let additions = 0
|
|
238
|
+
let deletions = 0
|
|
239
|
+
let inHunk = false
|
|
240
|
+
|
|
241
|
+
for (const line of file.patch.split("\n")) {
|
|
242
|
+
const hunk = line.match(hunkHeaderPattern)
|
|
243
|
+
if (hunk) {
|
|
244
|
+
inHunk = true
|
|
245
|
+
continue
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (!inHunk) continue
|
|
249
|
+
const firstChar = line[0]
|
|
250
|
+
if (firstChar === "+") additions++
|
|
251
|
+
else if (firstChar === "-") deletions++
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return { additions, deletions }
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export const diffFileStatText = (file: DiffFilePatch) => {
|
|
258
|
+
return diffFileStatsText(diffFileStats(file))
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export const diffFileStatsText = (stats: DiffFileStats) => {
|
|
150
262
|
return [
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
files,
|
|
263
|
+
stats.additions > 0 ? `+${stats.additions}` : null,
|
|
264
|
+
stats.deletions > 0 ? `-${stats.deletions}` : null,
|
|
154
265
|
].filter((part): part is string => part !== null).join(" ")
|
|
155
266
|
}
|
|
156
267
|
|
|
157
|
-
const
|
|
268
|
+
export const getDiffCommentAnchors = (file: DiffFilePatch, view: DiffView = "unified", wrapMode: DiffWrapMode = "none", width = 120): readonly DiffCommentAnchor[] => {
|
|
269
|
+
const anchors: DiffCommentAnchor[] = []
|
|
270
|
+
const lines = file.patch.split("\n")
|
|
271
|
+
const contentWidth = diffContentWidth(lines, view, width)
|
|
272
|
+
let oldLine = 0
|
|
273
|
+
let newLine = 0
|
|
274
|
+
let renderLine = 0
|
|
275
|
+
let inHunk = false
|
|
276
|
+
let deletions: Array<PendingDiffCommentAnchor & { readonly height: number }> = []
|
|
277
|
+
let additions: Array<PendingDiffCommentAnchor & { readonly height: number }> = []
|
|
278
|
+
|
|
279
|
+
const flushChangeBlock = () => {
|
|
280
|
+
if (deletions.length === 0 && additions.length === 0) return
|
|
281
|
+
if (view === "split") {
|
|
282
|
+
const rowCount = Math.max(deletions.length, additions.length)
|
|
283
|
+
for (let index = 0; index < rowCount; index++) {
|
|
284
|
+
const deletion = deletions[index]
|
|
285
|
+
const addition = additions[index]
|
|
286
|
+
if (deletion) anchors.push({ path: deletion.path, line: deletion.line, side: deletion.side, kind: deletion.kind, text: deletion.text, renderLine })
|
|
287
|
+
if (addition) anchors.push({ path: addition.path, line: addition.line, side: addition.side, kind: addition.kind, text: addition.text, renderLine })
|
|
288
|
+
renderLine += Math.max(deletion?.height ?? 1, addition?.height ?? 1)
|
|
289
|
+
}
|
|
290
|
+
} else {
|
|
291
|
+
for (const deletion of deletions) {
|
|
292
|
+
anchors.push({ path: deletion.path, line: deletion.line, side: deletion.side, kind: deletion.kind, text: deletion.text, renderLine })
|
|
293
|
+
renderLine += deletion.height
|
|
294
|
+
}
|
|
295
|
+
for (const addition of additions) {
|
|
296
|
+
anchors.push({ path: addition.path, line: addition.line, side: addition.side, kind: addition.kind, text: addition.text, renderLine })
|
|
297
|
+
renderLine += addition.height
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
deletions = []
|
|
301
|
+
additions = []
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
for (const line of lines) {
|
|
305
|
+
const hunk = line.match(hunkHeaderPattern)
|
|
306
|
+
if (hunk) {
|
|
307
|
+
flushChangeBlock()
|
|
308
|
+
oldLine = Number(hunk[1])
|
|
309
|
+
newLine = Number(hunk[3])
|
|
310
|
+
inHunk = true
|
|
311
|
+
continue
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (!inHunk) continue
|
|
315
|
+
|
|
316
|
+
const firstChar = line[0]
|
|
317
|
+
if (firstChar === "\\") continue
|
|
318
|
+
|
|
319
|
+
if (firstChar === "+") {
|
|
320
|
+
const text = line.slice(1)
|
|
321
|
+
additions.push({ path: file.name, line: newLine, side: "RIGHT", kind: "addition", text, height: estimatedWrappedLineCount(text, contentWidth, wrapMode) })
|
|
322
|
+
newLine++
|
|
323
|
+
continue
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (firstChar === "-") {
|
|
327
|
+
const text = line.slice(1)
|
|
328
|
+
deletions.push({ path: file.name, line: oldLine, side: "LEFT", kind: "deletion", text, height: estimatedWrappedLineCount(text, contentWidth, wrapMode) })
|
|
329
|
+
oldLine++
|
|
330
|
+
continue
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (firstChar === " ") {
|
|
334
|
+
flushChangeBlock()
|
|
335
|
+
const text = line.slice(1)
|
|
336
|
+
anchors.push({ path: file.name, line: newLine, side: "RIGHT", kind: "context", renderLine, text })
|
|
337
|
+
oldLine++
|
|
338
|
+
newLine++
|
|
339
|
+
renderLine += estimatedWrappedLineCount(text, contentWidth, wrapMode)
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
flushChangeBlock()
|
|
344
|
+
return anchors
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
export const getStackedDiffCommentAnchors = (
|
|
348
|
+
stackedFiles: readonly StackedDiffFilePatch[],
|
|
349
|
+
view: DiffView = "unified",
|
|
350
|
+
wrapMode: DiffWrapMode = "none",
|
|
351
|
+
width = 120,
|
|
352
|
+
): readonly StackedDiffCommentAnchor[] =>
|
|
353
|
+
stackedFiles.flatMap((stackedFile) => getDiffCommentAnchors(stackedFile.file, view, wrapMode, width).map((anchor) => ({
|
|
354
|
+
...anchor,
|
|
355
|
+
fileIndex: stackedFile.index,
|
|
356
|
+
localRenderLine: anchor.renderLine,
|
|
357
|
+
renderLine: stackedFile.diffStartLine + anchor.renderLine,
|
|
358
|
+
})))
|
|
359
|
+
|
|
360
|
+
export const nearestDiffCommentAnchorIndex = (anchors: readonly DiffCommentAnchor[], renderLine: number) => {
|
|
361
|
+
if (anchors.length === 0) return 0
|
|
362
|
+
const nextIndex = anchors.findIndex((anchor) => anchor.renderLine >= renderLine)
|
|
363
|
+
return nextIndex >= 0 ? nextIndex : anchors.length - 1
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
export const scrollTopForVisibleLine = (currentTop: number, viewportHeight: number, line: number, margin = 1) => {
|
|
367
|
+
const safeViewportHeight = Math.max(1, viewportHeight)
|
|
368
|
+
if (line < currentTop + margin) return Math.max(0, line - margin)
|
|
369
|
+
if (line >= currentTop + safeViewportHeight - margin) return Math.max(0, line - safeViewportHeight + margin + 1)
|
|
370
|
+
return currentTop
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const estimatedWrappedLineCount = (text: string, width: number, wrapMode: DiffWrapMode) => {
|
|
158
374
|
if (wrapMode === "none") return 1
|
|
159
375
|
return Math.max(1, Math.ceil(Bun.stringWidth(text) / Math.max(1, width)))
|
|
160
376
|
}
|
|
@@ -166,10 +382,10 @@ const patchLineNumberGutterWidth = (lines: readonly string[]) => {
|
|
|
166
382
|
let newLine = 0
|
|
167
383
|
|
|
168
384
|
for (const line of lines) {
|
|
169
|
-
const hunk = line.match(
|
|
385
|
+
const hunk = line.match(hunkHeaderPattern)
|
|
170
386
|
if (hunk) {
|
|
171
387
|
oldLine = Number(hunk[1])
|
|
172
|
-
newLine = Number(hunk[
|
|
388
|
+
newLine = Number(hunk[3])
|
|
173
389
|
maxLineNumber = Math.max(maxLineNumber, oldLine, newLine)
|
|
174
390
|
continue
|
|
175
391
|
}
|
|
@@ -194,12 +410,9 @@ const patchLineNumberGutterWidth = (lines: readonly string[]) => {
|
|
|
194
410
|
return Math.max(3, digits + 2) + (hasSigns ? 2 : 0)
|
|
195
411
|
}
|
|
196
412
|
|
|
197
|
-
export const patchRenderableLineCount = (patch: string, view:
|
|
413
|
+
export const patchRenderableLineCount = (patch: string, view: DiffView, wrapMode: DiffWrapMode, width: number) => {
|
|
198
414
|
const lines = patch.split("\n")
|
|
199
|
-
const
|
|
200
|
-
const splitPaneWidth = Math.max(1, Math.floor(width / 2) - lineNumberGutterWidth)
|
|
201
|
-
const unifiedPaneWidth = Math.max(1, width - lineNumberGutterWidth)
|
|
202
|
-
const contentWidth = view === "split" ? splitPaneWidth : unifiedPaneWidth
|
|
415
|
+
const contentWidth = diffContentWidth(lines, view, width)
|
|
203
416
|
let count = 0
|
|
204
417
|
let inHunk = false
|
|
205
418
|
let deletions: number[] = []
|