598cb2f0c63c11a33f5503ac3bd0227bed63baedb783078837f9a2fd4784d36001dd2aa2e7eb51b859b8e22b9f56819eff0f9e5ecdfe0e1f8d264b6040f4c3 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  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 { findLast } from '../../../base/common/arrays.js';
  6. import * as strings from '../../../base/common/strings.js';
  7. import { CursorColumns } from '../core/cursorColumns.js';
  8. import { Range } from '../core/range.js';
  9. import { TextModelPart } from './textModelPart.js';
  10. import { computeIndentLevel } from './utils.js';
  11. import { HorizontalGuidesState, IndentGuide, IndentGuideHorizontalLine } from '../textModelGuides.js';
  12. export class GuidesTextModelPart extends TextModelPart {
  13. constructor(textModel, languageConfigurationService) {
  14. super();
  15. this.textModel = textModel;
  16. this.languageConfigurationService = languageConfigurationService;
  17. }
  18. getLanguageConfiguration(languageId) {
  19. return this.languageConfigurationService.getLanguageConfiguration(languageId);
  20. }
  21. _computeIndentLevel(lineIndex) {
  22. return computeIndentLevel(this.textModel.getLineContent(lineIndex + 1), this.textModel.getOptions().tabSize);
  23. }
  24. getActiveIndentGuide(lineNumber, minLineNumber, maxLineNumber) {
  25. this.assertNotDisposed();
  26. const lineCount = this.textModel.getLineCount();
  27. if (lineNumber < 1 || lineNumber > lineCount) {
  28. throw new Error('Illegal value for lineNumber');
  29. }
  30. const foldingRules = this.getLanguageConfiguration(this.textModel.getLanguageId()).foldingRules;
  31. const offSide = Boolean(foldingRules && foldingRules.offSide);
  32. let up_aboveContentLineIndex = -2; /* -2 is a marker for not having computed it */
  33. let up_aboveContentLineIndent = -1;
  34. let up_belowContentLineIndex = -2; /* -2 is a marker for not having computed it */
  35. let up_belowContentLineIndent = -1;
  36. const up_resolveIndents = (lineNumber) => {
  37. if (up_aboveContentLineIndex !== -1 &&
  38. (up_aboveContentLineIndex === -2 ||
  39. up_aboveContentLineIndex > lineNumber - 1)) {
  40. up_aboveContentLineIndex = -1;
  41. up_aboveContentLineIndent = -1;
  42. // must find previous line with content
  43. for (let lineIndex = lineNumber - 2; lineIndex >= 0; lineIndex--) {
  44. const indent = this._computeIndentLevel(lineIndex);
  45. if (indent >= 0) {
  46. up_aboveContentLineIndex = lineIndex;
  47. up_aboveContentLineIndent = indent;
  48. break;
  49. }
  50. }
  51. }
  52. if (up_belowContentLineIndex === -2) {
  53. up_belowContentLineIndex = -1;
  54. up_belowContentLineIndent = -1;
  55. // must find next line with content
  56. for (let lineIndex = lineNumber; lineIndex < lineCount; lineIndex++) {
  57. const indent = this._computeIndentLevel(lineIndex);
  58. if (indent >= 0) {
  59. up_belowContentLineIndex = lineIndex;
  60. up_belowContentLineIndent = indent;
  61. break;
  62. }
  63. }
  64. }
  65. };
  66. let down_aboveContentLineIndex = -2; /* -2 is a marker for not having computed it */
  67. let down_aboveContentLineIndent = -1;
  68. let down_belowContentLineIndex = -2; /* -2 is a marker for not having computed it */
  69. let down_belowContentLineIndent = -1;
  70. const down_resolveIndents = (lineNumber) => {
  71. if (down_aboveContentLineIndex === -2) {
  72. down_aboveContentLineIndex = -1;
  73. down_aboveContentLineIndent = -1;
  74. // must find previous line with content
  75. for (let lineIndex = lineNumber - 2; lineIndex >= 0; lineIndex--) {
  76. const indent = this._computeIndentLevel(lineIndex);
  77. if (indent >= 0) {
  78. down_aboveContentLineIndex = lineIndex;
  79. down_aboveContentLineIndent = indent;
  80. break;
  81. }
  82. }
  83. }
  84. if (down_belowContentLineIndex !== -1 &&
  85. (down_belowContentLineIndex === -2 ||
  86. down_belowContentLineIndex < lineNumber - 1)) {
  87. down_belowContentLineIndex = -1;
  88. down_belowContentLineIndent = -1;
  89. // must find next line with content
  90. for (let lineIndex = lineNumber; lineIndex < lineCount; lineIndex++) {
  91. const indent = this._computeIndentLevel(lineIndex);
  92. if (indent >= 0) {
  93. down_belowContentLineIndex = lineIndex;
  94. down_belowContentLineIndent = indent;
  95. break;
  96. }
  97. }
  98. }
  99. };
  100. let startLineNumber = 0;
  101. let goUp = true;
  102. let endLineNumber = 0;
  103. let goDown = true;
  104. let indent = 0;
  105. let initialIndent = 0;
  106. for (let distance = 0; goUp || goDown; distance++) {
  107. const upLineNumber = lineNumber - distance;
  108. const downLineNumber = lineNumber + distance;
  109. if (distance > 1 && (upLineNumber < 1 || upLineNumber < minLineNumber)) {
  110. goUp = false;
  111. }
  112. if (distance > 1 &&
  113. (downLineNumber > lineCount || downLineNumber > maxLineNumber)) {
  114. goDown = false;
  115. }
  116. if (distance > 50000) {
  117. // stop processing
  118. goUp = false;
  119. goDown = false;
  120. }
  121. let upLineIndentLevel = -1;
  122. if (goUp && upLineNumber >= 1) {
  123. // compute indent level going up
  124. const currentIndent = this._computeIndentLevel(upLineNumber - 1);
  125. if (currentIndent >= 0) {
  126. // This line has content (besides whitespace)
  127. // Use the line's indent
  128. up_belowContentLineIndex = upLineNumber - 1;
  129. up_belowContentLineIndent = currentIndent;
  130. upLineIndentLevel = Math.ceil(currentIndent / this.textModel.getOptions().indentSize);
  131. }
  132. else {
  133. up_resolveIndents(upLineNumber);
  134. upLineIndentLevel = this._getIndentLevelForWhitespaceLine(offSide, up_aboveContentLineIndent, up_belowContentLineIndent);
  135. }
  136. }
  137. let downLineIndentLevel = -1;
  138. if (goDown && downLineNumber <= lineCount) {
  139. // compute indent level going down
  140. const currentIndent = this._computeIndentLevel(downLineNumber - 1);
  141. if (currentIndent >= 0) {
  142. // This line has content (besides whitespace)
  143. // Use the line's indent
  144. down_aboveContentLineIndex = downLineNumber - 1;
  145. down_aboveContentLineIndent = currentIndent;
  146. downLineIndentLevel = Math.ceil(currentIndent / this.textModel.getOptions().indentSize);
  147. }
  148. else {
  149. down_resolveIndents(downLineNumber);
  150. downLineIndentLevel = this._getIndentLevelForWhitespaceLine(offSide, down_aboveContentLineIndent, down_belowContentLineIndent);
  151. }
  152. }
  153. if (distance === 0) {
  154. initialIndent = upLineIndentLevel;
  155. continue;
  156. }
  157. if (distance === 1) {
  158. if (downLineNumber <= lineCount &&
  159. downLineIndentLevel >= 0 &&
  160. initialIndent + 1 === downLineIndentLevel) {
  161. // This is the beginning of a scope, we have special handling here, since we want the
  162. // child scope indent to be active, not the parent scope
  163. goUp = false;
  164. startLineNumber = downLineNumber;
  165. endLineNumber = downLineNumber;
  166. indent = downLineIndentLevel;
  167. continue;
  168. }
  169. if (upLineNumber >= 1 &&
  170. upLineIndentLevel >= 0 &&
  171. upLineIndentLevel - 1 === initialIndent) {
  172. // This is the end of a scope, just like above
  173. goDown = false;
  174. startLineNumber = upLineNumber;
  175. endLineNumber = upLineNumber;
  176. indent = upLineIndentLevel;
  177. continue;
  178. }
  179. startLineNumber = lineNumber;
  180. endLineNumber = lineNumber;
  181. indent = initialIndent;
  182. if (indent === 0) {
  183. // No need to continue
  184. return { startLineNumber, endLineNumber, indent };
  185. }
  186. }
  187. if (goUp) {
  188. if (upLineIndentLevel >= indent) {
  189. startLineNumber = upLineNumber;
  190. }
  191. else {
  192. goUp = false;
  193. }
  194. }
  195. if (goDown) {
  196. if (downLineIndentLevel >= indent) {
  197. endLineNumber = downLineNumber;
  198. }
  199. else {
  200. goDown = false;
  201. }
  202. }
  203. }
  204. return { startLineNumber, endLineNumber, indent };
  205. }
  206. getLinesBracketGuides(startLineNumber, endLineNumber, activePosition, options) {
  207. var _a;
  208. const result = [];
  209. for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) {
  210. result.push([]);
  211. }
  212. // If requested, this could be made configurable.
  213. const includeSingleLinePairs = true;
  214. const bracketPairs = this.textModel.bracketPairs.getBracketPairsInRangeWithMinIndentation(new Range(startLineNumber, 1, endLineNumber, this.textModel.getLineMaxColumn(endLineNumber)));
  215. let activeBracketPairRange = undefined;
  216. if (activePosition && bracketPairs.length > 0) {
  217. const bracketsContainingActivePosition = (startLineNumber <= activePosition.lineNumber &&
  218. activePosition.lineNumber <= endLineNumber
  219. // We don't need to query the brackets again if the cursor is in the viewport
  220. ? bracketPairs
  221. : this.textModel.bracketPairs.getBracketPairsInRange(Range.fromPositions(activePosition))).filter((bp) => Range.strictContainsPosition(bp.range, activePosition));
  222. activeBracketPairRange = (_a = findLast(bracketsContainingActivePosition, (i) => includeSingleLinePairs || i.range.startLineNumber !== i.range.endLineNumber)) === null || _a === void 0 ? void 0 : _a.range;
  223. }
  224. const independentColorPoolPerBracketType = this.textModel.getOptions().bracketPairColorizationOptions.independentColorPoolPerBracketType;
  225. const colorProvider = new BracketPairGuidesClassNames();
  226. for (const pair of bracketPairs) {
  227. /*
  228. {
  229. |
  230. }
  231. {
  232. |
  233. ----}
  234. ____{
  235. |test
  236. ----}
  237. renderHorizontalEndLineAtTheBottom:
  238. {
  239. |
  240. |x}
  241. --
  242. renderHorizontalEndLineAtTheBottom:
  243. ____{
  244. |test
  245. | x }
  246. ----
  247. */
  248. if (!pair.closingBracketRange) {
  249. continue;
  250. }
  251. const isActive = activeBracketPairRange && pair.range.equalsRange(activeBracketPairRange);
  252. if (!isActive && !options.includeInactive) {
  253. continue;
  254. }
  255. const className = colorProvider.getInlineClassName(pair.nestingLevel, pair.nestingLevelOfEqualBracketType, independentColorPoolPerBracketType) +
  256. (options.highlightActive && isActive
  257. ? ' ' + colorProvider.activeClassName
  258. : '');
  259. const start = pair.openingBracketRange.getStartPosition();
  260. const end = pair.closingBracketRange.getStartPosition();
  261. const horizontalGuides = options.horizontalGuides === HorizontalGuidesState.Enabled || (options.horizontalGuides === HorizontalGuidesState.EnabledForActive && isActive);
  262. if (pair.range.startLineNumber === pair.range.endLineNumber) {
  263. if (includeSingleLinePairs && horizontalGuides) {
  264. result[pair.range.startLineNumber - startLineNumber].push(new IndentGuide(-1, pair.openingBracketRange.getEndPosition().column, className, new IndentGuideHorizontalLine(false, end.column), -1, -1));
  265. }
  266. continue;
  267. }
  268. const endVisibleColumn = this.getVisibleColumnFromPosition(end);
  269. const startVisibleColumn = this.getVisibleColumnFromPosition(pair.openingBracketRange.getStartPosition());
  270. const guideVisibleColumn = Math.min(startVisibleColumn, endVisibleColumn, pair.minVisibleColumnIndentation + 1);
  271. let renderHorizontalEndLineAtTheBottom = false;
  272. const firstNonWsIndex = strings.firstNonWhitespaceIndex(this.textModel.getLineContent(pair.closingBracketRange.startLineNumber));
  273. const hasTextBeforeClosingBracket = firstNonWsIndex < pair.closingBracketRange.startColumn - 1;
  274. if (hasTextBeforeClosingBracket) {
  275. renderHorizontalEndLineAtTheBottom = true;
  276. }
  277. const visibleGuideStartLineNumber = Math.max(start.lineNumber, startLineNumber);
  278. const visibleGuideEndLineNumber = Math.min(end.lineNumber, endLineNumber);
  279. const offset = renderHorizontalEndLineAtTheBottom ? 1 : 0;
  280. for (let l = visibleGuideStartLineNumber; l < visibleGuideEndLineNumber + offset; l++) {
  281. result[l - startLineNumber].push(new IndentGuide(guideVisibleColumn, -1, className, null, l === start.lineNumber ? start.column : -1, l === end.lineNumber ? end.column : -1));
  282. }
  283. if (horizontalGuides) {
  284. if (start.lineNumber >= startLineNumber && startVisibleColumn > guideVisibleColumn) {
  285. result[start.lineNumber - startLineNumber].push(new IndentGuide(guideVisibleColumn, -1, className, new IndentGuideHorizontalLine(false, start.column), -1, -1));
  286. }
  287. if (end.lineNumber <= endLineNumber && endVisibleColumn > guideVisibleColumn) {
  288. result[end.lineNumber - startLineNumber].push(new IndentGuide(guideVisibleColumn, -1, className, new IndentGuideHorizontalLine(!renderHorizontalEndLineAtTheBottom, end.column), -1, -1));
  289. }
  290. }
  291. }
  292. for (const guides of result) {
  293. guides.sort((a, b) => a.visibleColumn - b.visibleColumn);
  294. }
  295. return result;
  296. }
  297. getVisibleColumnFromPosition(position) {
  298. return (CursorColumns.visibleColumnFromColumn(this.textModel.getLineContent(position.lineNumber), position.column, this.textModel.getOptions().tabSize) + 1);
  299. }
  300. getLinesIndentGuides(startLineNumber, endLineNumber) {
  301. this.assertNotDisposed();
  302. const lineCount = this.textModel.getLineCount();
  303. if (startLineNumber < 1 || startLineNumber > lineCount) {
  304. throw new Error('Illegal value for startLineNumber');
  305. }
  306. if (endLineNumber < 1 || endLineNumber > lineCount) {
  307. throw new Error('Illegal value for endLineNumber');
  308. }
  309. const options = this.textModel.getOptions();
  310. const foldingRules = this.getLanguageConfiguration(this.textModel.getLanguageId()).foldingRules;
  311. const offSide = Boolean(foldingRules && foldingRules.offSide);
  312. const result = new Array(endLineNumber - startLineNumber + 1);
  313. let aboveContentLineIndex = -2; /* -2 is a marker for not having computed it */
  314. let aboveContentLineIndent = -1;
  315. let belowContentLineIndex = -2; /* -2 is a marker for not having computed it */
  316. let belowContentLineIndent = -1;
  317. for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) {
  318. const resultIndex = lineNumber - startLineNumber;
  319. const currentIndent = this._computeIndentLevel(lineNumber - 1);
  320. if (currentIndent >= 0) {
  321. // This line has content (besides whitespace)
  322. // Use the line's indent
  323. aboveContentLineIndex = lineNumber - 1;
  324. aboveContentLineIndent = currentIndent;
  325. result[resultIndex] = Math.ceil(currentIndent / options.indentSize);
  326. continue;
  327. }
  328. if (aboveContentLineIndex === -2) {
  329. aboveContentLineIndex = -1;
  330. aboveContentLineIndent = -1;
  331. // must find previous line with content
  332. for (let lineIndex = lineNumber - 2; lineIndex >= 0; lineIndex--) {
  333. const indent = this._computeIndentLevel(lineIndex);
  334. if (indent >= 0) {
  335. aboveContentLineIndex = lineIndex;
  336. aboveContentLineIndent = indent;
  337. break;
  338. }
  339. }
  340. }
  341. if (belowContentLineIndex !== -1 &&
  342. (belowContentLineIndex === -2 || belowContentLineIndex < lineNumber - 1)) {
  343. belowContentLineIndex = -1;
  344. belowContentLineIndent = -1;
  345. // must find next line with content
  346. for (let lineIndex = lineNumber; lineIndex < lineCount; lineIndex++) {
  347. const indent = this._computeIndentLevel(lineIndex);
  348. if (indent >= 0) {
  349. belowContentLineIndex = lineIndex;
  350. belowContentLineIndent = indent;
  351. break;
  352. }
  353. }
  354. }
  355. result[resultIndex] = this._getIndentLevelForWhitespaceLine(offSide, aboveContentLineIndent, belowContentLineIndent);
  356. }
  357. return result;
  358. }
  359. _getIndentLevelForWhitespaceLine(offSide, aboveContentLineIndent, belowContentLineIndent) {
  360. const options = this.textModel.getOptions();
  361. if (aboveContentLineIndent === -1 || belowContentLineIndent === -1) {
  362. // At the top or bottom of the file
  363. return 0;
  364. }
  365. else if (aboveContentLineIndent < belowContentLineIndent) {
  366. // we are inside the region above
  367. return 1 + Math.floor(aboveContentLineIndent / options.indentSize);
  368. }
  369. else if (aboveContentLineIndent === belowContentLineIndent) {
  370. // we are in between two regions
  371. return Math.ceil(belowContentLineIndent / options.indentSize);
  372. }
  373. else {
  374. if (offSide) {
  375. // same level as region below
  376. return Math.ceil(belowContentLineIndent / options.indentSize);
  377. }
  378. else {
  379. // we are inside the region that ends below
  380. return 1 + Math.floor(belowContentLineIndent / options.indentSize);
  381. }
  382. }
  383. }
  384. }
  385. export class BracketPairGuidesClassNames {
  386. constructor() {
  387. this.activeClassName = 'indent-active';
  388. }
  389. getInlineClassName(nestingLevel, nestingLevelOfEqualBracketType, independentColorPoolPerBracketType) {
  390. return this.getInlineClassNameOfLevel(independentColorPoolPerBracketType ? nestingLevelOfEqualBracketType : nestingLevel);
  391. }
  392. getInlineClassNameOfLevel(level) {
  393. // To support a dynamic amount of colors up to 6 colors,
  394. // we use a number that is a lcm of all numbers from 1 to 6.
  395. return `bracket-indent-guide lvl-${level % 30}`;
  396. }
  397. }