3a610f25c2e76737848f0dfe5704138d0e4146fbfa09b582476336b55e52d22715b242720e72699b07f66c86f4ba6691829f80ae023c0bd2d5f032fb452e9b 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. /*---------------------------------------------------------------------------------------------
  2. * Copyright (c) Microsoft Corporation. All rights reserved.
  3. * Licensed under the MIT License. See License.txt in the project root for license information.
  4. *--------------------------------------------------------------------------------------------*/
  5. export const MAX_FOLDING_REGIONS = 0xFFFF;
  6. export const MAX_LINE_NUMBER = 0xFFFFFF;
  7. const MASK_INDENT = 0xFF000000;
  8. class BitField {
  9. constructor(size) {
  10. const numWords = Math.ceil(size / 32);
  11. this._states = new Uint32Array(numWords);
  12. }
  13. get(index) {
  14. const arrayIndex = (index / 32) | 0;
  15. const bit = index % 32;
  16. return (this._states[arrayIndex] & (1 << bit)) !== 0;
  17. }
  18. set(index, newState) {
  19. const arrayIndex = (index / 32) | 0;
  20. const bit = index % 32;
  21. const value = this._states[arrayIndex];
  22. if (newState) {
  23. this._states[arrayIndex] = value | (1 << bit);
  24. }
  25. else {
  26. this._states[arrayIndex] = value & ~(1 << bit);
  27. }
  28. }
  29. }
  30. export class FoldingRegions {
  31. constructor(startIndexes, endIndexes, types) {
  32. this.sourceAbbr = {
  33. [0 /* FoldSource.provider */]: ' ',
  34. [1 /* FoldSource.userDefined */]: 'u',
  35. [2 /* FoldSource.recovered */]: 'r',
  36. };
  37. if (startIndexes.length !== endIndexes.length || startIndexes.length > MAX_FOLDING_REGIONS) {
  38. throw new Error('invalid startIndexes or endIndexes size');
  39. }
  40. this._startIndexes = startIndexes;
  41. this._endIndexes = endIndexes;
  42. this._collapseStates = new BitField(startIndexes.length);
  43. this._userDefinedStates = new BitField(startIndexes.length);
  44. this._recoveredStates = new BitField(startIndexes.length);
  45. this._types = types;
  46. this._parentsComputed = false;
  47. }
  48. ensureParentIndices() {
  49. if (!this._parentsComputed) {
  50. this._parentsComputed = true;
  51. const parentIndexes = [];
  52. const isInsideLast = (startLineNumber, endLineNumber) => {
  53. const index = parentIndexes[parentIndexes.length - 1];
  54. return this.getStartLineNumber(index) <= startLineNumber && this.getEndLineNumber(index) >= endLineNumber;
  55. };
  56. for (let i = 0, len = this._startIndexes.length; i < len; i++) {
  57. const startLineNumber = this._startIndexes[i];
  58. const endLineNumber = this._endIndexes[i];
  59. if (startLineNumber > MAX_LINE_NUMBER || endLineNumber > MAX_LINE_NUMBER) {
  60. throw new Error('startLineNumber or endLineNumber must not exceed ' + MAX_LINE_NUMBER);
  61. }
  62. while (parentIndexes.length > 0 && !isInsideLast(startLineNumber, endLineNumber)) {
  63. parentIndexes.pop();
  64. }
  65. const parentIndex = parentIndexes.length > 0 ? parentIndexes[parentIndexes.length - 1] : -1;
  66. parentIndexes.push(i);
  67. this._startIndexes[i] = startLineNumber + ((parentIndex & 0xFF) << 24);
  68. this._endIndexes[i] = endLineNumber + ((parentIndex & 0xFF00) << 16);
  69. }
  70. }
  71. }
  72. get length() {
  73. return this._startIndexes.length;
  74. }
  75. getStartLineNumber(index) {
  76. return this._startIndexes[index] & MAX_LINE_NUMBER;
  77. }
  78. getEndLineNumber(index) {
  79. return this._endIndexes[index] & MAX_LINE_NUMBER;
  80. }
  81. getType(index) {
  82. return this._types ? this._types[index] : undefined;
  83. }
  84. hasTypes() {
  85. return !!this._types;
  86. }
  87. isCollapsed(index) {
  88. return this._collapseStates.get(index);
  89. }
  90. setCollapsed(index, newState) {
  91. this._collapseStates.set(index, newState);
  92. }
  93. isUserDefined(index) {
  94. return this._userDefinedStates.get(index);
  95. }
  96. setUserDefined(index, newState) {
  97. return this._userDefinedStates.set(index, newState);
  98. }
  99. isRecovered(index) {
  100. return this._recoveredStates.get(index);
  101. }
  102. setRecovered(index, newState) {
  103. return this._recoveredStates.set(index, newState);
  104. }
  105. getSource(index) {
  106. if (this.isUserDefined(index)) {
  107. return 1 /* FoldSource.userDefined */;
  108. }
  109. else if (this.isRecovered(index)) {
  110. return 2 /* FoldSource.recovered */;
  111. }
  112. return 0 /* FoldSource.provider */;
  113. }
  114. setSource(index, source) {
  115. if (source === 1 /* FoldSource.userDefined */) {
  116. this.setUserDefined(index, true);
  117. this.setRecovered(index, false);
  118. }
  119. else if (source === 2 /* FoldSource.recovered */) {
  120. this.setUserDefined(index, false);
  121. this.setRecovered(index, true);
  122. }
  123. else {
  124. this.setUserDefined(index, false);
  125. this.setRecovered(index, false);
  126. }
  127. }
  128. setCollapsedAllOfType(type, newState) {
  129. let hasChanged = false;
  130. if (this._types) {
  131. for (let i = 0; i < this._types.length; i++) {
  132. if (this._types[i] === type) {
  133. this.setCollapsed(i, newState);
  134. hasChanged = true;
  135. }
  136. }
  137. }
  138. return hasChanged;
  139. }
  140. toRegion(index) {
  141. return new FoldingRegion(this, index);
  142. }
  143. getParentIndex(index) {
  144. this.ensureParentIndices();
  145. const parent = ((this._startIndexes[index] & MASK_INDENT) >>> 24) + ((this._endIndexes[index] & MASK_INDENT) >>> 16);
  146. if (parent === MAX_FOLDING_REGIONS) {
  147. return -1;
  148. }
  149. return parent;
  150. }
  151. contains(index, line) {
  152. return this.getStartLineNumber(index) <= line && this.getEndLineNumber(index) >= line;
  153. }
  154. findIndex(line) {
  155. let low = 0, high = this._startIndexes.length;
  156. if (high === 0) {
  157. return -1; // no children
  158. }
  159. while (low < high) {
  160. const mid = Math.floor((low + high) / 2);
  161. if (line < this.getStartLineNumber(mid)) {
  162. high = mid;
  163. }
  164. else {
  165. low = mid + 1;
  166. }
  167. }
  168. return low - 1;
  169. }
  170. findRange(line) {
  171. let index = this.findIndex(line);
  172. if (index >= 0) {
  173. const endLineNumber = this.getEndLineNumber(index);
  174. if (endLineNumber >= line) {
  175. return index;
  176. }
  177. index = this.getParentIndex(index);
  178. while (index !== -1) {
  179. if (this.contains(index, line)) {
  180. return index;
  181. }
  182. index = this.getParentIndex(index);
  183. }
  184. }
  185. return -1;
  186. }
  187. toString() {
  188. const res = [];
  189. for (let i = 0; i < this.length; i++) {
  190. res[i] = `[${this.sourceAbbr[this.getSource(i)]}${this.isCollapsed(i) ? '+' : '-'}] ${this.getStartLineNumber(i)}/${this.getEndLineNumber(i)}`;
  191. }
  192. return res.join(', ');
  193. }
  194. toFoldRange(index) {
  195. return {
  196. startLineNumber: this._startIndexes[index] & MAX_LINE_NUMBER,
  197. endLineNumber: this._endIndexes[index] & MAX_LINE_NUMBER,
  198. type: this._types ? this._types[index] : undefined,
  199. isCollapsed: this.isCollapsed(index),
  200. source: this.getSource(index)
  201. };
  202. }
  203. static fromFoldRanges(ranges) {
  204. const rangesLength = ranges.length;
  205. const startIndexes = new Uint32Array(rangesLength);
  206. const endIndexes = new Uint32Array(rangesLength);
  207. let types = [];
  208. let gotTypes = false;
  209. for (let i = 0; i < rangesLength; i++) {
  210. const range = ranges[i];
  211. startIndexes[i] = range.startLineNumber;
  212. endIndexes[i] = range.endLineNumber;
  213. types.push(range.type);
  214. if (range.type) {
  215. gotTypes = true;
  216. }
  217. }
  218. if (!gotTypes) {
  219. types = undefined;
  220. }
  221. const regions = new FoldingRegions(startIndexes, endIndexes, types);
  222. for (let i = 0; i < rangesLength; i++) {
  223. if (ranges[i].isCollapsed) {
  224. regions.setCollapsed(i, true);
  225. }
  226. regions.setSource(i, ranges[i].source);
  227. }
  228. return regions;
  229. }
  230. /**
  231. * Two inputs, each a FoldingRegions or a FoldRange[], are merged.
  232. * Each input must be pre-sorted on startLineNumber.
  233. * The first list is assumed to always include all regions currently defined by range providers.
  234. * The second list only contains the previously collapsed and all manual ranges.
  235. * If the line position matches, the range of the new range is taken, and the range is no longer manual
  236. * When an entry in one list overlaps an entry in the other, the second list's entry "wins" and
  237. * overlapping entries in the first list are discarded.
  238. * Invalid entries are discarded. An entry is invalid if:
  239. * the start and end line numbers aren't a valid range of line numbers,
  240. * it is out of sequence or has the same start line as a preceding entry,
  241. * it overlaps a preceding entry and is not fully contained by that entry.
  242. */
  243. static sanitizeAndMerge(rangesA, rangesB, maxLineNumber) {
  244. maxLineNumber = maxLineNumber !== null && maxLineNumber !== void 0 ? maxLineNumber : Number.MAX_VALUE;
  245. const getIndexedFunction = (r, limit) => {
  246. return Array.isArray(r)
  247. ? ((i) => { return (i < limit) ? r[i] : undefined; })
  248. : ((i) => { return (i < limit) ? r.toFoldRange(i) : undefined; });
  249. };
  250. const getA = getIndexedFunction(rangesA, rangesA.length);
  251. const getB = getIndexedFunction(rangesB, rangesB.length);
  252. let indexA = 0;
  253. let indexB = 0;
  254. let nextA = getA(0);
  255. let nextB = getB(0);
  256. const stackedRanges = [];
  257. let topStackedRange;
  258. let prevLineNumber = 0;
  259. const resultRanges = [];
  260. while (nextA || nextB) {
  261. let useRange = undefined;
  262. if (nextB && (!nextA || nextA.startLineNumber >= nextB.startLineNumber)) {
  263. if (nextA && nextA.startLineNumber === nextB.startLineNumber) {
  264. if (nextB.source === 1 /* FoldSource.userDefined */) {
  265. // a user defined range (possibly unfolded)
  266. useRange = nextB;
  267. }
  268. else {
  269. // a previously folded range or a (possibly unfolded) recovered range
  270. useRange = nextA;
  271. useRange.isCollapsed = nextB.isCollapsed && nextA.endLineNumber === nextB.endLineNumber;
  272. useRange.source = 0 /* FoldSource.provider */;
  273. }
  274. nextA = getA(++indexA); // not necessary, just for speed
  275. }
  276. else {
  277. useRange = nextB;
  278. if (nextB.isCollapsed && nextB.source === 0 /* FoldSource.provider */) {
  279. // a previously collapsed range
  280. useRange.source = 2 /* FoldSource.recovered */;
  281. }
  282. }
  283. nextB = getB(++indexB);
  284. }
  285. else {
  286. // nextA is next. The user folded B set takes precedence and we sometimes need to look
  287. // ahead in it to check for an upcoming conflict.
  288. let scanIndex = indexB;
  289. let prescanB = nextB;
  290. while (true) {
  291. if (!prescanB || prescanB.startLineNumber > nextA.endLineNumber) {
  292. useRange = nextA;
  293. break; // no conflict, use this nextA
  294. }
  295. if (prescanB.source === 1 /* FoldSource.userDefined */ && prescanB.endLineNumber > nextA.endLineNumber) {
  296. // we found a user folded range, it wins
  297. break; // without setting nextResult, so this nextA gets skipped
  298. }
  299. prescanB = getB(++scanIndex);
  300. }
  301. nextA = getA(++indexA);
  302. }
  303. if (useRange) {
  304. while (topStackedRange
  305. && topStackedRange.endLineNumber < useRange.startLineNumber) {
  306. topStackedRange = stackedRanges.pop();
  307. }
  308. if (useRange.endLineNumber > useRange.startLineNumber
  309. && useRange.startLineNumber > prevLineNumber
  310. && useRange.endLineNumber <= maxLineNumber
  311. && (!topStackedRange
  312. || topStackedRange.endLineNumber >= useRange.endLineNumber)) {
  313. resultRanges.push(useRange);
  314. prevLineNumber = useRange.startLineNumber;
  315. if (topStackedRange) {
  316. stackedRanges.push(topStackedRange);
  317. }
  318. topStackedRange = useRange;
  319. }
  320. }
  321. }
  322. return resultRanges;
  323. }
  324. }
  325. export class FoldingRegion {
  326. constructor(ranges, index) {
  327. this.ranges = ranges;
  328. this.index = index;
  329. }
  330. get startLineNumber() {
  331. return this.ranges.getStartLineNumber(this.index);
  332. }
  333. get endLineNumber() {
  334. return this.ranges.getEndLineNumber(this.index);
  335. }
  336. get regionIndex() {
  337. return this.index;
  338. }
  339. get parentIndex() {
  340. return this.ranges.getParentIndex(this.index);
  341. }
  342. get isCollapsed() {
  343. return this.ranges.isCollapsed(this.index);
  344. }
  345. containedBy(range) {
  346. return range.startLineNumber <= this.startLineNumber && range.endLineNumber >= this.endLineNumber;
  347. }
  348. containsLine(lineNumber) {
  349. return this.startLineNumber <= lineNumber && lineNumber <= this.endLineNumber;
  350. }
  351. }