| 1 | import type { FileDiffMetadata } from "@pierre/diffs"; |
| 2 | import type { PullRequestDiffSide } from "@t3tools/contracts"; |
| 3 | |
| 4 | /** |
| 5 | * Whether a conversation's line is really in this file's hunks. |
| 6 | * |
| 7 | * A thread naming a file is not the same as a thread the diff can show: its line may have moved |
| 8 | * out of the change, or sit in a hunk the host withheld. Pinning it anyway would put the remark |
| 9 | * against whatever code now occupies that line number, and silently dropping it would lose the |
| 10 | * conversation, so the answer decides which of the two lists it belongs in. |
| 11 | */ |
| 12 | export function isLineInFileDiff( |
| 13 | file: FileDiffMetadata, |
| 14 | side: PullRequestDiffSide, |
| 15 | line: number, |
| 16 | ): boolean { |
| 17 | return file.hunks.some((hunk) => |
| 18 | side === "left" |
| 19 | ? line >= hunk.deletionStart && line < hunk.deletionStart + hunk.deletionCount |
| 20 | : line >= hunk.additionStart && line < hunk.additionStart + hunk.additionCount, |
| 21 | ); |
| 22 | } |
| 23 | |
| 24 | /** What the toolbar last asked of every file at once, null being the reader asking nothing yet. */ |
| 25 | export type DiffFoldOverride = "expanded" | "folded" | null; |
| 26 | |
| 27 | /** |
| 28 | * Whether a file is drawn folded. |
| 29 | * |
| 30 | * A diff arrives a slice at a time, so the reader's own choices are kept as the difference from |
| 31 | * what the toolbar last said rather than as the set of folded files: a file that has not loaded |
| 32 | * yet cannot be in a set, and would otherwise land expanded moments after the reader folded |
| 33 | * everything. The caller supplies the saved default until the toolbar overrides it; individual |
| 34 | * files can still be toggled independently. |
| 35 | */ |
| 36 | export function isFileDiffCollapsed( |
| 37 | fileKey: string, |
| 38 | foldOverride: DiffFoldOverride, |
| 39 | toggledFileKeys: ReadonlySet<string>, |
| 40 | ): boolean { |
| 41 | const foldedByDefault = foldOverride === "folded"; |
| 42 | return toggledFileKeys.has(fileKey) ? !foldedByDefault : foldedByDefault; |
| 43 | } |
| 44 | |
| 45 | /** |
| 46 | * The reader's fold choices after a file was ticked off, or put back. |
| 47 | * |
| 48 | * Clearing a file puts it away and un-clearing brings it back, so the tick moves the fold as if |
| 49 | * the reader had pressed the chevron themselves, which keeps folding a difference from what the |
| 50 | * toolbar last asked, and so keeps "collapse all" from ticking anything off. |
| 51 | */ |
| 52 | export function toggleFileDiffFoldForViewed( |
| 53 | fileKey: string, |
| 54 | viewed: boolean, |
| 55 | foldOverride: DiffFoldOverride, |
| 56 | toggledFileKeys: ReadonlySet<string>, |
| 57 | ): ReadonlySet<string> { |
| 58 | if (isFileDiffCollapsed(fileKey, foldOverride, toggledFileKeys) === viewed) |
| 59 | return toggledFileKeys; |
| 60 | const next = new Set(toggledFileKeys); |
| 61 | if (next.has(fileKey)) next.delete(fileKey); |
| 62 | else next.add(fileKey); |
| 63 | return next; |
| 64 | } |