refactor: Extract matching algorithm in line-by-line diff
This commit is contained in:
parent
0f08c85938
commit
a25d06a8d7
3 changed files with 74 additions and 84 deletions
|
|
@ -87,7 +87,7 @@ export default class LineByLineRenderer {
|
||||||
|
|
||||||
return file.blocks
|
return file.blocks
|
||||||
.map(block => {
|
.map(block => {
|
||||||
let lines = this.hoganUtils.render(genericTemplatesPath, "column-line-number", {
|
let lines = this.hoganUtils.render(genericTemplatesPath, "block-header", {
|
||||||
CSSLineClass: renderUtils.CSSLineClass,
|
CSSLineClass: renderUtils.CSSLineClass,
|
||||||
blockHeader: block.header,
|
blockHeader: block.header,
|
||||||
lineClass: "d2h-code-linenumber",
|
lineClass: "d2h-code-linenumber",
|
||||||
|
|
@ -96,33 +96,70 @@ export default class LineByLineRenderer {
|
||||||
let oldLines: DiffLine[] = [];
|
let oldLines: DiffLine[] = [];
|
||||||
let newLines: DiffLine[] = [];
|
let newLines: DiffLine[] = [];
|
||||||
|
|
||||||
const processChangeBlock = (): void => {
|
for (let i = 0; i < block.lines.length; i++) {
|
||||||
let matches;
|
const diffLine = block.lines[i];
|
||||||
let insertType: renderUtils.CSSLineClass;
|
const { prefix, content } = renderUtils.deconstructLine(diffLine.content, file.isCombined);
|
||||||
let deleteType: renderUtils.CSSLineClass;
|
|
||||||
|
|
||||||
|
if (
|
||||||
|
diffLine.type !== LineType.INSERT &&
|
||||||
|
(newLines.length > 0 || (diffLine.type !== LineType.DELETE && oldLines.length > 0))
|
||||||
|
) {
|
||||||
|
lines += this.processChangeBlock(file, oldLines, newLines, matcher);
|
||||||
|
oldLines = [];
|
||||||
|
newLines = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (diffLine.type === LineType.CONTEXT || (diffLine.type === LineType.INSERT && !oldLines.length)) {
|
||||||
|
lines += this.makeLineHtml(
|
||||||
|
file.isCombined,
|
||||||
|
renderUtils.toCSSClass(diffLine.type),
|
||||||
|
content,
|
||||||
|
diffLine.oldNumber,
|
||||||
|
diffLine.newNumber,
|
||||||
|
prefix
|
||||||
|
);
|
||||||
|
} else if (diffLine.type === LineType.DELETE) {
|
||||||
|
oldLines.push(diffLine);
|
||||||
|
} else if (diffLine.type === LineType.INSERT && Boolean(oldLines.length)) {
|
||||||
|
newLines.push(diffLine);
|
||||||
|
} else {
|
||||||
|
console.error("Unknown state in html line-by-line generator");
|
||||||
|
lines += this.processChangeBlock(file, oldLines, newLines, matcher);
|
||||||
|
oldLines = [];
|
||||||
|
newLines = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lines += this.processChangeBlock(file, oldLines, newLines, matcher);
|
||||||
|
oldLines = [];
|
||||||
|
newLines = [];
|
||||||
|
|
||||||
|
return lines;
|
||||||
|
})
|
||||||
|
.join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
processChangeBlock(
|
||||||
|
file: DiffFile,
|
||||||
|
oldLines: DiffLine[],
|
||||||
|
newLines: DiffLine[],
|
||||||
|
matcher: Rematch.MatcherFn<DiffLine>
|
||||||
|
): string {
|
||||||
const comparisons = oldLines.length * newLines.length;
|
const comparisons = oldLines.length * newLines.length;
|
||||||
|
|
||||||
const maxLineSizeInBlock = Math.max.apply(
|
const maxLineSizeInBlock = Math.max.apply(
|
||||||
null,
|
null,
|
||||||
[0].concat(oldLines.concat(newLines).map(elem => elem.content.length))
|
[0].concat(oldLines.concat(newLines).map(elem => elem.content.length))
|
||||||
);
|
);
|
||||||
|
|
||||||
const doMatching =
|
const doMatching =
|
||||||
comparisons < this.config.matchingMaxComparisons &&
|
comparisons < this.config.matchingMaxComparisons &&
|
||||||
maxLineSizeInBlock < this.config.maxLineSizeInBlockForComparison &&
|
maxLineSizeInBlock < this.config.maxLineSizeInBlockForComparison &&
|
||||||
(this.config.matching === "lines" || this.config.matching === "words");
|
(this.config.matching === "lines" || this.config.matching === "words");
|
||||||
|
|
||||||
if (doMatching) {
|
const [matches, insertType, deleteType] = doMatching
|
||||||
matches = matcher(oldLines, newLines);
|
? [matcher(oldLines, newLines), renderUtils.CSSLineClass.INSERT_CHANGES, renderUtils.CSSLineClass.DELETE_CHANGES]
|
||||||
insertType = renderUtils.CSSLineClass.INSERT_CHANGES;
|
: [[[oldLines, newLines]], renderUtils.CSSLineClass.INSERTS, renderUtils.CSSLineClass.DELETES];
|
||||||
deleteType = renderUtils.CSSLineClass.DELETE_CHANGES;
|
|
||||||
} else {
|
|
||||||
matches = [[oldLines, newLines]];
|
|
||||||
insertType = renderUtils.CSSLineClass.INSERTS;
|
|
||||||
deleteType = renderUtils.CSSLineClass.DELETES;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
let lines = "";
|
||||||
matches.forEach(match => {
|
matches.forEach(match => {
|
||||||
oldLines = match[0];
|
oldLines = match[0];
|
||||||
newLines = match[1];
|
newLines = match[1];
|
||||||
|
|
@ -161,54 +198,7 @@ export default class LineByLineRenderer {
|
||||||
lines += this.processLines(file.isCombined, oldLines.slice(common), newLines.slice(common));
|
lines += this.processLines(file.isCombined, oldLines.slice(common), newLines.slice(common));
|
||||||
});
|
});
|
||||||
|
|
||||||
oldLines = [];
|
|
||||||
newLines = [];
|
|
||||||
};
|
|
||||||
|
|
||||||
for (let i = 0; i < block.lines.length; i++) {
|
|
||||||
const diffLine = block.lines[i];
|
|
||||||
const { prefix, content } = renderUtils.deconstructLine(diffLine.content, file.isCombined);
|
|
||||||
|
|
||||||
if (
|
|
||||||
diffLine.type !== LineType.INSERT &&
|
|
||||||
(newLines.length > 0 || (diffLine.type !== LineType.DELETE && oldLines.length > 0))
|
|
||||||
) {
|
|
||||||
processChangeBlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (diffLine.type === LineType.CONTEXT) {
|
|
||||||
lines += this.makeLineHtml(
|
|
||||||
file.isCombined,
|
|
||||||
renderUtils.toCSSClass(diffLine.type),
|
|
||||||
content,
|
|
||||||
diffLine.oldNumber,
|
|
||||||
diffLine.newNumber,
|
|
||||||
prefix
|
|
||||||
);
|
|
||||||
} else if (diffLine.type === LineType.INSERT && !oldLines.length) {
|
|
||||||
lines += this.makeLineHtml(
|
|
||||||
file.isCombined,
|
|
||||||
renderUtils.toCSSClass(diffLine.type),
|
|
||||||
content,
|
|
||||||
diffLine.oldNumber,
|
|
||||||
diffLine.newNumber,
|
|
||||||
prefix
|
|
||||||
);
|
|
||||||
} else if (diffLine.type === LineType.DELETE) {
|
|
||||||
oldLines.push(diffLine);
|
|
||||||
} else if (diffLine.type === LineType.INSERT && Boolean(oldLines.length)) {
|
|
||||||
newLines.push(diffLine);
|
|
||||||
} else {
|
|
||||||
console.error("Unknown state in html line-by-line generator");
|
|
||||||
processChangeBlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
processChangeBlock();
|
|
||||||
|
|
||||||
return lines;
|
return lines;
|
||||||
})
|
|
||||||
.join("\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Make this private after improving tests
|
// TODO: Make this private after improving tests
|
||||||
|
|
|
||||||
|
|
@ -223,7 +223,7 @@ export default class SideBySideRenderer {
|
||||||
|
|
||||||
// TODO: Make this private after improving tests
|
// TODO: Make this private after improving tests
|
||||||
makeSideHtml(blockHeader: string): string {
|
makeSideHtml(blockHeader: string): string {
|
||||||
return this.hoganUtils.render(genericTemplatesPath, "column-line-number", {
|
return this.hoganUtils.render(genericTemplatesPath, "block-header", {
|
||||||
CSSLineClass: renderUtils.CSSLineClass,
|
CSSLineClass: renderUtils.CSSLineClass,
|
||||||
blockHeader: blockHeader,
|
blockHeader: blockHeader,
|
||||||
lineClass: "d2h-code-side-linenumber",
|
lineClass: "d2h-code-side-linenumber",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue