refactor: Separate matching in side-by-side algorithm
This commit is contained in:
parent
d8e0a99070
commit
5c35de28eb
4 changed files with 293 additions and 253 deletions
|
|
@ -141,11 +141,11 @@ const htmlSideExample1 =
|
||||||
' <div class="d2h-code-side-line d2h-info">@@ -1 +1 @@</div>\n' +
|
' <div class="d2h-code-side-line d2h-info">@@ -1 +1 @@</div>\n' +
|
||||||
" </td>\n" +
|
" </td>\n" +
|
||||||
"</tr><tr>\n" +
|
"</tr><tr>\n" +
|
||||||
' <td class="d2h-code-side-linenumber d2h-del">\n' +
|
' <td class="d2h-code-side-linenumber d2h-del d2h-change">\n' +
|
||||||
" 1\n" +
|
" 1\n" +
|
||||||
" </td>\n" +
|
" </td>\n" +
|
||||||
' <td class="d2h-del">\n' +
|
' <td class="d2h-del d2h-change">\n' +
|
||||||
' <div class="d2h-code-side-line d2h-del">\n' +
|
' <div class="d2h-code-side-line d2h-del d2h-change">\n' +
|
||||||
' <span class="d2h-code-line-prefix">-</span>\n' +
|
' <span class="d2h-code-line-prefix">-</span>\n' +
|
||||||
' <span class="d2h-code-line-ctn"><del>test</del></span>\n' +
|
' <span class="d2h-code-line-ctn"><del>test</del></span>\n' +
|
||||||
" </div>\n" +
|
" </div>\n" +
|
||||||
|
|
@ -165,11 +165,11 @@ const htmlSideExample1 =
|
||||||
' <div class="d2h-code-side-line d2h-info"></div>\n' +
|
' <div class="d2h-code-side-line d2h-info"></div>\n' +
|
||||||
" </td>\n" +
|
" </td>\n" +
|
||||||
"</tr><tr>\n" +
|
"</tr><tr>\n" +
|
||||||
' <td class="d2h-code-side-linenumber d2h-ins">\n' +
|
' <td class="d2h-code-side-linenumber d2h-ins d2h-change">\n' +
|
||||||
" 1\n" +
|
" 1\n" +
|
||||||
" </td>\n" +
|
" </td>\n" +
|
||||||
' <td class="d2h-ins">\n' +
|
' <td class="d2h-ins d2h-change">\n' +
|
||||||
' <div class="d2h-code-side-line d2h-ins">\n' +
|
' <div class="d2h-code-side-line d2h-ins d2h-change">\n' +
|
||||||
' <span class="d2h-code-line-prefix">+</span>\n' +
|
' <span class="d2h-code-line-prefix">+</span>\n' +
|
||||||
' <span class="d2h-code-line-ctn"><ins>test1</ins></span>\n' +
|
' <span class="d2h-code-line-ctn"><ins>test1</ins></span>\n' +
|
||||||
" </div>\n" +
|
" </div>\n" +
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ describe("SideBySideRenderer", () => {
|
||||||
isCombined: false
|
isCombined: false
|
||||||
};
|
};
|
||||||
|
|
||||||
const fileHtml = sideBySideRenderer.generateSideBySideFileHtml(file);
|
const fileHtml = sideBySideRenderer.generateFileHtml(file);
|
||||||
|
|
||||||
const expectedLeft =
|
const expectedLeft =
|
||||||
"<tr>\n" +
|
"<tr>\n" +
|
||||||
|
|
@ -94,11 +94,11 @@ describe("SideBySideRenderer", () => {
|
||||||
" </div>\n" +
|
" </div>\n" +
|
||||||
" </td>\n" +
|
" </td>\n" +
|
||||||
"</tr><tr>\n" +
|
"</tr><tr>\n" +
|
||||||
' <td class="d2h-code-side-linenumber d2h-del">\n' +
|
' <td class="d2h-code-side-linenumber d2h-del d2h-change">\n' +
|
||||||
" 20\n" +
|
" 20\n" +
|
||||||
" </td>\n" +
|
" </td>\n" +
|
||||||
' <td class="d2h-del">\n' +
|
' <td class="d2h-del d2h-change">\n' +
|
||||||
' <div class="d2h-code-side-line d2h-del">\n' +
|
' <div class="d2h-code-side-line d2h-del d2h-change">\n' +
|
||||||
' <span class="d2h-code-line-prefix">-</span>\n' +
|
' <span class="d2h-code-line-prefix">-</span>\n' +
|
||||||
' <span class="d2h-code-line-ctn"><del>removed</del></span>\n' +
|
' <span class="d2h-code-line-ctn"><del>removed</del></span>\n' +
|
||||||
" </div>\n" +
|
" </div>\n" +
|
||||||
|
|
@ -133,11 +133,11 @@ describe("SideBySideRenderer", () => {
|
||||||
" </div>\n" +
|
" </div>\n" +
|
||||||
" </td>\n" +
|
" </td>\n" +
|
||||||
"</tr><tr>\n" +
|
"</tr><tr>\n" +
|
||||||
' <td class="d2h-code-side-linenumber d2h-ins">\n' +
|
' <td class="d2h-code-side-linenumber d2h-ins d2h-change">\n' +
|
||||||
" 20\n" +
|
" 20\n" +
|
||||||
" </td>\n" +
|
" </td>\n" +
|
||||||
' <td class="d2h-ins">\n' +
|
' <td class="d2h-ins d2h-change">\n' +
|
||||||
' <div class="d2h-code-side-line d2h-ins">\n' +
|
' <div class="d2h-code-side-line d2h-ins d2h-change">\n' +
|
||||||
' <span class="d2h-code-line-prefix">+</span>\n' +
|
' <span class="d2h-code-line-prefix">+</span>\n' +
|
||||||
' <span class="d2h-code-line-ctn"><ins>added</ins></span>\n' +
|
' <span class="d2h-code-line-ctn"><ins>added</ins></span>\n' +
|
||||||
" </div>\n" +
|
" </div>\n" +
|
||||||
|
|
@ -163,38 +163,76 @@ describe("SideBySideRenderer", () => {
|
||||||
it("should work for insertions", () => {
|
it("should work for insertions", () => {
|
||||||
const hoganUtils = new HoganJsUtils({});
|
const hoganUtils = new HoganJsUtils({});
|
||||||
const sideBySideRenderer = new SideBySideRenderer(hoganUtils, {});
|
const sideBySideRenderer = new SideBySideRenderer(hoganUtils, {});
|
||||||
const fileHtml = sideBySideRenderer.generateSingleLineHtml(false, CSSLineClass.INSERTS, "test", 30, "+");
|
const fileHtml = sideBySideRenderer.generateSingleLineHtml(undefined, {
|
||||||
const expected =
|
type: CSSLineClass.INSERTS,
|
||||||
"<tr>\n" +
|
prefix: "+",
|
||||||
' <td class="d2h-code-side-linenumber d2h-ins">\n' +
|
content: "test",
|
||||||
" 30\n" +
|
number: 30
|
||||||
" </td>\n" +
|
});
|
||||||
' <td class="d2h-ins">\n' +
|
|
||||||
' <div class="d2h-code-side-line d2h-ins">\n' +
|
const expected = {
|
||||||
' <span class="d2h-code-line-prefix">+</span>\n' +
|
left:
|
||||||
' <span class="d2h-code-line-ctn">test</span>\n' +
|
"<tr>\n" +
|
||||||
" </div>\n" +
|
' <td class="d2h-code-side-linenumber d2h-code-side-emptyplaceholder d2h-cntx d2h-emptyplaceholder">\n' +
|
||||||
" </td>\n" +
|
" \n" +
|
||||||
"</tr>";
|
" </td>\n" +
|
||||||
|
' <td class="d2h-cntx d2h-emptyplaceholder">\n' +
|
||||||
|
' <div class="d2h-code-side-line d2h-code-side-emptyplaceholder d2h-cntx d2h-emptyplaceholder">\n' +
|
||||||
|
" </div>\n" +
|
||||||
|
" </td>\n" +
|
||||||
|
"</tr>",
|
||||||
|
right:
|
||||||
|
"<tr>\n" +
|
||||||
|
' <td class="d2h-code-side-linenumber d2h-ins">\n' +
|
||||||
|
" 30\n" +
|
||||||
|
" </td>\n" +
|
||||||
|
' <td class="d2h-ins">\n' +
|
||||||
|
' <div class="d2h-code-side-line d2h-ins">\n' +
|
||||||
|
' <span class="d2h-code-line-prefix">+</span>\n' +
|
||||||
|
' <span class="d2h-code-line-ctn">test</span>\n' +
|
||||||
|
" </div>\n" +
|
||||||
|
" </td>\n" +
|
||||||
|
"</tr>"
|
||||||
|
};
|
||||||
|
|
||||||
expect(fileHtml).toEqual(expected);
|
expect(fileHtml).toEqual(expected);
|
||||||
});
|
});
|
||||||
it("should work for deletions", () => {
|
it("should work for deletions", () => {
|
||||||
const hoganUtils = new HoganJsUtils({});
|
const hoganUtils = new HoganJsUtils({});
|
||||||
const sideBySideRenderer = new SideBySideRenderer(hoganUtils, {});
|
const sideBySideRenderer = new SideBySideRenderer(hoganUtils, {});
|
||||||
const fileHtml = sideBySideRenderer.generateSingleLineHtml(false, CSSLineClass.DELETES, "test", 30, "-");
|
const fileHtml = sideBySideRenderer.generateSingleLineHtml(
|
||||||
const expected =
|
{
|
||||||
"<tr>\n" +
|
type: CSSLineClass.DELETES,
|
||||||
' <td class="d2h-code-side-linenumber d2h-del">\n' +
|
prefix: "-",
|
||||||
" 30\n" +
|
content: "test",
|
||||||
" </td>\n" +
|
number: 30
|
||||||
' <td class="d2h-del">\n' +
|
},
|
||||||
' <div class="d2h-code-side-line d2h-del">\n' +
|
undefined
|
||||||
' <span class="d2h-code-line-prefix">-</span>\n' +
|
);
|
||||||
' <span class="d2h-code-line-ctn">test</span>\n' +
|
const expected = {
|
||||||
" </div>\n" +
|
left:
|
||||||
" </td>\n" +
|
"<tr>\n" +
|
||||||
"</tr>";
|
' <td class="d2h-code-side-linenumber d2h-del">\n' +
|
||||||
|
" 30\n" +
|
||||||
|
" </td>\n" +
|
||||||
|
' <td class="d2h-del">\n' +
|
||||||
|
' <div class="d2h-code-side-line d2h-del">\n' +
|
||||||
|
' <span class="d2h-code-line-prefix">-</span>\n' +
|
||||||
|
' <span class="d2h-code-line-ctn">test</span>\n' +
|
||||||
|
" </div>\n" +
|
||||||
|
" </td>\n" +
|
||||||
|
"</tr>",
|
||||||
|
right:
|
||||||
|
"<tr>\n" +
|
||||||
|
' <td class="d2h-code-side-linenumber d2h-code-side-emptyplaceholder d2h-cntx d2h-emptyplaceholder">\n' +
|
||||||
|
" \n" +
|
||||||
|
" </td>\n" +
|
||||||
|
' <td class="d2h-cntx d2h-emptyplaceholder">\n' +
|
||||||
|
' <div class="d2h-code-side-line d2h-code-side-emptyplaceholder d2h-cntx d2h-emptyplaceholder">\n' +
|
||||||
|
" </div>\n" +
|
||||||
|
" </td>\n" +
|
||||||
|
"</tr>"
|
||||||
|
};
|
||||||
|
|
||||||
expect(fileHtml).toEqual(expected);
|
expect(fileHtml).toEqual(expected);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,16 @@
|
||||||
import HoganJsUtils from "./hoganjs-utils";
|
import HoganJsUtils from "./hoganjs-utils";
|
||||||
import * as Rematch from "./rematch";
|
import * as Rematch from "./rematch";
|
||||||
import * as renderUtils from "./render-utils";
|
import * as renderUtils from "./render-utils";
|
||||||
import { DiffLine, LineType, DiffFile } from "./types";
|
import {
|
||||||
|
DiffLine,
|
||||||
|
LineType,
|
||||||
|
DiffFile,
|
||||||
|
DiffBlock,
|
||||||
|
DiffLineContext,
|
||||||
|
DiffLineDeleted,
|
||||||
|
DiffLineInserted,
|
||||||
|
DiffLineContent
|
||||||
|
} from "./types";
|
||||||
|
|
||||||
export interface SideBySideRendererConfig extends renderUtils.RenderConfig {
|
export interface SideBySideRendererConfig extends renderUtils.RenderConfig {
|
||||||
renderNothingWhenEmpty?: boolean;
|
renderNothingWhenEmpty?: boolean;
|
||||||
|
|
@ -35,7 +44,7 @@ export default class SideBySideRenderer {
|
||||||
.map(file => {
|
.map(file => {
|
||||||
let diffs;
|
let diffs;
|
||||||
if (file.blocks.length) {
|
if (file.blocks.length) {
|
||||||
diffs = this.generateSideBySideFileHtml(file);
|
diffs = this.generateFileHtml(file);
|
||||||
} else {
|
} else {
|
||||||
diffs = this.generateEmptyDiff();
|
diffs = this.generateEmptyDiff();
|
||||||
}
|
}
|
||||||
|
|
@ -82,147 +91,172 @@ export default class SideBySideRenderer {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Make this private after improving tests
|
generateFileHtml(file: DiffFile): FileHtml {
|
||||||
generateSideBySideFileHtml(file: DiffFile): FileHtml {
|
|
||||||
const matcher = Rematch.newMatcherFn(
|
const matcher = Rematch.newMatcherFn(
|
||||||
Rematch.newDistanceFn((e: DiffLine) => renderUtils.deconstructLine(e.content, file.isCombined).content)
|
Rematch.newDistanceFn((e: DiffLine) => renderUtils.deconstructLine(e.content, file.isCombined).content)
|
||||||
);
|
);
|
||||||
|
|
||||||
const fileHtml = {
|
return file.blocks
|
||||||
right: "",
|
.map(block => {
|
||||||
left: ""
|
const fileHtml = {
|
||||||
};
|
left: this.makeHeaderHtml(block.header),
|
||||||
|
right: this.makeHeaderHtml("")
|
||||||
|
};
|
||||||
|
|
||||||
file.blocks.forEach(block => {
|
this.applyLineGroupping(block).forEach(([contextLines, oldLines, newLines]) => {
|
||||||
fileHtml.left += this.makeSideHtml(block.header);
|
if (oldLines.length && newLines.length && !contextLines.length) {
|
||||||
fileHtml.right += this.makeSideHtml("");
|
this.applyRematchMatching(oldLines, newLines, matcher).map(([oldLines, newLines]) => {
|
||||||
|
const { left, right } = this.applyLineDiff(file, oldLines, newLines);
|
||||||
let oldLines: DiffLine[] = [];
|
fileHtml.left += left;
|
||||||
let newLines: DiffLine[] = [];
|
fileHtml.right += right;
|
||||||
|
});
|
||||||
const processChangeBlock = (): void => {
|
} else if (contextLines.length) {
|
||||||
let matches;
|
contextLines.forEach(line => {
|
||||||
let insertType: renderUtils.CSSLineClass;
|
const { prefix, content } = renderUtils.deconstructLine(line.content, file.isCombined);
|
||||||
let deleteType: renderUtils.CSSLineClass;
|
const { left, right } = this.generateSingleLineHtml(
|
||||||
|
{
|
||||||
const comparisons = oldLines.length * newLines.length;
|
type: renderUtils.CSSLineClass.CONTEXT,
|
||||||
|
prefix: prefix,
|
||||||
const maxLineSizeInBlock = Math.max.apply(
|
content: content,
|
||||||
null,
|
number: line.oldNumber
|
||||||
oldLines.concat(newLines).map(elem => elem.content.length)
|
},
|
||||||
);
|
{
|
||||||
|
type: renderUtils.CSSLineClass.CONTEXT,
|
||||||
const doMatching =
|
prefix: prefix,
|
||||||
comparisons < this.config.matchingMaxComparisons &&
|
content: content,
|
||||||
maxLineSizeInBlock < this.config.maxLineSizeInBlockForComparison &&
|
number: line.newNumber
|
||||||
(this.config.matching === "lines" || this.config.matching === "words");
|
}
|
||||||
|
);
|
||||||
if (doMatching) {
|
fileHtml.left += left;
|
||||||
matches = matcher(oldLines, newLines);
|
fileHtml.right += right;
|
||||||
insertType = renderUtils.CSSLineClass.INSERT_CHANGES;
|
});
|
||||||
deleteType = renderUtils.CSSLineClass.DELETE_CHANGES;
|
} else if (oldLines.length || newLines.length) {
|
||||||
} else {
|
const { left, right } = this.processLines(file.isCombined, oldLines, newLines);
|
||||||
matches = [[oldLines, newLines]];
|
fileHtml.left += left;
|
||||||
insertType = renderUtils.CSSLineClass.INSERTS;
|
fileHtml.right += right;
|
||||||
deleteType = renderUtils.CSSLineClass.DELETES;
|
} else {
|
||||||
}
|
console.error("Unknown state reached while processing groups of lines", contextLines, oldLines, newLines);
|
||||||
|
|
||||||
matches.forEach(match => {
|
|
||||||
oldLines = match[0];
|
|
||||||
newLines = match[1];
|
|
||||||
|
|
||||||
const common = Math.min(oldLines.length, newLines.length);
|
|
||||||
const max = Math.max(oldLines.length, newLines.length);
|
|
||||||
|
|
||||||
for (let j = 0; j < common; j++) {
|
|
||||||
const oldLine = oldLines[j];
|
|
||||||
const newLine = newLines[j];
|
|
||||||
|
|
||||||
const diff = renderUtils.diffHighlight(oldLine.content, newLine.content, file.isCombined, this.config);
|
|
||||||
|
|
||||||
fileHtml.left += this.generateSingleLineHtml(
|
|
||||||
file.isCombined,
|
|
||||||
deleteType,
|
|
||||||
diff.oldLine.content,
|
|
||||||
oldLine.oldNumber,
|
|
||||||
diff.oldLine.prefix
|
|
||||||
);
|
|
||||||
fileHtml.right += this.generateSingleLineHtml(
|
|
||||||
file.isCombined,
|
|
||||||
insertType,
|
|
||||||
diff.newLine.content,
|
|
||||||
newLine.newNumber,
|
|
||||||
diff.newLine.prefix
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (max > common) {
|
|
||||||
const oldSlice = oldLines.slice(common);
|
|
||||||
const newSlice = newLines.slice(common);
|
|
||||||
|
|
||||||
const tmpHtml = this.processLines(file.isCombined, oldSlice, newSlice);
|
|
||||||
fileHtml.left += tmpHtml.left;
|
|
||||||
fileHtml.right += tmpHtml.right;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return fileHtml;
|
||||||
|
})
|
||||||
|
.reduce(
|
||||||
|
(accomulated, html) => {
|
||||||
|
return { left: accomulated.left + html.left, right: accomulated.right + html.right };
|
||||||
|
},
|
||||||
|
{ left: "", right: "" }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
applyLineGroupping(block: DiffBlock): DiffLineGroups {
|
||||||
|
const blockLinesGroups: DiffLineGroups = [];
|
||||||
|
|
||||||
|
let oldLines: (DiffLineDeleted & DiffLineContent)[] = [];
|
||||||
|
let newLines: (DiffLineInserted & DiffLineContent)[] = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < block.lines.length; i++) {
|
||||||
|
const diffLine = block.lines[i];
|
||||||
|
|
||||||
|
if (
|
||||||
|
(diffLine.type !== LineType.INSERT && newLines.length) ||
|
||||||
|
(diffLine.type === LineType.CONTEXT && oldLines.length > 0)
|
||||||
|
) {
|
||||||
|
blockLinesGroups.push([[], oldLines, newLines]);
|
||||||
oldLines = [];
|
oldLines = [];
|
||||||
newLines = [];
|
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) {
|
|
||||||
fileHtml.left += this.generateSingleLineHtml(
|
|
||||||
file.isCombined,
|
|
||||||
renderUtils.toCSSClass(diffLine.type),
|
|
||||||
content,
|
|
||||||
diffLine.oldNumber,
|
|
||||||
prefix
|
|
||||||
);
|
|
||||||
fileHtml.right += this.generateSingleLineHtml(
|
|
||||||
file.isCombined,
|
|
||||||
renderUtils.toCSSClass(diffLine.type),
|
|
||||||
content,
|
|
||||||
diffLine.newNumber,
|
|
||||||
prefix
|
|
||||||
);
|
|
||||||
} else if (diffLine.type === LineType.INSERT && !oldLines.length) {
|
|
||||||
fileHtml.left += this.generateSingleLineHtml(file.isCombined, renderUtils.CSSLineClass.CONTEXT, "");
|
|
||||||
fileHtml.right += this.generateSingleLineHtml(
|
|
||||||
file.isCombined,
|
|
||||||
renderUtils.toCSSClass(diffLine.type),
|
|
||||||
content,
|
|
||||||
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 side-by-side generator");
|
|
||||||
processChangeBlock();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
processChangeBlock();
|
if (diffLine.type === LineType.CONTEXT) {
|
||||||
});
|
blockLinesGroups.push([[diffLine], [], []]);
|
||||||
|
} else if (diffLine.type === LineType.INSERT && oldLines.length === 0) {
|
||||||
|
blockLinesGroups.push([[], [], [diffLine]]);
|
||||||
|
} else if (diffLine.type === LineType.INSERT && oldLines.length > 0) {
|
||||||
|
newLines.push(diffLine);
|
||||||
|
} else if (diffLine.type === LineType.DELETE) {
|
||||||
|
oldLines.push(diffLine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldLines.length || newLines.length) {
|
||||||
|
blockLinesGroups.push([[], oldLines, newLines]);
|
||||||
|
oldLines = [];
|
||||||
|
newLines = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return blockLinesGroups;
|
||||||
|
}
|
||||||
|
|
||||||
|
applyRematchMatching(
|
||||||
|
oldLines: DiffLine[],
|
||||||
|
newLines: DiffLine[],
|
||||||
|
matcher: Rematch.MatcherFn<DiffLine>
|
||||||
|
): DiffLine[][][] {
|
||||||
|
const comparisons = oldLines.length * newLines.length;
|
||||||
|
const maxLineSizeInBlock = Math.max.apply(
|
||||||
|
null,
|
||||||
|
[0].concat(oldLines.concat(newLines).map(elem => elem.content.length))
|
||||||
|
);
|
||||||
|
const doMatching =
|
||||||
|
comparisons < this.config.matchingMaxComparisons &&
|
||||||
|
maxLineSizeInBlock < this.config.maxLineSizeInBlockForComparison &&
|
||||||
|
(this.config.matching === "lines" || this.config.matching === "words");
|
||||||
|
|
||||||
|
const matches = doMatching ? matcher(oldLines, newLines) : [[oldLines, newLines]];
|
||||||
|
|
||||||
|
return matches;
|
||||||
|
}
|
||||||
|
|
||||||
|
applyLineDiff(file: DiffFile, oldLines: DiffLine[], newLines: DiffLine[]): FileHtml {
|
||||||
|
const fileHtml = {
|
||||||
|
left: "",
|
||||||
|
right: ""
|
||||||
|
};
|
||||||
|
|
||||||
|
const common = Math.min(oldLines.length, newLines.length);
|
||||||
|
|
||||||
|
// Matched lines
|
||||||
|
for (let j = 0; j < common; j++) {
|
||||||
|
const oldLine = oldLines[j];
|
||||||
|
const newLine = newLines[j];
|
||||||
|
|
||||||
|
const diff = renderUtils.diffHighlight(oldLine.content, newLine.content, file.isCombined, this.config);
|
||||||
|
|
||||||
|
const preparedOldLine =
|
||||||
|
oldLine.oldNumber !== undefined
|
||||||
|
? {
|
||||||
|
type: renderUtils.CSSLineClass.DELETE_CHANGES,
|
||||||
|
prefix: diff.oldLine.prefix,
|
||||||
|
content: diff.oldLine.content,
|
||||||
|
number: oldLine.oldNumber
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const preparedNewLine =
|
||||||
|
newLine.newNumber !== undefined
|
||||||
|
? {
|
||||||
|
type: renderUtils.CSSLineClass.INSERT_CHANGES,
|
||||||
|
prefix: diff.newLine.prefix,
|
||||||
|
content: diff.newLine.content,
|
||||||
|
number: newLine.newNumber
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const { left, right } = this.generateSingleLineHtml(preparedOldLine, preparedNewLine);
|
||||||
|
fileHtml.left += left;
|
||||||
|
fileHtml.right += right;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remaining lines
|
||||||
|
const { left, right } = this.processLines(file.isCombined, oldLines.slice(common), newLines.slice(common));
|
||||||
|
fileHtml.left += left;
|
||||||
|
fileHtml.right += right;
|
||||||
|
|
||||||
return fileHtml;
|
return fileHtml;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Make this private after improving tests
|
// TODO: Make this private after improving tests
|
||||||
makeSideHtml(blockHeader: string): string {
|
makeHeaderHtml(blockHeader: string): string {
|
||||||
return this.hoganUtils.render(genericTemplatesPath, "block-header", {
|
return this.hoganUtils.render(genericTemplatesPath, "block-header", {
|
||||||
CSSLineClass: renderUtils.CSSLineClass,
|
CSSLineClass: renderUtils.CSSLineClass,
|
||||||
blockHeader: blockHeader,
|
blockHeader: blockHeader,
|
||||||
|
|
@ -243,105 +277,71 @@ export default class SideBySideRenderer {
|
||||||
const oldLine = oldLines[i];
|
const oldLine = oldLines[i];
|
||||||
const newLine = newLines[i];
|
const newLine = newLines[i];
|
||||||
|
|
||||||
let oldContent;
|
const preparedOldLine =
|
||||||
let newContent;
|
oldLine !== undefined && oldLine.oldNumber !== undefined
|
||||||
let oldPrefix;
|
? {
|
||||||
let newPrefix;
|
...renderUtils.deconstructLine(oldLine.content, isCombined),
|
||||||
|
type: renderUtils.toCSSClass(oldLine.type),
|
||||||
|
number: oldLine.oldNumber
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
|
||||||
if (oldLine) {
|
const preparedNewLine =
|
||||||
const { prefix, content } = renderUtils.deconstructLine(oldLine.content, isCombined);
|
newLine !== undefined && newLine.newNumber !== undefined
|
||||||
oldContent = content;
|
? {
|
||||||
oldPrefix = prefix;
|
...renderUtils.deconstructLine(newLine.content, isCombined),
|
||||||
} else {
|
type: renderUtils.toCSSClass(newLine.type),
|
||||||
oldContent = "";
|
number: newLine.newNumber
|
||||||
oldPrefix = "";
|
}
|
||||||
}
|
: undefined;
|
||||||
|
|
||||||
if (newLine) {
|
const { left, right } = this.generateSingleLineHtml(preparedOldLine, preparedNewLine);
|
||||||
const { prefix, content } = renderUtils.deconstructLine(newLine.content, isCombined);
|
fileHtml.left += left;
|
||||||
newContent = content;
|
fileHtml.right += right;
|
||||||
newPrefix = prefix;
|
|
||||||
} else {
|
|
||||||
newContent = "";
|
|
||||||
oldPrefix = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldLine && newLine) {
|
|
||||||
fileHtml.left += this.generateSingleLineHtml(
|
|
||||||
isCombined,
|
|
||||||
renderUtils.toCSSClass(oldLine.type),
|
|
||||||
oldContent,
|
|
||||||
oldLine.oldNumber,
|
|
||||||
oldPrefix
|
|
||||||
);
|
|
||||||
fileHtml.right += this.generateSingleLineHtml(
|
|
||||||
isCombined,
|
|
||||||
renderUtils.toCSSClass(newLine.type),
|
|
||||||
newContent,
|
|
||||||
newLine.newNumber,
|
|
||||||
newPrefix
|
|
||||||
);
|
|
||||||
} else if (oldLine) {
|
|
||||||
fileHtml.left += this.generateSingleLineHtml(
|
|
||||||
isCombined,
|
|
||||||
renderUtils.toCSSClass(oldLine.type),
|
|
||||||
oldContent,
|
|
||||||
oldLine.oldNumber,
|
|
||||||
oldPrefix
|
|
||||||
);
|
|
||||||
fileHtml.right += this.generateSingleLineHtml(isCombined, renderUtils.CSSLineClass.CONTEXT, "");
|
|
||||||
} else if (newLine) {
|
|
||||||
fileHtml.left += this.generateSingleLineHtml(isCombined, renderUtils.CSSLineClass.CONTEXT, "");
|
|
||||||
fileHtml.right += this.generateSingleLineHtml(
|
|
||||||
isCombined,
|
|
||||||
renderUtils.toCSSClass(newLine.type),
|
|
||||||
newContent,
|
|
||||||
newLine.newNumber,
|
|
||||||
newPrefix
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return fileHtml;
|
return fileHtml;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Make this private after improving tests
|
// TODO: Make this private after improving tests
|
||||||
generateSingleLineHtml(
|
generateSingleLineHtml(oldLine?: DiffPreparedLine, newLine?: DiffPreparedLine): FileHtml {
|
||||||
isCombined: boolean,
|
const lineClass = "d2h-code-side-linenumber";
|
||||||
type: renderUtils.CSSLineClass,
|
const contentClass = "d2h-code-side-line";
|
||||||
content: string,
|
|
||||||
number?: number,
|
|
||||||
possiblePrefix?: string
|
|
||||||
): string {
|
|
||||||
let lineWithoutPrefix = content;
|
|
||||||
let prefix = possiblePrefix;
|
|
||||||
let lineClass = "d2h-code-side-linenumber";
|
|
||||||
let contentClass = "d2h-code-side-line";
|
|
||||||
let preparedType: string = type;
|
|
||||||
|
|
||||||
if (!number && !content) {
|
return {
|
||||||
lineClass += " d2h-code-side-emptyplaceholder";
|
left: this.hoganUtils.render(genericTemplatesPath, "line", {
|
||||||
contentClass += " d2h-code-side-emptyplaceholder";
|
type: oldLine?.type || `${renderUtils.CSSLineClass.CONTEXT} d2h-emptyplaceholder`,
|
||||||
preparedType += " d2h-emptyplaceholder";
|
lineClass: oldLine !== undefined ? lineClass : `${lineClass} d2h-code-side-emptyplaceholder`,
|
||||||
prefix = " ";
|
contentClass: oldLine !== undefined ? contentClass : `${contentClass} d2h-code-side-emptyplaceholder`,
|
||||||
lineWithoutPrefix = " ";
|
prefix: oldLine?.prefix === " " ? " " : oldLine?.prefix || " ",
|
||||||
} else if (!prefix) {
|
content: oldLine?.content || " ",
|
||||||
const lineWithPrefix = renderUtils.deconstructLine(content, isCombined);
|
lineNumber: oldLine?.number
|
||||||
prefix = lineWithPrefix.prefix;
|
}),
|
||||||
lineWithoutPrefix = lineWithPrefix.content;
|
right: this.hoganUtils.render(genericTemplatesPath, "line", {
|
||||||
}
|
type: newLine?.type || `${renderUtils.CSSLineClass.CONTEXT} d2h-emptyplaceholder`,
|
||||||
|
lineClass: newLine !== undefined ? lineClass : `${lineClass} d2h-code-side-emptyplaceholder`,
|
||||||
return this.hoganUtils.render(genericTemplatesPath, "line", {
|
contentClass: newLine !== undefined ? contentClass : `${contentClass} d2h-code-side-emptyplaceholder`,
|
||||||
type: preparedType,
|
prefix: newLine?.prefix === " " ? " " : newLine?.prefix || " ",
|
||||||
lineClass: lineClass,
|
content: newLine?.content || " ",
|
||||||
contentClass: contentClass,
|
lineNumber: newLine?.number
|
||||||
prefix: prefix === " " ? " " : prefix,
|
})
|
||||||
content: lineWithoutPrefix,
|
};
|
||||||
lineNumber: number
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DiffLineGroups = [
|
||||||
|
(DiffLineContext & DiffLineContent)[],
|
||||||
|
(DiffLineDeleted & DiffLineContent)[],
|
||||||
|
(DiffLineInserted & DiffLineContent)[]
|
||||||
|
][];
|
||||||
|
|
||||||
|
type DiffPreparedLine = {
|
||||||
|
type: renderUtils.CSSLineClass;
|
||||||
|
prefix: string;
|
||||||
|
content: string;
|
||||||
|
number: number;
|
||||||
|
};
|
||||||
|
|
||||||
type FileHtml = {
|
type FileHtml = {
|
||||||
right: string;
|
right: string;
|
||||||
left: string;
|
left: string;
|
||||||
|
|
|
||||||
|
|
@ -27,10 +27,12 @@ export interface DiffLineContext {
|
||||||
newNumber: number;
|
newNumber: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DiffLine = (DiffLineDeleted | DiffLineInserted | DiffLineContext) & {
|
export type DiffLineContent = {
|
||||||
content: string;
|
content: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type DiffLine = (DiffLineDeleted | DiffLineInserted | DiffLineContext) & DiffLineContent;
|
||||||
|
|
||||||
export interface DiffBlock {
|
export interface DiffBlock {
|
||||||
oldStartLine: number;
|
oldStartLine: number;
|
||||||
oldStartLine2?: number;
|
oldStartLine2?: number;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue