react-diff-view
Version:
A git diff component to consume the git unified diff output.
146 lines • 5.69 kB
JavaScript
import { findLastIndex } from 'lodash';
import { isDelete, isInsert, isNormal } from '../parse';
import { computeLineNumberFactory } from './factory';
import { last } from './util';
const computeOldLineNumber = computeLineNumberFactory('old');
const computeNewLineNumber = computeLineNumberFactory('new');
function getOldRangeFromHunk({ oldStart, oldLines }) {
return [oldStart, oldStart + oldLines - 1];
}
function createHunkFromChanges(changes) {
if (!changes.length) {
return null;
}
const initial = {
isPlain: true,
content: '',
oldStart: -1,
oldLines: 0,
newStart: -1,
newLines: 0,
};
/* eslint-disable no-param-reassign */
const hunk = changes.reduce((hunk, change) => {
if (!isNormal(change)) {
hunk.isPlain = false;
}
if (!isInsert(change)) {
hunk.oldLines = hunk.oldLines + 1;
if (hunk.oldStart === -1) {
hunk.oldStart = computeOldLineNumber(change);
}
}
if (!isDelete(change)) {
hunk.newLines = hunk.newLines + 1;
if (hunk.newStart === -1) {
hunk.newStart = computeNewLineNumber(change);
}
}
return hunk;
}, initial);
/* eslint-enable no-param-reassign */
const { oldStart, oldLines, newStart, newLines } = hunk;
return {
...hunk,
content: `@@ -${oldStart},${oldLines} +${newStart},${newLines}`,
changes: changes,
};
}
export function textLinesToHunk(lines, oldStartLine, newStartLine) {
const lineToChange = (line, i) => {
return {
type: 'normal',
isNormal: true,
oldLineNumber: oldStartLine + i,
newLineNumber: newStartLine + i,
content: '' + line,
};
};
const changes = lines.map(lineToChange);
return createHunkFromChanges(changes);
}
function sliceHunk({ changes }, oldStartLine, oldEndLine) {
const changeIndex = changes.findIndex(change => computeOldLineNumber(change) >= oldStartLine);
if (changeIndex === -1) {
return null;
}
// It is possible to have some insert changes before `startOldLineNumber`,
// since we slice from old line number, these changes can be ommited, so we need to grab them back
const startIndex = (() => {
if (changeIndex === 0) {
return changeIndex;
}
const nearestHeadingNocmalChangeIndex = findLastIndex(changes, change => !isInsert(change), changeIndex - 1);
return nearestHeadingNocmalChangeIndex === -1 ? changeIndex : nearestHeadingNocmalChangeIndex + 1;
})();
if (oldEndLine === undefined) {
const slicedChanges = changes.slice(startIndex);
return createHunkFromChanges(slicedChanges);
}
const endIndex = findLastIndex(changes, change => computeOldLineNumber(change) <= oldEndLine);
const slicedChanges = changes.slice(startIndex, endIndex === -1 ? undefined : endIndex);
return createHunkFromChanges(slicedChanges);
}
function mergeHunk(previousHunk, nextHunk) {
if (!previousHunk) {
return nextHunk;
}
if (!nextHunk) {
return previousHunk;
}
const [previousStart, previousEnd] = getOldRangeFromHunk(previousHunk);
const [nextStart, nextEnd] = getOldRangeFromHunk(nextHunk);
// They are just neighboring, simply concat changes and adjust lines count
if (previousEnd + 1 === nextStart) {
return createHunkFromChanges([...previousHunk.changes, ...nextHunk.changes]);
}
// It is possible that `previousHunk` entirely **contains** `nextHunk`,
// and if we are merging a fake hunk with a valid hunk, we need to replace `nextHunk`'s corresponding range
if (previousStart <= nextStart && previousEnd >= nextEnd) {
if (previousHunk.isPlain && !nextHunk.isPlain) {
const head = sliceHunk(previousHunk, previousStart, nextStart);
const tail = sliceHunk(previousHunk, nextEnd + 1);
return mergeHunk(mergeHunk(head, nextHunk), tail);
}
return previousHunk;
}
// The 2 hunks have some overlapping, we need to slice the fake one in order to preserve non-normal changes
if (previousHunk.isPlain) {
const head = sliceHunk(previousHunk, previousStart, nextStart);
return mergeHunk(head, nextHunk);
}
const tail = sliceHunk(nextHunk, previousEnd + 1);
return mergeHunk(previousHunk, tail);
}
function appendOrMergeHunk(hunks, nextHunk) {
const lastHunk = last(hunks);
if (!lastHunk) {
return [nextHunk];
}
const expectedNextStart = lastHunk.oldStart + lastHunk.oldLines;
const actualNextStart = nextHunk.oldStart;
if (expectedNextStart < actualNextStart) {
return hunks.concat(nextHunk);
}
const mergedHunk = mergeHunk(lastHunk, nextHunk);
return mergedHunk ? [...hunks.slice(0, -1), mergedHunk] : hunks;
}
export function insertHunk(hunks, insertion) {
const insertionOldLineNumber = computeOldLineNumber(insertion.changes[0]);
const isLaterThanInsertion = ({ changes }) => {
if (!changes.length) {
return false;
}
return computeOldLineNumber(changes[0]) >= insertionOldLineNumber;
};
const insertPosition = hunks.findIndex(isLaterThanInsertion);
const hunksWithInsertion = insertPosition === -1
? hunks.concat(insertion)
: [
...hunks.slice(0, insertPosition),
insertion,
...hunks.slice(insertPosition),
];
return hunksWithInsertion.reduce(appendOrMergeHunk, []);
}
//# sourceMappingURL=insertHunk.js.map