diff --git a/core/acknowledgements.tid b/core/acknowledgements.tid index 3d4010bfd..63ff5ed6e 100644 --- a/core/acknowledgements.tid +++ b/core/acknowledgements.tid @@ -5,3 +5,4 @@ TiddlyWiki incorporates code from these fine OpenSource projects: * [[The Stanford Javascript Crypto Library|http://bitwiseshiftleft.github.io/sjcl/]] * [[The Jasmine JavaScript Test Framework|https://jasmine.github.io/]] * [[modern-normalize by Sindre Sorhus|https://github.com/sindresorhus/modern-normalize]] +* [[diff-match-patch-es by antfu|https://github.com/antfu/diff-match-patch-es]] diff --git a/core/modules/filters/strings.js b/core/modules/filters/strings.js index 4183bac7f..1f06c2907 100644 --- a/core/modules/filters/strings.js +++ b/core/modules/filters/strings.js @@ -71,107 +71,103 @@ exports.join = makeStringReducingOperator( },null ); -var dmp = require("$:/core/modules/utils/diff-match-patch/diff_match_patch.js"); +const dmp = require("$:/core/modules/utils/diff-match-patch/diff_match_patch.js"); exports.levenshtein = makeStringBinaryOperator( function(a,b) { - var dmpObject = new dmp.diff_match_patch(), - diffs = dmpObject.diff_main(a,b); - return [dmpObject.diff_levenshtein(diffs) + ""]; + const diffs = dmp.diffMain(a,b); + return [dmp.diffLevenshtein(diffs).toString()]; } ); // these two functions are adapted from https://github.com/google/diff-match-patch/wiki/Line-or-Word-Diffs function diffLineWordMode(text1,text2,mode) { - var dmpObject = new dmp.diff_match_patch(); var a = diffPartsToChars(text1,text2,mode); var lineText1 = a.chars1; var lineText2 = a.chars2; var lineArray = a.lineArray; - var diffs = dmpObject.diff_main(lineText1,lineText2,false); - dmpObject.diff_charsToLines_(diffs,lineArray); + var diffs = dmp.diffMain(lineText1,lineText2,false); + dmp.diffCharsToLines(diffs,lineArray); return diffs; } function diffPartsToChars(text1,text2,mode) { var lineArray = []; var lineHash = {}; - lineArray[0] = ''; + lineArray[0] = ""; - function diff_linesToPartsMunge_(text,mode) { - var chars = ''; - var lineStart = 0; - var lineEnd = -1; - var lineArrayLength = lineArray.length, - regexpResult; - var searchRegexp = /\W+/g; - while(lineEnd < text.length - 1) { - if(mode === "words") { - regexpResult = searchRegexp.exec(text); - lineEnd = searchRegexp.lastIndex; - if(regexpResult === null) { - lineEnd = text.length; - } - lineEnd = --lineEnd; - } else { - lineEnd = text.indexOf('\n', lineStart); - if(lineEnd == -1) { - lineEnd = text.length - 1; - } - } - var line = text.substring(lineStart, lineEnd + 1); + function diff_linesToPartsMunge_(text,mode) { + var chars = ""; + var lineStart = 0; + var lineEnd = -1; + var lineArrayLength = lineArray.length, + regexpResult; + var searchRegexp = /\W+/g; + while(lineEnd < text.length - 1) { + if(mode === "words") { + regexpResult = searchRegexp.exec(text); + lineEnd = searchRegexp.lastIndex; + if(regexpResult === null) { + lineEnd = text.length; + } + lineEnd = --lineEnd; + } else { + lineEnd = text.indexOf("\n", lineStart); + if(lineEnd == -1) { + lineEnd = text.length - 1; + } + } + var line = text.substring(lineStart, lineEnd + 1); - if(lineHash.hasOwnProperty ? lineHash.hasOwnProperty(line) : (lineHash[line] !== undefined)) { + if(lineHash.hasOwnProperty ? lineHash.hasOwnProperty(line) : (lineHash[line] !== undefined)) { chars += String.fromCharCode(lineHash[line]); - } else { - if(lineArrayLength == maxLines) { - line = text.substring(lineStart); - lineEnd = text.length; - } - chars += String.fromCharCode(lineArrayLength); - lineHash[line] = lineArrayLength; - lineArray[lineArrayLength++] = line; - } - lineStart = lineEnd + 1; - } - return chars; - } - var maxLines = 40000; - var chars1 = diff_linesToPartsMunge_(text1,mode); - maxLines = 65535; - var chars2 = diff_linesToPartsMunge_(text2,mode); - return {chars1: chars1, chars2: chars2, lineArray: lineArray}; + } else { + if(lineArrayLength == maxLines) { + line = text.substring(lineStart); + lineEnd = text.length; + } + chars += String.fromCharCode(lineArrayLength); + lineHash[line] = lineArrayLength; + lineArray[lineArrayLength++] = line; + } + lineStart = lineEnd + 1; + } + return chars; + } + var maxLines = 40000; + var chars1 = diff_linesToPartsMunge_(text1,mode); + maxLines = 65535; + var chars2 = diff_linesToPartsMunge_(text2,mode); + return {chars1: chars1, chars2: chars2, lineArray: lineArray}; }; exports.makepatches = function(source,operator,options) { - var dmpObject = new dmp.diff_match_patch(), - suffix = operator.suffix || "", + var suffix = operator.suffix || "", result = []; - source(function(tiddler,title) { - var diffs, patches; - if(suffix === "lines" || suffix === "words") { - diffs = diffLineWordMode(title,operator.operand,suffix); - patches = dmpObject.patch_make(title,diffs); - } else { - patches = dmpObject.patch_make(title,operator.operand); - } - Array.prototype.push.apply(result,[dmpObject.patch_toText(patches)]); - }); + source(function(tiddler,title) { + let diffs, patches; + if(suffix === "lines" || suffix === "words") { + diffs = diffLineWordMode(title,operator.operand,suffix); + patches = dmp.patchMake(title,diffs); + } else { + patches = dmp.patchMake(title,operator.operand); + } + Array.prototype.push.apply(result,[dmp.patchToText(patches)]); + }); return result; }; exports.applypatches = makeStringBinaryOperator( function(a,b) { - var dmpObject = new dmp.diff_match_patch(), - patches; + let patches; try { - patches = dmpObject.patch_fromText(b); + patches = dmp.patchFromText(b); } catch(e) { } if(patches) { - return [dmpObject.patch_apply(patches,a)[0]]; + return [dmp.patchApply(patches,a)[0]]; } else { return [a]; } diff --git a/core/modules/utils/diff-match-patch/diff_match_patch.js b/core/modules/utils/diff-match-patch/diff_match_patch.js index 879bf1e8d..0b4a2e160 100755 --- a/core/modules/utils/diff-match-patch/diff_match_patch.js +++ b/core/modules/utils/diff-match-patch/diff_match_patch.js @@ -1,53 +1,2 @@ -function diff_match_patch(){this.Diff_Timeout=1;this.Diff_EditCost=4;this.Match_Threshold=.5;this.Match_Distance=1E3;this.Patch_DeleteThreshold=.5;this.Patch_Margin=4;this.Match_MaxBits=32}var DIFF_DELETE=-1,DIFF_INSERT=1,DIFF_EQUAL=0; -diff_match_patch.prototype.diff_main=function(a,b,c,d){"undefined"==typeof d&&(d=0>=this.Diff_Timeout?Number.MAX_VALUE:(new Date).getTime()+1E3*this.Diff_Timeout);if(null==a||null==b)throw Error("Null input. (diff_main)");if(a==b)return a?[[DIFF_EQUAL,a]]:[];"undefined"==typeof c&&(c=!0);var e=c,f=this.diff_commonPrefix(a,b);c=a.substring(0,f);a=a.substring(f);b=b.substring(f);f=this.diff_commonSuffix(a,b);var g=a.substring(a.length-f);a=a.substring(0,a.length-f);b=b.substring(0,b.length-f);a=this.diff_compute_(a, -b,e,d);c&&a.unshift([DIFF_EQUAL,c]);g&&a.push([DIFF_EQUAL,g]);this.diff_cleanupMerge(a);return a}; -diff_match_patch.prototype.diff_compute_=function(a,b,c,d){if(!a)return[[DIFF_INSERT,b]];if(!b)return[[DIFF_DELETE,a]];var e=a.length>b.length?a:b,f=a.length>b.length?b:a,g=e.indexOf(f);return-1!=g?(c=[[DIFF_INSERT,e.substring(0,g)],[DIFF_EQUAL,f],[DIFF_INSERT,e.substring(g+f.length)]],a.length>b.length&&(c[0][0]=c[2][0]=DIFF_DELETE),c):1==f.length?[[DIFF_DELETE,a],[DIFF_INSERT,b]]:(e=this.diff_halfMatch_(a,b))?(b=e[1],f=e[3],a=e[4],e=this.diff_main(e[0],e[2],c,d),c=this.diff_main(b,f,c,d),e.concat([[DIFF_EQUAL, -a]],c)):c&&100c);t++){for(var v=-t+p;v<=t-x;v+=2){var n=f+v;var r=v==-t||v!=t&&h[n-1]d)x+=2;else if(y>e)p+=2;else if(m&&(n=f+k-v,0<=n&&n= -u)return this.diff_bisectSplit_(a,b,r,y,c)}}for(v=-t+w;v<=t-q;v+=2){n=f+v;u=v==-t||v!=t&&l[n-1]d)q+=2;else if(r>e)w+=2;else if(!m&&(n=f+k-v,0<=n&&n=u)))return this.diff_bisectSplit_(a,b,r,y,c)}}return[[DIFF_DELETE,a],[DIFF_INSERT,b]]}; -diff_match_patch.prototype.diff_bisectSplit_=function(a,b,c,d,e){var f=a.substring(0,c),g=b.substring(0,d);a=a.substring(c);b=b.substring(d);f=this.diff_main(f,g,!1,e);e=this.diff_main(a,b,!1,e);return f.concat(e)}; -diff_match_patch.prototype.diff_linesToChars_=function(a,b){function c(a){for(var b="",c=0,f=-1,g=d.length;fd?a=a.substring(c-d):c=a.length?[h,k,l,m,g]:null}if(0>=this.Diff_Timeout)return null; -var d=a.length>b.length?a:b,e=a.length>b.length?b:a;if(4>d.length||2*e.lengthd[4].length?g:d:d:g;else return null;if(a.length>b.length){d=g[0];e=g[1];var h=g[2];var l=g[3]}else h=g[0],l=g[1],d=g[2],e=g[3];return[d,e,h,l,g[4]]}; -diff_match_patch.prototype.diff_cleanupSemantic=function(a){for(var b=!1,c=[],d=0,e=null,f=0,g=0,h=0,l=0,k=0;f=e){if(d>=b.length/2||d>=c.length/2)a.splice(f,0,[DIFF_EQUAL,c.substring(0,d)]),a[f-1][1]=b.substring(0,b.length-d),a[f+1][1]=c.substring(d),f++}else if(e>=b.length/2||e>=c.length/2)a.splice(f,0,[DIFF_EQUAL,b.substring(0,e)]),a[f-1][0]=DIFF_INSERT,a[f-1][1]=c.substring(0,c.length-e),a[f+1][0]=DIFF_DELETE,a[f+1][1]=b.substring(e),f++;f++}f++}}; -diff_match_patch.prototype.diff_cleanupSemanticLossless=function(a){function b(a,b){if(!a||!b)return 6;var c=a.charAt(a.length-1),d=b.charAt(0),e=c.match(diff_match_patch.nonAlphaNumericRegex_),f=d.match(diff_match_patch.nonAlphaNumericRegex_),g=e&&c.match(diff_match_patch.whitespaceRegex_),h=f&&d.match(diff_match_patch.whitespaceRegex_);c=g&&c.match(diff_match_patch.linebreakRegex_);d=h&&d.match(diff_match_patch.linebreakRegex_);var k=c&&a.match(diff_match_patch.blanklineEndRegex_),l=d&&b.match(diff_match_patch.blanklineStartRegex_); -return k||l?5:c||d?4:e&&!g&&h?3:g||h?2:e||f?1:0}for(var c=1;c=k&&(k=m,g=d,h=e,l=f)}a[c-1][1]!=g&&(g?a[c-1][1]=g:(a.splice(c- -1,1),c--),a[c][1]=h,l?a[c+1][1]=l:(a.splice(c+1,1),c--))}c++}};diff_match_patch.nonAlphaNumericRegex_=/[^a-zA-Z0-9]/;diff_match_patch.whitespaceRegex_=/\s/;diff_match_patch.linebreakRegex_=/[\r\n]/;diff_match_patch.blanklineEndRegex_=/\n\r?\n$/;diff_match_patch.blanklineStartRegex_=/^\r?\n\r?\n/; -diff_match_patch.prototype.diff_cleanupEfficiency=function(a){for(var b=!1,c=[],d=0,e=null,f=0,g=!1,h=!1,l=!1,k=!1;fb)break;e=c;f=d}return a.length!=g&&a[g][0]===DIFF_DELETE?f:f+(b-e)}; -diff_match_patch.prototype.diff_prettyHtml=function(a){for(var b=[],c=/&/g,d=//g,f=/\n/g,g=0;g");switch(h){case DIFF_INSERT:b[g]=''+l+"";break;case DIFF_DELETE:b[g]=''+l+"";break;case DIFF_EQUAL:b[g]=""+l+""}}return b.join("")}; -diff_match_patch.prototype.diff_text1=function(a){for(var b=[],c=0;cthis.Match_MaxBits)throw Error("Pattern too long for this browser.");var e=this.match_alphabet_(b),f=this,g=this.Match_Threshold,h=a.indexOf(b,c);-1!=h&&(g=Math.min(d(0,h),g),h=a.lastIndexOf(b,c+b.length),-1!=h&&(g=Math.min(d(0,h),g)));var l=1<=k;q--){var t=e[a.charAt(q-1)];m[q]=0===w?(m[q+1]<<1|1)&t:(m[q+1]<<1|1)&t|(x[q+1]|x[q])<<1|1|x[q+1];if(m[q]&l&&(t=d(w,q-1),t<=g))if(g=t,h=q-1,h>c)k=Math.max(1,2*c-h);else break}if(d(w+1,c)>g)break;x=m}return h}; -diff_match_patch.prototype.match_alphabet_=function(a){for(var b={},c=0;c=2*this.Patch_Margin&&e&&(this.patch_addContext_(a,h),c.push(a),a=new diff_match_patch.patch_obj,e=0,h=d,f=g)}k!==DIFF_INSERT&&(f+=m.length);k!==DIFF_DELETE&&(g+=m.length)}e&&(this.patch_addContext_(a,h),c.push(a));return c}; -diff_match_patch.prototype.patch_deepCopy=function(a){for(var b=[],c=0;cthis.Match_MaxBits){var k=this.match_main(b,h.substring(0,this.Match_MaxBits),g);-1!=k&&(l=this.match_main(b,h.substring(h.length-this.Match_MaxBits),g+h.length-this.Match_MaxBits),-1==l||k>=l)&&(k=-1)}else k=this.match_main(b,h, -g);if(-1==k)e[f]=!1,d-=a[f].length2-a[f].length1;else if(e[f]=!0,d=k-g,g=-1==l?b.substring(k,k+h.length):b.substring(k,l+this.Match_MaxBits),h==g)b=b.substring(0,k)+this.diff_text2(a[f].diffs)+b.substring(k+h.length);else if(g=this.diff_main(h,g,!1),h.length>this.Match_MaxBits&&this.diff_levenshtein(g)/h.length>this.Patch_DeleteThreshold)e[f]=!1;else{this.diff_cleanupSemanticLossless(g);h=0;var m;for(l=0;le[0][1].length){var f=b-e[0][1].length;e[0][1]=c.substring(e[0][1].length)+e[0][1];d.start1-=f;d.start2-=f;d.length1+=f;d.length2+=f}d=a[a.length-1];e=d.diffs;0==e.length||e[e.length- -1][0]!=DIFF_EQUAL?(e.push([DIFF_EQUAL,c]),d.length1+=b,d.length2+=b):b>e[e.length-1][1].length&&(f=b-e[e.length-1][1].length,e[e.length-1][1]+=c.substring(0,f),d.length1+=f,d.length2+=f);return c}; -diff_match_patch.prototype.patch_splitMax=function(a){for(var b=this.Match_MaxBits,c=0;c2*b?(h.length1+=k.length,e+=k.length,l=!1,h.diffs.push([g,k]),d.diffs.shift()):(k=k.substring(0,b-h.length1-this.Patch_Margin),h.length1+=k.length,e+=k.length,g===DIFF_EQUAL?(h.length2+=k.length,f+=k.length):l=!1,h.diffs.push([g,k]),k==d.diffs[0][1]?d.diffs.shift():d.diffs[0][1]=d.diffs[0][1].substring(k.length))}g=this.diff_text2(h.diffs);g=g.substring(g.length-this.Patch_Margin);k=this.diff_text1(d.diffs).substring(0, -this.Patch_Margin);""!==k&&(h.length1+=k.length,h.length2+=k.length,0!==h.diffs.length&&h.diffs[h.diffs.length-1][0]===DIFF_EQUAL?h.diffs[h.diffs.length-1][1]+=k:h.diffs.push([DIFF_EQUAL,k]));l||a.splice(++c,0,h)}}};diff_match_patch.prototype.patch_toText=function(a){for(var b=[],c=0;ce in n?t(n,e,{enumerable:!0,configurable:!0,writable:!0,value:s}):n[e]=s,l=(t,l)=>{for(var i in l||(l={}))e.call(l,i)&&r(t,i,l[i]);if(n)for(var i of n(l))s.call(l,i)&&r(t,i,l[i]);return t};const i=Object.freeze({diffTimeout:1,diffEditCost:4,matchThreshold:.5,matchDistance:1e3,patchDeleteThreshold:.5,patchMargin:4,matchMaxBits:32});function h(t){if(null==t?void 0:t.__resolved)return t;const n=l(l({},i),t);return Object.defineProperty(n,"__resolved",{value:!0,enumerable:!1}),n}const g=-1;function o(t,n){return[t,n]}function f(t,n,e,s=!0,r){const l=h(e);void 0===r&&(r=l.diffTimeout<=0?Number.MAX_VALUE:(new Date).getTime()+1e3*l.diffTimeout);const i=r;if(null==t||null==n)throw new Error("Null input. (diff_main)");if(t===n)return t?[o(0,t)]:[];const d=s;let x=p(t,n);const M=t.substring(0,x);x=b(t=t.substring(x),n=n.substring(x));const w=t.substring(t.length-x),y=function(t,n,e,s,r){let l;if(!t)return[o(1,n)];if(!n)return[o(g,t)];const i=t.length>n.length?t:n,h=t.length>n.length?n:t,d=i.indexOf(h);if(-1!==d)return l=[o(1,i.substring(0,d)),o(0,h),o(1,i.substring(d+h.length))],t.length>n.length&&(l[0][0]=l[2][0]=g),l;if(1===h.length)return[o(g,t),o(1,n)];const x=function(t,n,e){if(e.diffTimeout<=0)return null;const s=t.length>n.length?t:n,r=t.length>n.length?n:t;if(s.length<4||2*r.length=t.length?[r,l,i,h,o]:null}const i=l(s,r,Math.ceil(s.length/4)),h=l(s,r,Math.ceil(s.length/2));let g,o,f,a,c;if(!i&&!h)return null;g=h?i&&i[4].length>h[4].length?i:h:i;t.length>n.length?(o=g[0],f=g[1],a=g[2],c=g[3]):(a=g[0],c=g[1],o=g[2],f=g[3]);const u=g[4];return[o,f,a,c,u]}(t,n,e);if(x){const t=x[0],n=x[1],l=x[2],i=x[3],h=x[4],g=f(t,l,e,s,r),a=f(n,i,e,s,r);return g.concat([o(0,h)],a)}return s&&t.length>100&&n.length>100?function(t,n,e,s){const r=c(t,n);t=r.chars1,n=r.chars2;const l=r.lineArray,i=f(t,n,e,!1,s);u(i,l),m(i),i.push(o(0,""));let h=0,a=0,p=0,b="",d="";for(;h=1&&p>=1){i.splice(h-a-p,a+p),h=h-a-p;const t=f(b,d,e,!1,s);for(let n=t.length-1;n>=0;n--)i.splice(h,0,t[n]);h+=t.length}p=0,a=0,b="",d=""}h++}return i.pop(),i}(t,n,e,r):function(t,n,e,s){const r=t.length,l=n.length,i=Math.ceil((r+l)/2),h=i,f=2*i,c=new Array(f),u=new Array(f);for(let g=0;gs);g++){for(let i=-g+d;i<=g-m;i+=2){const o=h+i;let x;x=i===-g||i!==g&&c[o-1]r)m+=2;else if(M>l)d+=2;else if(b){const l=h+p-i;if(l>=0&&l=r-u[l])return a(t,n,e,x,M,s)}}}for(let i=-g+x;i<=g-M;i+=2){const o=h+i;let d;d=i===-g||i!==g&&u[o-1]r)M+=2;else if(m>l)x+=2;else if(!b){const l=h+p-i;if(l>=0&&l=d)return a(t,n,e,i,h+i-l,s)}}}}return[o(g,t),o(1,n)]}(t,n,e,r)}(t=t.substring(0,t.length-x),n=n.substring(0,n.length-x),l,d,i);return M&&y.unshift(o(0,M)),w&&y.push(o(0,w)),k(y),y}function a(t,n,e,s,r,l){const i=t.substring(0,s),h=n.substring(0,r),g=t.substring(s),o=n.substring(r),a=f(i,h,e,!1,l),c=f(g,o,e,!1,l);return a.concat(c)}function c(t,n){const e=[],s={};let r=4e4;function l(t){let n="",l=0,i=-1,h=e.length;for(;is?t=t.substring(e-s):e0?e[s-1]:-1,i=0,h=0,f=0,a=0,r=null,n=!0)),l++;for(n&&k(t),O(t),l=1;l=r?(s>=n.length/2||s>=e.length/2)&&(t.splice(l,0,o(0,e.substring(0,s))),t[l-1][1]=n.substring(0,n.length-s),t[l+1][1]=e.substring(s),l++):(r>=n.length/2||r>=e.length/2)&&(t.splice(l,0,o(0,n.substring(0,r))),t[l-1][0]=1,t[l-1][1]=e.substring(0,e.length-r),t[l+1][0]=g,t[l+1][1]=n.substring(r),l++),l++}l++}}const x=/[^a-z0-9]/i,M=/\s/,w=/[\r\n]/,y=/\n\r?\n$/,A=/^\r?\n\r?\n/;function O(t){function n(t,n){if(!t||!n)return 6;const e=t.charAt(t.length-1),s=n.charAt(0),r=e.match(x),l=s.match(x),i=r&&e.match(M),h=l&&s.match(M),g=i&&e.match(w),o=h&&s.match(w),f=g&&t.match(y),a=o&&n.match(A);return f||a?5:g||o?4:r&&!i&&h?3:i||h?2:r||l?1:0}let e=1;for(;e=f&&(f=t,h=s,g=r,o=l)}t[e-1][1]!==h&&(h?t[e-1][1]=h:(t.splice(e-1,1),e--),t[e][1]=g,o?t[e+1][1]=o:(t.splice(e+1,1),e--))}e++}}function $(t,n={}){const{diffEditCost:e=i.diffEditCost}=n;let s=!1;const r=[];let l=0,h=null,f=0,a=!1,c=!1,u=!1,p=!1;for(;f0?r[l-1]:-1,u=p=!1),s=!0)}f++}s&&k(t)}function k(t){t.push(o(0,""));let n,e=0,s=0,r=0,l="",i="";for(;e1?(0!==s&&0!==r&&(n=p(i,l),0!==n&&(e-s-r>0&&0===t[e-s-r-1][0]?t[e-s-r-1][1]+=i.substring(0,n):(t.splice(0,0,o(0,i.substring(0,n))),e++),i=i.substring(n),l=l.substring(n)),n=b(i,l),0!==n&&(t[e][1]=i.substring(i.length-n)+t[e][1],i=i.substring(0,i.length-n),l=l.substring(0,l.length-n))),e-=s+r,t.splice(e,s+r),l.length&&(t.splice(e,0,o(g,l)),e++),i.length&&(t.splice(e,0,o(1,i)),e++),e++):0!==e&&0===t[e-1][0]?(t[e-1][1]+=t[e][1],t.splice(e,1)):e++,r=0,s=0,l="",i=""}""===t[t.length-1][1]&&t.pop();let h=!1;for(e=1;en));e++)l=s,i=r;return t.length!==e&&t[e][0]===g?i:i+(n-l)}function E(t){const n=[];for(let e=0;er.matchMaxBits)throw new Error("Pattern too long for this browser.");const l=D(n);function i(t,s){const l=t/n.length,i=Math.abs(e-s);return r.matchDistance?l+i/r.matchDistance:i?1:l}let g=r.matchThreshold,o=t.indexOf(n,e);-1!==o&&(g=Math.min(i(0,o),g),o=t.lastIndexOf(n,e+n.length),-1!==o&&(g=Math.min(i(0,o),g)));const f=1<=s;n--){const r=l[t.charAt(n-1)];if(b[n]=0===h?(b[n+1]<<1|1)&r:(b[n+1]<<1|1)&r|(p[n+1]|p[n])<<1|1|p[n+1],b[n]&f){const t=i(h,n-1);if(t<=g){if(g=t,o=n-1,!(o>e))break;s=Math.max(1,2*e-o)}}}if(i(h+1,e)>g)break;p=b}return o}function D(t){const n={};for(let e=0;en in t?B(t,n,{enumerable:!0,configurable:!0,writable:!0,value:e}):t[n]=e,F=(t,n)=>{for(var e in n||(n={}))P.call(n,e)&&S(t,e,n[e]);if(_)for(var e of _(n))N.call(n,e)&&S(t,e,n[e]);return t};function L(t,n,e){if(0===n.length)return;if(null===t.start2)throw new Error("patch not initialized");const{matchMaxBits:s=i.matchMaxBits,patchMargin:r=i.patchMargin}=e;let l=n.substring(t.start2,t.start2+t.length1),h=0;for(;n.indexOf(l)!==n.lastIndexOf(l)&&l.length2&&(m(h),$(h));else if(t&&"object"==typeof t&&void 0===n&&void 0===e)h=t,l=E(h);else if("string"==typeof t&&n&&"object"==typeof n&&void 0===e)l=t,h=n;else{if("string"!=typeof t||"string"!=typeof n||!e||"object"!=typeof e)throw new Error("Unknown call format to patch_make.");l=t,h=e}if(0===h.length)return[];const o=[];let a=q(),c=0,u=0,p=0,b=l,d=l;for(let i=0;i=2*r.patchMargin&&c&&(L(a,b,r),o.push(a),a=q(),c=0,b=d,u=p)}1!==t&&(u+=n.length),t!==g&&(p+=n.length)}return c&&(L(a,b,r),o.push(a)),o}function R(t){const n=[];for(let e=0;el[0][1].length){const t=e-l[0][1].length;l[0][1]=s.substring(l[0][1].length)+l[0][1],r.start1-=t,r.start2-=t,r.length1+=t,r.length2+=t}if(r=t[t.length-1],l=r.diffs,0===l.length||0!==l[l.length-1][0])l.push(o(0,s)),r.length1+=e,r.length2+=e;else if(e>l[l.length-1][1].length){const t=e-l[l.length-1][1].length;l[l.length-1][1]+=s.substring(0,t),r.length1+=t,r.length2+=t}return s}function X(t,n){const e=h(n);for(let s=0;s2*e.matchMaxBits?(h.length1+=s.length,r+=s.length,f=!1,h.diffs.push(o(t,s)),n.diffs.shift()):(s=s.substring(0,e.matchMaxBits-h.length1-e.patchMargin),h.length1+=s.length,r+=s.length,0===t?(h.length2+=s.length,l+=s.length):f=!1,h.diffs.push(o(t,s)),s===n.diffs[0][1]?n.diffs.shift():n.diffs[0][1]=n.diffs[0][1].substring(s.length))}i=I(h.diffs),i=i.substring(i.length-e.patchMargin);const a=E(n.diffs).substring(0,e.patchMargin);""!==a&&(h.length1+=a.length,h.length2+=a.length,0!==h.diffs.length&&0===h.diffs[h.diffs.length-1][0]?h.diffs[h.diffs.length-1][1]+=a:h.diffs.push(o(0,a))),f||t.splice(++s,0,h)}}}function q(){const t={diffs:[],start1:null,start2:null,length1:0,length2:0,toString:function(){let t,n;t=0===this.length1?`${this.start1},0`:1===this.length1?this.start1+1:`${this.start1+1},${this.length1}`,n=0===this.length2?`${this.start2},0`:1===this.length2?this.start2+1:`${this.start2+1},${this.length2}`;const e=[`@@ -${t} +${n} @@\n`];let s;for(let r=0;r/g,l=/\n/g;for(let i=0;i");switch(h){case 1:n[i]=`${o}`;break;case g:n[i]=`${o}`;break;case 0:n[i]=`${o}`}}return n.join("")},exports.diffText1=E,exports.diffText2=I,exports.diffToDelta=function(t){const n=[];for(let e=0;es.matchMaxBits?(a=T(n,o.substring(0,s.matchMaxBits),r,e),-1!==a&&(c=T(n,o.substring(o.length-s.matchMaxBits),r+o.length-s.matchMaxBits,e),(-1===c||a>=c)&&(a=-1))):a=T(n,o,r,e),-1===a)i[h]=!1,l-=t[h].length2-t[h].length1;else{let u;if(i[h]=!0,l=a-r,u=-1===c?n.substring(a,a+o.length):n.substring(a,c+s.matchMaxBits),o===u)n=n.substring(0,a)+I(t[h].diffs)+n.substring(a+o.length);else{const r=f(o,u,e,!1);if(o.length>s.matchMaxBits&&j(r)/o.length>s.patchDeleteThreshold)i[h]=!1;else{O(r);let e=0,s=0;for(let l=0;l key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; +var __spreadValues$1 = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp$1.call(b, prop)) + __defNormalProp$1(a, prop, b[prop]); + if (__getOwnPropSymbols$1) + for (var prop of __getOwnPropSymbols$1(b)) { + if (__propIsEnum$1.call(b, prop)) + __defNormalProp$1(a, prop, b[prop]); + } + return a; +}; +const defaultOptions = /* @__PURE__ */ Object.freeze({ + diffTimeout: 1, + diffEditCost: 4, + matchThreshold: 0.5, + matchDistance: 1e3, + patchDeleteThreshold: 0.5, + patchMargin: 4, + matchMaxBits: 32 +}); +function resolveOptions(options) { + if (options == null ? undefined : options.__resolved) + return options; + const resolved = __spreadValues$1(__spreadValues$1({}, defaultOptions), options); + Object.defineProperty(resolved, "__resolved", { value: true, enumerable: false }); + return resolved; } - -// DIFF FUNCTIONS - - -/** - * The data structure representing a diff is an array of tuples: - * [[DIFF_DELETE, 'Hello'], [DIFF_INSERT, 'Goodbye'], [DIFF_EQUAL, ' world.']] - * which means: delete 'Hello', add 'Goodbye' and keep ' world.' - */ -var DIFF_DELETE = -1; -var DIFF_INSERT = 1; -var DIFF_EQUAL = 0; - -/** @typedef {{0: number, 1: string}} */ -diff_match_patch.Diff; - - -/** - * Find the differences between two texts. Simplifies the problem by stripping - * any common prefix or suffix off the texts before diffing. - * @param {string} text1 Old string to be diffed. - * @param {string} text2 New string to be diffed. - * @param {boolean=} opt_checklines Optional speedup flag. If present and false, - * then don't run a line-level diff first to identify the changed areas. - * Defaults to true, which does a faster, slightly less optimal diff. - * @param {number} opt_deadline Optional time when the diff should be complete - * by. Used internally for recursive calls. Users should set DiffTimeout - * instead. - * @return {!Array.} Array of diff tuples. - */ -diff_match_patch.prototype.diff_main = function(text1, text2, opt_checklines, - opt_deadline) { - // Set a deadline by which time the diff must be complete. - if (typeof opt_deadline == 'undefined') { - if (this.Diff_Timeout <= 0) { +const DIFF_DELETE = -1; +const DIFF_INSERT = 1; +const DIFF_EQUAL = 0; +function createDiff(op, text) { + return [op, text]; +} +function diffMain(text1, text2, options, opt_checklines = true, opt_deadline) { + const resolved = resolveOptions(options); + if (typeof opt_deadline == "undefined") { + if (resolved.diffTimeout <= 0) opt_deadline = Number.MAX_VALUE; - } else { - opt_deadline = (new Date).getTime() + this.Diff_Timeout * 1000; - } + else + opt_deadline = (/* @__PURE__ */ new Date()).getTime() + resolved.diffTimeout * 1e3; } - var deadline = opt_deadline; - - // Check for null inputs. - if (text1 == null || text2 == null) { - throw new Error('Null input. (diff_main)'); - } - - // Check for equality (speedup). - if (text1 == text2) { - if (text1) { - return [[DIFF_EQUAL, text1]]; - } + const deadline = opt_deadline; + if (text1 == null || text2 == null) + throw new Error("Null input. (diff_main)"); + if (text1 === text2) { + if (text1) + return [createDiff(DIFF_EQUAL, text1)]; return []; } - - if (typeof opt_checklines == 'undefined') { - opt_checklines = true; - } - var checklines = opt_checklines; - - // Trim off common prefix (speedup). - var commonlength = this.diff_commonPrefix(text1, text2); - var commonprefix = text1.substring(0, commonlength); + const checklines = opt_checklines; + let commonlength = diffCommonPrefix(text1, text2); + const commonprefix = text1.substring(0, commonlength); text1 = text1.substring(commonlength); text2 = text2.substring(commonlength); - - // Trim off common suffix (speedup). - commonlength = this.diff_commonSuffix(text1, text2); - var commonsuffix = text1.substring(text1.length - commonlength); + commonlength = diffCommonSuffix(text1, text2); + const commonsuffix = text1.substring(text1.length - commonlength); text1 = text1.substring(0, text1.length - commonlength); text2 = text2.substring(0, text2.length - commonlength); - - // Compute the diff on the middle block. - var diffs = this.diff_compute_(text1, text2, checklines, deadline); - - // Restore the prefix and suffix. - if (commonprefix) { - diffs.unshift([DIFF_EQUAL, commonprefix]); - } - if (commonsuffix) { - diffs.push([DIFF_EQUAL, commonsuffix]); - } - this.diff_cleanupMerge(diffs); + const diffs = diffCompute(text1, text2, resolved, checklines, deadline); + if (commonprefix) + diffs.unshift(createDiff(DIFF_EQUAL, commonprefix)); + if (commonsuffix) + diffs.push(createDiff(DIFF_EQUAL, commonsuffix)); + diffCleanupMerge(diffs); return diffs; -}; - - -/** - * Find the differences between two texts. Assumes that the texts do not - * have any common prefix or suffix. - * @param {string} text1 Old string to be diffed. - * @param {string} text2 New string to be diffed. - * @param {boolean} checklines Speedup flag. If false, then don't run a - * line-level diff first to identify the changed areas. - * If true, then run a faster, slightly less optimal diff. - * @param {number} deadline Time when the diff should be complete by. - * @return {!Array.} Array of diff tuples. - * @private - */ -diff_match_patch.prototype.diff_compute_ = function(text1, text2, checklines, - deadline) { - var diffs; - +} +function diffCompute(text1, text2, options, checklines, deadline) { + let diffs; if (!text1) { - // Just add some text (speedup). - return [[DIFF_INSERT, text2]]; + return [createDiff(DIFF_INSERT, text2)]; } - if (!text2) { - // Just delete some text (speedup). - return [[DIFF_DELETE, text1]]; + return [createDiff(DIFF_DELETE, text1)]; } - - var longtext = text1.length > text2.length ? text1 : text2; - var shorttext = text1.length > text2.length ? text2 : text1; - var i = longtext.indexOf(shorttext); - if (i != -1) { - // Shorter text is inside the longer text (speedup). - diffs = [[DIFF_INSERT, longtext.substring(0, i)], - [DIFF_EQUAL, shorttext], - [DIFF_INSERT, longtext.substring(i + shorttext.length)]]; - // Swap insertions for deletions if diff is reversed. - if (text1.length > text2.length) { + const longtext = text1.length > text2.length ? text1 : text2; + const shorttext = text1.length > text2.length ? text2 : text1; + const i = longtext.indexOf(shorttext); + if (i !== -1) { + diffs = [createDiff(DIFF_INSERT, longtext.substring(0, i)), createDiff(DIFF_EQUAL, shorttext), createDiff(DIFF_INSERT, longtext.substring(i + shorttext.length))]; + if (text1.length > text2.length) diffs[0][0] = diffs[2][0] = DIFF_DELETE; - } return diffs; } - - if (shorttext.length == 1) { - // Single character string. - // After the previous speedup, the character can't be an equality. - return [[DIFF_DELETE, text1], [DIFF_INSERT, text2]]; + if (shorttext.length === 1) { + return [createDiff(DIFF_DELETE, text1), createDiff(DIFF_INSERT, text2)]; } - - // Check to see if the problem can be split in two. - var hm = this.diff_halfMatch_(text1, text2); + const hm = diffHalfMatch(text1, text2, options); if (hm) { - // A half-match was found, sort out the return data. - var text1_a = hm[0]; - var text1_b = hm[1]; - var text2_a = hm[2]; - var text2_b = hm[3]; - var mid_common = hm[4]; - // Send both pairs off for separate processing. - var diffs_a = this.diff_main(text1_a, text2_a, checklines, deadline); - var diffs_b = this.diff_main(text1_b, text2_b, checklines, deadline); - // Merge the results. - return diffs_a.concat([[DIFF_EQUAL, mid_common]], diffs_b); + const text1_a = hm[0]; + const text1_b = hm[1]; + const text2_a = hm[2]; + const text2_b = hm[3]; + const mid_common = hm[4]; + const diffs_a = diffMain(text1_a, text2_a, options, checklines, deadline); + const diffs_b = diffMain(text1_b, text2_b, options, checklines, deadline); + return diffs_a.concat([createDiff(DIFF_EQUAL, mid_common)], diffs_b); } - - if (checklines && text1.length > 100 && text2.length > 100) { - return this.diff_lineMode_(text1, text2, deadline); - } - - return this.diff_bisect_(text1, text2, deadline); -}; - - -/** - * Do a quick line-level diff on both strings, then rediff the parts for - * greater accuracy. - * This speedup can produce non-minimal diffs. - * @param {string} text1 Old string to be diffed. - * @param {string} text2 New string to be diffed. - * @param {number} deadline Time when the diff should be complete by. - * @return {!Array.} Array of diff tuples. - * @private - */ -diff_match_patch.prototype.diff_lineMode_ = function(text1, text2, deadline) { - // Scan the text on a line-by-line basis first. - var a = this.diff_linesToChars_(text1, text2); + if (checklines && text1.length > 100 && text2.length > 100) + return diffLineMode(text1, text2, options, deadline); + return diffBisect(text1, text2, options, deadline); +} +function diffLineMode(text1, text2, options, deadline) { + const a = diffLinesToChars(text1, text2); text1 = a.chars1; text2 = a.chars2; - var linearray = a.lineArray; - - var diffs = this.diff_main(text1, text2, false, deadline); - - // Convert the diff back to original text. - this.diff_charsToLines_(diffs, linearray); - // Eliminate freak matches (e.g. blank lines) - this.diff_cleanupSemantic(diffs); - - // Rediff any replacement blocks, this time character-by-character. - // Add a dummy entry at the end. - diffs.push([DIFF_EQUAL, '']); - var pointer = 0; - var count_delete = 0; - var count_insert = 0; - var text_delete = ''; - var text_insert = ''; + const linearray = a.lineArray; + const diffs = diffMain(text1, text2, options, false, deadline); + diffCharsToLines(diffs, linearray); + diffCleanupSemantic(diffs); + diffs.push(createDiff(DIFF_EQUAL, "")); + let pointer = 0; + let count_delete = 0; + let count_insert = 0; + let text_delete = ""; + let text_insert = ""; while (pointer < diffs.length) { switch (diffs[pointer][0]) { case DIFF_INSERT: @@ -254,282 +132,169 @@ diff_match_patch.prototype.diff_lineMode_ = function(text1, text2, deadline) { text_delete += diffs[pointer][1]; break; case DIFF_EQUAL: - // Upon reaching an equality, check for prior redundancies. if (count_delete >= 1 && count_insert >= 1) { - // Delete the offending records and add the merged ones. - diffs.splice(pointer - count_delete - count_insert, - count_delete + count_insert); + diffs.splice(pointer - count_delete - count_insert, count_delete + count_insert); pointer = pointer - count_delete - count_insert; - var a = this.diff_main(text_delete, text_insert, false, deadline); - for (var j = a.length - 1; j >= 0; j--) { - diffs.splice(pointer, 0, a[j]); - } - pointer = pointer + a.length; + const subDiff = diffMain(text_delete, text_insert, options, false, deadline); + for (let j = subDiff.length - 1; j >= 0; j--) + diffs.splice(pointer, 0, subDiff[j]); + pointer = pointer + subDiff.length; } count_insert = 0; count_delete = 0; - text_delete = ''; - text_insert = ''; + text_delete = ""; + text_insert = ""; break; } pointer++; } - diffs.pop(); // Remove the dummy entry at the end. - + diffs.pop(); return diffs; -}; - - -/** - * Find the 'middle snake' of a diff, split the problem in two - * and return the recursively constructed diff. - * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations. - * @param {string} text1 Old string to be diffed. - * @param {string} text2 New string to be diffed. - * @param {number} deadline Time at which to bail if not yet complete. - * @return {!Array.} Array of diff tuples. - * @private - */ -diff_match_patch.prototype.diff_bisect_ = function(text1, text2, deadline) { - // Cache the text lengths to prevent multiple calls. - var text1_length = text1.length; - var text2_length = text2.length; - var max_d = Math.ceil((text1_length + text2_length) / 2); - var v_offset = max_d; - var v_length = 2 * max_d; - var v1 = new Array(v_length); - var v2 = new Array(v_length); - // Setting all elements to -1 is faster in Chrome & Firefox than mixing - // integers and undefined. - for (var x = 0; x < v_length; x++) { +} +function diffBisect(text1, text2, options, deadline) { + const text1_length = text1.length; + const text2_length = text2.length; + const max_d = Math.ceil((text1_length + text2_length) / 2); + const v_offset = max_d; + const v_length = 2 * max_d; + const v1 = new Array(v_length); + const v2 = new Array(v_length); + for (let x = 0; x < v_length; x++) { v1[x] = -1; v2[x] = -1; } v1[v_offset + 1] = 0; v2[v_offset + 1] = 0; - var delta = text1_length - text2_length; - // If the total number of characters is odd, then the front path will collide - // with the reverse path. - var front = (delta % 2 != 0); - // Offsets for start and end of k loop. - // Prevents mapping of space beyond the grid. - var k1start = 0; - var k1end = 0; - var k2start = 0; - var k2end = 0; - for (var d = 0; d < max_d; d++) { - // Bail out if deadline is reached. - if ((new Date()).getTime() > deadline) { + const delta = text1_length - text2_length; + const front = delta % 2 !== 0; + let k1start = 0; + let k1end = 0; + let k2start = 0; + let k2end = 0; + for (let d = 0; d < max_d; d++) { + if ((/* @__PURE__ */ new Date()).getTime() > deadline) break; - } - - // Walk the front path one step. - for (var k1 = -d + k1start; k1 <= d - k1end; k1 += 2) { - var k1_offset = v_offset + k1; - var x1; - if (k1 == -d || (k1 != d && v1[k1_offset - 1] < v1[k1_offset + 1])) { + for (let k1 = -d + k1start; k1 <= d - k1end; k1 += 2) { + const k1_offset = v_offset + k1; + let x1; + if (k1 === -d || k1 !== d && v1[k1_offset - 1] < v1[k1_offset + 1]) x1 = v1[k1_offset + 1]; - } else { + else x1 = v1[k1_offset - 1] + 1; - } - var y1 = x1 - k1; - while (x1 < text1_length && y1 < text2_length && - text1.charAt(x1) == text2.charAt(y1)) { + let y1 = x1 - k1; + while (x1 < text1_length && y1 < text2_length && text1.charAt(x1) === text2.charAt(y1)) { x1++; y1++; } v1[k1_offset] = x1; if (x1 > text1_length) { - // Ran off the right of the graph. k1end += 2; } else if (y1 > text2_length) { - // Ran off the bottom of the graph. k1start += 2; } else if (front) { - var k2_offset = v_offset + delta - k1; - if (k2_offset >= 0 && k2_offset < v_length && v2[k2_offset] != -1) { - // Mirror x2 onto top-left coordinate system. - var x2 = text1_length - v2[k2_offset]; + const k2_offset = v_offset + delta - k1; + if (k2_offset >= 0 && k2_offset < v_length && v2[k2_offset] !== -1) { + const x2 = text1_length - v2[k2_offset]; if (x1 >= x2) { - // Overlap detected. - return this.diff_bisectSplit_(text1, text2, x1, y1, deadline); + return diffBisectSplit(text1, text2, options, x1, y1, deadline); } } } } - - // Walk the reverse path one step. - for (var k2 = -d + k2start; k2 <= d - k2end; k2 += 2) { - var k2_offset = v_offset + k2; - var x2; - if (k2 == -d || (k2 != d && v2[k2_offset - 1] < v2[k2_offset + 1])) { + for (let k2 = -d + k2start; k2 <= d - k2end; k2 += 2) { + const k2_offset = v_offset + k2; + let x2; + if (k2 === -d || k2 !== d && v2[k2_offset - 1] < v2[k2_offset + 1]) x2 = v2[k2_offset + 1]; - } else { + else x2 = v2[k2_offset - 1] + 1; - } - var y2 = x2 - k2; - while (x2 < text1_length && y2 < text2_length && - text1.charAt(text1_length - x2 - 1) == - text2.charAt(text2_length - y2 - 1)) { + let y2 = x2 - k2; + while (x2 < text1_length && y2 < text2_length && text1.charAt(text1_length - x2 - 1) === text2.charAt(text2_length - y2 - 1)) { x2++; y2++; } v2[k2_offset] = x2; if (x2 > text1_length) { - // Ran off the left of the graph. k2end += 2; } else if (y2 > text2_length) { - // Ran off the top of the graph. k2start += 2; } else if (!front) { - var k1_offset = v_offset + delta - k2; - if (k1_offset >= 0 && k1_offset < v_length && v1[k1_offset] != -1) { - var x1 = v1[k1_offset]; - var y1 = v_offset + x1 - k1_offset; - // Mirror x2 onto top-left coordinate system. + const k1_offset = v_offset + delta - k2; + if (k1_offset >= 0 && k1_offset < v_length && v1[k1_offset] !== -1) { + const x1 = v1[k1_offset]; + const y1 = v_offset + x1 - k1_offset; x2 = text1_length - x2; if (x1 >= x2) { - // Overlap detected. - return this.diff_bisectSplit_(text1, text2, x1, y1, deadline); + return diffBisectSplit(text1, text2, options, x1, y1, deadline); } } } } } - // Diff took too long and hit the deadline or - // number of diffs equals number of characters, no commonality at all. - return [[DIFF_DELETE, text1], [DIFF_INSERT, text2]]; -}; - - -/** - * Given the location of the 'middle snake', split the diff in two parts - * and recurse. - * @param {string} text1 Old string to be diffed. - * @param {string} text2 New string to be diffed. - * @param {number} x Index of split point in text1. - * @param {number} y Index of split point in text2. - * @param {number} deadline Time at which to bail if not yet complete. - * @return {!Array.} Array of diff tuples. - * @private - */ -diff_match_patch.prototype.diff_bisectSplit_ = function(text1, text2, x, y, - deadline) { - var text1a = text1.substring(0, x); - var text2a = text2.substring(0, y); - var text1b = text1.substring(x); - var text2b = text2.substring(y); - - // Compute both diffs serially. - var diffs = this.diff_main(text1a, text2a, false, deadline); - var diffsb = this.diff_main(text1b, text2b, false, deadline); - + return [createDiff(DIFF_DELETE, text1), createDiff(DIFF_INSERT, text2)]; +} +function diffBisectSplit(text1, text2, options, x, y, deadline) { + const text1a = text1.substring(0, x); + const text2a = text2.substring(0, y); + const text1b = text1.substring(x); + const text2b = text2.substring(y); + const diffs = diffMain(text1a, text2a, options, false, deadline); + const diffsb = diffMain(text1b, text2b, options, false, deadline); return diffs.concat(diffsb); -}; - - -/** - * Split two texts into an array of strings. Reduce the texts to a string of - * hashes where each Unicode character represents one line. - * @param {string} text1 First string. - * @param {string} text2 Second string. - * @return {{chars1: string, chars2: string, lineArray: !Array.}} - * An object containing the encoded text1, the encoded text2 and - * the array of unique strings. - * The zeroth element of the array of unique strings is intentionally blank. - * @private - */ -diff_match_patch.prototype.diff_linesToChars_ = function(text1, text2) { - var lineArray = []; // e.g. lineArray[4] == 'Hello\n' - var lineHash = {}; // e.g. lineHash['Hello\n'] == 4 - - // '\x00' is a valid character, but various debuggers don't like it. - // So we'll insert a junk entry to avoid generating a null character. - lineArray[0] = ''; - - /** - * Split a text into an array of strings. Reduce the texts to a string of - * hashes where each Unicode character represents one line. - * Modifies linearray and linehash through being a closure. - * @param {string} text String to encode. - * @return {string} Encoded string. - * @private - */ - function diff_linesToCharsMunge_(text) { - var chars = ''; - // Walk the text, pulling out a substring for each line. - // text.split('\n') would would temporarily double our memory footprint. - // Modifying text would create many large strings to garbage collect. - var lineStart = 0; - var lineEnd = -1; - // Keeping our own length variable is faster than looking it up. - var lineArrayLength = lineArray.length; +} +function diffLinesToChars(text1, text2) { + const lineArray = []; + const lineHash = {}; + let maxLines = 4e4; + lineArray[0] = ""; + function diffLinesToCharsMunge(text) { + let chars = ""; + let lineStart = 0; + let lineEnd = -1; + let lineArrayLength = lineArray.length; while (lineEnd < text.length - 1) { - lineEnd = text.indexOf('\n', lineStart); - if (lineEnd == -1) { + lineEnd = text.indexOf("\n", lineStart); + if (lineEnd === -1) lineEnd = text.length - 1; - } - var line = text.substring(lineStart, lineEnd + 1); - lineStart = lineEnd + 1; - - if (lineHash.hasOwnProperty ? lineHash.hasOwnProperty(line) : - (lineHash[line] !== undefined)) { + let line = text.substring(lineStart, lineEnd + 1); + if (lineHash.hasOwnProperty ? Object.prototype.hasOwnProperty.call(lineHash, line) : lineHash[line] !== undefined) { chars += String.fromCharCode(lineHash[line]); } else { + if (lineArrayLength === maxLines) { + line = text.substring(lineStart); + lineEnd = text.length; + } chars += String.fromCharCode(lineArrayLength); lineHash[line] = lineArrayLength; lineArray[lineArrayLength++] = line; } + lineStart = lineEnd + 1; } return chars; } - - var chars1 = diff_linesToCharsMunge_(text1); - var chars2 = diff_linesToCharsMunge_(text2); - return {chars1: chars1, chars2: chars2, lineArray: lineArray}; -}; - - -/** - * Rehydrate the text in a diff from a string of line hashes to real lines of - * text. - * @param {!Array.} diffs Array of diff tuples. - * @param {!Array.} lineArray Array of unique strings. - * @private - */ -diff_match_patch.prototype.diff_charsToLines_ = function(diffs, lineArray) { - for (var x = 0; x < diffs.length; x++) { - var chars = diffs[x][1]; - var text = []; - for (var y = 0; y < chars.length; y++) { - text[y] = lineArray[chars.charCodeAt(y)]; - } - diffs[x][1] = text.join(''); + const chars1 = diffLinesToCharsMunge(text1); + maxLines = 65535; + const chars2 = diffLinesToCharsMunge(text2); + return { chars1, chars2, lineArray }; +} +function diffCharsToLines(diffs, lineArray) { + for (let i = 0; i < diffs.length; i++) { + const chars = diffs[i][1]; + const text = []; + for (let j = 0; j < chars.length; j++) + text[j] = lineArray[chars.charCodeAt(j)]; + diffs[i][1] = text.join(""); } -}; - - -/** - * Determine the common prefix of two strings. - * @param {string} text1 First string. - * @param {string} text2 Second string. - * @return {number} The number of characters common to the start of each - * string. - */ -diff_match_patch.prototype.diff_commonPrefix = function(text1, text2) { - // Quick check for common null cases. - if (!text1 || !text2 || text1.charAt(0) != text2.charAt(0)) { +} +function diffCommonPrefix(text1, text2) { + if (!text1 || !text2 || text1.charAt(0) !== text2.charAt(0)) return 0; - } - // Binary search. - // Performance analysis: http://neil.fraser.name/news/2007/10/09/ - var pointermin = 0; - var pointermax = Math.min(text1.length, text2.length); - var pointermid = pointermax; - var pointerstart = 0; + let pointermin = 0; + let pointermax = Math.min(text1.length, text2.length); + let pointermid = pointermax; + let pointerstart = 0; while (pointermin < pointermid) { - if (text1.substring(pointerstart, pointermid) == - text2.substring(pointerstart, pointermid)) { + if (text1.substring(pointerstart, pointermid) === text2.substring(pointerstart, pointermid)) { pointermin = pointermid; pointerstart = pointermin; } else { @@ -538,30 +303,17 @@ diff_match_patch.prototype.diff_commonPrefix = function(text1, text2) { pointermid = Math.floor((pointermax - pointermin) / 2 + pointermin); } return pointermid; -}; - - -/** - * Determine the common suffix of two strings. - * @param {string} text1 First string. - * @param {string} text2 Second string. - * @return {number} The number of characters common to the end of each string. - */ -diff_match_patch.prototype.diff_commonSuffix = function(text1, text2) { - // Quick check for common null cases. - if (!text1 || !text2 || - text1.charAt(text1.length - 1) != text2.charAt(text2.length - 1)) { +} +function diffCommonSuffix(text1, text2) { + if (!text1 || !text2 || text1.charAt(text1.length - 1) !== text2.charAt(text2.length - 1)) { return 0; } - // Binary search. - // Performance analysis: http://neil.fraser.name/news/2007/10/09/ - var pointermin = 0; - var pointermax = Math.min(text1.length, text2.length); - var pointermid = pointermax; - var pointerend = 0; + let pointermin = 0; + let pointermax = Math.min(text1.length, text2.length); + let pointermid = pointermax; + let pointerend = 0; while (pointermin < pointermid) { - if (text1.substring(text1.length - pointermid, text1.length - pointerend) == - text2.substring(text2.length - pointermid, text2.length - pointerend)) { + if (text1.substring(text1.length - pointermid, text1.length - pointerend) === text2.substring(text2.length - pointermid, text2.length - pointerend)) { pointermin = pointermid; pointerend = pointermin; } else { @@ -570,128 +322,65 @@ diff_match_patch.prototype.diff_commonSuffix = function(text1, text2) { pointermid = Math.floor((pointermax - pointermin) / 2 + pointermin); } return pointermid; -}; - - -/** - * Determine if the suffix of one string is the prefix of another. - * @param {string} text1 First string. - * @param {string} text2 Second string. - * @return {number} The number of characters common to the end of the first - * string and the start of the second string. - * @private - */ -diff_match_patch.prototype.diff_commonOverlap_ = function(text1, text2) { - // Cache the text lengths to prevent multiple calls. - var text1_length = text1.length; - var text2_length = text2.length; - // Eliminate the null case. - if (text1_length == 0 || text2_length == 0) { +} +function diffCommonOverlap(text1, text2) { + const text1_length = text1.length; + const text2_length = text2.length; + if (text1_length === 0 || text2_length === 0) return 0; - } - // Truncate the longer string. - if (text1_length > text2_length) { + if (text1_length > text2_length) text1 = text1.substring(text1_length - text2_length); - } else if (text1_length < text2_length) { + else if (text1_length < text2_length) text2 = text2.substring(0, text1_length); - } - var text_length = Math.min(text1_length, text2_length); - // Quick check for the worst case. - if (text1 == text2) { + const text_length = Math.min(text1_length, text2_length); + if (text1 === text2) return text_length; - } - - // Start by looking for a single character match - // and increase length until no match is found. - // Performance analysis: http://neil.fraser.name/news/2010/11/04/ - var best = 0; - var length = 1; + let best = 0; + let length = 1; while (true) { - var pattern = text1.substring(text_length - length); - var found = text2.indexOf(pattern); - if (found == -1) { + const pattern = text1.substring(text_length - length); + const found = text2.indexOf(pattern); + if (found === -1) return best; - } length += found; - if (found == 0 || text1.substring(text_length - length) == - text2.substring(0, length)) { + if (found === 0 || text1.substring(text_length - length) === text2.substring(0, length)) { best = length; length++; } } -}; - - -/** - * Do the two texts share a substring which is at least half the length of the - * longer text? - * This speedup can produce non-minimal diffs. - * @param {string} text1 First string. - * @param {string} text2 Second string. - * @return {Array.} Five element Array, containing the prefix of - * text1, the suffix of text1, the prefix of text2, the suffix of - * text2 and the common middle. Or null if there was no match. - * @private - */ -diff_match_patch.prototype.diff_halfMatch_ = function(text1, text2) { - if (this.Diff_Timeout <= 0) { - // Don't risk returning a non-optimal diff if we have unlimited time. +} +function diffHalfMatch(text1, text2, options) { + if (options.diffTimeout <= 0) { return null; } - var longtext = text1.length > text2.length ? text1 : text2; - var shorttext = text1.length > text2.length ? text2 : text1; - if (longtext.length < 4 || shorttext.length * 2 < longtext.length) { - return null; // Pointless. - } - var dmp = this; // 'this' becomes 'window' in a closure. - - /** - * Does a substring of shorttext exist within longtext such that the substring - * is at least half the length of longtext? - * Closure, but does not reference any external variables. - * @param {string} longtext Longer string. - * @param {string} shorttext Shorter string. - * @param {number} i Start index of quarter length substring within longtext. - * @return {Array.} Five element Array, containing the prefix of - * longtext, the suffix of longtext, the prefix of shorttext, the suffix - * of shorttext and the common middle. Or null if there was no match. - * @private - */ - function diff_halfMatchI_(longtext, shorttext, i) { - // Start with a 1/4 length substring at position i as a seed. - var seed = longtext.substring(i, i + Math.floor(longtext.length / 4)); - var j = -1; - var best_common = ''; - var best_longtext_a, best_longtext_b, best_shorttext_a, best_shorttext_b; - while ((j = shorttext.indexOf(seed, j + 1)) != -1) { - var prefixLength = dmp.diff_commonPrefix(longtext.substring(i), - shorttext.substring(j)); - var suffixLength = dmp.diff_commonSuffix(longtext.substring(0, i), - shorttext.substring(0, j)); + const longtext = text1.length > text2.length ? text1 : text2; + const shorttext = text1.length > text2.length ? text2 : text1; + if (longtext.length < 4 || shorttext.length * 2 < longtext.length) + return null; + function diffHalfMatchI(longtext2, shorttext2, i) { + const seed = longtext2.substring(i, i + Math.floor(longtext2.length / 4)); + let j = -1; + let best_common = ""; + let best_longtext_a, best_longtext_b, best_shorttext_a, best_shorttext_b; + while ((j = shorttext2.indexOf(seed, j + 1)) !== -1) { + const prefixLength = diffCommonPrefix(longtext2.substring(i), shorttext2.substring(j)); + const suffixLength = diffCommonSuffix(longtext2.substring(0, i), shorttext2.substring(0, j)); if (best_common.length < suffixLength + prefixLength) { - best_common = shorttext.substring(j - suffixLength, j) + - shorttext.substring(j, j + prefixLength); - best_longtext_a = longtext.substring(0, i - suffixLength); - best_longtext_b = longtext.substring(i + prefixLength); - best_shorttext_a = shorttext.substring(0, j - suffixLength); - best_shorttext_b = shorttext.substring(j + prefixLength); + best_common = shorttext2.substring(j - suffixLength, j) + shorttext2.substring(j, j + prefixLength); + best_longtext_a = longtext2.substring(0, i - suffixLength); + best_longtext_b = longtext2.substring(i + prefixLength); + best_shorttext_a = shorttext2.substring(0, j - suffixLength); + best_shorttext_b = shorttext2.substring(j + prefixLength); } } - if (best_common.length * 2 >= longtext.length) { - return [best_longtext_a, best_longtext_b, - best_shorttext_a, best_shorttext_b, best_common]; - } else { + if (best_common.length * 2 >= longtext2.length) + return [best_longtext_a, best_longtext_b, best_shorttext_a, best_shorttext_b, best_common]; + else return null; - } } - - // First check if the second quarter is the seed for a half-match. - var hm1 = diff_halfMatchI_(longtext, shorttext, - Math.ceil(longtext.length / 4)); - // Check again based on the third quarter. - var hm2 = diff_halfMatchI_(longtext, shorttext, - Math.ceil(longtext.length / 2)); - var hm; + const hm1 = diffHalfMatchI(longtext, shorttext, Math.ceil(longtext.length / 4)); + const hm2 = diffHalfMatchI(longtext, shorttext, Math.ceil(longtext.length / 2)); + let hm; if (!hm1 && !hm2) { return null; } else if (!hm2) { @@ -699,12 +388,9 @@ diff_match_patch.prototype.diff_halfMatch_ = function(text1, text2) { } else if (!hm1) { hm = hm2; } else { - // Both matched. Select the longest. hm = hm1[4].length > hm2[4].length ? hm1 : hm2; } - - // A half-match was found, sort out the return data. - var text1_a, text1_b, text2_a, text2_b; + let text1_a, text1_b, text2_a, text2_b; if (text1.length > text2.length) { text1_a = hm[0]; text1_b = hm[1]; @@ -716,114 +402,72 @@ diff_match_patch.prototype.diff_halfMatch_ = function(text1, text2) { text1_a = hm[2]; text1_b = hm[3]; } - var mid_common = hm[4]; + const mid_common = hm[4]; return [text1_a, text1_b, text2_a, text2_b, mid_common]; -}; - - -/** - * Reduce the number of edits by eliminating semantically trivial equalities. - * @param {!Array.} diffs Array of diff tuples. - */ -diff_match_patch.prototype.diff_cleanupSemantic = function(diffs) { - var changes = false; - var equalities = []; // Stack of indices where equalities are found. - var equalitiesLength = 0; // Keeping our own length var is faster in JS. - /** @type {?string} */ - var lastequality = null; - // Always equal to diffs[equalities[equalitiesLength - 1]][1] - var pointer = 0; // Index of current position. - // Number of characters that changed prior to the equality. - var length_insertions1 = 0; - var length_deletions1 = 0; - // Number of characters that changed after the equality. - var length_insertions2 = 0; - var length_deletions2 = 0; +} +function diffCleanupSemantic(diffs) { + let changes = false; + const equalities = []; + let equalitiesLength = 0; + let lastEquality = null; + let pointer = 0; + let length_insertions1 = 0; + let length_deletions1 = 0; + let length_insertions2 = 0; + let length_deletions2 = 0; while (pointer < diffs.length) { - if (diffs[pointer][0] == DIFF_EQUAL) { // Equality found. + if (diffs[pointer][0] === DIFF_EQUAL) { equalities[equalitiesLength++] = pointer; length_insertions1 = length_insertions2; length_deletions1 = length_deletions2; length_insertions2 = 0; length_deletions2 = 0; - lastequality = diffs[pointer][1]; - } else { // An insertion or deletion. - if (diffs[pointer][0] == DIFF_INSERT) { + lastEquality = diffs[pointer][1]; + } else { + if (diffs[pointer][0] === DIFF_INSERT) length_insertions2 += diffs[pointer][1].length; - } else { + else length_deletions2 += diffs[pointer][1].length; - } - // Eliminate an equality that is smaller or equal to the edits on both - // sides of it. - if (lastequality && (lastequality.length <= - Math.max(length_insertions1, length_deletions1)) && - (lastequality.length <= Math.max(length_insertions2, - length_deletions2))) { - // Duplicate record. - diffs.splice(equalities[equalitiesLength - 1], 0, - [DIFF_DELETE, lastequality]); - // Change second copy to insert. + if (lastEquality && lastEquality.length <= Math.max(length_insertions1, length_deletions1) && lastEquality.length <= Math.max(length_insertions2, length_deletions2)) { + diffs.splice(equalities[equalitiesLength - 1], 0, createDiff(DIFF_DELETE, lastEquality)); diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT; - // Throw away the equality we just deleted. equalitiesLength--; - // Throw away the previous equality (it needs to be reevaluated). equalitiesLength--; pointer = equalitiesLength > 0 ? equalities[equalitiesLength - 1] : -1; - length_insertions1 = 0; // Reset the counters. + length_insertions1 = 0; length_deletions1 = 0; length_insertions2 = 0; length_deletions2 = 0; - lastequality = null; + lastEquality = null; changes = true; } } pointer++; } - - // Normalize the diff. - if (changes) { - this.diff_cleanupMerge(diffs); - } - this.diff_cleanupSemanticLossless(diffs); - - // Find any overlaps between deletions and insertions. - // e.g: abcxxxxxxdef - // -> abcxxxdef - // e.g: xxxabcdefxxx - // -> defxxxabc - // Only extract an overlap if it is as big as the edit ahead or behind it. + if (changes) + diffCleanupMerge(diffs); + diffCleanupSemanticLossless(diffs); pointer = 1; while (pointer < diffs.length) { - if (diffs[pointer - 1][0] == DIFF_DELETE && - diffs[pointer][0] == DIFF_INSERT) { - var deletion = diffs[pointer - 1][1]; - var insertion = diffs[pointer][1]; - var overlap_length1 = this.diff_commonOverlap_(deletion, insertion); - var overlap_length2 = this.diff_commonOverlap_(insertion, deletion); + if (diffs[pointer - 1][0] === DIFF_DELETE && diffs[pointer][0] === DIFF_INSERT) { + const deletion = diffs[pointer - 1][1]; + const insertion = diffs[pointer][1]; + const overlap_length1 = diffCommonOverlap(deletion, insertion); + const overlap_length2 = diffCommonOverlap(insertion, deletion); if (overlap_length1 >= overlap_length2) { - if (overlap_length1 >= deletion.length / 2 || - overlap_length1 >= insertion.length / 2) { - // Overlap found. Insert an equality and trim the surrounding edits. - diffs.splice(pointer, 0, - [DIFF_EQUAL, insertion.substring(0, overlap_length1)]); - diffs[pointer - 1][1] = - deletion.substring(0, deletion.length - overlap_length1); + if (overlap_length1 >= deletion.length / 2 || overlap_length1 >= insertion.length / 2) { + diffs.splice(pointer, 0, createDiff(DIFF_EQUAL, insertion.substring(0, overlap_length1))); + diffs[pointer - 1][1] = deletion.substring(0, deletion.length - overlap_length1); diffs[pointer + 1][1] = insertion.substring(overlap_length1); pointer++; } } else { - if (overlap_length2 >= deletion.length / 2 || - overlap_length2 >= insertion.length / 2) { - // Reverse overlap found. - // Insert an equality and swap and trim the surrounding edits. - diffs.splice(pointer, 0, - [DIFF_EQUAL, deletion.substring(0, overlap_length2)]); + if (overlap_length2 >= deletion.length / 2 || overlap_length2 >= insertion.length / 2) { + diffs.splice(pointer, 0, createDiff(DIFF_EQUAL, deletion.substring(0, overlap_length2))); diffs[pointer - 1][0] = DIFF_INSERT; - diffs[pointer - 1][1] = - insertion.substring(0, insertion.length - overlap_length2); + diffs[pointer - 1][1] = insertion.substring(0, insertion.length - overlap_length2); diffs[pointer + 1][0] = DIFF_DELETE; - diffs[pointer + 1][1] = - deletion.substring(overlap_length2); + diffs[pointer + 1][1] = deletion.substring(overlap_length2); pointer++; } } @@ -831,105 +475,62 @@ diff_match_patch.prototype.diff_cleanupSemantic = function(diffs) { } pointer++; } -}; - - -/** - * Look for single edits surrounded on both sides by equalities - * which can be shifted sideways to align the edit to a word boundary. - * e.g: The cat came. -> The cat came. - * @param {!Array.} diffs Array of diff tuples. - */ -diff_match_patch.prototype.diff_cleanupSemanticLossless = function(diffs) { - /** - * Given two strings, compute a score representing whether the internal - * boundary falls on logical boundaries. - * Scores range from 6 (best) to 0 (worst). - * Closure, but does not reference any external variables. - * @param {string} one First string. - * @param {string} two Second string. - * @return {number} The score. - * @private - */ - function diff_cleanupSemanticScore_(one, two) { +} +const nonAlphaNumericRegex_ = /[^a-z0-9]/i; +const whitespaceRegex_ = /\s/; +const linebreakRegex_ = /[\r\n]/; +const blanklineEndRegex_ = /\n\r?\n$/; +const blanklineStartRegex_ = /^\r?\n\r?\n/; +function diffCleanupSemanticLossless(diffs) { + function diffCleanupSemanticScore(one, two) { if (!one || !two) { - // Edges are the best. return 6; } - - // Each port of this function behaves slightly differently due to - // subtle differences in each language's definition of things like - // 'whitespace'. Since this function's purpose is largely cosmetic, - // the choice has been made to use each language's native features - // rather than force total conformity. - var char1 = one.charAt(one.length - 1); - var char2 = two.charAt(0); - var nonAlphaNumeric1 = char1.match(diff_match_patch.nonAlphaNumericRegex_); - var nonAlphaNumeric2 = char2.match(diff_match_patch.nonAlphaNumericRegex_); - var whitespace1 = nonAlphaNumeric1 && - char1.match(diff_match_patch.whitespaceRegex_); - var whitespace2 = nonAlphaNumeric2 && - char2.match(diff_match_patch.whitespaceRegex_); - var lineBreak1 = whitespace1 && - char1.match(diff_match_patch.linebreakRegex_); - var lineBreak2 = whitespace2 && - char2.match(diff_match_patch.linebreakRegex_); - var blankLine1 = lineBreak1 && - one.match(diff_match_patch.blanklineEndRegex_); - var blankLine2 = lineBreak2 && - two.match(diff_match_patch.blanklineStartRegex_); - + const char1 = one.charAt(one.length - 1); + const char2 = two.charAt(0); + const nonAlphaNumeric1 = char1.match(nonAlphaNumericRegex_); + const nonAlphaNumeric2 = char2.match(nonAlphaNumericRegex_); + const whitespace1 = nonAlphaNumeric1 && char1.match(whitespaceRegex_); + const whitespace2 = nonAlphaNumeric2 && char2.match(whitespaceRegex_); + const lineBreak1 = whitespace1 && char1.match(linebreakRegex_); + const lineBreak2 = whitespace2 && char2.match(linebreakRegex_); + const blankLine1 = lineBreak1 && one.match(blanklineEndRegex_); + const blankLine2 = lineBreak2 && two.match(blanklineStartRegex_); if (blankLine1 || blankLine2) { - // Five points for blank lines. return 5; } else if (lineBreak1 || lineBreak2) { - // Four points for line breaks. return 4; } else if (nonAlphaNumeric1 && !whitespace1 && whitespace2) { - // Three points for end of sentences. return 3; } else if (whitespace1 || whitespace2) { - // Two points for whitespace. return 2; } else if (nonAlphaNumeric1 || nonAlphaNumeric2) { - // One point for non-alphanumeric. return 1; } return 0; } - - var pointer = 1; - // Intentionally ignore the first and last element (don't need checking). + let pointer = 1; while (pointer < diffs.length - 1) { - if (diffs[pointer - 1][0] == DIFF_EQUAL && - diffs[pointer + 1][0] == DIFF_EQUAL) { - // This is a single edit surrounded by equalities. - var equality1 = diffs[pointer - 1][1]; - var edit = diffs[pointer][1]; - var equality2 = diffs[pointer + 1][1]; - - // First, shift the edit as far left as possible. - var commonOffset = this.diff_commonSuffix(equality1, edit); + if (diffs[pointer - 1][0] === DIFF_EQUAL && diffs[pointer + 1][0] === DIFF_EQUAL) { + let equality1 = diffs[pointer - 1][1]; + let edit = diffs[pointer][1]; + let equality2 = diffs[pointer + 1][1]; + const commonOffset = diffCommonSuffix(equality1, edit); if (commonOffset) { - var commonString = edit.substring(edit.length - commonOffset); + const commonString = edit.substring(edit.length - commonOffset); equality1 = equality1.substring(0, equality1.length - commonOffset); edit = commonString + edit.substring(0, edit.length - commonOffset); equality2 = commonString + equality2; } - - // Second, step character by character right, looking for the best fit. - var bestEquality1 = equality1; - var bestEdit = edit; - var bestEquality2 = equality2; - var bestScore = diff_cleanupSemanticScore_(equality1, edit) + - diff_cleanupSemanticScore_(edit, equality2); + let bestEquality1 = equality1; + let bestEdit = edit; + let bestEquality2 = equality2; + let bestScore = diffCleanupSemanticScore(equality1, edit) + diffCleanupSemanticScore(edit, equality2); while (edit.charAt(0) === equality2.charAt(0)) { equality1 += edit.charAt(0); edit = edit.substring(1) + equality2.charAt(0); equality2 = equality2.substring(1); - var score = diff_cleanupSemanticScore_(equality1, edit) + - diff_cleanupSemanticScore_(edit, equality2); - // The >= encourages trailing rather than leading whitespace on edits. + const score = diffCleanupSemanticScore(equality1, edit) + diffCleanupSemanticScore(edit, equality2); if (score >= bestScore) { bestScore = score; bestEquality1 = equality1; @@ -937,9 +538,7 @@ diff_match_patch.prototype.diff_cleanupSemanticLossless = function(diffs) { bestEquality2 = equality2; } } - - if (diffs[pointer - 1][1] != bestEquality1) { - // We have an improvement, save it back to the diff. + if (diffs[pointer - 1][1] !== bestEquality1) { if (bestEquality1) { diffs[pointer - 1][1] = bestEquality1; } else { @@ -957,82 +556,51 @@ diff_match_patch.prototype.diff_cleanupSemanticLossless = function(diffs) { } pointer++; } -}; - -// Define some regex patterns for matching boundaries. -diff_match_patch.nonAlphaNumericRegex_ = /[^a-zA-Z0-9]/; -diff_match_patch.whitespaceRegex_ = /\s/; -diff_match_patch.linebreakRegex_ = /[\r\n]/; -diff_match_patch.blanklineEndRegex_ = /\n\r?\n$/; -diff_match_patch.blanklineStartRegex_ = /^\r?\n\r?\n/; - -/** - * Reduce the number of edits by eliminating operationally trivial equalities. - * @param {!Array.} diffs Array of diff tuples. - */ -diff_match_patch.prototype.diff_cleanupEfficiency = function(diffs) { - var changes = false; - var equalities = []; // Stack of indices where equalities are found. - var equalitiesLength = 0; // Keeping our own length var is faster in JS. - /** @type {?string} */ - var lastequality = null; - // Always equal to diffs[equalities[equalitiesLength - 1]][1] - var pointer = 0; // Index of current position. - // Is there an insertion operation before the last equality. - var pre_ins = false; - // Is there a deletion operation before the last equality. - var pre_del = false; - // Is there an insertion operation after the last equality. - var post_ins = false; - // Is there a deletion operation after the last equality. - var post_del = false; +} +function diffCleanupEfficiency(diffs, options = {}) { + const { + diffEditCost = defaultOptions.diffEditCost + } = options; + let changes = false; + const equalities = []; + let equalitiesLength = 0; + let lastEquality = null; + let pointer = 0; + let pre_ins = false; + let pre_del = false; + let post_ins = false; + let post_del = false; while (pointer < diffs.length) { - if (diffs[pointer][0] == DIFF_EQUAL) { // Equality found. - if (diffs[pointer][1].length < this.Diff_EditCost && - (post_ins || post_del)) { - // Candidate found. + if (diffs[pointer][0] === DIFF_EQUAL) { + if (diffs[pointer][1].length < diffEditCost && (post_ins || post_del)) { equalities[equalitiesLength++] = pointer; pre_ins = post_ins; pre_del = post_del; - lastequality = diffs[pointer][1]; + lastEquality = diffs[pointer][1]; } else { - // Not a candidate, and can never become one. equalitiesLength = 0; - lastequality = null; + lastEquality = null; } post_ins = post_del = false; - } else { // An insertion or deletion. - if (diffs[pointer][0] == DIFF_DELETE) { + } else { + let booleanCount = function(...args) { + return args.filter(Boolean).length; + }; + if (diffs[pointer][0] === DIFF_DELETE) post_del = true; - } else { + else post_ins = true; - } - /* - * Five types to be split: - * ABXYCD - * AXCD - * ABXC - * AXCD - * ABXC - */ - if (lastequality && ((pre_ins && pre_del && post_ins && post_del) || - ((lastequality.length < this.Diff_EditCost / 2) && - (pre_ins + pre_del + post_ins + post_del) == 3))) { - // Duplicate record. - diffs.splice(equalities[equalitiesLength - 1], 0, - [DIFF_DELETE, lastequality]); - // Change second copy to insert. + if (lastEquality && (pre_ins && pre_del && post_ins && post_del || lastEquality.length < diffEditCost / 2 && booleanCount(pre_ins, pre_del, post_ins, post_del) === 3)) { + diffs.splice(equalities[equalitiesLength - 1], 0, createDiff(DIFF_DELETE, lastEquality)); diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT; - equalitiesLength--; // Throw away the equality we just deleted; - lastequality = null; + equalitiesLength--; + lastEquality = null; if (pre_ins && pre_del) { - // No changes made which could affect previous entry, keep going. post_ins = post_del = true; equalitiesLength = 0; } else { - equalitiesLength--; // Throw away the previous equality. - pointer = equalitiesLength > 0 ? - equalities[equalitiesLength - 1] : -1; + equalitiesLength--; + pointer = equalitiesLength > 0 ? equalities[equalitiesLength - 1] : -1; post_ins = post_del = false; } changes = true; @@ -1040,26 +608,17 @@ diff_match_patch.prototype.diff_cleanupEfficiency = function(diffs) { } pointer++; } - - if (changes) { - this.diff_cleanupMerge(diffs); - } -}; - - -/** - * Reorder and merge like edit sections. Merge equalities. - * Any edit section can move as long as it doesn't cross an equality. - * @param {!Array.} diffs Array of diff tuples. - */ -diff_match_patch.prototype.diff_cleanupMerge = function(diffs) { - diffs.push([DIFF_EQUAL, '']); // Add a dummy entry at the end. - var pointer = 0; - var count_delete = 0; - var count_insert = 0; - var text_delete = ''; - var text_insert = ''; - var commonlength; + if (changes) + diffCleanupMerge(diffs); +} +function diffCleanupMerge(diffs) { + diffs.push(createDiff(DIFF_EQUAL, "")); + let pointer = 0; + let count_delete = 0; + let count_insert = 0; + let text_delete = ""; + let text_insert = ""; + let commonlength; while (pointer < diffs.length) { switch (diffs[pointer][0]) { case DIFF_INSERT: @@ -1073,52 +632,38 @@ diff_match_patch.prototype.diff_cleanupMerge = function(diffs) { pointer++; break; case DIFF_EQUAL: - // Upon reaching an equality, check for prior redundancies. if (count_delete + count_insert > 1) { if (count_delete !== 0 && count_insert !== 0) { - // Factor out any common prefixies. - commonlength = this.diff_commonPrefix(text_insert, text_delete); + commonlength = diffCommonPrefix(text_insert, text_delete); if (commonlength !== 0) { - if ((pointer - count_delete - count_insert) > 0 && - diffs[pointer - count_delete - count_insert - 1][0] == - DIFF_EQUAL) { - diffs[pointer - count_delete - count_insert - 1][1] += - text_insert.substring(0, commonlength); + if (pointer - count_delete - count_insert > 0 && diffs[pointer - count_delete - count_insert - 1][0] === DIFF_EQUAL) { + diffs[pointer - count_delete - count_insert - 1][1] += text_insert.substring(0, commonlength); } else { - diffs.splice(0, 0, [DIFF_EQUAL, - text_insert.substring(0, commonlength)]); + diffs.splice(0, 0, createDiff(DIFF_EQUAL, text_insert.substring(0, commonlength))); pointer++; } text_insert = text_insert.substring(commonlength); text_delete = text_delete.substring(commonlength); } - // Factor out any common suffixies. - commonlength = this.diff_commonSuffix(text_insert, text_delete); + commonlength = diffCommonSuffix(text_insert, text_delete); if (commonlength !== 0) { - diffs[pointer][1] = text_insert.substring(text_insert.length - - commonlength) + diffs[pointer][1]; - text_insert = text_insert.substring(0, text_insert.length - - commonlength); - text_delete = text_delete.substring(0, text_delete.length - - commonlength); + diffs[pointer][1] = text_insert.substring(text_insert.length - commonlength) + diffs[pointer][1]; + text_insert = text_insert.substring(0, text_insert.length - commonlength); + text_delete = text_delete.substring(0, text_delete.length - commonlength); } } - // Delete the offending records and add the merged ones. - if (count_delete === 0) { - diffs.splice(pointer - count_insert, - count_delete + count_insert, [DIFF_INSERT, text_insert]); - } else if (count_insert === 0) { - diffs.splice(pointer - count_delete, - count_delete + count_insert, [DIFF_DELETE, text_delete]); - } else { - diffs.splice(pointer - count_delete - count_insert, - count_delete + count_insert, [DIFF_DELETE, text_delete], - [DIFF_INSERT, text_insert]); + pointer -= count_delete + count_insert; + diffs.splice(pointer, count_delete + count_insert); + if (text_delete.length) { + diffs.splice(pointer, 0, createDiff(DIFF_DELETE, text_delete)); + pointer++; } - pointer = pointer - count_delete - count_insert + - (count_delete ? 1 : 0) + (count_insert ? 1 : 0) + 1; - } else if (pointer !== 0 && diffs[pointer - 1][0] == DIFF_EQUAL) { - // Merge this equality with the previous one. + if (text_insert.length) { + diffs.splice(pointer, 0, createDiff(DIFF_INSERT, text_insert)); + pointer++; + } + pointer++; + } else if (pointer !== 0 && diffs[pointer - 1][0] === DIFF_EQUAL) { diffs[pointer - 1][1] += diffs[pointer][1]; diffs.splice(pointer, 1); } else { @@ -1126,167 +671,104 @@ diff_match_patch.prototype.diff_cleanupMerge = function(diffs) { } count_insert = 0; count_delete = 0; - text_delete = ''; - text_insert = ''; + text_delete = ""; + text_insert = ""; break; } } - if (diffs[diffs.length - 1][1] === '') { - diffs.pop(); // Remove the dummy entry at the end. - } - - // Second pass: look for single edits surrounded on both sides by equalities - // which can be shifted sideways to eliminate an equality. - // e.g: ABAC -> ABAC - var changes = false; + if (diffs[diffs.length - 1][1] === "") + diffs.pop(); + let changes = false; pointer = 1; - // Intentionally ignore the first and last element (don't need checking). while (pointer < diffs.length - 1) { - if (diffs[pointer - 1][0] == DIFF_EQUAL && - diffs[pointer + 1][0] == DIFF_EQUAL) { - // This is a single edit surrounded by equalities. - if (diffs[pointer][1].substring(diffs[pointer][1].length - - diffs[pointer - 1][1].length) == diffs[pointer - 1][1]) { - // Shift the edit over the previous equality. - diffs[pointer][1] = diffs[pointer - 1][1] + - diffs[pointer][1].substring(0, diffs[pointer][1].length - - diffs[pointer - 1][1].length); + if (diffs[pointer - 1][0] === DIFF_EQUAL && diffs[pointer + 1][0] === DIFF_EQUAL) { + if (diffs[pointer][1].substring(diffs[pointer][1].length - diffs[pointer - 1][1].length) === diffs[pointer - 1][1]) { + diffs[pointer][1] = diffs[pointer - 1][1] + diffs[pointer][1].substring(0, diffs[pointer][1].length - diffs[pointer - 1][1].length); diffs[pointer + 1][1] = diffs[pointer - 1][1] + diffs[pointer + 1][1]; diffs.splice(pointer - 1, 1); changes = true; - } else if (diffs[pointer][1].substring(0, diffs[pointer + 1][1].length) == - diffs[pointer + 1][1]) { - // Shift the edit over the next equality. + } else if (diffs[pointer][1].substring(0, diffs[pointer + 1][1].length) === diffs[pointer + 1][1]) { diffs[pointer - 1][1] += diffs[pointer + 1][1]; - diffs[pointer][1] = - diffs[pointer][1].substring(diffs[pointer + 1][1].length) + - diffs[pointer + 1][1]; + diffs[pointer][1] = diffs[pointer][1].substring(diffs[pointer + 1][1].length) + diffs[pointer + 1][1]; diffs.splice(pointer + 1, 1); changes = true; } } pointer++; } - // If shifts were made, the diff needs reordering and another shift sweep. - if (changes) { - this.diff_cleanupMerge(diffs); - } -}; - - -/** - * loc is a location in text1, compute and return the equivalent location in - * text2. - * e.g. 'The cat' vs 'The big cat', 1->1, 5->8 - * @param {!Array.} diffs Array of diff tuples. - * @param {number} loc Location within text1. - * @return {number} Location within text2. - */ -diff_match_patch.prototype.diff_xIndex = function(diffs, loc) { - var chars1 = 0; - var chars2 = 0; - var last_chars1 = 0; - var last_chars2 = 0; - var x; + if (changes) + diffCleanupMerge(diffs); +} +function diffXIndex(diffs, loc) { + let chars1 = 0; + let chars2 = 0; + let last_chars1 = 0; + let last_chars2 = 0; + let x; for (x = 0; x < diffs.length; x++) { - if (diffs[x][0] !== DIFF_INSERT) { // Equality or deletion. + if (diffs[x][0] !== DIFF_INSERT) { chars1 += diffs[x][1].length; } - if (diffs[x][0] !== DIFF_DELETE) { // Equality or insertion. + if (diffs[x][0] !== DIFF_DELETE) { chars2 += diffs[x][1].length; } - if (chars1 > loc) { // Overshot the location. + if (chars1 > loc) { break; } last_chars1 = chars1; last_chars2 = chars2; } - // Was the location was deleted? - if (diffs.length != x && diffs[x][0] === DIFF_DELETE) { + if (diffs.length !== x && diffs[x][0] === DIFF_DELETE) return last_chars2; - } - // Add the remaining character length. return last_chars2 + (loc - last_chars1); -}; - - -/** - * Convert a diff array into a pretty HTML report. - * @param {!Array.} diffs Array of diff tuples. - * @return {string} HTML representation. - */ -diff_match_patch.prototype.diff_prettyHtml = function(diffs) { - var html = []; - var pattern_amp = /&/g; - var pattern_lt = //g; - var pattern_para = /\n/g; - for (var x = 0; x < diffs.length; x++) { - var op = diffs[x][0]; // Operation (insert, delete, equal) - var data = diffs[x][1]; // Text of change. - var text = data.replace(pattern_amp, '&').replace(pattern_lt, '<') - .replace(pattern_gt, '>').replace(pattern_para, '¶
'); +} +function diffPrettyHtml(diffs) { + const html = []; + const pattern_amp = /&/g; + const pattern_lt = //g; + const pattern_para = /\n/g; + for (let x = 0; x < diffs.length; x++) { + const op = diffs[x][0]; + const data = diffs[x][1]; + const text = data.replace(pattern_amp, "&").replace(pattern_lt, "<").replace(pattern_gt, ">").replace(pattern_para, "¶
"); switch (op) { case DIFF_INSERT: - html[x] = '' + text + ''; + html[x] = `${text}`; break; case DIFF_DELETE: - html[x] = '' + text + ''; + html[x] = `${text}`; break; case DIFF_EQUAL: - html[x] = '' + text + ''; + html[x] = `${text}`; break; } } - return html.join(''); -}; - - -/** - * Compute and return the source text (all equalities and deletions). - * @param {!Array.} diffs Array of diff tuples. - * @return {string} Source text. - */ -diff_match_patch.prototype.diff_text1 = function(diffs) { - var text = []; - for (var x = 0; x < diffs.length; x++) { - if (diffs[x][0] !== DIFF_INSERT) { + return html.join(""); +} +function diffText1(diffs) { + const text = []; + for (let x = 0; x < diffs.length; x++) { + if (diffs[x][0] !== DIFF_INSERT) text[x] = diffs[x][1]; - } } - return text.join(''); -}; - - -/** - * Compute and return the destination text (all equalities and insertions). - * @param {!Array.} diffs Array of diff tuples. - * @return {string} Destination text. - */ -diff_match_patch.prototype.diff_text2 = function(diffs) { - var text = []; - for (var x = 0; x < diffs.length; x++) { - if (diffs[x][0] !== DIFF_DELETE) { + return text.join(""); +} +function diffText2(diffs) { + const text = []; + for (let x = 0; x < diffs.length; x++) { + if (diffs[x][0] !== DIFF_DELETE) text[x] = diffs[x][1]; - } } - return text.join(''); -}; - - -/** - * Compute the Levenshtein distance; the number of inserted, deleted or - * substituted characters. - * @param {!Array.} diffs Array of diff tuples. - * @return {number} Number of changes. - */ -diff_match_patch.prototype.diff_levenshtein = function(diffs) { - var levenshtein = 0; - var insertions = 0; - var deletions = 0; - for (var x = 0; x < diffs.length; x++) { - var op = diffs[x][0]; - var data = diffs[x][1]; + return text.join(""); +} +function diffLevenshtein(diffs) { + let levenshtein = 0; + let insertions = 0; + let deletions = 0; + for (let x = 0; x < diffs.length; x++) { + const op = diffs[x][0]; + const data = diffs[x][1]; switch (op) { case DIFF_INSERT: insertions += data.length; @@ -1295,7 +777,6 @@ diff_match_patch.prototype.diff_levenshtein = function(diffs) { deletions += data.length; break; case DIFF_EQUAL: - // A deletion and an insertion is one substitution. levenshtein += Math.max(insertions, deletions); insertions = 0; deletions = 0; @@ -1304,461 +785,285 @@ diff_match_patch.prototype.diff_levenshtein = function(diffs) { } levenshtein += Math.max(insertions, deletions); return levenshtein; -}; - - -/** - * Crush the diff into an encoded string which describes the operations - * required to transform text1 into text2. - * E.g. =3\t-2\t+ing -> Keep 3 chars, delete 2 chars, insert 'ing'. - * Operations are tab-separated. Inserted text is escaped using %xx notation. - * @param {!Array.} diffs Array of diff tuples. - * @return {string} Delta text. - */ -diff_match_patch.prototype.diff_toDelta = function(diffs) { - var text = []; - for (var x = 0; x < diffs.length; x++) { +} +function diffToDelta(diffs) { + const text = []; + for (let x = 0; x < diffs.length; x++) { switch (diffs[x][0]) { case DIFF_INSERT: - text[x] = '+' + encodeURI(diffs[x][1]); + text[x] = `+${encodeURI(diffs[x][1])}`; break; case DIFF_DELETE: - text[x] = '-' + diffs[x][1].length; + text[x] = `-${diffs[x][1].length}`; break; case DIFF_EQUAL: - text[x] = '=' + diffs[x][1].length; + text[x] = `=${diffs[x][1].length}`; break; } } - return text.join('\t').replace(/%20/g, ' '); -}; - - -/** - * Given the original text1, and an encoded string which describes the - * operations required to transform text1 into text2, compute the full diff. - * @param {string} text1 Source string for the diff. - * @param {string} delta Delta text. - * @return {!Array.} Array of diff tuples. - * @throws {!Error} If invalid input. - */ -diff_match_patch.prototype.diff_fromDelta = function(text1, delta) { - var diffs = []; - var diffsLength = 0; // Keeping our own length var is faster in JS. - var pointer = 0; // Cursor in text1 - var tokens = delta.split(/\t/g); - for (var x = 0; x < tokens.length; x++) { - // Each token begins with a one character parameter which specifies the - // operation of this token (delete, insert, equality). - var param = tokens[x].substring(1); + return text.join(" ").replace(/%20/g, " "); +} +function diffFromDelta(text1, delta) { + const diffs = []; + let diffsLength = 0; + let pointer = 0; + const tokens = delta.split(/\t/g); + for (let x = 0; x < tokens.length; x++) { + const param = tokens[x].substring(1); switch (tokens[x].charAt(0)) { - case '+': + case "+": try { - diffs[diffsLength++] = [DIFF_INSERT, decodeURI(param)]; - } catch (ex) { - // Malformed URI sequence. - throw new Error('Illegal escape in diff_fromDelta: ' + param); + diffs[diffsLength++] = createDiff(DIFF_INSERT, decodeURI(param)); + } catch (e) { + throw new Error(`Illegal escape in diff_fromDelta: ${param}`); } break; - case '-': - // Fall through. - case '=': - var n = parseInt(param, 10); - if (isNaN(n) || n < 0) { - throw new Error('Invalid number in diff_fromDelta: ' + param); - } - var text = text1.substring(pointer, pointer += n); - if (tokens[x].charAt(0) == '=') { - diffs[diffsLength++] = [DIFF_EQUAL, text]; - } else { - diffs[diffsLength++] = [DIFF_DELETE, text]; - } + case "-": + // Fall through. + case "=": { + const n = Number.parseInt(param, 10); + if (Number.isNaN(n) || n < 0) + throw new Error(`Invalid number in diff_fromDelta: ${param}`); + const text = text1.substring(pointer, pointer += n); + if (tokens[x].charAt(0) === "=") + diffs[diffsLength++] = createDiff(DIFF_EQUAL, text); + else + diffs[diffsLength++] = createDiff(DIFF_DELETE, text); break; + } default: - // Blank tokens are ok (from a trailing \t). - // Anything else is an error. - if (tokens[x]) { - throw new Error('Invalid diff operation in diff_fromDelta: ' + - tokens[x]); - } + if (tokens[x]) + throw new Error(`Invalid diff operation in diff_fromDelta: ${tokens[x]}`); } } - if (pointer != text1.length) { - throw new Error('Delta length (' + pointer + - ') does not equal source text length (' + text1.length + ').'); - } + if (pointer !== text1.length) + throw new Error(`Delta length (${pointer}) does not equal source text length (${text1.length}).`); return diffs; -}; - - -// MATCH FUNCTIONS - - -/** - * Locate the best instance of 'pattern' in 'text' near 'loc'. - * @param {string} text The text to search. - * @param {string} pattern The pattern to search for. - * @param {number} loc The location to search around. - * @return {number} Best match index or -1. - */ -diff_match_patch.prototype.match_main = function(text, pattern, loc) { - // Check for null inputs. - if (text == null || pattern == null || loc == null) { - throw new Error('Null input. (match_main)'); - } +} +function matchMain(text, pattern, loc, options) { + if (text == null || pattern == null || loc == null) + throw new Error("Null input. (match_main)"); loc = Math.max(0, Math.min(loc, text.length)); - if (text == pattern) { - // Shortcut (potentially not guaranteed by the algorithm) + if (text === pattern) { return 0; } else if (!text.length) { - // Nothing to match. return -1; - } else if (text.substring(loc, loc + pattern.length) == pattern) { - // Perfect match at the perfect spot! (Includes case of null pattern) + } else if (text.substring(loc, loc + pattern.length) === pattern) { return loc; } else { - // Do a fuzzy compare. - return this.match_bitap_(text, pattern, loc); + return matchBitap(text, pattern, loc, options); } -}; - - -/** - * Locate the best instance of 'pattern' in 'text' near 'loc' using the - * Bitap algorithm. - * @param {string} text The text to search. - * @param {string} pattern The pattern to search for. - * @param {number} loc The location to search around. - * @return {number} Best match index or -1. - * @private - */ -diff_match_patch.prototype.match_bitap_ = function(text, pattern, loc) { - if (pattern.length > this.Match_MaxBits) { - throw new Error('Pattern too long for this browser.'); - } - - // Initialise the alphabet. - var s = this.match_alphabet_(pattern); - - var dmp = this; // 'this' becomes 'window' in a closure. - - /** - * Compute and return the score for a match with e errors and x location. - * Accesses loc and pattern through being a closure. - * @param {number} e Number of errors in match. - * @param {number} x Location of match. - * @return {number} Overall score for match (0.0 = good, 1.0 = bad). - * @private - */ - function match_bitapScore_(e, x) { - var accuracy = e / pattern.length; - var proximity = Math.abs(loc - x); - if (!dmp.Match_Distance) { - // Dodge divide by zero error. - return proximity ? 1.0 : accuracy; +} +function matchBitap(text, pattern, loc, options) { + const resolved = resolveOptions(options); + if (pattern.length > resolved.matchMaxBits) + throw new Error("Pattern too long for this browser."); + const s = matchAlphabet(pattern); + function matchBitapScore(e, x) { + const accuracy = e / pattern.length; + const proximity = Math.abs(loc - x); + if (!resolved.matchDistance) { + return proximity ? 1 : accuracy; } - return accuracy + (proximity / dmp.Match_Distance); + return accuracy + proximity / resolved.matchDistance; } - - // Highest score beyond which we give up. - var score_threshold = this.Match_Threshold; - // Is there a nearby exact match? (speedup) - var best_loc = text.indexOf(pattern, loc); - if (best_loc != -1) { - score_threshold = Math.min(match_bitapScore_(0, best_loc), score_threshold); - // What about in the other direction? (speedup) + let score_threshold = resolved.matchThreshold; + let best_loc = text.indexOf(pattern, loc); + if (best_loc !== -1) { + score_threshold = Math.min(matchBitapScore(0, best_loc), score_threshold); best_loc = text.lastIndexOf(pattern, loc + pattern.length); - if (best_loc != -1) { - score_threshold = - Math.min(match_bitapScore_(0, best_loc), score_threshold); - } + if (best_loc !== -1) + score_threshold = Math.min(matchBitapScore(0, best_loc), score_threshold); } - - // Initialise the bit arrays. - var matchmask = 1 << (pattern.length - 1); + const matchmask = 1 << pattern.length - 1; best_loc = -1; - - var bin_min, bin_mid; - var bin_max = pattern.length + text.length; - var last_rd; - for (var d = 0; d < pattern.length; d++) { - // Scan for the best match; each iteration allows for one more error. - // Run a binary search to determine how far from 'loc' we can stray at this - // error level. + let bin_min, bin_mid; + let bin_max = pattern.length + text.length; + let last_rd = []; + for (let d = 0; d < pattern.length; d++) { bin_min = 0; bin_mid = bin_max; while (bin_min < bin_mid) { - if (match_bitapScore_(d, loc + bin_mid) <= score_threshold) { + if (matchBitapScore(d, loc + bin_mid) <= score_threshold) bin_min = bin_mid; - } else { + else bin_max = bin_mid; - } bin_mid = Math.floor((bin_max - bin_min) / 2 + bin_min); } - // Use the result from this iteration as the maximum for the next. bin_max = bin_mid; - var start = Math.max(1, loc - bin_mid + 1); - var finish = Math.min(loc + bin_mid, text.length) + pattern.length; - - var rd = Array(finish + 2); + let start = Math.max(1, loc - bin_mid + 1); + const finish = Math.min(loc + bin_mid, text.length) + pattern.length; + const rd = new Array(finish + 2); rd[finish + 1] = (1 << d) - 1; - for (var j = finish; j >= start; j--) { - // The alphabet (s) is a sparse hash, so the following line generates - // warnings. - var charMatch = s[text.charAt(j - 1)]; - if (d === 0) { // First pass: exact match. - rd[j] = ((rd[j + 1] << 1) | 1) & charMatch; - } else { // Subsequent passes: fuzzy match. - rd[j] = (((rd[j + 1] << 1) | 1) & charMatch) | - (((last_rd[j + 1] | last_rd[j]) << 1) | 1) | - last_rd[j + 1]; + for (let j = finish; j >= start; j--) { + const charMatch = s[text.charAt(j - 1)]; + if (d === 0) { + rd[j] = (rd[j + 1] << 1 | 1) & charMatch; + } else { + rd[j] = (rd[j + 1] << 1 | 1) & charMatch | ((last_rd[j + 1] | last_rd[j]) << 1 | 1) | last_rd[j + 1]; } if (rd[j] & matchmask) { - var score = match_bitapScore_(d, j - 1); - // This match will almost certainly be better than any existing match. - // But check anyway. + const score = matchBitapScore(d, j - 1); if (score <= score_threshold) { - // Told you so. score_threshold = score; best_loc = j - 1; if (best_loc > loc) { - // When passing loc, don't exceed our current distance from loc. start = Math.max(1, 2 * loc - best_loc); } else { - // Already passed loc, downhill from here on in. break; } } } } - // No hope for a (better) match at greater error levels. - if (match_bitapScore_(d + 1, loc) > score_threshold) { + if (matchBitapScore(d + 1, loc) > score_threshold) break; - } last_rd = rd; } return best_loc; -}; - - -/** - * Initialise the alphabet for the Bitap algorithm. - * @param {string} pattern The text to encode. - * @return {!Object} Hash of character locations. - * @private - */ -diff_match_patch.prototype.match_alphabet_ = function(pattern) { - var s = {}; - for (var i = 0; i < pattern.length; i++) { +} +function matchAlphabet(pattern) { + const s = {}; + for (let i = 0; i < pattern.length; i++) s[pattern.charAt(i)] = 0; - } - for (var i = 0; i < pattern.length; i++) { - s[pattern.charAt(i)] |= 1 << (pattern.length - i - 1); - } + for (let i = 0; i < pattern.length; i++) + s[pattern.charAt(i)] |= 1 << pattern.length - i - 1; return s; +} + +var __defProp = Object.defineProperty; +var __getOwnPropSymbols = Object.getOwnPropertySymbols; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __propIsEnum = Object.prototype.propertyIsEnumerable; +var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; +var __spreadValues = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp.call(b, prop)) + __defNormalProp(a, prop, b[prop]); + if (__getOwnPropSymbols) + for (var prop of __getOwnPropSymbols(b)) { + if (__propIsEnum.call(b, prop)) + __defNormalProp(a, prop, b[prop]); + } + return a; }; - - -// PATCH FUNCTIONS - - -/** - * Increase the context until it is unique, - * but don't let the pattern expand beyond Match_MaxBits. - * @param {!diff_match_patch.patch_obj} patch The patch to grow. - * @param {string} text Source text. - * @private - */ -diff_match_patch.prototype.patch_addContext_ = function(patch, text) { - if (text.length == 0) { +function patchAddContext(patch, text, options) { + if (text.length === 0) return; + if (patch.start2 === null) + throw new Error("patch not initialized"); + const { + matchMaxBits = defaultOptions.matchMaxBits, + patchMargin = defaultOptions.patchMargin + } = options; + let pattern = text.substring(patch.start2, patch.start2 + patch.length1); + let padding = 0; + while (text.indexOf(pattern) !== text.lastIndexOf(pattern) && pattern.length < matchMaxBits - patchMargin - patchMargin) { + padding += patchMargin; + pattern = text.substring(patch.start2 - padding, patch.start2 + patch.length1 + padding); } - var pattern = text.substring(patch.start2, patch.start2 + patch.length1); - var padding = 0; - - // Look for the first and last matches of pattern in text. If two different - // matches are found, increase the pattern length. - while (text.indexOf(pattern) != text.lastIndexOf(pattern) && - pattern.length < this.Match_MaxBits - this.Patch_Margin - - this.Patch_Margin) { - padding += this.Patch_Margin; - pattern = text.substring(patch.start2 - padding, - patch.start2 + patch.length1 + padding); - } - // Add one chunk for good luck. - padding += this.Patch_Margin; - - // Add the prefix. - var prefix = text.substring(patch.start2 - padding, patch.start2); - if (prefix) { - patch.diffs.unshift([DIFF_EQUAL, prefix]); - } - // Add the suffix. - var suffix = text.substring(patch.start2 + patch.length1, - patch.start2 + patch.length1 + padding); - if (suffix) { - patch.diffs.push([DIFF_EQUAL, suffix]); - } - - // Roll back the start points. + padding += patchMargin; + const prefix = text.substring(patch.start2 - padding, patch.start2); + if (prefix) + patch.diffs.unshift(createDiff(DIFF_EQUAL, prefix)); + const suffix = text.substring(patch.start2 + patch.length1, patch.start2 + patch.length1 + padding); + if (suffix) + patch.diffs.push(createDiff(DIFF_EQUAL, suffix)); patch.start1 -= prefix.length; patch.start2 -= prefix.length; - // Extend the lengths. patch.length1 += prefix.length + suffix.length; patch.length2 += prefix.length + suffix.length; -}; - - -/** - * Compute a list of patches to turn text1 into text2. - * Use diffs if provided, otherwise compute it ourselves. - * There are four ways to call this function, depending on what data is - * available to the caller: - * Method 1: - * a = text1, b = text2 - * Method 2: - * a = diffs - * Method 3 (optimal): - * a = text1, b = diffs - * Method 4 (deprecated, use method 3): - * a = text1, b = text2, c = diffs - * - * @param {string|!Array.} a text1 (methods 1,3,4) or - * Array of diff tuples for text1 to text2 (method 2). - * @param {string|!Array.} opt_b text2 (methods 1,4) or - * Array of diff tuples for text1 to text2 (method 3) or undefined (method 2). - * @param {string|!Array.} opt_c Array of diff tuples - * for text1 to text2 (method 4) or undefined (methods 1,2,3). - * @return {!Array.} Array of Patch objects. - */ -diff_match_patch.prototype.patch_make = function(a, opt_b, opt_c) { - var text1, diffs; - if (typeof a == 'string' && typeof opt_b == 'string' && - typeof opt_c == 'undefined') { - // Method 1: text1, text2 - // Compute diffs from text1 and text2. - text1 = /** @type {string} */(a); - diffs = this.diff_main(text1, /** @type {string} */(opt_b), true); +} +function patchMake(a, opt_b, opt_c, options = {}) { + const resolved = __spreadValues(__spreadValues({}, defaultOptions), options); + let text1, diffs; + if (typeof a == "string" && typeof opt_b == "string" && typeof opt_c == "undefined") { + text1 = a; + diffs = diffMain(text1, opt_b, resolved, true); if (diffs.length > 2) { - this.diff_cleanupSemantic(diffs); - this.diff_cleanupEfficiency(diffs); + diffCleanupSemantic(diffs); + diffCleanupEfficiency(diffs); } - } else if (a && typeof a == 'object' && typeof opt_b == 'undefined' && - typeof opt_c == 'undefined') { - // Method 2: diffs - // Compute text1 from diffs. - diffs = /** @type {!Array.} */(a); - text1 = this.diff_text1(diffs); - } else if (typeof a == 'string' && opt_b && typeof opt_b == 'object' && - typeof opt_c == 'undefined') { - // Method 3: text1, diffs - text1 = /** @type {string} */(a); - diffs = /** @type {!Array.} */(opt_b); - } else if (typeof a == 'string' && typeof opt_b == 'string' && - opt_c && typeof opt_c == 'object') { - // Method 4: text1, text2, diffs - // text2 is not used. - text1 = /** @type {string} */(a); - diffs = /** @type {!Array.} */(opt_c); + } else if (a && typeof a == "object" && typeof opt_b == "undefined" && typeof opt_c == "undefined") { + diffs = /** @type {Diff[]} */ + a; + text1 = diffText1(diffs); + } else if (typeof a == "string" && opt_b && typeof opt_b == "object" && typeof opt_c == "undefined") { + text1 = /** @type {string} */ + a; + diffs = /** @type {Diff[]} */ + opt_b; + } else if (typeof a == "string" && typeof opt_b == "string" && opt_c && typeof opt_c == "object") { + text1 = /** @type {string} */ + a; + diffs = /** @type {Diff[]} */ + opt_c; } else { - throw new Error('Unknown call format to patch_make.'); + throw new Error("Unknown call format to patch_make."); } - - if (diffs.length === 0) { - return []; // Get rid of the null case. - } - var patches = []; - var patch = new diff_match_patch.patch_obj(); - var patchDiffLength = 0; // Keeping our own length var is faster in JS. - var char_count1 = 0; // Number of characters into the text1 string. - var char_count2 = 0; // Number of characters into the text2 string. - // Start with text1 (prepatch_text) and apply the diffs until we arrive at - // text2 (postpatch_text). We recreate the patches one by one to determine - // context info. - var prepatch_text = text1; - var postpatch_text = text1; - for (var x = 0; x < diffs.length; x++) { - var diff_type = diffs[x][0]; - var diff_text = diffs[x][1]; - + if (diffs.length === 0) + return []; + const patches = []; + let patch = createPatch(); + let patchDiffLength = 0; + let char_count1 = 0; + let char_count2 = 0; + let prepatch_text = text1; + let postpatch_text = text1; + for (let x = 0; x < diffs.length; x++) { + const diff_type = diffs[x][0]; + const diff_text = diffs[x][1]; if (!patchDiffLength && diff_type !== DIFF_EQUAL) { - // A new patch starts here. patch.start1 = char_count1; patch.start2 = char_count2; } - switch (diff_type) { case DIFF_INSERT: patch.diffs[patchDiffLength++] = diffs[x]; patch.length2 += diff_text.length; - postpatch_text = postpatch_text.substring(0, char_count2) + diff_text + - postpatch_text.substring(char_count2); + postpatch_text = postpatch_text.substring(0, char_count2) + diff_text + postpatch_text.substring(char_count2); break; case DIFF_DELETE: patch.length1 += diff_text.length; patch.diffs[patchDiffLength++] = diffs[x]; - postpatch_text = postpatch_text.substring(0, char_count2) + - postpatch_text.substring(char_count2 + - diff_text.length); + postpatch_text = postpatch_text.substring(0, char_count2) + postpatch_text.substring(char_count2 + diff_text.length); break; case DIFF_EQUAL: - if (diff_text.length <= 2 * this.Patch_Margin && - patchDiffLength && diffs.length != x + 1) { - // Small equality inside a patch. + if (diff_text.length <= 2 * resolved.patchMargin && patchDiffLength && diffs.length !== x + 1) { patch.diffs[patchDiffLength++] = diffs[x]; patch.length1 += diff_text.length; patch.length2 += diff_text.length; - } else if (diff_text.length >= 2 * this.Patch_Margin) { - // Time for a new patch. + } else if (diff_text.length >= 2 * resolved.patchMargin) { if (patchDiffLength) { - this.patch_addContext_(patch, prepatch_text); + patchAddContext(patch, prepatch_text, resolved); patches.push(patch); - patch = new diff_match_patch.patch_obj(); + patch = createPatch(); patchDiffLength = 0; - // Unlike Unidiff, our patch lists have a rolling context. - // http://code.google.com/p/google-diff-match-patch/wiki/Unidiff - // Update prepatch text & pos to reflect the application of the - // just completed patch. prepatch_text = postpatch_text; char_count1 = char_count2; } } break; } - - // Update the current character count. - if (diff_type !== DIFF_INSERT) { + if (diff_type !== DIFF_INSERT) char_count1 += diff_text.length; - } - if (diff_type !== DIFF_DELETE) { + if (diff_type !== DIFF_DELETE) char_count2 += diff_text.length; - } } - // Pick up the leftover patch if not empty. if (patchDiffLength) { - this.patch_addContext_(patch, prepatch_text); + patchAddContext(patch, prepatch_text, resolved); patches.push(patch); } - return patches; -}; - - -/** - * Given an array of patches, return another array that is identical. - * @param {!Array.} patches Array of Patch objects. - * @return {!Array.} Array of Patch objects. - */ -diff_match_patch.prototype.patch_deepCopy = function(patches) { - // Making deep copies is hard in JavaScript. - var patchesCopy = []; - for (var x = 0; x < patches.length; x++) { - var patch = patches[x]; - var patchCopy = new diff_match_patch.patch_obj(); +} +function patchDeepCopy(patches) { + const patchesCopy = []; + for (let x = 0; x < patches.length; x++) { + const patch = patches[x]; + const patchCopy = createPatch(); patchCopy.diffs = []; - for (var y = 0; y < patch.diffs.length; y++) { - patchCopy.diffs[y] = patch.diffs[y].slice(); + for (let y = 0; y < patch.diffs.length; y++) { + patchCopy.diffs[y] = createDiff(patch.diffs[y][0], patch.diffs[y][1]); } patchCopy.start1 = patch.start1; patchCopy.start2 = patch.start2; @@ -1767,227 +1072,159 @@ diff_match_patch.prototype.patch_deepCopy = function(patches) { patchesCopy[x] = patchCopy; } return patchesCopy; -}; - - -/** - * Merge a set of patches onto the text. Return a patched text, as well - * as a list of true/false values indicating which patches were applied. - * @param {!Array.} patches Array of Patch objects. - * @param {string} text Old text. - * @return {!Array.>} Two element Array, containing the - * new text and an array of boolean values. - */ -diff_match_patch.prototype.patch_apply = function(patches, text) { - if (patches.length == 0) { +} +function patchApply(patches, text, options) { + if (patches.length === 0) return [text, []]; - } - - // Deep copy the patches so that no changes are made to originals. - patches = this.patch_deepCopy(patches); - - var nullPadding = this.patch_addPadding(patches); + patches = patchDeepCopy(patches); + const resolved = resolveOptions(options); + const nullPadding = patchAddPadding(patches, resolved); text = nullPadding + text + nullPadding; - - this.patch_splitMax(patches); - // delta keeps track of the offset between the expected and actual location - // of the previous patch. If there are patches expected at positions 10 and - // 20, but the first patch was found at 12, delta is 2 and the second patch - // has an effective expected position of 22. - var delta = 0; - var results = []; - for (var x = 0; x < patches.length; x++) { - var expected_loc = patches[x].start2 + delta; - var text1 = this.diff_text1(patches[x].diffs); - var start_loc; - var end_loc = -1; - if (text1.length > this.Match_MaxBits) { - // patch_splitMax will only provide an oversized pattern in the case of - // a monster delete. - start_loc = this.match_main(text, text1.substring(0, this.Match_MaxBits), - expected_loc); - if (start_loc != -1) { - end_loc = this.match_main(text, - text1.substring(text1.length - this.Match_MaxBits), - expected_loc + text1.length - this.Match_MaxBits); - if (end_loc == -1 || start_loc >= end_loc) { - // Can't find valid trailing context. Drop this patch. + patchSplitMax(patches, resolved); + let delta = 0; + const results = []; + for (let x = 0; x < patches.length; x++) { + const expected_loc = patches[x].start2 + delta; + const text1 = diffText1(patches[x].diffs); + let start_loc; + let end_loc = -1; + if (text1.length > resolved.matchMaxBits) { + start_loc = matchMain( + text, + text1.substring(0, resolved.matchMaxBits), + expected_loc, + options + ); + if (start_loc !== -1) { + end_loc = matchMain( + text, + text1.substring(text1.length - resolved.matchMaxBits), + expected_loc + text1.length - resolved.matchMaxBits, + options + ); + if (end_loc === -1 || start_loc >= end_loc) { start_loc = -1; } } } else { - start_loc = this.match_main(text, text1, expected_loc); + start_loc = matchMain(text, text1, expected_loc, options); } - if (start_loc == -1) { - // No match found. :( + if (start_loc === -1) { results[x] = false; - // Subtract the delta for this failed patch from subsequent patches. delta -= patches[x].length2 - patches[x].length1; } else { - // Found a match. :) results[x] = true; delta = start_loc - expected_loc; - var text2; - if (end_loc == -1) { + let text2; + if (end_loc === -1) text2 = text.substring(start_loc, start_loc + text1.length); + else + text2 = text.substring(start_loc, end_loc + resolved.matchMaxBits); + if (text1 === text2) { + text = text.substring(0, start_loc) + diffText2(patches[x].diffs) + text.substring(start_loc + text1.length); } else { - text2 = text.substring(start_loc, end_loc + this.Match_MaxBits); - } - if (text1 == text2) { - // Perfect match, just shove the replacement text in. - text = text.substring(0, start_loc) + - this.diff_text2(patches[x].diffs) + - text.substring(start_loc + text1.length); - } else { - // Imperfect match. Run a diff to get a framework of equivalent - // indices. - var diffs = this.diff_main(text1, text2, false); - if (text1.length > this.Match_MaxBits && - this.diff_levenshtein(diffs) / text1.length > - this.Patch_DeleteThreshold) { - // The end points match, but the content is unacceptably bad. + const diffs = diffMain(text1, text2, options, false); + if (text1.length > resolved.matchMaxBits && diffLevenshtein(diffs) / text1.length > resolved.patchDeleteThreshold) { results[x] = false; } else { - this.diff_cleanupSemanticLossless(diffs); - var index1 = 0; - var index2; - for (var y = 0; y < patches[x].diffs.length; y++) { - var mod = patches[x].diffs[y]; - if (mod[0] !== DIFF_EQUAL) { - index2 = this.diff_xIndex(diffs, index1); + diffCleanupSemanticLossless(diffs); + let index1 = 0; + let index2 = 0; + for (let y = 0; y < patches[x].diffs.length; y++) { + const mod = patches[x].diffs[y]; + if (mod[0] !== DIFF_EQUAL) + index2 = diffXIndex(diffs, index1); + if (mod[0] === DIFF_INSERT) { + text = text.substring(0, start_loc + index2) + mod[1] + text.substring(start_loc + index2); + } else if (mod[0] === DIFF_DELETE) { + text = text.substring(0, start_loc + index2) + text.substring(start_loc + diffXIndex(diffs, index1 + mod[1].length)); } - if (mod[0] === DIFF_INSERT) { // Insertion - text = text.substring(0, start_loc + index2) + mod[1] + - text.substring(start_loc + index2); - } else if (mod[0] === DIFF_DELETE) { // Deletion - text = text.substring(0, start_loc + index2) + - text.substring(start_loc + this.diff_xIndex(diffs, - index1 + mod[1].length)); - } - if (mod[0] !== DIFF_DELETE) { + if (mod[0] !== DIFF_DELETE) index1 += mod[1].length; - } } } } } } - // Strip the padding off. text = text.substring(nullPadding.length, text.length - nullPadding.length); return [text, results]; -}; - - -/** - * Add some padding on text start and end so that edges can match something. - * Intended to be called only from within patch_apply. - * @param {!Array.} patches Array of Patch objects. - * @return {string} The padding string added to each side. - */ -diff_match_patch.prototype.patch_addPadding = function(patches) { - var paddingLength = this.Patch_Margin; - var nullPadding = ''; - for (var x = 1; x <= paddingLength; x++) { +} +function patchAddPadding(patches, options = {}) { + const { + patchMargin: paddingLength = defaultOptions.patchMargin + } = options; + let nullPadding = ""; + for (let x = 1; x <= paddingLength; x++) nullPadding += String.fromCharCode(x); - } - - // Bump all the patches forward. - for (var x = 0; x < patches.length; x++) { + for (let x = 0; x < patches.length; x++) { patches[x].start1 += paddingLength; patches[x].start2 += paddingLength; } - - // Add some padding on start of first diff. - var patch = patches[0]; - var diffs = patch.diffs; - if (diffs.length == 0 || diffs[0][0] != DIFF_EQUAL) { - // Add nullPadding equality. - diffs.unshift([DIFF_EQUAL, nullPadding]); - patch.start1 -= paddingLength; // Should be 0. - patch.start2 -= paddingLength; // Should be 0. + let patch = patches[0]; + let diffs = patch.diffs; + if (diffs.length === 0 || diffs[0][0] !== DIFF_EQUAL) { + diffs.unshift(createDiff(DIFF_EQUAL, nullPadding)); + patch.start1 -= paddingLength; + patch.start2 -= paddingLength; patch.length1 += paddingLength; patch.length2 += paddingLength; } else if (paddingLength > diffs[0][1].length) { - // Grow first equality. - var extraLength = paddingLength - diffs[0][1].length; + const extraLength = paddingLength - diffs[0][1].length; diffs[0][1] = nullPadding.substring(diffs[0][1].length) + diffs[0][1]; patch.start1 -= extraLength; patch.start2 -= extraLength; patch.length1 += extraLength; patch.length2 += extraLength; } - - // Add some padding on end of last diff. patch = patches[patches.length - 1]; diffs = patch.diffs; - if (diffs.length == 0 || diffs[diffs.length - 1][0] != DIFF_EQUAL) { - // Add nullPadding equality. - diffs.push([DIFF_EQUAL, nullPadding]); + if (diffs.length === 0 || diffs[diffs.length - 1][0] !== DIFF_EQUAL) { + diffs.push(createDiff(DIFF_EQUAL, nullPadding)); patch.length1 += paddingLength; patch.length2 += paddingLength; } else if (paddingLength > diffs[diffs.length - 1][1].length) { - // Grow last equality. - var extraLength = paddingLength - diffs[diffs.length - 1][1].length; + const extraLength = paddingLength - diffs[diffs.length - 1][1].length; diffs[diffs.length - 1][1] += nullPadding.substring(0, extraLength); patch.length1 += extraLength; patch.length2 += extraLength; } - return nullPadding; -}; - - -/** - * Look through the patches and break up any which are longer than the maximum - * limit of the match algorithm. - * Intended to be called only from within patch_apply. - * @param {!Array.} patches Array of Patch objects. - */ -diff_match_patch.prototype.patch_splitMax = function(patches) { - var patch_size = this.Match_MaxBits; - for (var x = 0; x < patches.length; x++) { - if (patches[x].length1 <= patch_size) { +} +function patchSplitMax(patches, options) { + const resolved = resolveOptions(options); + for (let x = 0; x < patches.length; x++) { + if (patches[x].length1 <= resolved.matchMaxBits) continue; - } - var bigpatch = patches[x]; - // Remove the big old patch. + const bigpatch = patches[x]; patches.splice(x--, 1); - var start1 = bigpatch.start1; - var start2 = bigpatch.start2; - var precontext = ''; + let start1 = bigpatch.start1; + let start2 = bigpatch.start2; + let precontext = ""; while (bigpatch.diffs.length !== 0) { - // Create one of several smaller patches. - var patch = new diff_match_patch.patch_obj(); - var empty = true; + const patch = createPatch(); + let empty = true; patch.start1 = start1 - precontext.length; patch.start2 = start2 - precontext.length; - if (precontext !== '') { + if (precontext !== "") { patch.length1 = patch.length2 = precontext.length; - patch.diffs.push([DIFF_EQUAL, precontext]); + patch.diffs.push(createDiff(DIFF_EQUAL, precontext)); } - while (bigpatch.diffs.length !== 0 && - patch.length1 < patch_size - this.Patch_Margin) { - var diff_type = bigpatch.diffs[0][0]; - var diff_text = bigpatch.diffs[0][1]; + while (bigpatch.diffs.length !== 0 && patch.length1 < resolved.matchMaxBits - resolved.patchMargin) { + const diff_type = bigpatch.diffs[0][0]; + let diff_text = bigpatch.diffs[0][1]; if (diff_type === DIFF_INSERT) { - // Insertions are harmless. patch.length2 += diff_text.length; start2 += diff_text.length; patch.diffs.push(bigpatch.diffs.shift()); empty = false; - } else if (diff_type === DIFF_DELETE && patch.diffs.length == 1 && - patch.diffs[0][0] == DIFF_EQUAL && - diff_text.length > 2 * patch_size) { - // This is a large deletion. Let it pass in one chunk. + } else if (diff_type === DIFF_DELETE && patch.diffs.length === 1 && patch.diffs[0][0] === DIFF_EQUAL && diff_text.length > 2 * resolved.matchMaxBits) { patch.length1 += diff_text.length; start1 += diff_text.length; empty = false; - patch.diffs.push([diff_type, diff_text]); + patch.diffs.push(createDiff(diff_type, diff_text)); bigpatch.diffs.shift(); } else { - // Deletion or equality. Only take as much as we can stomach. - diff_text = diff_text.substring(0, - patch_size - patch.length1 - this.Patch_Margin); + diff_text = diff_text.substring(0, resolved.matchMaxBits - patch.length1 - resolved.patchMargin); patch.length1 += diff_text.length; start1 += diff_text.length; if (diff_type === DIFF_EQUAL) { @@ -1996,197 +1233,171 @@ diff_match_patch.prototype.patch_splitMax = function(patches) { } else { empty = false; } - patch.diffs.push([diff_type, diff_text]); - if (diff_text == bigpatch.diffs[0][1]) { + patch.diffs.push(createDiff(diff_type, diff_text)); + if (diff_text === bigpatch.diffs[0][1]) { bigpatch.diffs.shift(); } else { - bigpatch.diffs[0][1] = - bigpatch.diffs[0][1].substring(diff_text.length); + bigpatch.diffs[0][1] = bigpatch.diffs[0][1].substring(diff_text.length); } } } - // Compute the head context for the next patch. - precontext = this.diff_text2(patch.diffs); - precontext = - precontext.substring(precontext.length - this.Patch_Margin); - // Append the end context for this patch. - var postcontext = this.diff_text1(bigpatch.diffs) - .substring(0, this.Patch_Margin); - if (postcontext !== '') { + precontext = diffText2(patch.diffs); + precontext = precontext.substring(precontext.length - resolved.patchMargin); + const postcontext = diffText1(bigpatch.diffs).substring(0, resolved.patchMargin); + if (postcontext !== "") { patch.length1 += postcontext.length; patch.length2 += postcontext.length; - if (patch.diffs.length !== 0 && - patch.diffs[patch.diffs.length - 1][0] === DIFF_EQUAL) { + if (patch.diffs.length !== 0 && patch.diffs[patch.diffs.length - 1][0] === DIFF_EQUAL) { patch.diffs[patch.diffs.length - 1][1] += postcontext; } else { - patch.diffs.push([DIFF_EQUAL, postcontext]); + patch.diffs.push(createDiff(DIFF_EQUAL, postcontext)); } } - if (!empty) { + if (!empty) patches.splice(++x, 0, patch); - } } } -}; - - -/** - * Take a list of patches and return a textual representation. - * @param {!Array.} patches Array of Patch objects. - * @return {string} Text representation of patches. - */ -diff_match_patch.prototype.patch_toText = function(patches) { - var text = []; - for (var x = 0; x < patches.length; x++) { +} +function patchToText(patches) { + const text = []; + for (let x = 0; x < patches.length; x++) text[x] = patches[x]; - } - return text.join(''); -}; - - -/** - * Parse a textual representation of patches and return a list of Patch objects. - * @param {string} textline Text representation of patches. - * @return {!Array.} Array of Patch objects. - * @throws {!Error} If invalid input. - */ -diff_match_patch.prototype.patch_fromText = function(textline) { - var patches = []; - if (!textline) { + return text.join(""); +} +function patchFromText(textline) { + const patches = []; + if (!textline) return patches; - } - var text = textline.split('\n'); - var textPointer = 0; - var patchHeader = /^@@ -(\d+),?(\d*) \+(\d+),?(\d*) @@$/; + const text = textline.split("\n"); + let textPointer = 0; + const patchHeader = /^@@ -(\d+),?(\d*) \+(\d+),?(\d*) @@$/; while (textPointer < text.length) { - var m = text[textPointer].match(patchHeader); - if (!m) { - throw new Error('Invalid patch string: ' + text[textPointer]); - } - var patch = new diff_match_patch.patch_obj(); + const m = text[textPointer].match(patchHeader); + if (!m) + throw new Error(`Invalid patch string: ${text[textPointer]}`); + const patch = createPatch(); patches.push(patch); - patch.start1 = parseInt(m[1], 10); - if (m[2] === '') { + patch.start1 = Number.parseInt(m[1], 10); + if (m[2] === "") { patch.start1--; patch.length1 = 1; - } else if (m[2] == '0') { + } else if (m[2] === "0") { patch.length1 = 0; } else { patch.start1--; - patch.length1 = parseInt(m[2], 10); + patch.length1 = Number.parseInt(m[2], 10); } - - patch.start2 = parseInt(m[3], 10); - if (m[4] === '') { + patch.start2 = Number.parseInt(m[3], 10); + if (m[4] === "") { patch.start2--; patch.length2 = 1; - } else if (m[4] == '0') { + } else if (m[4] === "0") { patch.length2 = 0; } else { patch.start2--; - patch.length2 = parseInt(m[4], 10); + patch.length2 = Number.parseInt(m[4], 10); } textPointer++; - while (textPointer < text.length) { - var sign = text[textPointer].charAt(0); + const sign = text[textPointer].charAt(0); + let line = ""; try { - var line = decodeURI(text[textPointer].substring(1)); - } catch (ex) { - // Malformed URI sequence. - throw new Error('Illegal escape in patch_fromText: ' + line); + line = decodeURI(text[textPointer].substring(1)); + } catch (e) { + throw new Error(`Illegal escape in patch_fromText: ${line}`); } - if (sign == '-') { - // Deletion. - patch.diffs.push([DIFF_DELETE, line]); - } else if (sign == '+') { - // Insertion. - patch.diffs.push([DIFF_INSERT, line]); - } else if (sign == ' ') { - // Minor equality. - patch.diffs.push([DIFF_EQUAL, line]); - } else if (sign == '@') { - // Start of next patch. + if (sign === "-") { + patch.diffs.push(createDiff(DIFF_DELETE, line)); + } else if (sign === "+") { + patch.diffs.push(createDiff(DIFF_INSERT, line)); + } else if (sign === " ") { + patch.diffs.push(createDiff(DIFF_EQUAL, line)); + } else if (sign === "@") { break; - } else if (sign === '') { - // Blank line? Whatever. - } else { - // WTF? - throw new Error('Invalid patch mode "' + sign + '" in: ' + line); + } else if (sign === "") ; else { + throw new Error(`Invalid patch mode "${sign}" in: ${line}`); } textPointer++; } } return patches; -}; - - -/** - * Class representing one patch operation. - * @constructor - */ -diff_match_patch.patch_obj = function() { - /** @type {!Array.} */ - this.diffs = []; - /** @type {?number} */ - this.start1 = null; - /** @type {?number} */ - this.start2 = null; - /** @type {number} */ - this.length1 = 0; - /** @type {number} */ - this.length2 = 0; -}; - - -/** - * Emmulate GNU diff's format. - * Header: @@ -382,8 +481,9 @@ - * Indicies are printed as 1-based, not 0-based. - * @return {string} The GNU diff string. - */ -diff_match_patch.patch_obj.prototype.toString = function() { - var coords1, coords2; - if (this.length1 === 0) { - coords1 = this.start1 + ',0'; - } else if (this.length1 == 1) { - coords1 = this.start1 + 1; - } else { - coords1 = (this.start1 + 1) + ',' + this.length1; - } - if (this.length2 === 0) { - coords2 = this.start2 + ',0'; - } else if (this.length2 == 1) { - coords2 = this.start2 + 1; - } else { - coords2 = (this.start2 + 1) + ',' + this.length2; - } - var text = ['@@ -' + coords1 + ' +' + coords2 + ' @@\n']; - var op; - // Escape the body of the patch with %xx notation. - for (var x = 0; x < this.diffs.length; x++) { - switch (this.diffs[x][0]) { - case DIFF_INSERT: - op = '+'; - break; - case DIFF_DELETE: - op = '-'; - break; - case DIFF_EQUAL: - op = ' '; - break; +} +function createPatch() { + const patch = { + diffs: [], + start1: null, + start2: null, + length1: 0, + length2: 0 + }; + patch.toString = function() { + let coords1, coords2; + if (this.length1 === 0) + coords1 = `${this.start1},0`; + else if (this.length1 === 1) + coords1 = this.start1 + 1; + else + coords1 = `${this.start1 + 1},${this.length1}`; + if (this.length2 === 0) + coords2 = `${this.start2},0`; + else if (this.length2 === 1) + coords2 = this.start2 + 1; + else + coords2 = `${this.start2 + 1},${this.length2}`; + const text = [`@@ -${coords1} +${coords2} @@ +`]; + let op; + for (let x = 0; x < this.diffs.length; x++) { + switch (this.diffs[x][0]) { + case DIFF_INSERT: + op = "+"; + break; + case DIFF_DELETE: + op = "-"; + break; + case DIFF_EQUAL: + op = " "; + break; + } + text[x + 1] = `${op + encodeURI(this.diffs[x][1])} +`; } - text[x + 1] = op + encodeURI(this.diffs[x][1]) + '\n'; - } - return text.join('').replace(/%20/g, ' '); -}; + return text.join("").replace(/%20/g, " "); + }; + return patch; +} - -// Export these global variables so that they survive Google's JS compiler. -// In a browser, 'this' will be 'window'. -// Users of node.js should 'require' the uncompressed version since Google's -// JS compiler may break the following exports for non-browser environments. -this['diff_match_patch'] = diff_match_patch; -this['DIFF_DELETE'] = DIFF_DELETE; -this['DIFF_INSERT'] = DIFF_INSERT; -this['DIFF_EQUAL'] = DIFF_EQUAL; +exports.DIFF_DELETE = DIFF_DELETE; +exports.DIFF_EQUAL = DIFF_EQUAL; +exports.DIFF_INSERT = DIFF_INSERT; +exports.defaultOptions = defaultOptions; +exports.diff = diffMain; +exports.diffCharsToLines = diffCharsToLines; +exports.diffCleanupEfficiency = diffCleanupEfficiency; +exports.diffCleanupMerge = diffCleanupMerge; +exports.diffCleanupSemantic = diffCleanupSemantic; +exports.diffCleanupSemanticLossless = diffCleanupSemanticLossless; +exports.diffCommonPrefix = diffCommonPrefix; +exports.diffCommonSuffix = diffCommonSuffix; +exports.diffFromDelta = diffFromDelta; +exports.diffLevenshtein = diffLevenshtein; +exports.diffLinesToChars = diffLinesToChars; +exports.diffMain = diffMain; +exports.diffPrettyHtml = diffPrettyHtml; +exports.diffText1 = diffText1; +exports.diffText2 = diffText2; +exports.diffToDelta = diffToDelta; +exports.diffXIndex = diffXIndex; +exports.match = matchMain; +exports.matchAlphabet = matchAlphabet; +exports.matchBitap = matchBitap; +exports.matchMain = matchMain; +exports.patch = patchMake; +exports.patchAddPadding = patchAddPadding; +exports.patchApply = patchApply; +exports.patchDeepCopy = patchDeepCopy; +exports.patchFromText = patchFromText; +exports.patchMake = patchMake; +exports.patchSplitMax = patchSplitMax; +exports.patchToText = patchToText; +exports.resolveOptions = resolveOptions; diff --git a/core/modules/widgets/diff-text.js b/core/modules/widgets/diff-text.js index 88175b17a..b9786aa25 100644 --- a/core/modules/widgets/diff-text.js +++ b/core/modules/widgets/diff-text.js @@ -9,8 +9,8 @@ Widget to display a diff between two texts "use strict"; -var Widget = require("$:/core/modules/widgets/widget.js").widget, - dmp = require("$:/core/modules/utils/diff-match-patch/diff_match_patch.js"); +var Widget = require("$:/core/modules/widgets/widget.js").widget; +const dmp = require("$:/core/modules/utils/diff-match-patch/diff_match_patch.js"); var DiffTextWidget = function(parseTreeNode,options) { this.initialise(parseTreeNode,options); @@ -35,19 +35,18 @@ DiffTextWidget.prototype.render = function(parent,nextSibling) { this.computeAttributes(); this.execute(); // Create the diff object - var dmpObject = new dmp.diff_match_patch(); - dmpObject.Diff_EditCost = $tw.utils.parseNumber(this.getAttribute("editcost","4")); - var diffs = dmpObject.diff_main(this.getAttribute("source",""),this.getAttribute("dest","")); + const editCost = $tw.utils.parseNumber(this.getAttribute("editcost","4")); + const diffs = dmp.diffMain(this.getAttribute("source",""),this.getAttribute("dest",""),{diffEditCost: editCost}); // Apply required cleanup switch(this.getAttribute("cleanup","semantic")) { case "none": // No cleanup break; case "efficiency": - dmpObject.diff_cleanupEfficiency(diffs); + dmp.diffCleanupEfficiency(diffs, {diffEditCost: editCost}); break; default: // case "semantic" - dmpObject.diff_cleanupSemantic(diffs); + dmp.diffCleanupSemantic(diffs); break; } // Create the elements diff --git a/editions/tw5.com/tiddlers/releasenotes/5.4.0/migrate-to-dmp-es - api.tid b/editions/tw5.com/tiddlers/releasenotes/5.4.0/migrate-to-dmp-es - api.tid new file mode 100644 index 000000000..c7d6c38a4 --- /dev/null +++ b/editions/tw5.com/tiddlers/releasenotes/5.4.0/migrate-to-dmp-es - api.tid @@ -0,0 +1,11 @@ +title: $:/changenotes/5.4.0/#9511/impacts/api +changenote: $:/changenotes/5.4.0/#9511 +created: 20251220010540143 +modified: 20251220010540143 +tags: $:/tags/ImpactNote +description: The diff-match-patch-es library uses different APIs +impact-type: compatibility-break + +* Default export and the class constructor has been removed +* Function name has been unified to camelCase +* Previous options like Diff_Timeout and Diff_EditCost are now passed as an options object in the arguments if needed \ No newline at end of file diff --git a/editions/tw5.com/tiddlers/releasenotes/5.4.0/migrate-to-dmp-es.tid b/editions/tw5.com/tiddlers/releasenotes/5.4.0/migrate-to-dmp-es.tid new file mode 100644 index 000000000..a73267300 --- /dev/null +++ b/editions/tw5.com/tiddlers/releasenotes/5.4.0/migrate-to-dmp-es.tid @@ -0,0 +1,10 @@ +title: $:/changenotes/5.4.0/#9511 +description: Migrate diff-match-patch library to diff-match-patch-es +release: 5.4.0 +tags: $:/tags/ChangeNote +change-type: enhancement +change-category: developer +github-links: https://github.com/TiddlyWiki/TiddlyWiki5/pull/9511 +github-contributors: Leilei332 + +Migrate the unmaintained [[diff-match-patch|https://github.com/google/diff-match-patch]] library to the maintained and modern fork [[diff-match-patch-es|https://github.com/antfu/diff-match-patch-es]].