@@ -1031,10 +1031,18 @@ export class Delta {
10311031 return this . name === other . name && fun . equalityDeep ( this . attrs , other . attrs ) && fun . equalityDeep ( this . children , other . children ) && this . childCnt === other . childCnt
10321032 }
10331033
1034+
1035+ /**
1036+ * @return {DeltaBuilder<NodeName,Attrs,Children,Text,Schema> }
1037+ */
1038+ clone ( ) {
1039+ return this . slice ( 0 , this . childCnt )
1040+ }
1041+
10341042 /**
10351043 * @param {number } start
10361044 * @param {number } end
1037- * @return {Delta <NodeName,Attrs,Children,Text,Schema> }
1045+ * @return {DeltaBuilder <NodeName,Attrs,Children,Text,Schema> }
10381046 */
10391047 slice ( start = 0 , end = this . childCnt ) {
10401048 const cpy = /** @type {DeltaAny } */ ( new DeltaBuilder ( /** @type {any } */ ( this . name ) , this . $schema ) )
@@ -1101,7 +1109,7 @@ export class Delta {
11011109/**
11021110 * @template {DeltaAny} D
11031111 * @param {D } d
1104- * @return {D extends Delta <infer NodeName,infer Attrs,infer Children,infer Text,infer Schema> ? DeltaBuilder<NodeName,Attrs,Children,Text,Schema> : never }
1112+ * @return {D extends DeltaBuilder <infer NodeName,infer Attrs,infer Children,infer Text,infer Schema> ? DeltaBuilder<NodeName,Attrs,Children,Text,Schema> : never }
11051113 */
11061114export const clone = d => /** @type {any } */ ( d . slice ( 0 , d . childCnt ) )
11071115
@@ -1469,26 +1477,50 @@ export class DeltaBuilder extends Delta {
14691477 if ( offset === 0 ) {
14701478 list . insertBetween ( this . children , opsI == null ? this . children . end : opsI . prev , opsI , scheduleForMerge ( op . clone ( ) ) )
14711479 } else {
1480+ // @todo inmplement "splitHelper" and "insertHelper" - I'm splitting all the time and
1481+ // forget to update opsI
14721482 if ( opsI == null ) error . unexpectedCase ( )
14731483 const cpy = scheduleForMerge ( opsI . clone ( offset ) )
14741484 opsI . _splice ( offset , opsI . length - offset )
14751485 list . insertBetween ( this . children , opsI , opsI . next || null , cpy )
14761486 list . insertBetween ( this . children , opsI , cpy || null , scheduleForMerge ( op . clone ( ) ) )
1487+ opsI = cpy
14771488 offset = 0
14781489 }
14791490 this . childCnt += op . insert . length
14801491 } else if ( $retainOp . check ( op ) ) {
1481- let skipLen = op . length
1482- while ( opsI != null && opsI . length - offset <= skipLen ) {
1483- skipLen -= opsI . length - offset
1492+ let retainLen = op . length
1493+
1494+ if ( offset > 0 && opsI != null && op . format != null && ! $deleteOp . check ( opsI ) && ! object . every ( op . format , ( v , k ) => fun . equalityDeep ( v , /** @type {InsertOp<any>|RetainOp|ModifyOp } */ ( opsI ) . format ?. [ k ] || null ) ) ) {
1495+ // need to split current op
1496+ const cpy = scheduleForMerge ( opsI . clone ( offset ) )
1497+ opsI . _splice ( offset , opsI . length - offset )
1498+ list . insertBetween ( this . children , opsI , opsI . next || null , cpy )
1499+ opsI = cpy
1500+ offset = 0
1501+ }
1502+
1503+ while ( opsI != null && opsI . length - offset <= retainLen ) {
1504+ op . format != null && updateOpFormat ( opsI , op . format )
1505+ retainLen -= opsI . length - offset
14841506 opsI = opsI ?. next || null
14851507 offset = 0
14861508 }
1509+
14871510 if ( opsI != null ) {
1488- offset += skipLen
1489- } else {
1490- list . pushEnd ( this . children , scheduleForMerge ( new RetainOp ( skipLen , op . format , op . attribution ) ) )
1491- this . childCnt += skipLen
1511+ if ( op . format != null && retainLen > 0 ) {
1512+ // split current op and apply format
1513+ const cpy = scheduleForMerge ( opsI . clone ( retainLen ) )
1514+ opsI . _splice ( retainLen , opsI . length - retainLen )
1515+ list . insertBetween ( this . children , opsI , opsI . next || null , cpy )
1516+ updateOpFormat ( opsI , op . format )
1517+ opsI = cpy
1518+ } else {
1519+ offset += retainLen
1520+ }
1521+ } else if ( retainLen > 0 ) {
1522+ list . pushEnd ( this . children , scheduleForMerge ( new RetainOp ( retainLen , op . format , op . attribution ) ) )
1523+ this . childCnt += retainLen
14921524 }
14931525 } else if ( $deleteOp . check ( op ) ) {
14941526 let remainingLen = op . delete
@@ -1745,16 +1777,40 @@ export class DeltaBuilder extends Delta {
17451777 * @return {CastToDelta<OtherDelta> extends Delta<any,any,infer OtherChildren,infer OtherText,any> ? DeltaBuilder<NodeName,Attrs,Children|OtherChildren,Text|OtherText,Schema> : never }
17461778 */
17471779 append ( other ) {
1780+ const children = this . children
1781+ let prevLast = children . end
17481782 // @todo Investigate. Above is a typescript issue. It is necessary to cast OtherDelta to a Delta first before
17491783 // inferring type, otherwise Children will contain Text.
17501784 for ( const child of other . children ) {
1751- list . pushEnd ( this . children , child . clone ( ) )
1785+ list . pushEnd ( children , child . clone ( ) )
17521786 }
1787+ this . childCnt += other . childCnt
1788+ prevLast ?. next && tryMergeWithPrev ( children , prevLast . next )
17531789 // @ts -ignore
17541790 return this
17551791 }
17561792}
17571793
1794+ /**
1795+ * @param {ChildrenOpAny } op
1796+ * @param {{[k:string]:any} } formatUpdate
1797+ */
1798+ const updateOpFormat = ( op , formatUpdate ) => {
1799+ if ( ! $deleteOp . check ( op ) ) {
1800+ // apply formatting attributes
1801+ for ( const k in formatUpdate ) {
1802+ const v = formatUpdate [ k ]
1803+ if ( v != null || $retainOp . check ( op ) ) {
1804+ // never modify formats
1805+ /** @type {any } */ ( op ) . format = object . assign ( { } , op . format , { [ k ] : v } )
1806+ } else if ( op . format != null ) {
1807+ const { [ k ] : _ , ...rest } = op . format
1808+ ; /** @type {any } */ ( op ) . format = rest
1809+ }
1810+ }
1811+ }
1812+ }
1813+
17581814/**
17591815 * @template {DeltaAny} D
17601816 * @typedef {D extends DeltaBuilder<infer N,infer Attrs,infer Children,infer Text,infer Schema> ? Delta<N,Attrs,Children,Text,Schema> : D } CastToDelta
@@ -1815,8 +1871,8 @@ export class $Delta extends s.Schema {
18151871 const { $name, $attrs, $children, hasText, $formats } = this . shape
18161872 if ( ! ( o instanceof Delta ) ) {
18171873 err ?. extend ( null , 'Delta' , o ?. constructor . name , 'Constructor match failed' )
1818- } else if ( ! $name . check ( o . name , err ) ) {
1819- err ?. extend ( 'Delta.name' , $name . toString ( ) , o . name , 'Constructor match failed ' )
1874+ } else if ( o . name != null && ! $name . check ( o . name , err ) ) {
1875+ err ?. extend ( 'Delta.name' , $name . toString ( ) , o . name , 'Unexpected node name ' )
18201876 } else if ( list . toArray ( o . children ) . some ( c => ( ! hasText && $textOp . check ( c ) ) || ( hasText && $textOp . check ( c ) && c . format != null && ! $formats . check ( c . format ) ) || ( $insertOp . check ( c ) && ! c . insert . every ( ins => $children . check ( ins ) ) ) ) ) {
18211877 err ?. extend ( 'Delta.children' , '' , '' , 'Children don\'t match the schema' )
18221878 } else if ( object . some ( o . attrs , ( op , k ) => $insertOp . check ( op ) && ! $attrs . check ( { [ k ] : op . value } , err ) ) ) {
@@ -1913,14 +1969,21 @@ export const _$delta = ({ name, attrs, children, text, recursive }) => {
19131969 */
19141970export const $deltaAny = /** @type {any } */ ( s . $instanceOf ( Delta ) )
19151971
1972+ /**
1973+ * @type {s.Schema<DeltaBuilderAny> }
1974+ */
1975+ export const $deltaBuilderAny = /** @type {any } */ ( s . $instanceOf ( DeltaBuilder ) )
1976+
19161977/**
19171978 * Helper function to merge attribution and attributes. The latter input "wins".
19181979 *
19191980 * @template {{ [key: string]: any }} T
19201981 * @param {T | null } a
19211982 * @param {T | null } b
19221983 */
1923- export const mergeAttrs = ( a , b ) => object . isEmpty ( a ) ? b : ( object . isEmpty ( b ) ? a : object . assign ( { } , a , b ) )
1984+ export const mergeAttrs = ( a , b ) => object . isEmpty ( a )
1985+ ? ( object . isEmpty ( b ) ? null : b )
1986+ : ( object . isEmpty ( b ) ? a : object . assign ( { } , a , b ) )
19241987
19251988/**
19261989 * @template {DeltaAny|null} D
@@ -2102,7 +2165,7 @@ export const map = $schema => /** @type {any} */ (create(/** @type {any} */ ($sc
21022165 * @template {DeltaAny} D
21032166 * @param {D } d1
21042167 * @param {NoInfer<D> } d2
2105- * @return {D }
2168+ * @return {D extends Delta<infer N,infer Attrs,infer Children,infer Text,any> ? DeltaBuilder<N,Attrs,Children,Text,null> : never }
21062169 */
21072170export const diff = ( d1 , d2 ) => {
21082171 /**
@@ -2153,11 +2216,14 @@ export const diff = (d1, d2) => {
21532216 d . retain ( change . index - lastIndex1 )
21542217 // insert minimal diff at curred position in d
21552218 /**
2156- * @param {DeltaBuilderAny } d
2219+ *
2220+ * @todo it would be better if these would be slices of delta (an actual delta)
2221+ *
21572222 * @param {ChildrenOpAny[] } opsIs
21582223 * @param {ChildrenOpAny[] } opsShould
21592224 */
2160- const diffAndApply = ( d , opsIs , opsShould ) => {
2225+ const diffAndApply = ( opsIs , opsShould ) => {
2226+ const d = create ( )
21612227 // @todo unoptimized implementation. Convert content to array and diff that based on
21622228 // generated fingerprints. We probably could do better and cache more information.
21632229 // - benchmark
@@ -2172,6 +2238,7 @@ export const diff = (d1, d2) => {
21722238 const shouldContent = opsShould . flatMap ( op => $insertOp . check ( op ) ? op . insert : ( $textOp . check ( op ) ? op . insert . split ( '' ) : error . unexpectedCase ( ) ) )
21732239 const isContentFingerprinted = isContent . map ( c => s . $string . check ( c ) ? c : fingerprintTrait . fingerprint ( c ) )
21742240 const shouldContentFingerprinted = shouldContent . map ( c => s . $string . check ( c ) ? c : fingerprintTrait . fingerprint ( c ) )
2241+ const hasFormatting = opsIs . some ( op => ! $deleteOp . check ( op ) && op . format != null ) || opsShould . some ( op => ! $deleteOp . check ( op ) && op . format != null )
21752242 /**
21762243 * @type {{ index: number, insert: Array<string|DeltaAny|fingerprintTrait.Fingerprintable>, remove: Array<string|DeltaAny|fingerprintTrait.Fingerprintable> }[] }
21772244 */
@@ -2193,7 +2260,7 @@ export const diff = (d1, d2) => {
21932260 const a = cd . insert [ cdii ]
21942261 const b = cd . remove [ cdri ]
21952262 if ( $deltaAny . check ( a ) && $deltaAny . check ( b ) && a . name === b . name ) {
2196- d . modify ( diff ( a , b ) )
2263+ d . modify ( diff ( b , a ) )
21972264 cdii ++
21982265 cdri ++
21992266 } else if ( $deltaAny . check ( b ) ) {
@@ -2210,8 +2277,64 @@ export const diff = (d1, d2) => {
22102277 }
22112278 d . delete ( cd . remove . length - cdri )
22122279 }
2280+ // create the diff for formatting
2281+ if ( hasFormatting ) {
2282+ const formattingDiff = create ( )
2283+ // update opsIs with content diff. then we can figure out the formatting diff.
2284+ const isUpdated = create ( )
2285+ // copy opsIs to fresh delta
2286+ opsIs . forEach ( op => {
2287+ isUpdated . childCnt += op . length
2288+ list . pushEnd ( isUpdated . children , op . clone ( ) )
2289+ } )
2290+ isUpdated . apply ( d )
2291+ let shouldI = 0
2292+ let shouldOffset = 0
2293+ let isOp = isUpdated . children . start
2294+ let isOffset = 0
2295+ while ( shouldI < opsShould . length && isOp != null ) {
2296+ const shouldOp = opsShould [ shouldI ]
2297+ if ( ! $deleteOp . check ( shouldOp ) && ! $deleteOp . check ( isOp ) ) {
2298+ const isFormat = isOp . format
2299+ const minForward = math . min ( shouldOp . length - shouldOffset , isOp . length - isOffset )
2300+ shouldOffset += minForward
2301+ isOffset += minForward
2302+ if ( fun . equalityDeep ( shouldOp . format , isFormat ) ) {
2303+ formattingDiff . retain ( minForward )
2304+ } else {
2305+ /**
2306+ * @type {FormattingAttributes }
2307+ */
2308+ const fupdate = { }
2309+ shouldOp . format != null && object . forEach ( shouldOp . format , ( v , k ) => {
2310+ if ( ! fun . equalityDeep ( v , isFormat ?. [ k ] || null ) ) {
2311+ fupdate [ k ] = v
2312+ }
2313+ } )
2314+ isFormat && object . forEach ( isFormat , ( _ , k ) => {
2315+ if ( shouldOp ?. format ?. [ k ] === undefined ) {
2316+ fupdate [ k ] = null
2317+ }
2318+ } )
2319+ formattingDiff . retain ( minForward , fupdate )
2320+ }
2321+ // update offset and iterators
2322+ if ( shouldOffset >= shouldOp . length ) {
2323+ shouldI ++
2324+ shouldOffset = 0
2325+ }
2326+ if ( isOffset >= isOp . length ) {
2327+ isOp = isOp . next
2328+ isOffset = 0
2329+ }
2330+ }
2331+ }
2332+ d . apply ( formattingDiff )
2333+ }
2334+ return d
22132335 }
2214- diffAndApply ( d , ops1 . slice ( change . index , change . index + change . remove . length ) , ops2 . slice ( change . index + currIndexOffset2 , change . index + currIndexOffset2 + change . insert . length ) )
2336+ const subd = diffAndApply ( ops1 . slice ( change . index , change . index + change . remove . length ) , ops2 . slice ( change . index + currIndexOffset2 , change . index + currIndexOffset2 + change . insert . length ) )
2337+ d . append ( subd )
22152338 lastIndex1 = change . index + change . remove . length
22162339 currIndexOffset2 += change . insert . length - change . remove . length
22172340 }
@@ -2233,5 +2356,5 @@ export const diff = (d1, d2) => {
22332356 }
22342357 }
22352358 }
2236- return /** @type {D } */ ( d . done ( false ) )
2359+ return /** @type {any } */ ( d . done ( false ) )
22372360}
0 commit comments