@pierre/diffs 1.0.0-beta.2 → 1.0.0

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 CHANGED
@@ -23,4 +23,5 @@ bun test --update-snapshots
23
23
  bun run tsc
24
24
  ```
25
25
 
26
- Tests are located in the `test/` folder and use Bun's native testing framework with snapshot support.
26
+ Tests are located in the `test/` folder and use Bun's native testing framework
27
+ with snapshot support.
@@ -9,7 +9,7 @@ async function resolveLanguage(lang) {
9
9
  if (resolver != null) return resolver;
10
10
  try {
11
11
  const loader = bundledLanguages[lang];
12
- if (loader == null) throw new Error("resolveLanguage: \"${lang}\" not found in bundled languages");
12
+ if (loader == null) throw new Error(`resolveLanguage: "${lang}" not found in bundled languages`);
13
13
  const resolver$1 = loader().then(({ default: data }) => {
14
14
  const resolvedLang = {
15
15
  name: lang,
@@ -1 +1 @@
1
- {"version":3,"file":"resolveLanguage.js","names":["resolver"],"sources":["../../../src/highlighter/languages/resolveLanguage.ts"],"sourcesContent":["import { bundledLanguages } from 'shiki';\n\nimport type { SupportedLanguages } from '../../types';\nimport { isWorkerContext } from '../../utils/isWorkerContext';\nimport type { ResolvedLanguage } from '../../worker';\nimport { ResolvedLanguages, ResolvingLanguages } from './constants';\n\nexport async function resolveLanguage(\n lang: Exclude<SupportedLanguages, 'text'>\n): Promise<ResolvedLanguage> {\n // Prevent dynamic imports in worker contexts\n if (isWorkerContext()) {\n throw new Error(\n `resolveLanguage(\"${lang}\") cannot be called from a worker context. ` +\n 'Languages must be pre-resolved on the main thread and passed to the worker via the resolvedLanguages parameter.'\n );\n }\n\n const resolver = ResolvingLanguages.get(lang);\n if (resolver != null) {\n return resolver;\n }\n\n try {\n const loader = bundledLanguages[lang];\n if (loader == null) {\n throw new Error(\n 'resolveLanguage: \"${lang}\" not found in bundled languages'\n );\n }\n\n const resolver = loader().then(({ default: data }) => {\n const resolvedLang = { name: lang, data };\n if (!ResolvedLanguages.has(lang)) {\n ResolvedLanguages.set(lang, resolvedLang);\n }\n return resolvedLang;\n });\n ResolvingLanguages.set(lang, resolver);\n return await resolver;\n } finally {\n ResolvingLanguages.delete(lang);\n }\n}\n"],"mappings":";;;;;AAOA,eAAsB,gBACpB,MAC2B;AAE3B,KAAI,iBAAiB,CACnB,OAAM,IAAI,MACR,oBAAoB,KAAK,4JAE1B;CAGH,MAAM,WAAW,mBAAmB,IAAI,KAAK;AAC7C,KAAI,YAAY,KACd,QAAO;AAGT,KAAI;EACF,MAAM,SAAS,iBAAiB;AAChC,MAAI,UAAU,KACZ,OAAM,IAAI,MACR,8DACD;EAGH,MAAMA,aAAW,QAAQ,CAAC,MAAM,EAAE,SAAS,WAAW;GACpD,MAAM,eAAe;IAAE,MAAM;IAAM;IAAM;AACzC,OAAI,CAAC,kBAAkB,IAAI,KAAK,CAC9B,mBAAkB,IAAI,MAAM,aAAa;AAE3C,UAAO;IACP;AACF,qBAAmB,IAAI,MAAMA,WAAS;AACtC,SAAO,MAAMA;WACL;AACR,qBAAmB,OAAO,KAAK"}
1
+ {"version":3,"file":"resolveLanguage.js","names":["resolver"],"sources":["../../../src/highlighter/languages/resolveLanguage.ts"],"sourcesContent":["import { bundledLanguages } from 'shiki';\n\nimport type { SupportedLanguages } from '../../types';\nimport { isWorkerContext } from '../../utils/isWorkerContext';\nimport type { ResolvedLanguage } from '../../worker';\nimport { ResolvedLanguages, ResolvingLanguages } from './constants';\n\nexport async function resolveLanguage(\n lang: Exclude<SupportedLanguages, 'text'>\n): Promise<ResolvedLanguage> {\n // Prevent dynamic imports in worker contexts\n if (isWorkerContext()) {\n throw new Error(\n `resolveLanguage(\"${lang}\") cannot be called from a worker context. ` +\n 'Languages must be pre-resolved on the main thread and passed to the worker via the resolvedLanguages parameter.'\n );\n }\n\n const resolver = ResolvingLanguages.get(lang);\n if (resolver != null) {\n return resolver;\n }\n\n try {\n const loader = bundledLanguages[lang];\n if (loader == null) {\n throw new Error(\n `resolveLanguage: \"${lang}\" not found in bundled languages`\n );\n }\n\n const resolver = loader().then(({ default: data }) => {\n const resolvedLang = { name: lang, data };\n if (!ResolvedLanguages.has(lang)) {\n ResolvedLanguages.set(lang, resolvedLang);\n }\n return resolvedLang;\n });\n ResolvingLanguages.set(lang, resolver);\n return await resolver;\n } finally {\n ResolvingLanguages.delete(lang);\n }\n}\n"],"mappings":";;;;;AAOA,eAAsB,gBACpB,MAC2B;AAE3B,KAAI,iBAAiB,CACnB,OAAM,IAAI,MACR,oBAAoB,KAAK,4JAE1B;CAGH,MAAM,WAAW,mBAAmB,IAAI,KAAK;AAC7C,KAAI,YAAY,KACd,QAAO;AAGT,KAAI;EACF,MAAM,SAAS,iBAAiB;AAChC,MAAI,UAAU,KACZ,OAAM,IAAI,MACR,qBAAqB,KAAK,kCAC3B;EAGH,MAAMA,aAAW,QAAQ,CAAC,MAAM,EAAE,SAAS,WAAW;GACpD,MAAM,eAAe;IAAE,MAAM;IAAM;IAAM;AACzC,OAAI,CAAC,kBAAkB,IAAI,KAAK,CAC9B,mBAAkB,IAAI,MAAM,aAAa;AAE3C,UAAO;IACP;AACF,qBAAmB,IAAI,MAAMA,WAAS;AACtC,SAAO,MAAMA;WACL;AACR,qBAAmB,OAAO,KAAK"}
@@ -50,6 +50,7 @@ declare class LineSelectionManager {
50
50
  private notifySelectionStart;
51
51
  private notifySelectionEnd;
52
52
  private getMouseEventDataForPath;
53
+ private getLineNumber;
53
54
  private getLineIndex;
54
55
  private getLineSideFromElement;
55
56
  }
@@ -1 +1 @@
1
- {"version":3,"file":"LineSelectionManager.d.ts","names":["options: LineSelectionOptions"],"sources":["../../src/managers/LineSelectionManager.ts"],"sourcesContent":[],"mappings":";;;KAGY,aAAA,GAAgB;UAEX,iBAAA;EAFjB,KAAY,EAAA,MAAA;EAEZ,IAAiB,CAAA,EAER,aAFQ;EAOjB,GAAiB,EAAA,MAAA;EAEU,OAAA,CAAA,EALf,aAKe;;AAEI,UAJd,oBAAA,CAIc;EAAA,mBAAA,CAAA,EAAA,OAAA;EAgB/B,cAAa,CAAA,EAAA,CAAA,KAAA,EAlBc,iBAkBd,GAAA,IAAA,EAAA,GAAA,IAAA;EAOkB,oBAAA,CAAA,EAAA,CAAA,KAAA,EAxBE,iBAwBF,GAAA,IAAA,EAAA,GAAA,IAAA;EAET,kBAAA,CAAA,EAAA,CAAA,KAAA,EAzBS,iBAyBT,GAAA,IAAA,EAAA,GAAA,IAAA;;;;;AAuZtB;;;;AAIE,cApaW,oBAAA,CAoaX;EACC,QAAA,OAAA;EAAuB,QAAA,GAAA;EAAA,QAAA,aAAA;;;;wBA9ZK;sBAET;;aAoBT;;;sBA2BS;kBAaJ;;;;;;;;;;;;;;;;;;iBA2VF,yBAAA;;;;;GAKb,uBAAuB"}
1
+ {"version":3,"file":"LineSelectionManager.d.ts","names":["options: LineSelectionOptions"],"sources":["../../src/managers/LineSelectionManager.ts"],"sourcesContent":[],"mappings":";;;KAGY,aAAA,GAAgB;UAEX,iBAAA;EAFjB,KAAY,EAAA,MAAA;EAEZ,IAAiB,CAAA,EAER,aAFQ;EAOjB,GAAiB,EAAA,MAAA;EAEU,OAAA,CAAA,EALf,aAKe;;AAEI,UAJd,oBAAA,CAIc;EAAA,mBAAA,CAAA,EAAA,OAAA;EAgB/B,cAAa,CAAA,EAAA,CAAA,KAAA,EAlBc,iBAkBd,GAAA,IAAA,EAAA,GAAA,IAAA;EAOkB,oBAAA,CAAA,EAAA,CAAA,KAAA,EAxBE,iBAwBF,GAAA,IAAA,EAAA,GAAA,IAAA;EAET,kBAAA,CAAA,EAAA,CAAA,KAAA,EAzBS,iBAyBT,GAAA,IAAA,EAAA,GAAA,IAAA;;;;;AAsbtB;;;;AAIE,cAncW,oBAAA,CAmcX;EACC,QAAA,OAAA;EAAuB,QAAA,GAAA;EAAA,QAAA,aAAA;;;;wBA7bK;sBAET;;aAoBT;;;sBA2BS;kBAaJ;;;;;;;;;;;;;;;;;;;iBA0XF,yBAAA;;;;;GAKb,uBAAuB"}
@@ -81,7 +81,7 @@ var LineSelectionManager = class {
81
81
  event.preventDefault();
82
82
  const { lineNumber, eventSide, lineIndex } = mouseEventData;
83
83
  if (event.shiftKey && this.selectedRange != null) {
84
- const range = this.deriveRowRangeFromDOM(this.selectedRange);
84
+ const range = this.deriveRowRangeFromDOM(this.selectedRange, this.pre?.dataset.type === "split");
85
85
  if (range == null) return;
86
86
  const useStart = range.start <= range.end ? lineIndex >= range.start : lineIndex <= range.end;
87
87
  this.anchor = {
@@ -150,7 +150,8 @@ var LineSelectionManager = class {
150
150
  console.error(codeElements);
151
151
  throw new Error("LineSelectionManager.applySelectionToDOM: Somehow there are more than 2 code elements...");
152
152
  }
153
- const rowRange = this.deriveRowRangeFromDOM(this.selectedRange);
153
+ const split = this.pre.dataset.type === "split";
154
+ const rowRange = this.deriveRowRangeFromDOM(this.selectedRange, split);
154
155
  if (rowRange == null) {
155
156
  console.error({
156
157
  rowRange,
@@ -163,7 +164,7 @@ var LineSelectionManager = class {
163
164
  const last = Math.max(rowRange.start, rowRange.end);
164
165
  for (const code of codeElements) for (const element of code.children) {
165
166
  if (!(element instanceof HTMLElement)) continue;
166
- const lineIndex = this.getLineIndex(element);
167
+ const lineIndex = this.getLineIndex(element, split);
167
168
  if ((lineIndex ?? 0) > last) break;
168
169
  if (lineIndex == null || lineIndex < first) continue;
169
170
  let attributeValue = isSingle ? "single" : lineIndex === first ? "first" : lineIndex === last ? "last" : "";
@@ -178,24 +179,24 @@ var LineSelectionManager = class {
178
179
  }
179
180
  }
180
181
  };
181
- deriveRowRangeFromDOM(range) {
182
+ deriveRowRangeFromDOM(range, split) {
182
183
  if (range == null) return void 0;
183
- const start = this.findRowIndexForLineNumber(range.start, range.side);
184
- const end = range.end === range.start && (range.endSide == null || range.endSide === range.side) ? start : this.findRowIndexForLineNumber(range.end, range.endSide ?? range.side);
184
+ const start = this.findRowIndexForLineNumber(range.start, range.side, split);
185
+ const end = range.end === range.start && (range.endSide == null || range.endSide === range.side) ? start : this.findRowIndexForLineNumber(range.end, range.endSide ?? range.side, split);
185
186
  return start != null && end != null ? {
186
187
  start,
187
188
  end
188
189
  } : void 0;
189
190
  }
190
- findRowIndexForLineNumber(lineNumber, targetSide = "additions") {
191
+ findRowIndexForLineNumber(lineNumber, targetSide = "additions", split) {
191
192
  if (this.pre == null) return void 0;
192
193
  const elements = Array.from(this.pre.querySelectorAll(`[data-line="${lineNumber}"]`));
193
194
  elements.push(...Array.from(this.pre.querySelectorAll(`[data-alt-line="${lineNumber}"]`)));
194
195
  if (elements.length === 0) return void 0;
195
196
  for (const element of elements) {
196
197
  if (!(element instanceof HTMLElement)) continue;
197
- if (this.getLineSideFromElement(element) === targetSide) return this.getLineIndex(element);
198
- else if (parseInt(element.dataset.altLine ?? "") === lineNumber) return this.getLineIndex(element);
198
+ if (this.getLineSideFromElement(element) === targetSide) return this.getLineIndex(element, split);
199
+ else if (parseInt(element.dataset.altLine ?? "") === lineNumber) return this.getLineIndex(element, split);
199
200
  }
200
201
  console.error("LineSelectionManager.findRowIndexForLineNumber: Invalid selection", lineNumber, targetSide);
201
202
  }
@@ -226,11 +227,11 @@ var LineSelectionManager = class {
226
227
  continue;
227
228
  }
228
229
  if (element.hasAttribute("data-line")) {
229
- lineNumber = parseInt(element.dataset.line ?? "", 10);
230
- lineIndex = parseInt(element.dataset.lineIndex ?? "", 10);
230
+ lineNumber = this.getLineNumber(element);
231
+ lineIndex = this.getLineIndex(element, this.pre?.dataset.type === "split");
231
232
  if (element.dataset.lineType === "change-deletion") eventSide = "deletions";
232
233
  else if (element.dataset.lineType === "change-additions") eventSide = "additions";
233
- if (Number.isNaN(lineIndex) || Number.isNaN(lineNumber)) {
234
+ if (lineIndex == null || lineNumber == null) {
234
235
  lineIndex = void 0;
235
236
  lineNumber = void 0;
236
237
  break;
@@ -250,9 +251,14 @@ var LineSelectionManager = class {
250
251
  eventSide: eventSide ?? "additions"
251
252
  };
252
253
  }
253
- getLineIndex(element) {
254
- const lineIndex = parseInt(element.dataset.lineIndex ?? "", 10);
255
- return !Number.isNaN(lineIndex) ? lineIndex : void 0;
254
+ getLineNumber(element) {
255
+ const lineNumber = parseInt(element.dataset.line ?? "", 10);
256
+ return !Number.isNaN(lineNumber) ? lineNumber : void 0;
257
+ }
258
+ getLineIndex(element, split) {
259
+ const lineIndexes = (element.dataset.lineIndex ?? "").split(",").map((value) => parseInt(value)).filter((value) => !Number.isNaN(value));
260
+ if (split && lineIndexes.length === 2) return lineIndexes[1];
261
+ else if (!split) return lineIndexes[0];
256
262
  }
257
263
  getLineSideFromElement(element) {
258
264
  if (element.dataset.lineType === "change-deletion") return "deletions";
@@ -1 +1 @@
1
- {"version":3,"file":"LineSelectionManager.js","names":["options: LineSelectionOptions","lineNumber: number | undefined","lineIndex: number | undefined","eventSide: AnnotationSide | undefined"],"sources":["../../src/managers/LineSelectionManager.ts"],"sourcesContent":["import type { AnnotationSide } from '../types';\nimport { areSelectionsEqual } from '../utils/areSelectionsEqual';\n\nexport type SelectionSide = AnnotationSide;\n\nexport interface SelectedLineRange {\n start: number;\n side?: SelectionSide;\n end: number;\n endSide?: SelectionSide;\n}\n\nexport interface LineSelectionOptions {\n enableLineSelection?: boolean;\n onLineSelected?: (range: SelectedLineRange | null) => void;\n onLineSelectionStart?: (range: SelectedLineRange | null) => void;\n onLineSelectionEnd?: (range: SelectedLineRange | null) => void;\n}\n\ninterface MouseInfo {\n lineNumber: number;\n eventSide: AnnotationSide;\n lineIndex: number;\n}\n\n/**\n * Manages line selection state and interactions for code/diff viewers.\n * Handles:\n * - Click and drag selection\n * - Shift-click to extend selection\n * - DOM attribute updates (data-selected-line)\n */\nexport class LineSelectionManager {\n private pre: HTMLPreElement | undefined;\n private selectedRange: SelectedLineRange | null = null;\n private renderedSelectionRange: SelectedLineRange | null | undefined;\n private anchor: { line: number; side: SelectionSide } | undefined;\n private _queuedRender: number | undefined;\n\n constructor(private options: LineSelectionOptions = {}) {}\n\n setOptions(options: LineSelectionOptions): void {\n this.options = { ...this.options, ...options };\n this.removeEventListeners();\n if (this.options.enableLineSelection === true) {\n this.attachEventListeners();\n }\n }\n\n cleanUp(): void {\n this.removeEventListeners();\n if (this._queuedRender != null) {\n cancelAnimationFrame(this._queuedRender);\n this._queuedRender = undefined;\n }\n if (this.pre != null) {\n delete this.pre.dataset.interactiveLineNumbers;\n }\n this.pre = undefined;\n }\n\n setup(pre: HTMLPreElement): void {\n // Assume we are always dirty after a setup...\n this.setDirty();\n if (this.pre !== pre) {\n this.cleanUp();\n }\n this.pre = pre;\n const { enableLineSelection = false } = this.options;\n if (enableLineSelection) {\n this.pre.dataset.interactiveLineNumbers = '';\n this.attachEventListeners();\n } else {\n this.removeEventListeners();\n delete this.pre.dataset.interactiveLineNumbers;\n }\n\n this.setSelection(this.selectedRange);\n }\n\n setDirty(): void {\n this.renderedSelectionRange = undefined;\n }\n\n isDirty(): boolean {\n return this.renderedSelectionRange === undefined;\n }\n\n setSelection(range: SelectedLineRange | null): void {\n const isRangeChange = !(\n range === this.selectedRange ||\n areSelectionsEqual(range ?? undefined, this.selectedRange ?? undefined)\n );\n if (!this.isDirty() && !isRangeChange) return;\n this.selectedRange = range;\n this.renderSelection();\n if (isRangeChange) {\n this.notifySelectionChange();\n }\n }\n\n getSelection(): SelectedLineRange | null {\n return this.selectedRange;\n }\n\n private attachEventListeners(): void {\n if (this.pre == null) return;\n // Lets run a cleanup, just in case\n this.removeEventListeners();\n this.pre.addEventListener('mousedown', this.handleMouseDown);\n }\n\n private removeEventListeners(): void {\n if (this.pre == null) return;\n this.pre.removeEventListener('mousedown', this.handleMouseDown);\n document.removeEventListener('mousemove', this.handleMouseMove);\n document.removeEventListener('mouseup', this.handleMouseUp);\n }\n\n private handleMouseDown = (event: MouseEvent): void => {\n // Only handle left mouse button\n const mouseEventData =\n event.button === 0\n ? this.getMouseEventDataForPath(event.composedPath(), 'click')\n : undefined;\n if (mouseEventData == null) {\n return;\n }\n event.preventDefault();\n const { lineNumber, eventSide, lineIndex } = mouseEventData;\n if (event.shiftKey && this.selectedRange != null) {\n const range = this.deriveRowRangeFromDOM(this.selectedRange);\n if (range == null) return;\n const useStart =\n range.start <= range.end\n ? lineIndex >= range.start\n : lineIndex <= range.end;\n this.anchor = {\n line: useStart ? this.selectedRange.start : this.selectedRange.end,\n side:\n (useStart\n ? this.selectedRange.side\n : (this.selectedRange.endSide ?? this.selectedRange.side)) ??\n 'additions',\n };\n this.updateSelection(lineNumber, eventSide);\n this.notifySelectionStart(this.selectedRange);\n } else {\n // Check if clicking on already selected single line to unselect\n if (\n this.selectedRange?.start === lineNumber &&\n this.selectedRange?.end === lineNumber\n ) {\n this.updateSelection(null);\n this.notifySelectionEnd(null);\n this.notifySelectionChange();\n return;\n }\n this.selectedRange = null;\n this.anchor = { line: lineNumber, side: eventSide };\n this.updateSelection(lineNumber, eventSide);\n this.notifySelectionStart(this.selectedRange);\n }\n\n document.addEventListener('mousemove', this.handleMouseMove);\n document.addEventListener('mouseup', this.handleMouseUp);\n };\n\n private handleMouseMove = (event: MouseEvent): void => {\n const mouseEventData = this.getMouseEventDataForPath(\n event.composedPath(),\n 'move'\n );\n if (mouseEventData == null || this.anchor == null) return;\n const { lineNumber, eventSide } = mouseEventData;\n this.updateSelection(lineNumber, eventSide);\n };\n\n private handleMouseUp = (): void => {\n this.anchor = undefined;\n document.removeEventListener('mousemove', this.handleMouseMove);\n document.removeEventListener('mouseup', this.handleMouseUp);\n this.notifySelectionEnd(this.selectedRange);\n this.notifySelectionChange();\n };\n\n private updateSelection(currentLine: null): void;\n private updateSelection(currentLine: number, side: AnnotationSide): void;\n private updateSelection(\n currentLine: number | null,\n side?: AnnotationSide\n ): void {\n if (currentLine == null) {\n this.selectedRange = null;\n } else {\n const anchorSide = this.anchor?.side ?? side;\n const anchorLine = this.anchor?.line ?? currentLine;\n this.selectedRange = {\n start: anchorLine,\n end: currentLine,\n side: anchorSide,\n endSide: anchorSide !== side ? side : undefined,\n };\n }\n this._queuedRender ??= requestAnimationFrame(this.renderSelection);\n }\n\n private renderSelection = (): void => {\n if (this._queuedRender != null) {\n cancelAnimationFrame(this._queuedRender);\n this._queuedRender = undefined;\n }\n if (\n this.pre == null ||\n this.renderedSelectionRange === this.selectedRange\n ) {\n return;\n }\n\n // First clear existing selections, maybe we\n // can cache this to better avoid this query?\n const allSelected = this.pre.querySelectorAll('[data-selected-line]');\n for (const element of allSelected) {\n element.removeAttribute('data-selected-line');\n }\n\n this.renderedSelectionRange = this.selectedRange;\n if (this.selectedRange == null) {\n return;\n }\n\n const codeElements = this.pre.querySelectorAll('[data-code]');\n if (codeElements.length === 0) return;\n if (codeElements.length > 2) {\n console.error(codeElements);\n throw new Error(\n 'LineSelectionManager.applySelectionToDOM: Somehow there are more than 2 code elements...'\n );\n }\n const rowRange = this.deriveRowRangeFromDOM(this.selectedRange);\n if (rowRange == null) {\n console.error({ rowRange, selectedRange: this.selectedRange });\n throw new Error(\n 'LineSelectionManager.renderSelection: No valid rowRange'\n );\n }\n const isSingle = rowRange.start === rowRange.end;\n const first = Math.min(rowRange.start, rowRange.end);\n const last = Math.max(rowRange.start, rowRange.end);\n for (const code of codeElements) {\n for (const element of code.children) {\n if (!(element instanceof HTMLElement)) continue;\n const lineIndex = this.getLineIndex(element);\n if ((lineIndex ?? 0) > last) break;\n if (lineIndex == null || lineIndex < first) continue;\n let attributeValue = isSingle\n ? 'single'\n : lineIndex === first\n ? 'first'\n : lineIndex === last\n ? 'last'\n : '';\n element.setAttribute('data-selected-line', attributeValue);\n // If we have a line annotation following our selected line, we should\n // mark it as selected as well\n if (\n element.nextSibling instanceof HTMLElement &&\n element.nextSibling.hasAttribute('data-line-annotation')\n ) {\n // Depending on the line's attribute value, lets go ahead and correct\n // it when adding in the annotation row\n if (isSingle) {\n // Single technically becomes 2 selected lines\n attributeValue = 'last';\n element.setAttribute('data-selected-line', 'first');\n } else if (lineIndex === first) {\n // We don't want apply 'first' to the line annotation\n attributeValue = '';\n } else if (lineIndex === last) {\n // the annotation will become the last selected line and therefore\n // our existing line should no longer be last\n element.setAttribute('data-selected-line', '');\n }\n element.nextSibling.setAttribute(\n 'data-selected-line',\n attributeValue\n );\n }\n }\n }\n };\n\n private deriveRowRangeFromDOM(\n range: SelectedLineRange\n ): { start: number; end: number } | undefined {\n if (range == null) return undefined;\n const start = this.findRowIndexForLineNumber(range.start, range.side);\n const end =\n range.end === range.start &&\n (range.endSide == null || range.endSide === range.side)\n ? start\n : this.findRowIndexForLineNumber(\n range.end,\n range.endSide ?? range.side\n );\n return start != null && end != null ? { start, end } : undefined;\n }\n\n private findRowIndexForLineNumber(\n lineNumber: number,\n targetSide: SelectionSide = 'additions'\n ): number | undefined {\n if (this.pre == null) return undefined;\n const elements = Array.from(\n this.pre.querySelectorAll(`[data-line=\"${lineNumber}\"]`)\n );\n // Given how unified diffs can order things, we need to always process\n // `[data-line]` elements before `[data-alt-line]`\n elements.push(\n ...Array.from(\n this.pre.querySelectorAll(`[data-alt-line=\"${lineNumber}\"]`)\n )\n );\n if (elements.length === 0) return undefined;\n\n for (const element of elements) {\n if (!(element instanceof HTMLElement)) {\n continue;\n }\n const side = this.getLineSideFromElement(element);\n if (side === targetSide) {\n return this.getLineIndex(element);\n } else if (parseInt(element.dataset.altLine ?? '') === lineNumber) {\n return this.getLineIndex(element);\n }\n }\n console.error(\n 'LineSelectionManager.findRowIndexForLineNumber: Invalid selection',\n lineNumber,\n targetSide\n );\n return undefined;\n }\n\n private notifySelectionChange(): void {\n const { onLineSelected } = this.options;\n if (onLineSelected == null) return;\n\n onLineSelected(this.selectedRange ?? null);\n }\n\n private notifySelectionStart(range: SelectedLineRange | null): void {\n const { onLineSelectionStart } = this.options;\n if (onLineSelectionStart == null) return;\n onLineSelectionStart(range);\n }\n\n private notifySelectionEnd(range: SelectedLineRange | null): void {\n const { onLineSelectionEnd } = this.options;\n if (onLineSelectionEnd == null) return;\n onLineSelectionEnd(range);\n }\n\n private getMouseEventDataForPath(\n path: (EventTarget | undefined)[],\n eventType: 'click' | 'move'\n ): MouseInfo | undefined {\n let lineNumber: number | undefined;\n let lineIndex: number | undefined;\n let isNumberColumn = false;\n let eventSide: AnnotationSide | undefined;\n for (const element of path) {\n if (!(element instanceof HTMLElement)) {\n continue;\n }\n if (element.hasAttribute('data-column-number')) {\n isNumberColumn = true;\n continue;\n }\n if (element.hasAttribute('data-line')) {\n lineNumber = parseInt(element.dataset.line ?? '', 10);\n lineIndex = parseInt(element.dataset.lineIndex ?? '', 10);\n if (element.dataset.lineType === 'change-deletion') {\n eventSide = 'deletions';\n } else if (element.dataset.lineType === 'change-additions') {\n eventSide = 'additions';\n }\n // if we can't pull out an index or line number, we can't do anything.\n if (Number.isNaN(lineIndex) || Number.isNaN(lineNumber)) {\n lineIndex = undefined;\n lineNumber = undefined;\n break;\n }\n // If we already have an eventSide, we done computin\n if (eventSide != null) {\n break;\n } else {\n // context type lines will need to be discovered higher up\n // at the data-code level\n }\n continue;\n }\n if (element.hasAttribute('data-code')) {\n eventSide ??= element.hasAttribute('data-deletions')\n ? 'deletions'\n : // context in unified style are assumed to be additions based on\n // their line numbers\n 'additions';\n // If we got to the code element, we def done, son\n break;\n }\n }\n if (\n (eventType === 'click' && !isNumberColumn) ||\n lineIndex == null ||\n lineNumber == null\n ) {\n return undefined;\n }\n return {\n lineIndex,\n lineNumber,\n // Normally this shouldn't hit unless we broke early for whatever reason,\n // but for types lets ensure it's additions if undefined\n eventSide: eventSide ?? 'additions',\n };\n }\n\n private getLineIndex(element: HTMLElement): number | undefined {\n const lineIndex = parseInt(element.dataset.lineIndex ?? '', 10);\n return !Number.isNaN(lineIndex) ? lineIndex : undefined;\n }\n\n private getLineSideFromElement(element: HTMLElement): SelectionSide {\n if (element.dataset.lineType === 'change-deletion') {\n return 'deletions';\n }\n if (element.dataset.lineType === 'change-addition') {\n return 'additions';\n }\n const parent = element.closest('[data-code]');\n if (!(parent instanceof HTMLElement)) {\n return 'additions';\n }\n return parent.hasAttribute('data-deletions') ? 'deletions' : 'additions';\n }\n}\n\nexport function pluckLineSelectionOptions({\n enableLineSelection,\n onLineSelected,\n onLineSelectionStart,\n onLineSelectionEnd,\n}: LineSelectionOptions): LineSelectionOptions {\n return {\n enableLineSelection,\n onLineSelected,\n onLineSelectionStart,\n onLineSelectionEnd,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"LineSelectionManager.js","names":["options: LineSelectionOptions","lineNumber: number | undefined","lineIndex: number | undefined","eventSide: AnnotationSide | undefined"],"sources":["../../src/managers/LineSelectionManager.ts"],"sourcesContent":["import type { AnnotationSide } from '../types';\nimport { areSelectionsEqual } from '../utils/areSelectionsEqual';\n\nexport type SelectionSide = AnnotationSide;\n\nexport interface SelectedLineRange {\n start: number;\n side?: SelectionSide;\n end: number;\n endSide?: SelectionSide;\n}\n\nexport interface LineSelectionOptions {\n enableLineSelection?: boolean;\n onLineSelected?: (range: SelectedLineRange | null) => void;\n onLineSelectionStart?: (range: SelectedLineRange | null) => void;\n onLineSelectionEnd?: (range: SelectedLineRange | null) => void;\n}\n\ninterface MouseInfo {\n lineNumber: number;\n eventSide: AnnotationSide;\n lineIndex: number;\n}\n\n/**\n * Manages line selection state and interactions for code/diff viewers.\n * Handles:\n * - Click and drag selection\n * - Shift-click to extend selection\n * - DOM attribute updates (data-selected-line)\n */\nexport class LineSelectionManager {\n private pre: HTMLPreElement | undefined;\n private selectedRange: SelectedLineRange | null = null;\n private renderedSelectionRange: SelectedLineRange | null | undefined;\n private anchor: { line: number; side: SelectionSide } | undefined;\n private _queuedRender: number | undefined;\n\n constructor(private options: LineSelectionOptions = {}) {}\n\n setOptions(options: LineSelectionOptions): void {\n this.options = { ...this.options, ...options };\n this.removeEventListeners();\n if (this.options.enableLineSelection === true) {\n this.attachEventListeners();\n }\n }\n\n cleanUp(): void {\n this.removeEventListeners();\n if (this._queuedRender != null) {\n cancelAnimationFrame(this._queuedRender);\n this._queuedRender = undefined;\n }\n if (this.pre != null) {\n delete this.pre.dataset.interactiveLineNumbers;\n }\n this.pre = undefined;\n }\n\n setup(pre: HTMLPreElement): void {\n // Assume we are always dirty after a setup...\n this.setDirty();\n if (this.pre !== pre) {\n this.cleanUp();\n }\n this.pre = pre;\n const { enableLineSelection = false } = this.options;\n if (enableLineSelection) {\n this.pre.dataset.interactiveLineNumbers = '';\n this.attachEventListeners();\n } else {\n this.removeEventListeners();\n delete this.pre.dataset.interactiveLineNumbers;\n }\n\n this.setSelection(this.selectedRange);\n }\n\n setDirty(): void {\n this.renderedSelectionRange = undefined;\n }\n\n isDirty(): boolean {\n return this.renderedSelectionRange === undefined;\n }\n\n setSelection(range: SelectedLineRange | null): void {\n const isRangeChange = !(\n range === this.selectedRange ||\n areSelectionsEqual(range ?? undefined, this.selectedRange ?? undefined)\n );\n if (!this.isDirty() && !isRangeChange) return;\n this.selectedRange = range;\n this.renderSelection();\n if (isRangeChange) {\n this.notifySelectionChange();\n }\n }\n\n getSelection(): SelectedLineRange | null {\n return this.selectedRange;\n }\n\n private attachEventListeners(): void {\n if (this.pre == null) return;\n // Lets run a cleanup, just in case\n this.removeEventListeners();\n this.pre.addEventListener('mousedown', this.handleMouseDown);\n }\n\n private removeEventListeners(): void {\n if (this.pre == null) return;\n this.pre.removeEventListener('mousedown', this.handleMouseDown);\n document.removeEventListener('mousemove', this.handleMouseMove);\n document.removeEventListener('mouseup', this.handleMouseUp);\n }\n\n private handleMouseDown = (event: MouseEvent): void => {\n // Only handle left mouse button\n const mouseEventData =\n event.button === 0\n ? this.getMouseEventDataForPath(event.composedPath(), 'click')\n : undefined;\n if (mouseEventData == null) {\n return;\n }\n event.preventDefault();\n const { lineNumber, eventSide, lineIndex } = mouseEventData;\n if (event.shiftKey && this.selectedRange != null) {\n const range = this.deriveRowRangeFromDOM(\n this.selectedRange,\n this.pre?.dataset.type === 'split'\n );\n if (range == null) return;\n const useStart =\n range.start <= range.end\n ? lineIndex >= range.start\n : lineIndex <= range.end;\n this.anchor = {\n line: useStart ? this.selectedRange.start : this.selectedRange.end,\n side:\n (useStart\n ? this.selectedRange.side\n : (this.selectedRange.endSide ?? this.selectedRange.side)) ??\n 'additions',\n };\n this.updateSelection(lineNumber, eventSide);\n this.notifySelectionStart(this.selectedRange);\n } else {\n // Check if clicking on already selected single line to unselect\n if (\n this.selectedRange?.start === lineNumber &&\n this.selectedRange?.end === lineNumber\n ) {\n this.updateSelection(null);\n this.notifySelectionEnd(null);\n this.notifySelectionChange();\n return;\n }\n this.selectedRange = null;\n this.anchor = { line: lineNumber, side: eventSide };\n this.updateSelection(lineNumber, eventSide);\n this.notifySelectionStart(this.selectedRange);\n }\n\n document.addEventListener('mousemove', this.handleMouseMove);\n document.addEventListener('mouseup', this.handleMouseUp);\n };\n\n private handleMouseMove = (event: MouseEvent): void => {\n const mouseEventData = this.getMouseEventDataForPath(\n event.composedPath(),\n 'move'\n );\n if (mouseEventData == null || this.anchor == null) return;\n const { lineNumber, eventSide } = mouseEventData;\n this.updateSelection(lineNumber, eventSide);\n };\n\n private handleMouseUp = (): void => {\n this.anchor = undefined;\n document.removeEventListener('mousemove', this.handleMouseMove);\n document.removeEventListener('mouseup', this.handleMouseUp);\n this.notifySelectionEnd(this.selectedRange);\n this.notifySelectionChange();\n };\n\n private updateSelection(currentLine: null): void;\n private updateSelection(currentLine: number, side: AnnotationSide): void;\n private updateSelection(\n currentLine: number | null,\n side?: AnnotationSide\n ): void {\n if (currentLine == null) {\n this.selectedRange = null;\n } else {\n const anchorSide = this.anchor?.side ?? side;\n const anchorLine = this.anchor?.line ?? currentLine;\n this.selectedRange = {\n start: anchorLine,\n end: currentLine,\n side: anchorSide,\n endSide: anchorSide !== side ? side : undefined,\n };\n }\n this._queuedRender ??= requestAnimationFrame(this.renderSelection);\n }\n\n private renderSelection = (): void => {\n if (this._queuedRender != null) {\n cancelAnimationFrame(this._queuedRender);\n this._queuedRender = undefined;\n }\n if (\n this.pre == null ||\n this.renderedSelectionRange === this.selectedRange\n ) {\n return;\n }\n\n // First clear existing selections, maybe we\n // can cache this to better avoid this query?\n const allSelected = this.pre.querySelectorAll('[data-selected-line]');\n for (const element of allSelected) {\n element.removeAttribute('data-selected-line');\n }\n\n this.renderedSelectionRange = this.selectedRange;\n if (this.selectedRange == null) {\n return;\n }\n\n const codeElements = this.pre.querySelectorAll('[data-code]');\n if (codeElements.length === 0) return;\n if (codeElements.length > 2) {\n console.error(codeElements);\n throw new Error(\n 'LineSelectionManager.applySelectionToDOM: Somehow there are more than 2 code elements...'\n );\n }\n const split = this.pre.dataset.type === 'split';\n const rowRange = this.deriveRowRangeFromDOM(this.selectedRange, split);\n if (rowRange == null) {\n console.error({ rowRange, selectedRange: this.selectedRange });\n throw new Error(\n 'LineSelectionManager.renderSelection: No valid rowRange'\n );\n }\n const isSingle = rowRange.start === rowRange.end;\n const first = Math.min(rowRange.start, rowRange.end);\n const last = Math.max(rowRange.start, rowRange.end);\n for (const code of codeElements) {\n for (const element of code.children) {\n if (!(element instanceof HTMLElement)) continue;\n const lineIndex = this.getLineIndex(element, split);\n if ((lineIndex ?? 0) > last) break;\n if (lineIndex == null || lineIndex < first) continue;\n let attributeValue = isSingle\n ? 'single'\n : lineIndex === first\n ? 'first'\n : lineIndex === last\n ? 'last'\n : '';\n element.setAttribute('data-selected-line', attributeValue);\n // If we have a line annotation following our selected line, we should\n // mark it as selected as well\n if (\n element.nextSibling instanceof HTMLElement &&\n element.nextSibling.hasAttribute('data-line-annotation')\n ) {\n // Depending on the line's attribute value, lets go ahead and correct\n // it when adding in the annotation row\n if (isSingle) {\n // Single technically becomes 2 selected lines\n attributeValue = 'last';\n element.setAttribute('data-selected-line', 'first');\n } else if (lineIndex === first) {\n // We don't want apply 'first' to the line annotation\n attributeValue = '';\n } else if (lineIndex === last) {\n // the annotation will become the last selected line and therefore\n // our existing line should no longer be last\n element.setAttribute('data-selected-line', '');\n }\n element.nextSibling.setAttribute(\n 'data-selected-line',\n attributeValue\n );\n }\n }\n }\n };\n\n private deriveRowRangeFromDOM(\n range: SelectedLineRange,\n split: boolean\n ): { start: number; end: number } | undefined {\n if (range == null) return undefined;\n const start = this.findRowIndexForLineNumber(\n range.start,\n range.side,\n split\n );\n const end =\n range.end === range.start &&\n (range.endSide == null || range.endSide === range.side)\n ? start\n : this.findRowIndexForLineNumber(\n range.end,\n range.endSide ?? range.side,\n split\n );\n return start != null && end != null ? { start, end } : undefined;\n }\n\n private findRowIndexForLineNumber(\n lineNumber: number,\n targetSide: SelectionSide = 'additions',\n split: boolean\n ): number | undefined {\n if (this.pre == null) return undefined;\n const elements = Array.from(\n this.pre.querySelectorAll(`[data-line=\"${lineNumber}\"]`)\n );\n // Given how unified diffs can order things, we need to always process\n // `[data-line]` elements before `[data-alt-line]`\n elements.push(\n ...Array.from(\n this.pre.querySelectorAll(`[data-alt-line=\"${lineNumber}\"]`)\n )\n );\n if (elements.length === 0) return undefined;\n\n for (const element of elements) {\n if (!(element instanceof HTMLElement)) {\n continue;\n }\n const side = this.getLineSideFromElement(element);\n if (side === targetSide) {\n return this.getLineIndex(element, split);\n } else if (parseInt(element.dataset.altLine ?? '') === lineNumber) {\n return this.getLineIndex(element, split);\n }\n }\n console.error(\n 'LineSelectionManager.findRowIndexForLineNumber: Invalid selection',\n lineNumber,\n targetSide\n );\n return undefined;\n }\n\n private notifySelectionChange(): void {\n const { onLineSelected } = this.options;\n if (onLineSelected == null) return;\n\n onLineSelected(this.selectedRange ?? null);\n }\n\n private notifySelectionStart(range: SelectedLineRange | null): void {\n const { onLineSelectionStart } = this.options;\n if (onLineSelectionStart == null) return;\n onLineSelectionStart(range);\n }\n\n private notifySelectionEnd(range: SelectedLineRange | null): void {\n const { onLineSelectionEnd } = this.options;\n if (onLineSelectionEnd == null) return;\n onLineSelectionEnd(range);\n }\n\n private getMouseEventDataForPath(\n path: (EventTarget | undefined)[],\n eventType: 'click' | 'move'\n ): MouseInfo | undefined {\n let lineNumber: number | undefined;\n let lineIndex: number | undefined;\n let isNumberColumn = false;\n let eventSide: AnnotationSide | undefined;\n for (const element of path) {\n if (!(element instanceof HTMLElement)) {\n continue;\n }\n if (element.hasAttribute('data-column-number')) {\n isNumberColumn = true;\n continue;\n }\n if (element.hasAttribute('data-line')) {\n lineNumber = this.getLineNumber(element);\n lineIndex = this.getLineIndex(\n element,\n this.pre?.dataset.type === 'split'\n );\n if (element.dataset.lineType === 'change-deletion') {\n eventSide = 'deletions';\n } else if (element.dataset.lineType === 'change-additions') {\n eventSide = 'additions';\n }\n // if we can't pull out an index or line number, we can't do anything.\n if (lineIndex == null || lineNumber == null) {\n lineIndex = undefined;\n lineNumber = undefined;\n break;\n }\n // If we already have an eventSide, we done computin\n if (eventSide != null) {\n break;\n } else {\n // context type lines will need to be discovered higher up\n // at the data-code level\n }\n continue;\n }\n if (element.hasAttribute('data-code')) {\n eventSide ??= element.hasAttribute('data-deletions')\n ? 'deletions'\n : // context in unified style are assumed to be additions based on\n // their line numbers\n 'additions';\n // If we got to the code element, we def done, son\n break;\n }\n }\n if (\n (eventType === 'click' && !isNumberColumn) ||\n lineIndex == null ||\n lineNumber == null\n ) {\n return undefined;\n }\n return {\n lineIndex,\n lineNumber,\n // Normally this shouldn't hit unless we broke early for whatever reason,\n // but for types lets ensure it's additions if undefined\n eventSide: eventSide ?? 'additions',\n };\n }\n\n private getLineNumber(element: HTMLElement): number | undefined {\n const lineNumber = parseInt(element.dataset.line ?? '', 10);\n return !Number.isNaN(lineNumber) ? lineNumber : undefined;\n }\n\n private getLineIndex(\n element: HTMLElement,\n split: boolean\n ): number | undefined {\n const lineIndexes = (element.dataset.lineIndex ?? '')\n .split(',')\n .map((value) => parseInt(value))\n .filter((value) => !Number.isNaN(value));\n\n if (split && lineIndexes.length === 2) {\n return lineIndexes[1];\n } else if (!split) {\n return lineIndexes[0];\n }\n return undefined;\n }\n\n private getLineSideFromElement(element: HTMLElement): SelectionSide {\n if (element.dataset.lineType === 'change-deletion') {\n return 'deletions';\n }\n if (element.dataset.lineType === 'change-addition') {\n return 'additions';\n }\n const parent = element.closest('[data-code]');\n if (!(parent instanceof HTMLElement)) {\n return 'additions';\n }\n return parent.hasAttribute('data-deletions') ? 'deletions' : 'additions';\n }\n}\n\nexport function pluckLineSelectionOptions({\n enableLineSelection,\n onLineSelected,\n onLineSelectionStart,\n onLineSelectionEnd,\n}: LineSelectionOptions): LineSelectionOptions {\n return {\n enableLineSelection,\n onLineSelected,\n onLineSelectionStart,\n onLineSelectionEnd,\n };\n}\n"],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -120,7 +120,7 @@ interface LineInfo {
120
120
  type: LineTypes;
121
121
  lineNumber: number;
122
122
  altLineNumber?: number;
123
- lineIndex: number;
123
+ lineIndex: number | `${number},${number}`;
124
124
  }
125
125
  interface SharedRenderState {
126
126
  lineInfo: Record<number, LineInfo | undefined> | ((shikiLineNumber: number) => LineInfo);
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","names":[],"sources":["../src/types.ts"],"sourcesContent":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","names":[],"sources":["../src/types.ts"],"sourcesContent":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"parsePatchFiles.d.ts","names":[],"sources":["../../src/utils/parsePatchFiles.ts"],"sourcesContent":[],"mappings":";;;;;;AA8QA;;;;;;iBAAgB,eAAA,yCAGb"}
1
+ {"version":3,"file":"parsePatchFiles.d.ts","names":[],"sources":["../../src/utils/parsePatchFiles.ts"],"sourcesContent":[],"mappings":";;;;;;AAqRA;;;;;;iBAAgB,eAAA,yCAGb"}
@@ -85,6 +85,7 @@ function processPatch(data, cacheKeyPrefix) {
85
85
  } else {
86
86
  let currentContent;
87
87
  let lastLineType;
88
+ while (lines.length > 0 && (lines[lines.length - 1] === "\n" || lines[lines.length - 1] === "")) lines.pop();
88
89
  for (const rawLine of lines) {
89
90
  const parsedLine = parseLineType(rawLine);
90
91
  if (parsedLine == null) continue;
@@ -132,10 +133,10 @@ function processPatch(data, cacheKeyPrefix) {
132
133
  splitLineStart: 0,
133
134
  unifiedLineCount: lines.length,
134
135
  unifiedLineStart: 0,
135
- additionCount: parseInt(match[4] ?? "0"),
136
+ additionCount: parseInt(match[4] ?? "1"),
136
137
  additionStart: parseInt(match[3]),
137
138
  additionLines,
138
- deletionCount: parseInt(match[2] ?? "0"),
139
+ deletionCount: parseInt(match[2] ?? "1"),
139
140
  deletionStart: parseInt(match[1]),
140
141
  deletionLines,
141
142
  hunkContent,
@@ -1 +1 @@
1
- {"version":3,"file":"parsePatchFiles.js","names":["patchMetadata: string | undefined","files: FileDiffMetadata[]","currentFile: FileDiffMetadata | undefined","hunkContent: (ContextContent | ChangeContent)[]","currentContent: ContextContent | ChangeContent | undefined","lastLineType: 'context' | 'addition' | 'deletion' | undefined","hunkData: Hunk","patches: ParsedPatch[]"],"sources":["../../src/utils/parsePatchFiles.ts"],"sourcesContent":["import {\n ALTERNATE_FILE_NAMES_GIT,\n COMMIT_METADATA_SPLIT,\n FILENAME_HEADER_REGEX,\n FILENAME_HEADER_REGEX_GIT,\n FILE_CONTEXT_BLOB,\n FILE_MODE_FROM_INDEX,\n GIT_DIFF_FILE_BREAK_REGEX,\n HUNK_HEADER,\n SPLIT_WITH_NEWLINES,\n UNIFIED_DIFF_FILE_BREAK_REGEX,\n} from '../constants';\nimport type {\n ChangeContent,\n ContextContent,\n FileDiffMetadata,\n Hunk,\n ParsedPatch,\n} from '../types';\nimport { cleanLastNewline } from './cleanLastNewline';\nimport { parseLineType } from './parseLineType';\n\nfunction processPatch(data: string, cacheKeyPrefix?: string): ParsedPatch {\n const isGitDiff = GIT_DIFF_FILE_BREAK_REGEX.test(data);\n const rawFiles = data.split(\n isGitDiff ? GIT_DIFF_FILE_BREAK_REGEX : UNIFIED_DIFF_FILE_BREAK_REGEX\n );\n let patchMetadata: string | undefined;\n const files: FileDiffMetadata[] = [];\n let currentFile: FileDiffMetadata | undefined;\n for (const file of rawFiles) {\n if (isGitDiff && !GIT_DIFF_FILE_BREAK_REGEX.test(file)) {\n if (patchMetadata == null) {\n patchMetadata = file;\n } else {\n console.error('parsePatchContent: unknown file blob:', file);\n }\n // If we get in here, it's most likely the introductory metadata from the\n // patch, or something is fucked with the diff format\n continue;\n } else if (!isGitDiff && !UNIFIED_DIFF_FILE_BREAK_REGEX.test(file)) {\n if (patchMetadata == null) {\n patchMetadata = file;\n } else {\n console.error('parsePatchContent: unknown file blob:', file);\n }\n continue;\n }\n let lastHunkEnd = 0;\n const hunks = file.split(FILE_CONTEXT_BLOB);\n currentFile = undefined;\n for (const hunk of hunks) {\n const lines = hunk.split(SPLIT_WITH_NEWLINES);\n const firstLine = lines.shift();\n if (firstLine == null) {\n console.error('parsePatchContent: invalid hunk', hunk);\n continue;\n }\n const match = firstLine.match(HUNK_HEADER);\n const hunkContent: (ContextContent | ChangeContent)[] = [];\n let additionLines = 0;\n let deletionLines = 0;\n if (match == null || currentFile == null) {\n if (currentFile != null) {\n console.error('parsePatchContent: Invalid hunk', hunk);\n continue;\n }\n currentFile = {\n name: '',\n prevName: undefined,\n type: 'change',\n hunks: [],\n splitLineCount: 0,\n unifiedLineCount: 0,\n cacheKey:\n cacheKeyPrefix != null\n ? `${cacheKeyPrefix}-${files.length}`\n : undefined,\n };\n // Push that first line back into the group of lines so we can properly\n // parse it out\n lines.unshift(firstLine);\n for (const line of lines) {\n const filenameMatch = line.match(\n isGitDiff ? FILENAME_HEADER_REGEX_GIT : FILENAME_HEADER_REGEX\n );\n if (line.startsWith('diff --git')) {\n const [, , prevName, , name] =\n line.trim().match(ALTERNATE_FILE_NAMES_GIT) ?? [];\n currentFile.name = name.trim();\n if (prevName !== name) {\n currentFile.prevName = prevName.trim();\n }\n } else if (filenameMatch != null) {\n const [, type, fileName] = filenameMatch;\n if (type === '---' && fileName !== '/dev/null') {\n currentFile.prevName = fileName.trim();\n currentFile.name = fileName.trim();\n } else if (type === '+++' && fileName !== '/dev/null') {\n currentFile.name = fileName.trim();\n }\n }\n // Git diffs have a bunch of additional metadata we can pull from\n else if (isGitDiff) {\n if (line.startsWith('new mode ')) {\n currentFile.mode = line.replace('new mode', '').trim();\n }\n if (line.startsWith('old mode ')) {\n currentFile.oldMode = line.replace('old mode', '').trim();\n }\n if (line.startsWith('new file mode')) {\n currentFile.type = 'new';\n currentFile.mode = line.replace('new file mode', '').trim();\n }\n if (line.startsWith('deleted file mode')) {\n currentFile.type = 'deleted';\n currentFile.mode = line.replace('deleted file mode', '').trim();\n }\n if (line.startsWith('similarity index')) {\n if (line.startsWith('similarity index 100%')) {\n currentFile.type = 'rename-pure';\n } else {\n currentFile.type = 'rename-changed';\n }\n }\n if (line.startsWith('index ')) {\n const [, mode] = line.trim().match(FILE_MODE_FROM_INDEX) ?? [];\n if (mode != null) {\n currentFile.mode = mode;\n }\n }\n // We have to handle these for pure renames because there won't be\n // --- and +++ lines\n if (line.startsWith('rename from ')) {\n currentFile.prevName = line.replace('rename from ', '');\n }\n if (line.startsWith('rename to ')) {\n currentFile.name = line.replace('rename to ', '').trim();\n }\n }\n }\n continue;\n } else {\n let currentContent: ContextContent | ChangeContent | undefined;\n let lastLineType: 'context' | 'addition' | 'deletion' | undefined;\n for (const rawLine of lines) {\n const parsedLine = parseLineType(rawLine);\n if (parsedLine == null) {\n continue;\n }\n const { type, line } = parsedLine;\n if (type === 'addition') {\n if (currentContent == null || currentContent.type !== 'change') {\n currentContent = createContentGroup('change');\n hunkContent.push(currentContent);\n }\n currentContent.additions.push(line);\n additionLines++;\n lastLineType = 'addition';\n } else if (type === 'deletion') {\n if (currentContent == null || currentContent.type !== 'change') {\n currentContent = createContentGroup('change');\n hunkContent.push(currentContent);\n }\n currentContent.deletions.push(line);\n deletionLines++;\n lastLineType = 'deletion';\n } else if (type === 'context') {\n if (currentContent == null || currentContent.type !== 'context') {\n currentContent = createContentGroup('context');\n hunkContent.push(currentContent);\n }\n currentContent.lines.push(line);\n lastLineType = 'context';\n } else if (type === 'metadata' && currentContent != null) {\n if (currentContent.type === 'context') {\n currentContent.noEOFCR = true;\n } else if (lastLineType === 'deletion') {\n currentContent.noEOFCRDeletions = true;\n const lastIndex = currentContent.deletions.length - 1;\n if (lastIndex >= 0) {\n currentContent.deletions[lastIndex] = cleanLastNewline(\n currentContent.deletions[lastIndex]\n );\n }\n } else if (lastLineType === 'addition') {\n currentContent.noEOFCRAdditions = true;\n const lastIndex = currentContent.additions.length - 1;\n if (lastIndex >= 0) {\n currentContent.additions[lastIndex] = cleanLastNewline(\n currentContent.additions[lastIndex]\n );\n }\n }\n }\n }\n }\n const hunkData: Hunk = {\n collapsedBefore: 0,\n splitLineCount: 0,\n splitLineStart: 0,\n unifiedLineCount: lines.length,\n unifiedLineStart: 0,\n additionCount: parseInt(match[4] ?? '0'),\n additionStart: parseInt(match[3]),\n additionLines,\n deletionCount: parseInt(match[2] ?? '0'),\n deletionStart: parseInt(match[1]),\n deletionLines,\n hunkContent,\n hunkContext: match[5],\n hunkSpecs: firstLine,\n };\n if (\n isNaN(hunkData.additionCount) ||\n isNaN(hunkData.deletionCount) ||\n isNaN(hunkData.additionStart) ||\n isNaN(hunkData.deletionStart)\n ) {\n console.error('parsePatchContent: invalid hunk metadata', hunkData);\n continue;\n }\n hunkData.collapsedBefore = Math.max(\n hunkData.additionStart - 1 - lastHunkEnd,\n 0\n );\n currentFile.hunks.push(hunkData);\n lastHunkEnd = hunkData.additionStart + hunkData.additionCount - 1;\n hunkData.splitLineCount = Math.max(\n hunkData.additionCount,\n hunkData.deletionCount\n );\n hunkData.splitLineStart = currentFile.splitLineCount;\n hunkData.unifiedLineStart = currentFile.unifiedLineCount;\n\n currentFile.splitLineCount += hunkData.splitLineCount;\n currentFile.unifiedLineCount += hunkData.unifiedLineCount;\n }\n if (currentFile != null) {\n if (\n !isGitDiff &&\n currentFile.prevName != null &&\n currentFile.name !== currentFile.prevName\n ) {\n if (currentFile.hunks.length > 0) {\n currentFile.type = 'rename-changed';\n } else {\n currentFile.type = 'rename-pure';\n }\n }\n if (\n currentFile.type !== 'rename-pure' &&\n currentFile.type !== 'rename-changed'\n ) {\n currentFile.prevName = undefined;\n }\n files.push(currentFile);\n }\n }\n return { patchMetadata, files };\n}\n\n/**\n * Parses a patch file string into an array of parsed patches.\n *\n * @param data - The raw patch file content (supports multi-commit patches)\n * @param cacheKeyPrefix - Optional prefix for generating cache keys. When provided,\n * each file in the patch will get a cache key in the format `prefix-patchIndex-fileIndex`.\n * This enables caching of rendered diff results in the worker pool.\n */\nexport function parsePatchFiles(\n data: string,\n cacheKeyPrefix?: string\n): ParsedPatch[] {\n // NOTE(amadeus): This function is pretty forgiving in that it can accept a\n // patch file that includes commit metdata, multiple commits, or not\n const patches: ParsedPatch[] = [];\n for (const patch of data.split(COMMIT_METADATA_SPLIT)) {\n try {\n patches.push(\n processPatch(\n patch,\n cacheKeyPrefix != null\n ? `${cacheKeyPrefix}-${patches.length}`\n : undefined\n )\n );\n } catch (error) {\n console.error(error);\n }\n }\n return patches;\n}\n\nfunction createContentGroup(type: 'change'): ChangeContent;\nfunction createContentGroup(type: 'context'): ContextContent;\nfunction createContentGroup(\n type: 'change' | 'context'\n): ChangeContent | ContextContent {\n if (type === 'change') {\n return {\n type: 'change',\n additions: [],\n deletions: [],\n noEOFCRAdditions: false,\n noEOFCRDeletions: false,\n };\n }\n return { type: 'context', lines: [], noEOFCR: false };\n}\n"],"mappings":";;;;;AAsBA,SAAS,aAAa,MAAc,gBAAsC;CACxE,MAAM,YAAY,0BAA0B,KAAK,KAAK;CACtD,MAAM,WAAW,KAAK,MACpB,YAAY,4BAA4B,8BACzC;CACD,IAAIA;CACJ,MAAMC,QAA4B,EAAE;CACpC,IAAIC;AACJ,MAAK,MAAM,QAAQ,UAAU;AAC3B,MAAI,aAAa,CAAC,0BAA0B,KAAK,KAAK,EAAE;AACtD,OAAI,iBAAiB,KACnB,iBAAgB;OAEhB,SAAQ,MAAM,yCAAyC,KAAK;AAI9D;aACS,CAAC,aAAa,CAAC,8BAA8B,KAAK,KAAK,EAAE;AAClE,OAAI,iBAAiB,KACnB,iBAAgB;OAEhB,SAAQ,MAAM,yCAAyC,KAAK;AAE9D;;EAEF,IAAI,cAAc;EAClB,MAAM,QAAQ,KAAK,MAAM,kBAAkB;AAC3C,gBAAc;AACd,OAAK,MAAM,QAAQ,OAAO;GACxB,MAAM,QAAQ,KAAK,MAAM,oBAAoB;GAC7C,MAAM,YAAY,MAAM,OAAO;AAC/B,OAAI,aAAa,MAAM;AACrB,YAAQ,MAAM,mCAAmC,KAAK;AACtD;;GAEF,MAAM,QAAQ,UAAU,MAAM,YAAY;GAC1C,MAAMC,cAAkD,EAAE;GAC1D,IAAI,gBAAgB;GACpB,IAAI,gBAAgB;AACpB,OAAI,SAAS,QAAQ,eAAe,MAAM;AACxC,QAAI,eAAe,MAAM;AACvB,aAAQ,MAAM,mCAAmC,KAAK;AACtD;;AAEF,kBAAc;KACZ,MAAM;KACN,UAAU;KACV,MAAM;KACN,OAAO,EAAE;KACT,gBAAgB;KAChB,kBAAkB;KAClB,UACE,kBAAkB,OACd,GAAG,eAAe,GAAG,MAAM,WAC3B;KACP;AAGD,UAAM,QAAQ,UAAU;AACxB,SAAK,MAAM,QAAQ,OAAO;KACxB,MAAM,gBAAgB,KAAK,MACzB,YAAY,4BAA4B,sBACzC;AACD,SAAI,KAAK,WAAW,aAAa,EAAE;MACjC,MAAM,KAAK,YAAY,QACrB,KAAK,MAAM,CAAC,MAAM,yBAAyB,IAAI,EAAE;AACnD,kBAAY,OAAO,KAAK,MAAM;AAC9B,UAAI,aAAa,KACf,aAAY,WAAW,SAAS,MAAM;gBAE/B,iBAAiB,MAAM;MAChC,MAAM,GAAG,MAAM,YAAY;AAC3B,UAAI,SAAS,SAAS,aAAa,aAAa;AAC9C,mBAAY,WAAW,SAAS,MAAM;AACtC,mBAAY,OAAO,SAAS,MAAM;iBACzB,SAAS,SAAS,aAAa,YACxC,aAAY,OAAO,SAAS,MAAM;gBAI7B,WAAW;AAClB,UAAI,KAAK,WAAW,YAAY,CAC9B,aAAY,OAAO,KAAK,QAAQ,YAAY,GAAG,CAAC,MAAM;AAExD,UAAI,KAAK,WAAW,YAAY,CAC9B,aAAY,UAAU,KAAK,QAAQ,YAAY,GAAG,CAAC,MAAM;AAE3D,UAAI,KAAK,WAAW,gBAAgB,EAAE;AACpC,mBAAY,OAAO;AACnB,mBAAY,OAAO,KAAK,QAAQ,iBAAiB,GAAG,CAAC,MAAM;;AAE7D,UAAI,KAAK,WAAW,oBAAoB,EAAE;AACxC,mBAAY,OAAO;AACnB,mBAAY,OAAO,KAAK,QAAQ,qBAAqB,GAAG,CAAC,MAAM;;AAEjE,UAAI,KAAK,WAAW,mBAAmB,CACrC,KAAI,KAAK,WAAW,wBAAwB,CAC1C,aAAY,OAAO;UAEnB,aAAY,OAAO;AAGvB,UAAI,KAAK,WAAW,SAAS,EAAE;OAC7B,MAAM,GAAG,QAAQ,KAAK,MAAM,CAAC,MAAM,qBAAqB,IAAI,EAAE;AAC9D,WAAI,QAAQ,KACV,aAAY,OAAO;;AAKvB,UAAI,KAAK,WAAW,eAAe,CACjC,aAAY,WAAW,KAAK,QAAQ,gBAAgB,GAAG;AAEzD,UAAI,KAAK,WAAW,aAAa,CAC/B,aAAY,OAAO,KAAK,QAAQ,cAAc,GAAG,CAAC,MAAM;;;AAI9D;UACK;IACL,IAAIC;IACJ,IAAIC;AACJ,SAAK,MAAM,WAAW,OAAO;KAC3B,MAAM,aAAa,cAAc,QAAQ;AACzC,SAAI,cAAc,KAChB;KAEF,MAAM,EAAE,MAAM,SAAS;AACvB,SAAI,SAAS,YAAY;AACvB,UAAI,kBAAkB,QAAQ,eAAe,SAAS,UAAU;AAC9D,wBAAiB,mBAAmB,SAAS;AAC7C,mBAAY,KAAK,eAAe;;AAElC,qBAAe,UAAU,KAAK,KAAK;AACnC;AACA,qBAAe;gBACN,SAAS,YAAY;AAC9B,UAAI,kBAAkB,QAAQ,eAAe,SAAS,UAAU;AAC9D,wBAAiB,mBAAmB,SAAS;AAC7C,mBAAY,KAAK,eAAe;;AAElC,qBAAe,UAAU,KAAK,KAAK;AACnC;AACA,qBAAe;gBACN,SAAS,WAAW;AAC7B,UAAI,kBAAkB,QAAQ,eAAe,SAAS,WAAW;AAC/D,wBAAiB,mBAAmB,UAAU;AAC9C,mBAAY,KAAK,eAAe;;AAElC,qBAAe,MAAM,KAAK,KAAK;AAC/B,qBAAe;gBACN,SAAS,cAAc,kBAAkB,MAClD;UAAI,eAAe,SAAS,UAC1B,gBAAe,UAAU;eAChB,iBAAiB,YAAY;AACtC,sBAAe,mBAAmB;OAClC,MAAM,YAAY,eAAe,UAAU,SAAS;AACpD,WAAI,aAAa,EACf,gBAAe,UAAU,aAAa,iBACpC,eAAe,UAAU,WAC1B;iBAEM,iBAAiB,YAAY;AACtC,sBAAe,mBAAmB;OAClC,MAAM,YAAY,eAAe,UAAU,SAAS;AACpD,WAAI,aAAa,EACf,gBAAe,UAAU,aAAa,iBACpC,eAAe,UAAU,WAC1B;;;;;GAMX,MAAMC,WAAiB;IACrB,iBAAiB;IACjB,gBAAgB;IAChB,gBAAgB;IAChB,kBAAkB,MAAM;IACxB,kBAAkB;IAClB,eAAe,SAAS,MAAM,MAAM,IAAI;IACxC,eAAe,SAAS,MAAM,GAAG;IACjC;IACA,eAAe,SAAS,MAAM,MAAM,IAAI;IACxC,eAAe,SAAS,MAAM,GAAG;IACjC;IACA;IACA,aAAa,MAAM;IACnB,WAAW;IACZ;AACD,OACE,MAAM,SAAS,cAAc,IAC7B,MAAM,SAAS,cAAc,IAC7B,MAAM,SAAS,cAAc,IAC7B,MAAM,SAAS,cAAc,EAC7B;AACA,YAAQ,MAAM,4CAA4C,SAAS;AACnE;;AAEF,YAAS,kBAAkB,KAAK,IAC9B,SAAS,gBAAgB,IAAI,aAC7B,EACD;AACD,eAAY,MAAM,KAAK,SAAS;AAChC,iBAAc,SAAS,gBAAgB,SAAS,gBAAgB;AAChE,YAAS,iBAAiB,KAAK,IAC7B,SAAS,eACT,SAAS,cACV;AACD,YAAS,iBAAiB,YAAY;AACtC,YAAS,mBAAmB,YAAY;AAExC,eAAY,kBAAkB,SAAS;AACvC,eAAY,oBAAoB,SAAS;;AAE3C,MAAI,eAAe,MAAM;AACvB,OACE,CAAC,aACD,YAAY,YAAY,QACxB,YAAY,SAAS,YAAY,SAEjC,KAAI,YAAY,MAAM,SAAS,EAC7B,aAAY,OAAO;OAEnB,aAAY,OAAO;AAGvB,OACE,YAAY,SAAS,iBACrB,YAAY,SAAS,iBAErB,aAAY,WAAW;AAEzB,SAAM,KAAK,YAAY;;;AAG3B,QAAO;EAAE;EAAe;EAAO;;;;;;;;;;AAWjC,SAAgB,gBACd,MACA,gBACe;CAGf,MAAMC,UAAyB,EAAE;AACjC,MAAK,MAAM,SAAS,KAAK,MAAM,sBAAsB,CACnD,KAAI;AACF,UAAQ,KACN,aACE,OACA,kBAAkB,OACd,GAAG,eAAe,GAAG,QAAQ,WAC7B,OACL,CACF;UACM,OAAO;AACd,UAAQ,MAAM,MAAM;;AAGxB,QAAO;;AAKT,SAAS,mBACP,MACgC;AAChC,KAAI,SAAS,SACX,QAAO;EACL,MAAM;EACN,WAAW,EAAE;EACb,WAAW,EAAE;EACb,kBAAkB;EAClB,kBAAkB;EACnB;AAEH,QAAO;EAAE,MAAM;EAAW,OAAO,EAAE;EAAE,SAAS;EAAO"}
1
+ {"version":3,"file":"parsePatchFiles.js","names":["patchMetadata: string | undefined","files: FileDiffMetadata[]","currentFile: FileDiffMetadata | undefined","hunkContent: (ContextContent | ChangeContent)[]","currentContent: ContextContent | ChangeContent | undefined","lastLineType: 'context' | 'addition' | 'deletion' | undefined","hunkData: Hunk","patches: ParsedPatch[]"],"sources":["../../src/utils/parsePatchFiles.ts"],"sourcesContent":["import {\n ALTERNATE_FILE_NAMES_GIT,\n COMMIT_METADATA_SPLIT,\n FILENAME_HEADER_REGEX,\n FILENAME_HEADER_REGEX_GIT,\n FILE_CONTEXT_BLOB,\n FILE_MODE_FROM_INDEX,\n GIT_DIFF_FILE_BREAK_REGEX,\n HUNK_HEADER,\n SPLIT_WITH_NEWLINES,\n UNIFIED_DIFF_FILE_BREAK_REGEX,\n} from '../constants';\nimport type {\n ChangeContent,\n ContextContent,\n FileDiffMetadata,\n Hunk,\n ParsedPatch,\n} from '../types';\nimport { cleanLastNewline } from './cleanLastNewline';\nimport { parseLineType } from './parseLineType';\n\nfunction processPatch(data: string, cacheKeyPrefix?: string): ParsedPatch {\n const isGitDiff = GIT_DIFF_FILE_BREAK_REGEX.test(data);\n const rawFiles = data.split(\n isGitDiff ? GIT_DIFF_FILE_BREAK_REGEX : UNIFIED_DIFF_FILE_BREAK_REGEX\n );\n let patchMetadata: string | undefined;\n const files: FileDiffMetadata[] = [];\n let currentFile: FileDiffMetadata | undefined;\n for (const file of rawFiles) {\n if (isGitDiff && !GIT_DIFF_FILE_BREAK_REGEX.test(file)) {\n if (patchMetadata == null) {\n patchMetadata = file;\n } else {\n console.error('parsePatchContent: unknown file blob:', file);\n }\n // If we get in here, it's most likely the introductory metadata from the\n // patch, or something is fucked with the diff format\n continue;\n } else if (!isGitDiff && !UNIFIED_DIFF_FILE_BREAK_REGEX.test(file)) {\n if (patchMetadata == null) {\n patchMetadata = file;\n } else {\n console.error('parsePatchContent: unknown file blob:', file);\n }\n continue;\n }\n let lastHunkEnd = 0;\n const hunks = file.split(FILE_CONTEXT_BLOB);\n currentFile = undefined;\n for (const hunk of hunks) {\n const lines = hunk.split(SPLIT_WITH_NEWLINES);\n const firstLine = lines.shift();\n if (firstLine == null) {\n console.error('parsePatchContent: invalid hunk', hunk);\n continue;\n }\n const match = firstLine.match(HUNK_HEADER);\n const hunkContent: (ContextContent | ChangeContent)[] = [];\n let additionLines = 0;\n let deletionLines = 0;\n if (match == null || currentFile == null) {\n if (currentFile != null) {\n console.error('parsePatchContent: Invalid hunk', hunk);\n continue;\n }\n currentFile = {\n name: '',\n prevName: undefined,\n type: 'change',\n hunks: [],\n splitLineCount: 0,\n unifiedLineCount: 0,\n cacheKey:\n cacheKeyPrefix != null\n ? `${cacheKeyPrefix}-${files.length}`\n : undefined,\n };\n // Push that first line back into the group of lines so we can properly\n // parse it out\n lines.unshift(firstLine);\n for (const line of lines) {\n const filenameMatch = line.match(\n isGitDiff ? FILENAME_HEADER_REGEX_GIT : FILENAME_HEADER_REGEX\n );\n if (line.startsWith('diff --git')) {\n const [, , prevName, , name] =\n line.trim().match(ALTERNATE_FILE_NAMES_GIT) ?? [];\n currentFile.name = name.trim();\n if (prevName !== name) {\n currentFile.prevName = prevName.trim();\n }\n } else if (filenameMatch != null) {\n const [, type, fileName] = filenameMatch;\n if (type === '---' && fileName !== '/dev/null') {\n currentFile.prevName = fileName.trim();\n currentFile.name = fileName.trim();\n } else if (type === '+++' && fileName !== '/dev/null') {\n currentFile.name = fileName.trim();\n }\n }\n // Git diffs have a bunch of additional metadata we can pull from\n else if (isGitDiff) {\n if (line.startsWith('new mode ')) {\n currentFile.mode = line.replace('new mode', '').trim();\n }\n if (line.startsWith('old mode ')) {\n currentFile.oldMode = line.replace('old mode', '').trim();\n }\n if (line.startsWith('new file mode')) {\n currentFile.type = 'new';\n currentFile.mode = line.replace('new file mode', '').trim();\n }\n if (line.startsWith('deleted file mode')) {\n currentFile.type = 'deleted';\n currentFile.mode = line.replace('deleted file mode', '').trim();\n }\n if (line.startsWith('similarity index')) {\n if (line.startsWith('similarity index 100%')) {\n currentFile.type = 'rename-pure';\n } else {\n currentFile.type = 'rename-changed';\n }\n }\n if (line.startsWith('index ')) {\n const [, mode] = line.trim().match(FILE_MODE_FROM_INDEX) ?? [];\n if (mode != null) {\n currentFile.mode = mode;\n }\n }\n // We have to handle these for pure renames because there won't be\n // --- and +++ lines\n if (line.startsWith('rename from ')) {\n currentFile.prevName = line.replace('rename from ', '');\n }\n if (line.startsWith('rename to ')) {\n currentFile.name = line.replace('rename to ', '').trim();\n }\n }\n }\n continue;\n } else {\n let currentContent: ContextContent | ChangeContent | undefined;\n let lastLineType: 'context' | 'addition' | 'deletion' | undefined;\n // Strip trailing bare newlines (format-patch separators between commits)\n while (\n lines.length > 0 &&\n (lines[lines.length - 1] === '\\n' || lines[lines.length - 1] === '')\n ) {\n lines.pop();\n }\n for (const rawLine of lines) {\n const parsedLine = parseLineType(rawLine);\n if (parsedLine == null) {\n continue;\n }\n const { type, line } = parsedLine;\n if (type === 'addition') {\n if (currentContent == null || currentContent.type !== 'change') {\n currentContent = createContentGroup('change');\n hunkContent.push(currentContent);\n }\n currentContent.additions.push(line);\n additionLines++;\n lastLineType = 'addition';\n } else if (type === 'deletion') {\n if (currentContent == null || currentContent.type !== 'change') {\n currentContent = createContentGroup('change');\n hunkContent.push(currentContent);\n }\n currentContent.deletions.push(line);\n deletionLines++;\n lastLineType = 'deletion';\n } else if (type === 'context') {\n if (currentContent == null || currentContent.type !== 'context') {\n currentContent = createContentGroup('context');\n hunkContent.push(currentContent);\n }\n currentContent.lines.push(line);\n lastLineType = 'context';\n } else if (type === 'metadata' && currentContent != null) {\n if (currentContent.type === 'context') {\n currentContent.noEOFCR = true;\n } else if (lastLineType === 'deletion') {\n currentContent.noEOFCRDeletions = true;\n const lastIndex = currentContent.deletions.length - 1;\n if (lastIndex >= 0) {\n currentContent.deletions[lastIndex] = cleanLastNewline(\n currentContent.deletions[lastIndex]\n );\n }\n } else if (lastLineType === 'addition') {\n currentContent.noEOFCRAdditions = true;\n const lastIndex = currentContent.additions.length - 1;\n if (lastIndex >= 0) {\n currentContent.additions[lastIndex] = cleanLastNewline(\n currentContent.additions[lastIndex]\n );\n }\n }\n }\n }\n }\n const hunkData: Hunk = {\n collapsedBefore: 0,\n splitLineCount: 0,\n splitLineStart: 0,\n unifiedLineCount: lines.length,\n unifiedLineStart: 0,\n additionCount: parseInt(match[4] ?? '1'),\n additionStart: parseInt(match[3]),\n additionLines,\n deletionCount: parseInt(match[2] ?? '1'),\n deletionStart: parseInt(match[1]),\n deletionLines,\n hunkContent,\n hunkContext: match[5],\n hunkSpecs: firstLine,\n };\n if (\n isNaN(hunkData.additionCount) ||\n isNaN(hunkData.deletionCount) ||\n isNaN(hunkData.additionStart) ||\n isNaN(hunkData.deletionStart)\n ) {\n console.error('parsePatchContent: invalid hunk metadata', hunkData);\n continue;\n }\n hunkData.collapsedBefore = Math.max(\n hunkData.additionStart - 1 - lastHunkEnd,\n 0\n );\n currentFile.hunks.push(hunkData);\n lastHunkEnd = hunkData.additionStart + hunkData.additionCount - 1;\n hunkData.splitLineCount = Math.max(\n hunkData.additionCount,\n hunkData.deletionCount\n );\n hunkData.splitLineStart = currentFile.splitLineCount;\n hunkData.unifiedLineStart = currentFile.unifiedLineCount;\n\n currentFile.splitLineCount += hunkData.splitLineCount;\n currentFile.unifiedLineCount += hunkData.unifiedLineCount;\n }\n if (currentFile != null) {\n if (\n !isGitDiff &&\n currentFile.prevName != null &&\n currentFile.name !== currentFile.prevName\n ) {\n if (currentFile.hunks.length > 0) {\n currentFile.type = 'rename-changed';\n } else {\n currentFile.type = 'rename-pure';\n }\n }\n if (\n currentFile.type !== 'rename-pure' &&\n currentFile.type !== 'rename-changed'\n ) {\n currentFile.prevName = undefined;\n }\n files.push(currentFile);\n }\n }\n return { patchMetadata, files };\n}\n\n/**\n * Parses a patch file string into an array of parsed patches.\n *\n * @param data - The raw patch file content (supports multi-commit patches)\n * @param cacheKeyPrefix - Optional prefix for generating cache keys. When provided,\n * each file in the patch will get a cache key in the format `prefix-patchIndex-fileIndex`.\n * This enables caching of rendered diff results in the worker pool.\n */\nexport function parsePatchFiles(\n data: string,\n cacheKeyPrefix?: string\n): ParsedPatch[] {\n // NOTE(amadeus): This function is pretty forgiving in that it can accept a\n // patch file that includes commit metdata, multiple commits, or not\n const patches: ParsedPatch[] = [];\n for (const patch of data.split(COMMIT_METADATA_SPLIT)) {\n try {\n patches.push(\n processPatch(\n patch,\n cacheKeyPrefix != null\n ? `${cacheKeyPrefix}-${patches.length}`\n : undefined\n )\n );\n } catch (error) {\n console.error(error);\n }\n }\n return patches;\n}\n\nfunction createContentGroup(type: 'change'): ChangeContent;\nfunction createContentGroup(type: 'context'): ContextContent;\nfunction createContentGroup(\n type: 'change' | 'context'\n): ChangeContent | ContextContent {\n if (type === 'change') {\n return {\n type: 'change',\n additions: [],\n deletions: [],\n noEOFCRAdditions: false,\n noEOFCRDeletions: false,\n };\n }\n return { type: 'context', lines: [], noEOFCR: false };\n}\n"],"mappings":";;;;;AAsBA,SAAS,aAAa,MAAc,gBAAsC;CACxE,MAAM,YAAY,0BAA0B,KAAK,KAAK;CACtD,MAAM,WAAW,KAAK,MACpB,YAAY,4BAA4B,8BACzC;CACD,IAAIA;CACJ,MAAMC,QAA4B,EAAE;CACpC,IAAIC;AACJ,MAAK,MAAM,QAAQ,UAAU;AAC3B,MAAI,aAAa,CAAC,0BAA0B,KAAK,KAAK,EAAE;AACtD,OAAI,iBAAiB,KACnB,iBAAgB;OAEhB,SAAQ,MAAM,yCAAyC,KAAK;AAI9D;aACS,CAAC,aAAa,CAAC,8BAA8B,KAAK,KAAK,EAAE;AAClE,OAAI,iBAAiB,KACnB,iBAAgB;OAEhB,SAAQ,MAAM,yCAAyC,KAAK;AAE9D;;EAEF,IAAI,cAAc;EAClB,MAAM,QAAQ,KAAK,MAAM,kBAAkB;AAC3C,gBAAc;AACd,OAAK,MAAM,QAAQ,OAAO;GACxB,MAAM,QAAQ,KAAK,MAAM,oBAAoB;GAC7C,MAAM,YAAY,MAAM,OAAO;AAC/B,OAAI,aAAa,MAAM;AACrB,YAAQ,MAAM,mCAAmC,KAAK;AACtD;;GAEF,MAAM,QAAQ,UAAU,MAAM,YAAY;GAC1C,MAAMC,cAAkD,EAAE;GAC1D,IAAI,gBAAgB;GACpB,IAAI,gBAAgB;AACpB,OAAI,SAAS,QAAQ,eAAe,MAAM;AACxC,QAAI,eAAe,MAAM;AACvB,aAAQ,MAAM,mCAAmC,KAAK;AACtD;;AAEF,kBAAc;KACZ,MAAM;KACN,UAAU;KACV,MAAM;KACN,OAAO,EAAE;KACT,gBAAgB;KAChB,kBAAkB;KAClB,UACE,kBAAkB,OACd,GAAG,eAAe,GAAG,MAAM,WAC3B;KACP;AAGD,UAAM,QAAQ,UAAU;AACxB,SAAK,MAAM,QAAQ,OAAO;KACxB,MAAM,gBAAgB,KAAK,MACzB,YAAY,4BAA4B,sBACzC;AACD,SAAI,KAAK,WAAW,aAAa,EAAE;MACjC,MAAM,KAAK,YAAY,QACrB,KAAK,MAAM,CAAC,MAAM,yBAAyB,IAAI,EAAE;AACnD,kBAAY,OAAO,KAAK,MAAM;AAC9B,UAAI,aAAa,KACf,aAAY,WAAW,SAAS,MAAM;gBAE/B,iBAAiB,MAAM;MAChC,MAAM,GAAG,MAAM,YAAY;AAC3B,UAAI,SAAS,SAAS,aAAa,aAAa;AAC9C,mBAAY,WAAW,SAAS,MAAM;AACtC,mBAAY,OAAO,SAAS,MAAM;iBACzB,SAAS,SAAS,aAAa,YACxC,aAAY,OAAO,SAAS,MAAM;gBAI7B,WAAW;AAClB,UAAI,KAAK,WAAW,YAAY,CAC9B,aAAY,OAAO,KAAK,QAAQ,YAAY,GAAG,CAAC,MAAM;AAExD,UAAI,KAAK,WAAW,YAAY,CAC9B,aAAY,UAAU,KAAK,QAAQ,YAAY,GAAG,CAAC,MAAM;AAE3D,UAAI,KAAK,WAAW,gBAAgB,EAAE;AACpC,mBAAY,OAAO;AACnB,mBAAY,OAAO,KAAK,QAAQ,iBAAiB,GAAG,CAAC,MAAM;;AAE7D,UAAI,KAAK,WAAW,oBAAoB,EAAE;AACxC,mBAAY,OAAO;AACnB,mBAAY,OAAO,KAAK,QAAQ,qBAAqB,GAAG,CAAC,MAAM;;AAEjE,UAAI,KAAK,WAAW,mBAAmB,CACrC,KAAI,KAAK,WAAW,wBAAwB,CAC1C,aAAY,OAAO;UAEnB,aAAY,OAAO;AAGvB,UAAI,KAAK,WAAW,SAAS,EAAE;OAC7B,MAAM,GAAG,QAAQ,KAAK,MAAM,CAAC,MAAM,qBAAqB,IAAI,EAAE;AAC9D,WAAI,QAAQ,KACV,aAAY,OAAO;;AAKvB,UAAI,KAAK,WAAW,eAAe,CACjC,aAAY,WAAW,KAAK,QAAQ,gBAAgB,GAAG;AAEzD,UAAI,KAAK,WAAW,aAAa,CAC/B,aAAY,OAAO,KAAK,QAAQ,cAAc,GAAG,CAAC,MAAM;;;AAI9D;UACK;IACL,IAAIC;IACJ,IAAIC;AAEJ,WACE,MAAM,SAAS,MACd,MAAM,MAAM,SAAS,OAAO,QAAQ,MAAM,MAAM,SAAS,OAAO,IAEjE,OAAM,KAAK;AAEb,SAAK,MAAM,WAAW,OAAO;KAC3B,MAAM,aAAa,cAAc,QAAQ;AACzC,SAAI,cAAc,KAChB;KAEF,MAAM,EAAE,MAAM,SAAS;AACvB,SAAI,SAAS,YAAY;AACvB,UAAI,kBAAkB,QAAQ,eAAe,SAAS,UAAU;AAC9D,wBAAiB,mBAAmB,SAAS;AAC7C,mBAAY,KAAK,eAAe;;AAElC,qBAAe,UAAU,KAAK,KAAK;AACnC;AACA,qBAAe;gBACN,SAAS,YAAY;AAC9B,UAAI,kBAAkB,QAAQ,eAAe,SAAS,UAAU;AAC9D,wBAAiB,mBAAmB,SAAS;AAC7C,mBAAY,KAAK,eAAe;;AAElC,qBAAe,UAAU,KAAK,KAAK;AACnC;AACA,qBAAe;gBACN,SAAS,WAAW;AAC7B,UAAI,kBAAkB,QAAQ,eAAe,SAAS,WAAW;AAC/D,wBAAiB,mBAAmB,UAAU;AAC9C,mBAAY,KAAK,eAAe;;AAElC,qBAAe,MAAM,KAAK,KAAK;AAC/B,qBAAe;gBACN,SAAS,cAAc,kBAAkB,MAClD;UAAI,eAAe,SAAS,UAC1B,gBAAe,UAAU;eAChB,iBAAiB,YAAY;AACtC,sBAAe,mBAAmB;OAClC,MAAM,YAAY,eAAe,UAAU,SAAS;AACpD,WAAI,aAAa,EACf,gBAAe,UAAU,aAAa,iBACpC,eAAe,UAAU,WAC1B;iBAEM,iBAAiB,YAAY;AACtC,sBAAe,mBAAmB;OAClC,MAAM,YAAY,eAAe,UAAU,SAAS;AACpD,WAAI,aAAa,EACf,gBAAe,UAAU,aAAa,iBACpC,eAAe,UAAU,WAC1B;;;;;GAMX,MAAMC,WAAiB;IACrB,iBAAiB;IACjB,gBAAgB;IAChB,gBAAgB;IAChB,kBAAkB,MAAM;IACxB,kBAAkB;IAClB,eAAe,SAAS,MAAM,MAAM,IAAI;IACxC,eAAe,SAAS,MAAM,GAAG;IACjC;IACA,eAAe,SAAS,MAAM,MAAM,IAAI;IACxC,eAAe,SAAS,MAAM,GAAG;IACjC;IACA;IACA,aAAa,MAAM;IACnB,WAAW;IACZ;AACD,OACE,MAAM,SAAS,cAAc,IAC7B,MAAM,SAAS,cAAc,IAC7B,MAAM,SAAS,cAAc,IAC7B,MAAM,SAAS,cAAc,EAC7B;AACA,YAAQ,MAAM,4CAA4C,SAAS;AACnE;;AAEF,YAAS,kBAAkB,KAAK,IAC9B,SAAS,gBAAgB,IAAI,aAC7B,EACD;AACD,eAAY,MAAM,KAAK,SAAS;AAChC,iBAAc,SAAS,gBAAgB,SAAS,gBAAgB;AAChE,YAAS,iBAAiB,KAAK,IAC7B,SAAS,eACT,SAAS,cACV;AACD,YAAS,iBAAiB,YAAY;AACtC,YAAS,mBAAmB,YAAY;AAExC,eAAY,kBAAkB,SAAS;AACvC,eAAY,oBAAoB,SAAS;;AAE3C,MAAI,eAAe,MAAM;AACvB,OACE,CAAC,aACD,YAAY,YAAY,QACxB,YAAY,SAAS,YAAY,SAEjC,KAAI,YAAY,MAAM,SAAS,EAC7B,aAAY,OAAO;OAEnB,aAAY,OAAO;AAGvB,OACE,YAAY,SAAS,iBACrB,YAAY,SAAS,iBAErB,aAAY,WAAW;AAEzB,SAAM,KAAK,YAAY;;;AAG3B,QAAO;EAAE;EAAe;EAAO;;;;;;;;;;AAWjC,SAAgB,gBACd,MACA,gBACe;CAGf,MAAMC,UAAyB,EAAE;AACjC,MAAK,MAAM,SAAS,KAAK,MAAM,sBAAsB,CACnD,KAAI;AACF,UAAQ,KACN,aACE,OACA,kBAAkB,OACd,GAAG,eAAe,GAAG,QAAQ,WAC7B,OACL,CACF;UACM,OAAO;AACd,UAAQ,MAAM,MAAM;;AAGxB,QAAO;;AAKT,SAAS,mBACP,MACgC;AAChC,KAAI,SAAS,SACX,QAAO;EACL,MAAM;EACN,WAAW,EAAE;EACb,WAAW,EAAE;EACb,kBAAkB;EAClB,kBAAkB;EACnB;AAEH,QAAO;EAAE,MAAM;EAAW,OAAO,EAAE;EAAE,SAAS;EAAO"}
@@ -32,7 +32,7 @@ function processLine(node, line, state) {
32
32
  "data-line": lineInfo.lineNumber,
33
33
  "data-alt-line": lineInfo.altLineNumber,
34
34
  "data-line-type": lineInfo.type,
35
- "data-line-index": lineInfo.lineIndex >= 0 ? lineInfo.lineIndex : void 0
35
+ "data-line-index": lineInfo.lineIndex
36
36
  }
37
37
  });
38
38
  }
@@ -1 +1 @@
1
- {"version":3,"file":"processLine.js","names":[],"sources":["../../src/utils/processLine.ts"],"sourcesContent":["import type { ElementContent, Element as HASTElement } from 'hast';\n\nimport type { SharedRenderState } from '../types';\nimport { createHastElement, createTextNodeElement } from './hast_utils';\n\nexport function processLine(\n node: HASTElement,\n line: number,\n state: SharedRenderState\n): ElementContent {\n const lineInfo =\n typeof state.lineInfo === 'function'\n ? state.lineInfo(line)\n : state.lineInfo[line];\n if (lineInfo == null) {\n console.error({ node, line, state });\n throw new Error(`processLine: line ${line}, contains no state.lineInfo`);\n }\n // We need to convert the current line to a div but keep all the decorations\n // that may be applied\n node.tagName = 'span';\n node.properties['data-column-content'] = '';\n\n // NOTE(amadeus): We need to push newline characters into empty rows or else\n // copy/pasta will have issues\n if (node.children.length === 0) {\n node.children.push(createTextNodeElement('\\n'));\n }\n const children = [\n createHastElement({\n tagName: 'span',\n children: [\n createHastElement({\n tagName: 'span',\n children: [{ type: 'text', value: `${lineInfo.lineNumber}` }],\n properties: { 'data-line-number-content': '' },\n }),\n ],\n properties: { 'data-column-number': '' },\n }),\n node,\n ];\n return createHastElement({\n tagName: 'div',\n children,\n properties: {\n 'data-line': lineInfo.lineNumber,\n 'data-alt-line': lineInfo.altLineNumber,\n 'data-line-type': lineInfo.type,\n 'data-line-index':\n lineInfo.lineIndex >= 0 ? lineInfo.lineIndex : undefined,\n },\n });\n}\n"],"mappings":";;;AAKA,SAAgB,YACd,MACA,MACA,OACgB;CAChB,MAAM,WACJ,OAAO,MAAM,aAAa,aACtB,MAAM,SAAS,KAAK,GACpB,MAAM,SAAS;AACrB,KAAI,YAAY,MAAM;AACpB,UAAQ,MAAM;GAAE;GAAM;GAAM;GAAO,CAAC;AACpC,QAAM,IAAI,MAAM,qBAAqB,KAAK,8BAA8B;;AAI1E,MAAK,UAAU;AACf,MAAK,WAAW,yBAAyB;AAIzC,KAAI,KAAK,SAAS,WAAW,EAC3B,MAAK,SAAS,KAAK,sBAAsB,KAAK,CAAC;AAgBjD,QAAO,kBAAkB;EACvB,SAAS;EACT,UAhBe,CACf,kBAAkB;GAChB,SAAS;GACT,UAAU,CACR,kBAAkB;IAChB,SAAS;IACT,UAAU,CAAC;KAAE,MAAM;KAAQ,OAAO,GAAG,SAAS;KAAc,CAAC;IAC7D,YAAY,EAAE,4BAA4B,IAAI;IAC/C,CAAC,CACH;GACD,YAAY,EAAE,sBAAsB,IAAI;GACzC,CAAC,EACF,KACD;EAIC,YAAY;GACV,aAAa,SAAS;GACtB,iBAAiB,SAAS;GAC1B,kBAAkB,SAAS;GAC3B,mBACE,SAAS,aAAa,IAAI,SAAS,YAAY;GAClD;EACF,CAAC"}
1
+ {"version":3,"file":"processLine.js","names":[],"sources":["../../src/utils/processLine.ts"],"sourcesContent":["import type { ElementContent, Element as HASTElement } from 'hast';\n\nimport type { SharedRenderState } from '../types';\nimport { createHastElement, createTextNodeElement } from './hast_utils';\n\nexport function processLine(\n node: HASTElement,\n line: number,\n state: SharedRenderState\n): ElementContent {\n const lineInfo =\n typeof state.lineInfo === 'function'\n ? state.lineInfo(line)\n : state.lineInfo[line];\n if (lineInfo == null) {\n console.error({ node, line, state });\n throw new Error(`processLine: line ${line}, contains no state.lineInfo`);\n }\n // We need to convert the current line to a div but keep all the decorations\n // that may be applied\n node.tagName = 'span';\n node.properties['data-column-content'] = '';\n\n // NOTE(amadeus): We need to push newline characters into empty rows or else\n // copy/pasta will have issues\n if (node.children.length === 0) {\n node.children.push(createTextNodeElement('\\n'));\n }\n const children = [\n createHastElement({\n tagName: 'span',\n children: [\n createHastElement({\n tagName: 'span',\n children: [{ type: 'text', value: `${lineInfo.lineNumber}` }],\n properties: { 'data-line-number-content': '' },\n }),\n ],\n properties: { 'data-column-number': '' },\n }),\n node,\n ];\n return createHastElement({\n tagName: 'div',\n children,\n properties: {\n 'data-line': lineInfo.lineNumber,\n 'data-alt-line': lineInfo.altLineNumber,\n 'data-line-type': lineInfo.type,\n 'data-line-index': lineInfo.lineIndex,\n },\n });\n}\n"],"mappings":";;;AAKA,SAAgB,YACd,MACA,MACA,OACgB;CAChB,MAAM,WACJ,OAAO,MAAM,aAAa,aACtB,MAAM,SAAS,KAAK,GACpB,MAAM,SAAS;AACrB,KAAI,YAAY,MAAM;AACpB,UAAQ,MAAM;GAAE;GAAM;GAAM;GAAO,CAAC;AACpC,QAAM,IAAI,MAAM,qBAAqB,KAAK,8BAA8B;;AAI1E,MAAK,UAAU;AACf,MAAK,WAAW,yBAAyB;AAIzC,KAAI,KAAK,SAAS,WAAW,EAC3B,MAAK,SAAS,KAAK,sBAAsB,KAAK,CAAC;AAgBjD,QAAO,kBAAkB;EACvB,SAAS;EACT,UAhBe,CACf,kBAAkB;GAChB,SAAS;GACT,UAAU,CACR,kBAAkB;IAChB,SAAS;IACT,UAAU,CAAC;KAAE,MAAM;KAAQ,OAAO,GAAG,SAAS;KAAc,CAAC;IAC7D,YAAY,EAAE,4BAA4B,IAAI;IAC/C,CAAC,CACH;GACD,YAAY,EAAE,sBAAsB,IAAI;GACzC,CAAC,EACF,KACD;EAIC,YAAY;GACV,aAAa,SAAS;GACtB,iBAAiB,SAAS;GAC1B,kBAAkB,SAAS;GAC3B,mBAAmB,SAAS;GAC7B;EACF,CAAC"}
@@ -48,11 +48,13 @@ function renderDiffWithHighlighter(diff, highlighter, options, forcePlainText =
48
48
  };
49
49
  }
50
50
  const hunks = [];
51
- let lineIndex = 0;
51
+ let splitLineIndex = 0;
52
+ let unifiedLineIndex = 0;
52
53
  for (const hunk of diff.hunks) {
53
- const { oldContent, newContent, oldInfo, newInfo, oldDecorations, newDecorations, lineIndex: newLineIndex } = processLines({
54
+ const { oldContent, newContent, oldInfo, newInfo, oldDecorations, newDecorations, splitLineIndex: newSplitLineIndex, unifiedLineIndex: newUnifiedLineIndex } = processLines({
54
55
  hunks: [hunk],
55
- lineIndex,
56
+ splitLineIndex,
57
+ unifiedLineIndex,
56
58
  lineDiffType: options.lineDiffType
57
59
  });
58
60
  const oldFile = {
@@ -74,7 +76,8 @@ function renderDiffWithHighlighter(diff, highlighter, options, forcePlainText =
74
76
  options,
75
77
  languageOverride: forcePlainText ? "text" : diff.lang
76
78
  }));
77
- lineIndex = newLineIndex;
79
+ splitLineIndex = newSplitLineIndex;
80
+ unifiedLineIndex = newUnifiedLineIndex;
78
81
  }
79
82
  return {
80
83
  code: (() => {
@@ -148,7 +151,7 @@ function computeLineDiffDecorations({ oldLine, newLine, oldLineIndex, newLineInd
148
151
  spanIndex += span[1].length;
149
152
  }
150
153
  }
151
- function processLines({ hunks, oldLines, newLines, lineIndex = 0, lineDiffType }) {
154
+ function processLines({ hunks, oldLines, newLines, splitLineIndex = 0, unifiedLineIndex = 0, lineDiffType }) {
152
155
  const oldInfo = {};
153
156
  const newInfo = {};
154
157
  const oldDecorations = [];
@@ -164,12 +167,14 @@ function processLines({ hunks, oldLines, newLines, lineIndex = 0, lineDiffType }
164
167
  oldInfo[oldLineIndex] = {
165
168
  type: "context-expanded",
166
169
  lineNumber: oldLineNumber,
167
- lineIndex
170
+ altLineNumber: newLineNumber,
171
+ lineIndex: `${unifiedLineIndex},${splitLineIndex}`
168
172
  };
169
173
  newInfo[newLineIndex] = {
170
174
  type: "context-expanded",
171
175
  lineNumber: newLineNumber,
172
- lineIndex
176
+ altLineNumber: oldLineNumber,
177
+ lineIndex: `${unifiedLineIndex},${splitLineIndex}`
173
178
  };
174
179
  oldContent += oldLines[oldLineIndex - 1];
175
180
  newContent += newLines[newLineIndex - 1];
@@ -177,7 +182,8 @@ function processLines({ hunks, oldLines, newLines, lineIndex = 0, lineDiffType }
177
182
  newLineIndex++;
178
183
  oldLineNumber++;
179
184
  newLineNumber++;
180
- lineIndex++;
185
+ splitLineIndex++;
186
+ unifiedLineIndex++;
181
187
  }
182
188
  oldLineNumber = hunk.deletionStart;
183
189
  newLineNumber = hunk.additionStart;
@@ -185,12 +191,14 @@ function processLines({ hunks, oldLines, newLines, lineIndex = 0, lineDiffType }
185
191
  oldInfo[oldLineIndex] = {
186
192
  type: "context",
187
193
  lineNumber: oldLineNumber,
188
- lineIndex
194
+ altLineNumber: newLineNumber,
195
+ lineIndex: `${unifiedLineIndex},${splitLineIndex}`
189
196
  };
190
197
  newInfo[newLineIndex] = {
191
198
  type: "context",
192
199
  lineNumber: newLineNumber,
193
- lineIndex
200
+ altLineNumber: oldLineNumber,
201
+ lineIndex: `${unifiedLineIndex},${splitLineIndex}`
194
202
  };
195
203
  oldContent += line;
196
204
  newContent += line;
@@ -198,11 +206,13 @@ function processLines({ hunks, oldLines, newLines, lineIndex = 0, lineDiffType }
198
206
  newLineIndex++;
199
207
  newLineNumber++;
200
208
  oldLineNumber++;
201
- lineIndex++;
209
+ splitLineIndex++;
210
+ unifiedLineIndex++;
202
211
  }
203
212
  else {
204
213
  const len = Math.max(hunkContent.additions.length, hunkContent.deletions.length);
205
214
  let i = 0;
215
+ let _unifiedLineIndex = unifiedLineIndex;
206
216
  while (i < len) {
207
217
  const oldLine = hunkContent.deletions[i];
208
218
  const newLine = hunkContent.additions[i];
@@ -219,7 +229,7 @@ function processLines({ hunks, oldLines, newLines, lineIndex = 0, lineDiffType }
219
229
  oldInfo[oldLineIndex] = {
220
230
  type: "change-deletion",
221
231
  lineNumber: oldLineNumber,
222
- lineIndex
232
+ lineIndex: `${_unifiedLineIndex},${splitLineIndex}`
223
233
  };
224
234
  oldContent += oldLine;
225
235
  oldLineIndex++;
@@ -229,15 +239,17 @@ function processLines({ hunks, oldLines, newLines, lineIndex = 0, lineDiffType }
229
239
  newInfo[newLineIndex] = {
230
240
  type: "change-addition",
231
241
  lineNumber: newLineNumber,
232
- lineIndex
242
+ lineIndex: `${_unifiedLineIndex + hunkContent.deletions.length},${splitLineIndex}`
233
243
  };
234
244
  newContent += newLine;
235
245
  newLineIndex++;
236
246
  newLineNumber++;
237
247
  }
238
- lineIndex++;
248
+ splitLineIndex++;
249
+ _unifiedLineIndex++;
239
250
  i++;
240
251
  }
252
+ unifiedLineIndex += hunkContent.additions.length + hunkContent.deletions.length;
241
253
  }
242
254
  if (oldLines == null || newLines == null || hunk !== hunks[hunks.length - 1]) continue;
243
255
  while (oldLineIndex <= oldLines.length || newLineIndex <= oldLines.length) {
@@ -248,7 +260,8 @@ function processLines({ hunks, oldLines, newLines, lineIndex = 0, lineDiffType }
248
260
  oldInfo[oldLineIndex] = {
249
261
  type: "context-expanded",
250
262
  lineNumber: oldLineNumber,
251
- lineIndex
263
+ altLineNumber: newLineNumber,
264
+ lineIndex: `${unifiedLineIndex},${splitLineIndex}`
252
265
  };
253
266
  oldContent += oldLine;
254
267
  oldLineIndex++;
@@ -258,13 +271,15 @@ function processLines({ hunks, oldLines, newLines, lineIndex = 0, lineDiffType }
258
271
  newInfo[newLineIndex] = {
259
272
  type: "context-expanded",
260
273
  lineNumber: newLineNumber,
261
- lineIndex
274
+ altLineNumber: oldLineNumber,
275
+ lineIndex: `${unifiedLineIndex},${splitLineIndex}`
262
276
  };
263
277
  newContent += newLine;
264
278
  newLineIndex++;
265
279
  newLineNumber++;
266
280
  }
267
- lineIndex++;
281
+ splitLineIndex++;
282
+ unifiedLineIndex++;
268
283
  }
269
284
  }
270
285
  return {
@@ -274,7 +289,8 @@ function processLines({ hunks, oldLines, newLines, lineIndex = 0, lineDiffType }
274
289
  newInfo,
275
290
  oldDecorations,
276
291
  newDecorations,
277
- lineIndex
292
+ splitLineIndex,
293
+ unifiedLineIndex
278
294
  };
279
295
  }
280
296
  function renderTwoFiles({ oldFile, newFile, oldInfo, newInfo, highlighter, oldDecorations, newDecorations, languageOverride, options: { theme: themeOrThemes = DEFAULT_THEMES,...options } }) {