indentRangeProvider.js 6.9 KB

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