UNPKG

json-joy

Version:

Collection of libraries for building collaborative editing apps.

242 lines (241 loc) 10.1 kB
import * as str from './str'; /** * Aggregate character-by-character patch into a line-by-line patch. * * @param patch Character-level patch * @returns Line-level patch */ export const agg = (patch) => { // console.log(patch); const lines = []; const length = patch.length; let line = []; const push = (type, str) => { if (!str.length) return; const length = line.length; if (length) { const lastOp = line[length - 1]; if (lastOp[0] === type) { lastOp[1] += str; return; } } line.push([type, str]); }; // console.log("PATCH", patch); LINES: for (let i = 0; i < length; i++) { const op = patch[i]; const type = op[0]; const str = op[1]; const index = str.indexOf('\n'); if (index < 0) { push(type, str); continue LINES; } else { push(type, str.slice(0, index + 1)); if (line.length) lines.push(line); line = []; } let prevIndex = index; const strLen = str.length; LINE: while (prevIndex < strLen) { const nextIndex = str.indexOf('\n', prevIndex + 1); if (nextIndex < 0) { push(type, str.slice(prevIndex + 1)); break LINE; } lines.push([[type, str.slice(prevIndex + 1, nextIndex + 1)]]); prevIndex = nextIndex; } } if (line.length) lines.push(line); // console.log("LINES", lines); { const length = lines.length; for (let i = 0; i < length; i++) { const line = lines[i]; let lineLength = line.length; NORMALIZE_LINE_START: { if (lineLength < 2) break NORMALIZE_LINE_START; const firstOp = line[0]; const secondOp = line[1]; const secondOpType = secondOp[0]; if (firstOp[0] === 0 /* str.PATCH_OP_TYPE.EQL */ && (secondOpType === -1 /* str.PATCH_OP_TYPE.DEL */ || secondOpType === 1 /* str.PATCH_OP_TYPE.INS */)) { for (let j = 2; j < lineLength; j++) if (line[j][0] !== secondOpType) break NORMALIZE_LINE_START; for (let j = i + 1; j < length; j++) { const targetLine = lines[j]; const targetLineLength = targetLine.length; const pfx = firstOp[1]; let targetLineFirstOp; let targetLineSecondOp; if (targetLine.length > 1 && (targetLineFirstOp = targetLine[0])[0] === secondOpType && (targetLineSecondOp = targetLine[1])[0] === 0 /* str.PATCH_OP_TYPE.EQL */ && pfx === targetLineFirstOp[1]) { line.splice(0, 1); secondOp[1] = pfx + secondOp[1]; targetLineSecondOp[1] = pfx + targetLineSecondOp[1]; targetLine.splice(0, 1); } else { for (let k = 0; k < targetLineLength; k++) if (targetLine[k][0] !== secondOpType) break NORMALIZE_LINE_START; } } } } lineLength = line.length; NORMALIZE_LINE_END: { if (lineLength < 2) break NORMALIZE_LINE_END; const lastOp = line[line.length - 1]; const lastOpStr = lastOp[1]; const secondLastOp = line[line.length - 2]; if (lastOp[0] === -1 /* str.PATCH_OP_TYPE.DEL */) { // if (lastOp[0] === PATCH_OP_TYPE.DELETE && secondLastOp[0] === PATCH_OP_TYPE.EQUAL) { for (let j = i + 1; j < length; j++) { const targetLine = lines[j]; const targetLineLength = targetLine.length; if (targetLineLength <= 1) { if (targetLine[0][0] !== -1 /* str.PATCH_OP_TYPE.DEL */) break NORMALIZE_LINE_END; } else { const targetLineLastOp = targetLine[targetLine.length - 1]; if (targetLineLastOp[0] !== 0 /* str.PATCH_OP_TYPE.EQL */) break NORMALIZE_LINE_END; for (let k = 0; k < targetLine.length - 1; k++) if (targetLine[k][0] !== -1 /* str.PATCH_OP_TYPE.DEL */) break NORMALIZE_LINE_END; let keepStr = targetLineLastOp[1]; const keepStrEndsWithNl = keepStr.endsWith('\n'); if (!keepStrEndsWithNl) keepStr += '\n'; if (keepStr.length > lastOpStr.length) break NORMALIZE_LINE_END; if (!lastOpStr.endsWith(keepStr)) break NORMALIZE_LINE_END; const index = lastOpStr.length - keepStr.length; if (index < 0) { lastOp[0] = 0 /* str.PATCH_OP_TYPE.EQL */; if (secondLastOp[0] === 0 /* str.PATCH_OP_TYPE.EQL */) { secondLastOp[1] += lastOpStr; line.splice(lineLength - 1, 1); } } else if (index === 0) { line.splice(lineLength - 1, 1); if (secondLastOp[0] === 0 /* str.PATCH_OP_TYPE.EQL */) { secondLastOp[1] += keepStr; } else { line.push([0 /* str.PATCH_OP_TYPE.EQL */, keepStr]); } } else { lastOp[1] = lastOpStr.slice(0, index); line.push([0 /* str.PATCH_OP_TYPE.EQL */, keepStr]); } const targetLineSecondLastOp = targetLine[targetLine.length - 2]; if (targetLineSecondLastOp[0] === -1 /* str.PATCH_OP_TYPE.DEL */) { targetLineSecondLastOp[1] += keepStrEndsWithNl ? keepStr : keepStr.slice(0, -1); targetLine.splice(targetLineLength - 1, 1); } else { targetLineLastOp[0] = -1 /* str.PATCH_OP_TYPE.DEL */; } } } } } } } // console.log("NORMALIZED LINES", lines); return lines; }; export const diff = (src, dst) => { const srcTxt = src.join('\n') + '\n'; const dstTxt = dst.join('\n') + '\n'; if (srcTxt === dstTxt) return []; const strPatch = str.diff(srcTxt, dstTxt); const lines = agg(strPatch); const length = lines.length; const patch = []; let srcIdx = -1; let dstIdx = -1; const srcLength = src.length; const dstLength = dst.length; for (let i = 0; i < length; i++) { const line = lines[i]; let lineLength = line.length; if (!lineLength) continue; const lastOp = line[lineLength - 1]; const lastOpType = lastOp[0]; const txt = lastOp[1]; if (txt === '\n') line.splice(lineLength - 1, 1); else { const strLength = txt.length; if (txt[strLength - 1] === '\n') { if (strLength === 1) line.splice(lineLength - 1, 1); else lastOp[1] = txt.slice(0, strLength - 1); } } let lineType = 0 /* LINE_PATCH_OP_TYPE.EQL */; lineLength = line.length; if (i + 1 === length) { if (srcIdx + 1 < srcLength) { if (dstIdx + 1 < dstLength) { lineType = lineLength === 1 && line[0][0] === 0 /* str.PATCH_OP_TYPE.EQL */ ? 0 /* LINE_PATCH_OP_TYPE.EQL */ : 2 /* LINE_PATCH_OP_TYPE.MIX */; srcIdx++; dstIdx++; } else { lineType = -1 /* LINE_PATCH_OP_TYPE.DEL */; srcIdx++; } } else { lineType = 1 /* LINE_PATCH_OP_TYPE.INS */; dstIdx++; } } else { const op = line[0]; const type = op[0]; if (lineLength === 1 && type === lastOpType && type === 0 /* str.PATCH_OP_TYPE.EQL */) { srcIdx++; dstIdx++; } else if (lastOpType === 0 /* str.PATCH_OP_TYPE.EQL */) { lineType = 2 /* LINE_PATCH_OP_TYPE.MIX */; srcIdx++; dstIdx++; } else if (lastOpType === 1 /* str.PATCH_OP_TYPE.INS */) { lineType = 1 /* LINE_PATCH_OP_TYPE.INS */; dstIdx++; } else if (lastOpType === -1 /* str.PATCH_OP_TYPE.DEL */) { lineType = -1 /* LINE_PATCH_OP_TYPE.DEL */; srcIdx++; } } patch.push([lineType, srcIdx, dstIdx, line]); } return patch; };