11module ts {
2- export class TextChangeRangeObject implements TextChangeRange {
3- public static unchanged = new TextChangeRangeObject ( createTextSpan ( 0 , 0 ) , 0 ) ;
4-
5- private _span : TextSpan ;
6- private _newLength : number ;
7-
8- /**
9- * Initializes a new instance of TextChangeRange.
10- */
11- constructor ( span : TextSpan , newLength : number ) {
12- Debug . assert ( newLength >= 0 , "newLength" ) ;
13-
14- this . _span = span ;
15- this . _newLength = newLength ;
16- }
17-
18- /**
19- * The span of text before the edit which is being changed
20- */
21- public span ( ) : TextSpan {
22- return this . _span ;
23- }
24-
25- /**
26- * Width of the span after the edit. A 0 here would represent a delete
27- */
28- public newLength ( ) : number {
29- return this . _newLength ;
30- }
31-
32- public newSpan ( ) : TextSpan {
33- return createTextSpan ( this . span ( ) . start ( ) , this . newLength ( ) ) ;
34- }
35-
36- public isUnchanged ( ) : boolean {
37- return this . span ( ) . isEmpty ( ) && this . newLength ( ) === 0 ;
38- }
39-
40- /**
41- * Called to merge all the changes that occurred across several versions of a script snapshot
42- * into a single change. i.e. if a user keeps making successive edits to a script we will
43- * have a text change from V1 to V2, V2 to V3, ..., Vn.
44- *
45- * This function will then merge those changes into a single change range valid between V1 and
46- * Vn.
47- */
48- public static collapseChangesAcrossMultipleVersions ( changes : TextChangeRange [ ] ) : TextChangeRange {
49- if ( changes . length === 0 ) {
50- return TextChangeRangeObject . unchanged ;
51- }
52-
53- if ( changes . length === 1 ) {
54- return changes [ 0 ] ;
55- }
56-
57- // We change from talking about { { oldStart, oldLength }, newLength } to { oldStart, oldEnd, newEnd }
58- // as it makes things much easier to reason about.
59- var change0 = changes [ 0 ] ;
60-
61- var oldStartN = change0 . span ( ) . start ( ) ;
62- var oldEndN = change0 . span ( ) . end ( ) ;
63- var newEndN = oldStartN + change0 . newLength ( ) ;
64-
65- for ( var i = 1 ; i < changes . length ; i ++ ) {
66- var nextChange = changes [ i ] ;
67-
68- // Consider the following case:
69- // i.e. two edits. The first represents the text change range { { 10, 50 }, 30 }. i.e. The span starting
70- // at 10, with length 50 is reduced to length 30. The second represents the text change range { { 30, 30 }, 40 }.
71- // i.e. the span starting at 30 with length 30 is increased to length 40.
72- //
73- // 0 10 20 30 40 50 60 70 80 90 100
74- // -------------------------------------------------------------------------------------------------------
75- // | /
76- // | /----
77- // T1 | /----
78- // | /----
79- // | /----
80- // -------------------------------------------------------------------------------------------------------
81- // | \
82- // | \
83- // T2 | \
84- // | \
85- // | \
86- // -------------------------------------------------------------------------------------------------------
87- //
88- // Merging these turns out to not be too difficult. First, determining the new start of the change is trivial
89- // it's just the min of the old and new starts. i.e.:
90- //
91- // 0 10 20 30 40 50 60 70 80 90 100
92- // ------------------------------------------------------------*------------------------------------------
93- // | /
94- // | /----
95- // T1 | /----
96- // | /----
97- // | /----
98- // ----------------------------------------$-------------------$------------------------------------------
99- // . | \
100- // . | \
101- // T2 . | \
102- // . | \
103- // . | \
104- // ----------------------------------------------------------------------*--------------------------------
105- //
106- // (Note the dots represent the newly inferrred start.
107- // Determining the new and old end is also pretty simple. Basically it boils down to paying attention to the
108- // absolute positions at the asterixes, and the relative change between the dollar signs. Basically, we see
109- // which if the two $'s precedes the other, and we move that one forward until they line up. in this case that
110- // means:
111- //
112- // 0 10 20 30 40 50 60 70 80 90 100
113- // --------------------------------------------------------------------------------*----------------------
114- // | /
115- // | /----
116- // T1 | /----
117- // | /----
118- // | /----
119- // ------------------------------------------------------------$------------------------------------------
120- // . | \
121- // . | \
122- // T2 . | \
123- // . | \
124- // . | \
125- // ----------------------------------------------------------------------*--------------------------------
126- //
127- // In other words (in this case), we're recognizing that the second edit happened after where the first edit
128- // ended with a delta of 20 characters (60 - 40). Thus, if we go back in time to where the first edit started
129- // that's the same as if we started at char 80 instead of 60.
130- //
131- // As it so happens, the same logic applies if the second edit precedes the first edit. In that case rahter
132- // than pusing the first edit forward to match the second, we'll push the second edit forward to match the
133- // first.
134- //
135- // In this case that means we have { oldStart: 10, oldEnd: 80, newEnd: 70 } or, in TextChangeRange
136- // semantics: { { start: 10, length: 70 }, newLength: 60 }
137- //
138- // The math then works out as follows.
139- // If we have { oldStart1, oldEnd1, newEnd1 } and { oldStart2, oldEnd2, newEnd2 } then we can compute the
140- // final result like so:
141- //
142- // {
143- // oldStart3: Min(oldStart1, oldStart2),
144- // oldEnd3 : Max(oldEnd1, oldEnd1 + (oldEnd2 - newEnd1)),
145- // newEnd3 : Max(newEnd2, newEnd2 + (newEnd1 - oldEnd2))
146- // }
147-
148- var oldStart1 = oldStartN ;
149- var oldEnd1 = oldEndN ;
150- var newEnd1 = newEndN ;
151-
152- var oldStart2 = nextChange . span ( ) . start ( ) ;
153- var oldEnd2 = nextChange . span ( ) . end ( ) ;
154- var newEnd2 = oldStart2 + nextChange . newLength ( ) ;
155-
156- oldStartN = Math . min ( oldStart1 , oldStart2 ) ;
157- oldEndN = Math . max ( oldEnd1 , oldEnd1 + ( oldEnd2 - newEnd1 ) ) ;
158- newEndN = Math . max ( newEnd2 , newEnd2 + ( newEnd1 - oldEnd2 ) ) ;
159- }
160-
161- return new TextChangeRangeObject ( createTextSpanFromBounds ( oldStartN , oldEndN ) , /*newLength: */ newEndN - oldStartN ) ;
162- }
163- }
1642}
0 commit comments