UNPKG

react-diff-view

Version:

A git diff component to consume the git unified diff output.

138 lines 6.56 kB
import { isNormal } from '../parse'; import { insertHunk, textLinesToHunk } from './insertHunk'; import { computeLineNumberFactory, isInHunkFactory, isBetweenHunksFactory, getCorrespondingLineNumberFactory, } from './factory'; import { first } from './util'; const getCorrespondingNewLineNumber = getCorrespondingLineNumberFactory('old'); const computeOldLineNumber = computeLineNumberFactory('old'); const isOldLineNumberInHunk = isInHunkFactory('oldStart', 'oldLines'); const isOldLineNumberBetweenHunks = isBetweenHunksFactory('oldStart', 'oldLines'); function findCorrespondingValidHunkIndex(hunks, oldLineNumber) { if (!hunks.length) { return -1; } const firstHunk = first(hunks); if (oldLineNumber < firstHunk.oldStart || isOldLineNumberInHunk(firstHunk, oldLineNumber)) { return 0; } for (let i = 1; i < hunks.length; i++) { const currentHunk = hunks[i]; if (isOldLineNumberInHunk(currentHunk, oldLineNumber)) { return i; } const previousHunk = hunks[i - 1]; if (isOldLineNumberBetweenHunks(previousHunk, currentHunk, oldLineNumber)) { return i; } } return -1; } function findNearestNormalChangeIndex({ changes }, start) { const index = changes.findIndex(change => computeOldLineNumber(change) === start); if (index < 0) { return -1; } for (let i = index; i < changes.length; i++) { const change = changes[i]; if (isNormal(change)) { return i; } } return -1; } function splitRangeToValidOnes(hunks, start, end) { const correspondingHunkIndex = findCorrespondingValidHunkIndex(hunks, start); // `start` is after all hunks, we believe all left lines are normal. if (correspondingHunkIndex === -1) { return [[start, end]]; } const correspondingHunk = hunks[correspondingHunkIndex]; // If `start` points to a line before this hunk, we collect all heading normal changes if (start < correspondingHunk.oldStart) { const headingChangesCount = correspondingHunk.changes.findIndex(change => !isNormal(change)); const validEnd = correspondingHunk.oldStart + Math.max(headingChangesCount, 0); if (validEnd >= end) { return [[start, end]]; } return [ [start, validEnd], ...splitRangeToValidOnes(hunks, validEnd + 1, end), ]; } // Now the `correspondingHunk` must be a hunk containing `start`, // however it is still possible that `start` is not a normal change const { changes } = correspondingHunk; const nearestNormalChangeIndex = findNearestNormalChangeIndex(correspondingHunk, start); // If there is no normal changes after `start`, splitting ends up here if (nearestNormalChangeIndex === -1) { return []; } const validStartChange = changes[nearestNormalChangeIndex]; const validStart = computeOldLineNumber(validStartChange); // Iterate to `end`, if `end` falls out of hunk, we can split it to 2 ranges const adjacentChangesCount = changes.slice(nearestNormalChangeIndex + 1).findIndex(change => !isNormal(change)); const validEnd = computeOldLineNumber(validStartChange) + Math.max(adjacentChangesCount, 0); if (validEnd >= end) { return [[validStart, end]]; } return [ [validStart, validEnd], ...splitRangeToValidOnes(hunks, validEnd + 1, end), ]; } function expandCodeByValidRange(hunks, source, [start, end]) { // Note `end` is not inclusive, this is the same as `Array.prototype.slice` method const linesOfCode = typeof source === 'string' ? source.split('\n') : source; const slicedLines = linesOfCode.slice(Math.max(start, 1) - 1, end - 1); if (!slicedLines.length) { return hunks; } const slicedHunk = textLinesToHunk(slicedLines, start, getCorrespondingNewLineNumber(hunks, start)); return slicedHunk ? insertHunk(hunks, slicedHunk) : hunks; } export function expandFromRawCode(hunks, source, start, end) { // It is possible to have some insert or delete changes between `start` and `end`, // in order to be 100% safe, we need to split the range to one or more ranges which contains only normal changes. // // For each `start` line number, we can either: // // 1. Find a change and adjust to a nearest normal one. // 2. Find no corresponding change so it must be a collapsed normal change. // // For both cases we can have a starting normal change, then we iterate over its subsequent changes // (line numbers with no corresponding change is considered a normal one) // until an insert or delete is encountered, this is a **valid range**. // // After one valid range is resolved, discard all line numbers related to delete changes, the next normal change // is the start of next valid range. const validRanges = splitRangeToValidOnes(hunks, start, end); return validRanges.reduce((hunks, range) => expandCodeByValidRange(hunks, source, range), hunks); } export function getCollapsedLinesCountBetween(previousHunk, nextHunk) { if (!previousHunk) { return nextHunk.oldStart - 1; } const previousEnd = previousHunk.oldStart + previousHunk.oldLines; const nextStart = nextHunk.oldStart; return nextStart - previousEnd; } export function expandCollapsedBlockBy(hunks, source, predicate) { const linesOfCode = typeof source === 'string' ? source.split('\n') : source; const firstHunk = first(hunks); const initialExpandingBlocks = predicate(firstHunk.oldStart - 1, 1, 1) ? [[1, firstHunk.oldStart]] : []; const expandingBlocks = hunks.reduce((expandingBlocks, currentHunk, index, hunks) => { const nextHunk = hunks[index + 1]; const oldStart = currentHunk.oldStart + currentHunk.oldLines; const newStart = currentHunk.newStart + currentHunk.newLines; const lines = nextHunk ? getCollapsedLinesCountBetween(currentHunk, nextHunk) : linesOfCode.length - oldStart + 1; const shouldExpand = predicate(lines, oldStart, newStart); if (shouldExpand) { // initialExpandingBlocks is scoped, it is redundant to copy the array expandingBlocks.push([oldStart, oldStart + lines]); } return expandingBlocks; }, initialExpandingBlocks); return expandingBlocks.reduce((hunks, [start, end]) => expandFromRawCode(hunks, linesOfCode, start, end), hunks); } //# sourceMappingURL=expandCollapsedBlockBy.js.map