|
4 | 4 | /// <reference path="..\compiler\parser.ts"/>
|
5 | 5 | /// <reference path="..\compiler\checker.ts"/>
|
6 | 6 |
|
7 |
| -/// <reference path='text.ts' /> |
8 | 7 | /// <reference path='outliningElementsCollector.ts' />
|
9 | 8 | /// <reference path='navigationBar.ts' />
|
10 | 9 | /// <reference path='breakpoints.ts' />
|
@@ -933,6 +932,301 @@ module ts {
|
933 | 932 | dispose(): void;
|
934 | 933 | }
|
935 | 934 |
|
| 935 | + export class TextSpan { |
| 936 | + private _start: number; |
| 937 | + private _length: number; |
| 938 | + |
| 939 | + /** |
| 940 | + * Creates a TextSpan instance beginning with the position Start and having the Length |
| 941 | + * specified with length. |
| 942 | + */ |
| 943 | + constructor(start: number, length: number) { |
| 944 | + Debug.assert(start >= 0, "start"); |
| 945 | + Debug.assert(length >= 0, "length"); |
| 946 | + |
| 947 | + this._start = start; |
| 948 | + this._length = length; |
| 949 | + } |
| 950 | + |
| 951 | + public toJSON(key: any): any { |
| 952 | + return { start: this._start, length: this._length }; |
| 953 | + } |
| 954 | + |
| 955 | + public start(): number { |
| 956 | + return this._start; |
| 957 | + } |
| 958 | + |
| 959 | + public length(): number { |
| 960 | + return this._length; |
| 961 | + } |
| 962 | + |
| 963 | + public end(): number { |
| 964 | + return this._start + this._length; |
| 965 | + } |
| 966 | + |
| 967 | + public isEmpty(): boolean { |
| 968 | + return this._length === 0; |
| 969 | + } |
| 970 | + |
| 971 | + /** |
| 972 | + * Determines whether the position lies within the span. Returns true if the position is greater than or equal to Start and strictly less |
| 973 | + * than End, otherwise false. |
| 974 | + * @param position The position to check. |
| 975 | + */ |
| 976 | + public containsPosition(position: number): boolean { |
| 977 | + return position >= this._start && position < this.end(); |
| 978 | + } |
| 979 | + |
| 980 | + /** |
| 981 | + * Determines whether span falls completely within this span. Returns true if the specified span falls completely within this span, otherwise false. |
| 982 | + * @param span The span to check. |
| 983 | + */ |
| 984 | + public containsTextSpan(span: TextSpan): boolean { |
| 985 | + return span._start >= this._start && span.end() <= this.end(); |
| 986 | + } |
| 987 | + |
| 988 | + /** |
| 989 | + * Determines whether the given span overlaps this span. Two spans are considered to overlap |
| 990 | + * if they have positions in common and neither is empty. Empty spans do not overlap with any |
| 991 | + * other span. Returns true if the spans overlap, false otherwise. |
| 992 | + * @param span The span to check. |
| 993 | + */ |
| 994 | + public overlapsWith(span: TextSpan): boolean { |
| 995 | + var overlapStart = Math.max(this._start, span._start); |
| 996 | + var overlapEnd = Math.min(this.end(), span.end()); |
| 997 | + |
| 998 | + return overlapStart < overlapEnd; |
| 999 | + } |
| 1000 | + |
| 1001 | + /** |
| 1002 | + * Returns the overlap with the given span, or undefined if there is no overlap. |
| 1003 | + * @param span The span to check. |
| 1004 | + */ |
| 1005 | + public overlap(span: TextSpan): TextSpan { |
| 1006 | + var overlapStart = Math.max(this._start, span._start); |
| 1007 | + var overlapEnd = Math.min(this.end(), span.end()); |
| 1008 | + |
| 1009 | + if (overlapStart < overlapEnd) { |
| 1010 | + return TextSpan.fromBounds(overlapStart, overlapEnd); |
| 1011 | + } |
| 1012 | + |
| 1013 | + return undefined; |
| 1014 | + } |
| 1015 | + |
| 1016 | + /** |
| 1017 | + * Determines whether span intersects this span. Two spans are considered to |
| 1018 | + * intersect if they have positions in common or the end of one span |
| 1019 | + * coincides with the start of the other span. Returns true if the spans intersect, false otherwise. |
| 1020 | + * @param The span to check. |
| 1021 | + */ |
| 1022 | + public intersectsWithTextSpan(span: TextSpan): boolean { |
| 1023 | + return span._start <= this.end() && span.end() >= this._start; |
| 1024 | + } |
| 1025 | + |
| 1026 | + public intersectsWith(start: number, length: number): boolean { |
| 1027 | + var end = start + length; |
| 1028 | + return start <= this.end() && end >= this._start; |
| 1029 | + } |
| 1030 | + |
| 1031 | + /** |
| 1032 | + * Determines whether the given position intersects this span. |
| 1033 | + * A position is considered to intersect if it is between the start and |
| 1034 | + * end positions (inclusive) of this span. Returns true if the position intersects, false otherwise. |
| 1035 | + * @param position The position to check. |
| 1036 | + */ |
| 1037 | + public intersectsWithPosition(position: number): boolean { |
| 1038 | + return position <= this.end() && position >= this._start; |
| 1039 | + } |
| 1040 | + |
| 1041 | + /** |
| 1042 | + * Returns the intersection with the given span, or undefined if there is no intersection. |
| 1043 | + * @param span The span to check. |
| 1044 | + */ |
| 1045 | + public intersection(span: TextSpan): TextSpan { |
| 1046 | + var intersectStart = Math.max(this._start, span._start); |
| 1047 | + var intersectEnd = Math.min(this.end(), span.end()); |
| 1048 | + |
| 1049 | + if (intersectStart <= intersectEnd) { |
| 1050 | + return TextSpan.fromBounds(intersectStart, intersectEnd); |
| 1051 | + } |
| 1052 | + |
| 1053 | + return undefined; |
| 1054 | + } |
| 1055 | + |
| 1056 | + /** |
| 1057 | + * Creates a new TextSpan from the given start and end positions |
| 1058 | + * as opposed to a position and length. |
| 1059 | + */ |
| 1060 | + public static fromBounds(start: number, end: number): TextSpan { |
| 1061 | + Debug.assert(start >= 0); |
| 1062 | + Debug.assert(end - start >= 0); |
| 1063 | + return new TextSpan(start, end - start); |
| 1064 | + } |
| 1065 | + } |
| 1066 | + |
| 1067 | + export class TextChangeRange { |
| 1068 | + public static unchanged = new TextChangeRange(new TextSpan(0, 0), 0); |
| 1069 | + |
| 1070 | + private _span: TextSpan; |
| 1071 | + private _newLength: number; |
| 1072 | + |
| 1073 | + /** |
| 1074 | + * Initializes a new instance of TextChangeRange. |
| 1075 | + */ |
| 1076 | + constructor(span: TextSpan, newLength: number) { |
| 1077 | + Debug.assert(newLength >= 0, "newLength"); |
| 1078 | + |
| 1079 | + this._span = span; |
| 1080 | + this._newLength = newLength; |
| 1081 | + } |
| 1082 | + |
| 1083 | + /** |
| 1084 | + * The span of text before the edit which is being changed |
| 1085 | + */ |
| 1086 | + public span(): TextSpan { |
| 1087 | + return this._span; |
| 1088 | + } |
| 1089 | + |
| 1090 | + /** |
| 1091 | + * Width of the span after the edit. A 0 here would represent a delete |
| 1092 | + */ |
| 1093 | + public newLength(): number { |
| 1094 | + return this._newLength; |
| 1095 | + } |
| 1096 | + |
| 1097 | + public newSpan(): TextSpan { |
| 1098 | + return new TextSpan(this.span().start(), this.newLength()); |
| 1099 | + } |
| 1100 | + |
| 1101 | + public isUnchanged(): boolean { |
| 1102 | + return this.span().isEmpty() && this.newLength() === 0; |
| 1103 | + } |
| 1104 | + |
| 1105 | + /** |
| 1106 | + * Called to merge all the changes that occurred across several versions of a script snapshot |
| 1107 | + * into a single change. i.e. if a user keeps making successive edits to a script we will |
| 1108 | + * have a text change from V1 to V2, V2 to V3, ..., Vn. |
| 1109 | + * |
| 1110 | + * This function will then merge those changes into a single change range valid between V1 and |
| 1111 | + * Vn. |
| 1112 | + */ |
| 1113 | + public static collapseChangesAcrossMultipleVersions(changes: TextChangeRange[]): TextChangeRange { |
| 1114 | + if (changes.length === 0) { |
| 1115 | + return TextChangeRange.unchanged; |
| 1116 | + } |
| 1117 | + |
| 1118 | + if (changes.length === 1) { |
| 1119 | + return changes[0]; |
| 1120 | + } |
| 1121 | + |
| 1122 | + // We change from talking about { { oldStart, oldLength }, newLength } to { oldStart, oldEnd, newEnd } |
| 1123 | + // as it makes things much easier to reason about. |
| 1124 | + var change0 = changes[0]; |
| 1125 | + |
| 1126 | + var oldStartN = change0.span().start(); |
| 1127 | + var oldEndN = change0.span().end(); |
| 1128 | + var newEndN = oldStartN + change0.newLength(); |
| 1129 | + |
| 1130 | + for (var i = 1; i < changes.length; i++) { |
| 1131 | + var nextChange = changes[i]; |
| 1132 | + |
| 1133 | + // Consider the following case: |
| 1134 | + // i.e. two edits. The first represents the text change range { { 10, 50 }, 30 }. i.e. The span starting |
| 1135 | + // at 10, with length 50 is reduced to length 30. The second represents the text change range { { 30, 30 }, 40 }. |
| 1136 | + // i.e. the span starting at 30 with length 30 is increased to length 40. |
| 1137 | + // |
| 1138 | + // 0 10 20 30 40 50 60 70 80 90 100 |
| 1139 | + // ------------------------------------------------------------------------------------------------------- |
| 1140 | + // | / |
| 1141 | + // | /---- |
| 1142 | + // T1 | /---- |
| 1143 | + // | /---- |
| 1144 | + // | /---- |
| 1145 | + // ------------------------------------------------------------------------------------------------------- |
| 1146 | + // | \ |
| 1147 | + // | \ |
| 1148 | + // T2 | \ |
| 1149 | + // | \ |
| 1150 | + // | \ |
| 1151 | + // ------------------------------------------------------------------------------------------------------- |
| 1152 | + // |
| 1153 | + // Merging these turns out to not be too difficult. First, determining the new start of the change is trivial |
| 1154 | + // it's just the min of the old and new starts. i.e.: |
| 1155 | + // |
| 1156 | + // 0 10 20 30 40 50 60 70 80 90 100 |
| 1157 | + // ------------------------------------------------------------*------------------------------------------ |
| 1158 | + // | / |
| 1159 | + // | /---- |
| 1160 | + // T1 | /---- |
| 1161 | + // | /---- |
| 1162 | + // | /---- |
| 1163 | + // ----------------------------------------$-------------------$------------------------------------------ |
| 1164 | + // . | \ |
| 1165 | + // . | \ |
| 1166 | + // T2 . | \ |
| 1167 | + // . | \ |
| 1168 | + // . | \ |
| 1169 | + // ----------------------------------------------------------------------*-------------------------------- |
| 1170 | + // |
| 1171 | + // (Note the dots represent the newly inferrred start. |
| 1172 | + // Determining the new and old end is also pretty simple. Basically it boils down to paying attention to the |
| 1173 | + // absolute positions at the asterixes, and the relative change between the dollar signs. Basically, we see |
| 1174 | + // which if the two $'s precedes the other, and we move that one forward until they line up. in this case that |
| 1175 | + // means: |
| 1176 | + // |
| 1177 | + // 0 10 20 30 40 50 60 70 80 90 100 |
| 1178 | + // --------------------------------------------------------------------------------*---------------------- |
| 1179 | + // | / |
| 1180 | + // | /---- |
| 1181 | + // T1 | /---- |
| 1182 | + // | /---- |
| 1183 | + // | /---- |
| 1184 | + // ------------------------------------------------------------$------------------------------------------ |
| 1185 | + // . | \ |
| 1186 | + // . | \ |
| 1187 | + // T2 . | \ |
| 1188 | + // . | \ |
| 1189 | + // . | \ |
| 1190 | + // ----------------------------------------------------------------------*-------------------------------- |
| 1191 | + // |
| 1192 | + // In other words (in this case), we're recognizing that the second edit happened after where the first edit |
| 1193 | + // ended with a delta of 20 characters (60 - 40). Thus, if we go back in time to where the first edit started |
| 1194 | + // that's the same as if we started at char 80 instead of 60. |
| 1195 | + // |
| 1196 | + // As it so happens, the same logic applies if the second edit precedes the first edit. In that case rahter |
| 1197 | + // than pusing the first edit forward to match the second, we'll push the second edit forward to match the |
| 1198 | + // first. |
| 1199 | + // |
| 1200 | + // In this case that means we have { oldStart: 10, oldEnd: 80, newEnd: 70 } or, in TextChangeRange |
| 1201 | + // semantics: { { start: 10, length: 70 }, newLength: 60 } |
| 1202 | + // |
| 1203 | + // The math then works out as follows. |
| 1204 | + // If we have { oldStart1, oldEnd1, newEnd1 } and { oldStart2, oldEnd2, newEnd2 } then we can compute the |
| 1205 | + // final result like so: |
| 1206 | + // |
| 1207 | + // { |
| 1208 | + // oldStart3: Min(oldStart1, oldStart2), |
| 1209 | + // oldEnd3 : Max(oldEnd1, oldEnd1 + (oldEnd2 - newEnd1)), |
| 1210 | + // newEnd3 : Max(newEnd2, newEnd2 + (newEnd1 - oldEnd2)) |
| 1211 | + // } |
| 1212 | + |
| 1213 | + var oldStart1 = oldStartN; |
| 1214 | + var oldEnd1 = oldEndN; |
| 1215 | + var newEnd1 = newEndN; |
| 1216 | + |
| 1217 | + var oldStart2 = nextChange.span().start(); |
| 1218 | + var oldEnd2 = nextChange.span().end(); |
| 1219 | + var newEnd2 = oldStart2 + nextChange.newLength(); |
| 1220 | + |
| 1221 | + oldStartN = Math.min(oldStart1, oldStart2); |
| 1222 | + oldEndN = Math.max(oldEnd1, oldEnd1 + (oldEnd2 - newEnd1)); |
| 1223 | + newEndN = Math.max(newEnd2, newEnd2 + (newEnd1 - oldEnd2)); |
| 1224 | + } |
| 1225 | + |
| 1226 | + return new TextChangeRange(TextSpan.fromBounds(oldStartN, oldEndN), /*newLength: */newEndN - oldStartN); |
| 1227 | + } |
| 1228 | + } |
| 1229 | + |
936 | 1230 | export interface ClassifiedSpan {
|
937 | 1231 | textSpan: TextSpan;
|
938 | 1232 | classificationType: string; // ClassificationTypeNames
|
|
0 commit comments