9d447378daba45b2f165a3718d1375b7845a12b1ccdd74ff491a243036c066040094bea89b9b156be5cdecccd0a633b92212f3f6da8e4c1ce19cf997b2d149 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  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. import { computeIndentLevel } from '../../../common/model/utils.js';
  6. import { FoldingRegions, MAX_LINE_NUMBER } from './foldingRanges.js';
  7. const MAX_FOLDING_REGIONS_FOR_INDENT_DEFAULT = 5000;
  8. export const ID_INDENT_PROVIDER = 'indent';
  9. export class IndentRangeProvider {
  10. constructor(editorModel, languageConfigurationService, maxFoldingRegions) {
  11. this.editorModel = editorModel;
  12. this.languageConfigurationService = languageConfigurationService;
  13. this.maxFoldingRegions = maxFoldingRegions;
  14. this.id = ID_INDENT_PROVIDER;
  15. }
  16. dispose() { }
  17. compute(cancelationToken, notifyTooManyRegions) {
  18. const foldingRules = this.languageConfigurationService.getLanguageConfiguration(this.editorModel.getLanguageId()).foldingRules;
  19. const offSide = foldingRules && !!foldingRules.offSide;
  20. const markers = foldingRules && foldingRules.markers;
  21. return Promise.resolve(computeRanges(this.editorModel, offSide, markers, this.maxFoldingRegions, notifyTooManyRegions));
  22. }
  23. }
  24. // public only for testing
  25. export class RangesCollector {
  26. constructor(foldingRangesLimit, _notifyTooManyRegions) {
  27. this._notifyTooManyRegions = _notifyTooManyRegions;
  28. this._startIndexes = [];
  29. this._endIndexes = [];
  30. this._indentOccurrences = [];
  31. this._length = 0;
  32. this._foldingRangesLimit = foldingRangesLimit;
  33. }
  34. insertFirst(startLineNumber, endLineNumber, indent) {
  35. if (startLineNumber > MAX_LINE_NUMBER || endLineNumber > MAX_LINE_NUMBER) {
  36. return;
  37. }
  38. const index = this._length;
  39. this._startIndexes[index] = startLineNumber;
  40. this._endIndexes[index] = endLineNumber;
  41. this._length++;
  42. if (indent < 1000) {
  43. this._indentOccurrences[indent] = (this._indentOccurrences[indent] || 0) + 1;
  44. }
  45. }
  46. toIndentRanges(model) {
  47. var _a;
  48. if (this._length <= this._foldingRangesLimit) {
  49. // reverse and create arrays of the exact length
  50. const startIndexes = new Uint32Array(this._length);
  51. const endIndexes = new Uint32Array(this._length);
  52. for (let i = this._length - 1, k = 0; i >= 0; i--, k++) {
  53. startIndexes[k] = this._startIndexes[i];
  54. endIndexes[k] = this._endIndexes[i];
  55. }
  56. return new FoldingRegions(startIndexes, endIndexes);
  57. }
  58. else {
  59. (_a = this._notifyTooManyRegions) === null || _a === void 0 ? void 0 : _a.call(this, this._foldingRangesLimit);
  60. let entries = 0;
  61. let maxIndent = this._indentOccurrences.length;
  62. for (let i = 0; i < this._indentOccurrences.length; i++) {
  63. const n = this._indentOccurrences[i];
  64. if (n) {
  65. if (n + entries > this._foldingRangesLimit) {
  66. maxIndent = i;
  67. break;
  68. }
  69. entries += n;
  70. }
  71. }
  72. const tabSize = model.getOptions().tabSize;
  73. // reverse and create arrays of the exact length
  74. const startIndexes = new Uint32Array(this._foldingRangesLimit);
  75. const endIndexes = new Uint32Array(this._foldingRangesLimit);
  76. for (let i = this._length - 1, k = 0; i >= 0; i--) {
  77. const startIndex = this._startIndexes[i];
  78. const lineContent = model.getLineContent(startIndex);
  79. const indent = computeIndentLevel(lineContent, tabSize);
  80. if (indent < maxIndent || (indent === maxIndent && entries++ < this._foldingRangesLimit)) {
  81. startIndexes[k] = startIndex;
  82. endIndexes[k] = this._endIndexes[i];
  83. k++;
  84. }
  85. }
  86. return new FoldingRegions(startIndexes, endIndexes);
  87. }
  88. }
  89. }
  90. export function computeRanges(model, offSide, markers, foldingRangesLimit, notifyTooManyRegions) {
  91. const tabSize = model.getOptions().tabSize;
  92. foldingRangesLimit = foldingRangesLimit !== null && foldingRangesLimit !== void 0 ? foldingRangesLimit : MAX_FOLDING_REGIONS_FOR_INDENT_DEFAULT;
  93. const result = new RangesCollector(foldingRangesLimit, notifyTooManyRegions);
  94. let pattern = undefined;
  95. if (markers) {
  96. pattern = new RegExp(`(${markers.start.source})|(?:${markers.end.source})`);
  97. }
  98. const previousRegions = [];
  99. const line = model.getLineCount() + 1;
  100. previousRegions.push({ indent: -1, endAbove: line, line }); // sentinel, to make sure there's at least one entry
  101. for (let line = model.getLineCount(); line > 0; line--) {
  102. const lineContent = model.getLineContent(line);
  103. const indent = computeIndentLevel(lineContent, tabSize);
  104. let previous = previousRegions[previousRegions.length - 1];
  105. if (indent === -1) {
  106. if (offSide) {
  107. // for offSide languages, empty lines are associated to the previous block
  108. // note: the next block is already written to the results, so this only
  109. // impacts the end position of the block before
  110. previous.endAbove = line;
  111. }
  112. continue; // only whitespace
  113. }
  114. let m;
  115. if (pattern && (m = lineContent.match(pattern))) {
  116. // folding pattern match
  117. if (m[1]) { // start pattern match
  118. // discard all regions until the folding pattern
  119. let i = previousRegions.length - 1;
  120. while (i > 0 && previousRegions[i].indent !== -2) {
  121. i--;
  122. }
  123. if (i > 0) {
  124. previousRegions.length = i + 1;
  125. previous = previousRegions[i];
  126. // new folding range from pattern, includes the end line
  127. result.insertFirst(line, previous.line, indent);
  128. previous.line = line;
  129. previous.indent = indent;
  130. previous.endAbove = line;
  131. continue;
  132. }
  133. else {
  134. // no end marker found, treat line as a regular line
  135. }
  136. }
  137. else { // end pattern match
  138. previousRegions.push({ indent: -2, endAbove: line, line });
  139. continue;
  140. }
  141. }
  142. if (previous.indent > indent) {
  143. // discard all regions with larger indent
  144. do {
  145. previousRegions.pop();
  146. previous = previousRegions[previousRegions.length - 1];
  147. } while (previous.indent > indent);
  148. // new folding range
  149. const endLineNumber = previous.endAbove - 1;
  150. if (endLineNumber - line >= 1) { // needs at east size 1
  151. result.insertFirst(line, endLineNumber, indent);
  152. }
  153. }
  154. if (previous.indent === indent) {
  155. previous.endAbove = line;
  156. }
  157. else { // previous.indent < indent
  158. // new region with a bigger indent
  159. previousRegions.push({ indent, endAbove: line, line });
  160. }
  161. }
  162. return result.toIndentRanges(model);
  163. }