UNPKG

@fishan/myers-core-diff

Version:

A high-performance core diff engine based on Myers' algorithm, with plugin support for custom strategies (e.g., Patience, Preserve Structure).

8 lines (7 loc) 12.6 kB
/** * @license * Copyright (c) 2025, Internal Implementation * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */const j=!1;var Q=(o=>(o[o.EQUAL=0]="EQUAL",o[o.ADD=1]="ADD",o[o.REMOVE=2]="REMOVE",o))(Q||{});class X{P=31;M=1000000009;P_POW;currentHash=0;tokens;length;constructor(e,s){this.tokens=e,this.length=s,this.P_POW=this._power(this.P,this.length-1),this.currentHash=this._calculateInitialHash()}getHash(){return this.currentHash}slide(e,s){const o=this.M,l=this.P,c=this.P_POW;let t=this.currentHash-e*c%o;t<0&&(t+=o),t=t*l%o,t+=s,t>=o&&(t-=o),this.currentHash=t}_power(e,s){const o=this.M;let l=1;for(e%=o;s>0;)s&1&&(l=l*e%o),e=e*e%o,s>>=1;return l}_calculateInitialHash(){const e=this.M,s=this.P,o=this.tokens,l=this.length;let c=0;for(let t=0;t<l;t++)c=(c*s+o[t])%e;return c}}class G{static strategyRegistry=new Map;static isDefaultRegistered=!1;static defaultOptions={diffStrategyName:"commonSES",minMatchLength:30,quickDiffThreshold:64,hugeDiffThreshold:256,lookahead:10,corridorWidth:10,skipTrimming:!1,jumpStep:30,huntChunkSize:10,minAnchorConfidence:.8,useAnchors:!0,localgap:10,preservePositions:!0,localLookahead:50,anchorSearchMode:"combo",positionalAnchorMaxDrift:20};static ensureDefaultStrategyRegistered(e){G.isDefaultRegistered||(G.registerStrategy("commonSES",e._strategycommonSES.bind(e)),G.isDefaultRegistered=!0)}static registerStrategy(e,s){G.strategyRegistry.set(e,s)}constructor(){G.ensureDefaultStrategyRegistered(this)}diff(e,s,o=!1,l){const c={...G.defaultOptions,...l},{hashedOld:t,hashedNew:r,idToString:_}=this._tokenize(e,s,o);let d=[],a=[],i=0,n=t.length,f=0,p=r.length;if(!c.skipTrimming){const u=this._trimCommonPrefixSuffix(t,0,t.length,r,0,r.length,_);d=u.prefix,a=u.suffix,i=u.newOldStart,n=u.newOldEnd,f=u.newNewStart,p=u.newNewEnd}const m=c.diffStrategyName,g=G.strategyRegistry.get(m);if(!g)throw new Error(`[MyersCoreDiff] Strategy '${m}' is not registered.`);const D=g(this,t,i,n,r,f,p,_,c,o);return d.concat(D).concat(a)}_strategycommonSES(e,s,o,l,c,t,r,_,d,a){const i=l-o+(r-t);let n=[];if(d.useAnchors&&i>d.quickDiffThreshold){const p=this._findAnchors(s,o,l,c,t,r,d,a);n=this._mergeAndFilterAnchors(p,d,a),n.length}let f=[];return n.length>0?f=this._processWithAnchors(s,o,l,c,t,r,n,_,d,a,0):d.preservePositions?f=this._calculateStableDiff(s,o,l,c,t,r,_,d,a):f=this._recursiveDiff(s,o,l,c,t,r,_,d,a),f}_findAnchors(e,s,o,l,c,t,r,_){const d=r.anchorSearchMode??"combo",a=r.positionalAnchorMaxDrift,{jumpStep:i,huntChunkSize:n,minMatchLength:f,minAnchorConfidence:p}=r,m=o-s,g=t-c;if(m+g<r.quickDiffThreshold)return[];if(n<=0||f<n)return[];const D=[],u=new Uint8Array(l.length),h=new Map,b=new X(new Uint32Array(0),0),y=t-c;if(y>=n)for(let E=0;E<=y-n;E+=1){const $=c+E,A=l.subarray($,$+n);let x=0;for(let L=0;L<A.length;L++)x=(x*b.P+A[L])%b.M;h.has(x)||h.set(x,[]),h.get(x).push({pos:$})}if(h.size!==0)for(let E=0;E<=m-n;E+=i){const $=s+E,A=e.subarray($,$+n);let x=0;for(let N=0;N<A.length;N++)x=(x*b.P+A[N])%b.M;const L=h.get(x);if(L)for(const N of L){if(u[N.pos])continue;const V=[{oldPos:$,newPos:N.pos}];let P=$+n;const C=10;let z=1;for(let M=1;M*n<f;M++){let O=!1;const I=V[V.length-1];for(let v=0;v<C;v++){const S=P+v*i;if(S+n>o)break;const q=e.subarray(S,S+n);let F=0;for(let U=0;U<q.length;U++)F=(F*b.P+q[U])%b.M;const k=h.get(F);if(k){for(const U of k)if(U.pos>I.newPos&&!u[U.pos]){V.push({oldPos:S,newPos:U.pos}),P=S+n,O=!0,z++;break}}if(O)break}if(!O)break}if(z*n/f>=p){const M=V[0];let O=0;const I=M.oldPos,v=M.newPos;for(;I+O<o&&v+O<t&&!u[v+O]&&e[I+O]===l[v+O];)O++;if(O>=f){const S=Math.abs(v-I),q=O>0?S/O:0,F=Math.max(100,Math.min(m,g)*.1),k=Math.max(0,1-S/F),U=Math.min(1,O/(f*2)),W=k*.3+U*.7,K={oldPos:I,newPos:v,length:O,confidence:W,driftDistance:S,driftRatio:q};D.push(K);for(let H=0;H<O;H++)v+H<l.length&&(u[v+H]=1);E=I-s+O-i;break}}}}let w;return d==="positional"?w=D.filter(E=>E.driftDistance<=a):d==="floating"?w=D.filter(E=>E.driftDistance>a):w=D,w.filter(E=>E.confidence>=p)}_mergeAndFilterAnchors(e,s,o){if(e.length===0)return[];e.sort((a,i)=>a.oldPos-i.oldPos);const l=e.length,c=new Array(l).fill(0),t=new Array(l).fill(-1);for(let a=0;a<l;a++){const i=e[a];c[a]=i.length;for(let n=0;n<a;n++){const f=e[n];if(i.oldPos>=f.oldPos+f.length&&i.newPos>=f.newPos+f.length){const m=i.oldPos>f.oldPos,g=i.newPos>f.newPos;(m||g)&&c[n]+i.length>c[a]&&(c[a]=c[n]+i.length,t[a]=n)}}}let r=0;for(let a=1;a<l;a++)c[a]>c[r]&&(r=a);const _=[];let d=r;for(;d!==-1;)_.push(e[d]),d=t[d];_.reverse();for(let a=1;a<_.length;a++){const i=_[a-1],n=_[a],f=n.oldPos-(i.oldPos+i.length),p=n.newPos-(i.newPos+i.length);if(f<0||p<0)return[]}return _}_processWithAnchors(e,s,o,l,c,t,r,_,d,a,i=0){if(s>o||c>t)return this._processGap({oldStart:s,oldEnd:o,newStart:c,newEnd:t},e,l,_,d,a);if(r.length===0)return this._processGap({oldStart:s,oldEnd:o,newStart:c,newEnd:t},e,l,_,d,a);const n=[];let f=s,p=c;for(let m=0;m<r.length;m++){const g=r[m];if(g.oldPos>f||g.newPos>p){const u=this._processGap({oldStart:f,oldEnd:g.oldPos,newStart:p,newEnd:g.newPos},e,l,_,d,a);for(let h=0;h<u.length;h++)n.push(u[h])}const D=[];for(let u=0;u<g.length;u++){const h=_[e[g.oldPos+u]];D.push(h),n.push([0,h])}f=g.oldPos+g.length,p=g.newPos+g.length}if(f<o||p<t){const m=this._processGap({oldStart:f,oldEnd:o,newStart:p,newEnd:t},e,l,_,d,a);for(let g=0;g<m.length;g++)n.push(m[g])}return n}_processGap(e,s,o,l,c,t){const r=e.oldEnd-e.oldStart,_=e.newEnd-e.newStart,d=r+_;if(d===0)return[];if((r>0&&_>0?Math.max(r/_,_/r):0)>100&&d>500){const i=this._createDeletions(s,e.oldStart,e.oldEnd,l),n=this._createAdditions(o,e.newStart,e.newEnd,l);return i.push.apply(i,n),i}return d>c.hugeDiffThreshold?this._guidedCalculateDiff(s,e.oldStart,e.oldEnd,o,e.newStart,e.newEnd,l,c,t):this._recursiveDiff(s,e.oldStart,e.oldEnd,o,e.newStart,e.newEnd,l,c,t)}_recursiveDiff(e,s,o,l,c,t,r,_,d){const a=o-s,i=t-c;if(a<0||i<0)throw new Error("Negative length detected at recursiveDiff entry");if(a===0&&i===0)return[];if(a===0)return this._createAdditions(l,c,t,r);if(i===0)return this._createDeletions(e,s,o,r);if(a+i<_.quickDiffThreshold)return this.calculateDiff(e,s,o,l,c,t,r,_,d);const n=this._findMiddleSnake(e,s,o,l,c,t,d);if(!n||n.u-n.x<=0)return this._guidedCalculateDiff(e,s,o,l,c,t,r,_,d);const f=n.u-n.x;for(let A=0;A<f;A++){const x=e[s+n.x+A],L=l[c+n.y+A];if(x!==L)return this.calculateDiff(e,s,o,l,c,t,r,_,d)}const p=s,m=s+n.x,g=c,D=c+n.y;if(m-p<0||D-g<0)throw new Error("Negative length detected in left recursive part");const u=this._recursiveDiff(e,p,m,l,g,D,r,_,d),h=new Array(f);for(let A=0;A<f;A++)h[A]=[0,r[e[s+n.x+A]]];const b=s+n.u,y=o,w=c+n.v,R=t;if(y-b<0||R-w<0)throw new Error("Negative length detected in right recursive part");const E=this._recursiveDiff(e,b,y,l,w,R,r,_,d);return u.concat(h,E)}forwardBuffer=new Int32Array(0);backwardBuffer=new Int32Array(0);_validateInputs(e,s,o,l,c,t){return!(s<0||o<s||o>e.length||c<0||t<c||t>l.length)}_findMiddleSnake(e,s,o,l,c,t,r){if(!this._validateInputs(e,s,o,l,c,t))return;const _=o-s,d=t-c,a=_+d,i=2*a+2;if(this.forwardBuffer.length<i){const b=i*2;this.forwardBuffer=new Int32Array(b),this.backwardBuffer=new Int32Array(b)}const n=this.forwardBuffer.subarray(0,i),f=this.backwardBuffer.subarray(0,i);n.fill(0),f.fill(0);const p=_-d,m=(p&1)===0,g=a+1;n[g]=0,f[g]=0;const D=_+d,u=D>1e4,h=typeof process<"u"&&process.stdout;for(let b=0;b<=D;b++){u&&b>0&&b%50===0&&h&&process.stdout.write(`\r - Middle snake search progress: ${b} / max ${D}`);for(let y=-b;y<=b;y+=2){const w=a+y,R=w-1,E=w+1;let $;y===-b||y!==b&&n[R]<n[E]?$=n[E]:$=n[R]+1;let A=$-y;const x=$,L=A;for(;$<_&&A<d&&e[s+$]===l[c+A];)$++,A++;if(n[w]=$<_?$:_,!m){const N=y-p;if(N>=-(b-1)&&N<=b-1){const V=_-f[a+N];if($>=V){const P=V-y;if(V>=0&&P>=0&&P<=d&&A>=0)return{x:V,y:P,u:$,v:A}}}}}for(let y=-b;y<=b;y+=2){const w=a+y,R=w-1,E=w+1;let $;y===-b||y!==b&&f[R]<f[E]?$=f[E]:$=f[R]+1;let A=$-y;const x=$,L=A,N=o-1,V=t-1;for(;$<_&&A<d&&e[N-$]===l[V-A];)$++,A++;if(f[w]=$<_?$:_,m){const P=y+p;if(P>=-b&&P<=b){const C=n[a+P],z=_-$;if(C>=z){const B=C-P,M=d-A;if(z>=0&&M>=0&&B>=M)return{x:z,y:M,u:C,v:B}}}}}}}_guidedCalculateDiff(e,s,o,l,c,t,r,_,d){const a=o-s,i=t-c;if((a>0&&i>0?Math.max(a/i,i/a):0)>100&&a+i>500){const V=this._createDeletions(e,s,o,r),P=this._createAdditions(l,c,t,r);return V.push.apply(V,P),V}const f=a+i,p=new Uint8Array(f),m=new Array(f);let g=0;const D=(V,P)=>{p[g]=V,m[g]=P,g++};let u=s,h=c;const b=c-s,y=Math.min(_.corridorWidth,Math.max(10,Math.floor((a+i)/100))),w=Math.min(_.lookahead,Math.max(5,Math.floor((a+i)/200))),R=a+i+100;let E=0,$=0,A=u,x=h;const L=Math.max(50,Math.floor(R/10));for(;u<o||h<t;){if(E++,E-$>L){for(;u<o;)D(2,r[e[u++]]);for(;h<t;)D(1,r[l[h++]]);break}if((u>A||h>x)&&($=E,A=u,x=h),E>R){for(;u<o;)D(2,r[e[u++]]);for(;h<t;)D(1,r[l[h++]]);break}const V=u<o,P=h<t;if(V&&P&&e[u]===l[h]){D(0,r[e[u]]),u++,h++;continue}if(!V){D(1,r[l[h]]),h++;continue}if(!P){D(2,r[e[u]]),u++;continue}const C=h-u;if(Math.abs(C-b)>y){C>b?(D(2,r[e[u]]),u++):(D(1,r[l[h]]),h++);continue}const B=e[u],M=l[h];let O=-1;const I=Math.min(t,h+w);for(let k=h+1;k<I;k++)if(l[k]===B){O=k;break}let v=-1;const S=Math.min(o,u+w);for(let k=u+1;k<S;k++)if(e[k]===M){v=k;break}if(O!==-1&&v===-1){D(1,r[l[h]]),h++;continue}if(v!==-1&&O===-1){D(2,r[e[u]]),u++;continue}if(O!==-1&&v!==-1){const k=O-h,U=v-u;k<U?(D(1,r[l[h]]),h++):(D(2,r[e[u]]),u++);continue}const q=this._isTokenRare(B,e,u,o,3),F=this._isTokenRare(M,l,h,t,3);if(q&&!F){D(1,r[l[h]]),h++;continue}if(F&&!q){D(2,r[e[u]]),u++;continue}o-u>t-h?(D(2,r[e[u]]),u++):(D(1,r[l[h]]),h++)}const N=new Array(g);for(let V=0;V<g;V++)N[V]=[p[V],m[V]];return N}calculateDiff(e,s,o,l,c,t,r,_,d){const a=o-s,i=t-c;if(a===0)return this._createAdditions(l,c,t,r);if(i===0)return this._createDeletions(e,s,o,r);const n=a+i,f=n,p=new Int32Array(2*n+2),m=[];p[f+1]=0;for(let g=0;g<=n;g++){m.push(p.slice());for(let D=-g;D<=g;D+=2){const u=D+f;let h;D===-g||D!==g&&p[u-1]<p[u+1]?h=p[u+1]:h=p[u-1]+1;let b=h-D;for(;h<a&&b<i&&e[s+h]===l[c+b];)h++,b++;if(p[u]=h,h>=a&&b>=i)return this.buildValues(m,e,s,o,l,c,t,r)}}return[]}_calculateStableDiff(e,s,o,l,c,t,r,_,d){const a=[];let i=s,n=c;for(;i<o&&n<t;)if(e[i]===l[n])a.push([0,r[e[i]]]),i++,n++;else{const f=this._findNextLocalAnchor(e,i,o,l,n,t,_.localLookahead||50,d),p=f?.oldPos??o,m=f?.newPos??t,g=this._processLocalGap(e,i,p,l,n,m,r,_,d);a.push(...g),f?(i=f.oldPos,n=f.newPos):(i=o,n=t)}for(;i<o;)a.push([2,r[e[i++]]]);for(;n<t;)a.push([1,r[l[n++]]]);return a}_findNextLocalAnchor(e,s,o,l,c,t,r,_){const d=Math.min(o,s+r),a=Math.min(t,c+r);for(let i=1;i<=r;i++){const n=s+i,f=c+i;if(n>=o||f>=t)break;if(e[n]===l[f])return{oldPos:n,newPos:f}}for(let i=1;i<=Math.min(r/2,10);i++)for(let n=-i;n<=i;n++){const f=s+i,p=c+i+n;if(f<o&&p>=c&&p<t&&e[f]===l[p])return{oldPos:f,newPos:p}}return null}_processLocalGap(e,s,o,l,c,t,r,_,d){const a=o-s,i=t-c,n=[];if(a+i<(_.localgap||10)){for(let f=s;f<o;f++)n.push([2,r[e[f]]]);for(let f=c;f<t;f++)n.push([1,r[l[f]]])}else{const f=this.calculateDiff(e,s,o,l,c,t,r,_,d);n.push(...f)}return n}_trimCommonPrefixSuffix(e,s,o,l,c,t,r,_){const d=o-s,a=t-c;let i=0;const n=Math.min(d,a);for(;i<n&&e[s+i]===l[c+i];)i++;let f=0;const p=n-i;for(;f<p&&e[o-1-f]===l[t-1-f];)f++;const m=new Array(i);for(let u=0;u<i;u++)m[u]=[0,r[e[s+u]]];const g=new Array(f);for(let u=0;u<f;u++)g[u]=[0,r[e[o-f+u]]];return{prefix:m,suffix:g,newOldStart:s+i,newOldEnd:o-f,newNewStart:c+i,newNewEnd:t-f}}_tokenize(e,s,o){const l=e.length+s.length,c=new Map,t=[];let r=0;const _=new Uint32Array(e.length),d=new Uint32Array(s.length);for(let a=0;a<e.length;a++){const i=e[a];let n=c.get(i);n===void 0&&(n=r++,c.set(i,n),t.push(i)),_[a]=n}for(let a=0;a<s.length;a++){const i=s[a];let n=c.get(i);n===void 0&&(n=r++,c.set(i,n),t.push(i)),d[a]=n}return{hashedOld:_,hashedNew:d,idToString:t}}_isTokenRare(e,s,o,l,c,t){let r=0;for(let d=o;d<l;d++)if(s[d]===e&&(r++,r>c))return!1;return r<=c}_createAdditions(e,s,o,l,c){const t=new Array(o-s);for(let r=0;r<t.length;r++)t[r]=[1,l[e[s+r]]];return t}_createDeletions(e,s,o,l,c){const t=new Array(o-s);for(let r=0;r<t.length;r++)t[r]=[2,l[e[s+r]]];return t}buildValues(e,s,o,l,c,t,r,_,d){let a=l-o,i=r-t;const n=[],f=l-o+r-t;for(let p=e.length-1;p>=0;p--){const m=e[p],g=a-i,D=g+f,u=g===-p||g!==p&&m[D-1]<m[D+1]?g+1:g-1,h=u+f,b=m[h],y=b-u;let w=a,R=i;for(;w>b&&R>y;){const E=_[s[o+w-1]];n.unshift([0,E]),w--,R--}if(p>0)if(b===w){const E=_[c[t+R-1]];n.unshift([1,E])}else{const E=_[s[o+w-1]];n.unshift([2,E])}if(a=b,i=y,a<=0&&i<=0)break}return n}}export{Q as DiffOperation,G as MyersCoreDiff};