0796571e753c64ee78378b651e7bc7cd674d67618fe804673ffa8bcd1fd7b21d009f0af36b1c48239af7b9976dc3e2101a169c5fdf702eed367fae2330c618 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  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 { Position } from '../core/position.js';
  6. import { Range } from '../core/range.js';
  7. import { InlineDecoration, ViewModelDecoration } from '../viewModel.js';
  8. import { filterValidationDecorations } from '../config/editorOptions.js';
  9. export class ViewModelDecorations {
  10. constructor(editorId, model, configuration, linesCollection, coordinatesConverter) {
  11. this.editorId = editorId;
  12. this.model = model;
  13. this.configuration = configuration;
  14. this._linesCollection = linesCollection;
  15. this._coordinatesConverter = coordinatesConverter;
  16. this._decorationsCache = Object.create(null);
  17. this._cachedModelDecorationsResolver = null;
  18. this._cachedModelDecorationsResolverViewRange = null;
  19. }
  20. _clearCachedModelDecorationsResolver() {
  21. this._cachedModelDecorationsResolver = null;
  22. this._cachedModelDecorationsResolverViewRange = null;
  23. }
  24. dispose() {
  25. this._decorationsCache = Object.create(null);
  26. this._clearCachedModelDecorationsResolver();
  27. }
  28. reset() {
  29. this._decorationsCache = Object.create(null);
  30. this._clearCachedModelDecorationsResolver();
  31. }
  32. onModelDecorationsChanged() {
  33. this._decorationsCache = Object.create(null);
  34. this._clearCachedModelDecorationsResolver();
  35. }
  36. onLineMappingChanged() {
  37. this._decorationsCache = Object.create(null);
  38. this._clearCachedModelDecorationsResolver();
  39. }
  40. _getOrCreateViewModelDecoration(modelDecoration) {
  41. const id = modelDecoration.id;
  42. let r = this._decorationsCache[id];
  43. if (!r) {
  44. const modelRange = modelDecoration.range;
  45. const options = modelDecoration.options;
  46. let viewRange;
  47. if (options.isWholeLine) {
  48. const start = this._coordinatesConverter.convertModelPositionToViewPosition(new Position(modelRange.startLineNumber, 1), 0 /* PositionAffinity.Left */);
  49. const end = this._coordinatesConverter.convertModelPositionToViewPosition(new Position(modelRange.endLineNumber, this.model.getLineMaxColumn(modelRange.endLineNumber)), 1 /* PositionAffinity.Right */);
  50. viewRange = new Range(start.lineNumber, start.column, end.lineNumber, end.column);
  51. }
  52. else {
  53. // For backwards compatibility reasons, we want injected text before any decoration.
  54. // Thus, move decorations to the right.
  55. viewRange = this._coordinatesConverter.convertModelRangeToViewRange(modelRange, 1 /* PositionAffinity.Right */);
  56. }
  57. r = new ViewModelDecoration(viewRange, options);
  58. this._decorationsCache[id] = r;
  59. }
  60. return r;
  61. }
  62. getDecorationsViewportData(viewRange) {
  63. let cacheIsValid = (this._cachedModelDecorationsResolver !== null);
  64. cacheIsValid = cacheIsValid && (viewRange.equalsRange(this._cachedModelDecorationsResolverViewRange));
  65. if (!cacheIsValid) {
  66. this._cachedModelDecorationsResolver = this._getDecorationsInRange(viewRange);
  67. this._cachedModelDecorationsResolverViewRange = viewRange;
  68. }
  69. return this._cachedModelDecorationsResolver;
  70. }
  71. getInlineDecorationsOnLine(lineNumber) {
  72. const range = new Range(lineNumber, this._linesCollection.getViewLineMinColumn(lineNumber), lineNumber, this._linesCollection.getViewLineMaxColumn(lineNumber));
  73. return this._getDecorationsInRange(range).inlineDecorations[0];
  74. }
  75. _getDecorationsInRange(viewRange) {
  76. const modelDecorations = this._linesCollection.getDecorationsInRange(viewRange, this.editorId, filterValidationDecorations(this.configuration.options));
  77. const startLineNumber = viewRange.startLineNumber;
  78. const endLineNumber = viewRange.endLineNumber;
  79. const decorationsInViewport = [];
  80. let decorationsInViewportLen = 0;
  81. const inlineDecorations = [];
  82. for (let j = startLineNumber; j <= endLineNumber; j++) {
  83. inlineDecorations[j - startLineNumber] = [];
  84. }
  85. for (let i = 0, len = modelDecorations.length; i < len; i++) {
  86. const modelDecoration = modelDecorations[i];
  87. const decorationOptions = modelDecoration.options;
  88. if (!isModelDecorationVisible(this.model, modelDecoration)) {
  89. continue;
  90. }
  91. const viewModelDecoration = this._getOrCreateViewModelDecoration(modelDecoration);
  92. const viewRange = viewModelDecoration.range;
  93. decorationsInViewport[decorationsInViewportLen++] = viewModelDecoration;
  94. if (decorationOptions.inlineClassName) {
  95. const inlineDecoration = new InlineDecoration(viewRange, decorationOptions.inlineClassName, decorationOptions.inlineClassNameAffectsLetterSpacing ? 3 /* InlineDecorationType.RegularAffectingLetterSpacing */ : 0 /* InlineDecorationType.Regular */);
  96. const intersectedStartLineNumber = Math.max(startLineNumber, viewRange.startLineNumber);
  97. const intersectedEndLineNumber = Math.min(endLineNumber, viewRange.endLineNumber);
  98. for (let j = intersectedStartLineNumber; j <= intersectedEndLineNumber; j++) {
  99. inlineDecorations[j - startLineNumber].push(inlineDecoration);
  100. }
  101. }
  102. if (decorationOptions.beforeContentClassName) {
  103. if (startLineNumber <= viewRange.startLineNumber && viewRange.startLineNumber <= endLineNumber) {
  104. const inlineDecoration = new InlineDecoration(new Range(viewRange.startLineNumber, viewRange.startColumn, viewRange.startLineNumber, viewRange.startColumn), decorationOptions.beforeContentClassName, 1 /* InlineDecorationType.Before */);
  105. inlineDecorations[viewRange.startLineNumber - startLineNumber].push(inlineDecoration);
  106. }
  107. }
  108. if (decorationOptions.afterContentClassName) {
  109. if (startLineNumber <= viewRange.endLineNumber && viewRange.endLineNumber <= endLineNumber) {
  110. const inlineDecoration = new InlineDecoration(new Range(viewRange.endLineNumber, viewRange.endColumn, viewRange.endLineNumber, viewRange.endColumn), decorationOptions.afterContentClassName, 2 /* InlineDecorationType.After */);
  111. inlineDecorations[viewRange.endLineNumber - startLineNumber].push(inlineDecoration);
  112. }
  113. }
  114. }
  115. return {
  116. decorations: decorationsInViewport,
  117. inlineDecorations: inlineDecorations
  118. };
  119. }
  120. }
  121. export function isModelDecorationVisible(model, decoration) {
  122. if (decoration.options.hideInCommentTokens && isModelDecorationInComment(model, decoration)) {
  123. return false;
  124. }
  125. if (decoration.options.hideInStringTokens && isModelDecorationInString(model, decoration)) {
  126. return false;
  127. }
  128. return true;
  129. }
  130. export function isModelDecorationInComment(model, decoration) {
  131. return testTokensInRange(model, decoration.range, (tokenType) => tokenType === 1 /* StandardTokenType.Comment */);
  132. }
  133. export function isModelDecorationInString(model, decoration) {
  134. return testTokensInRange(model, decoration.range, (tokenType) => tokenType === 2 /* StandardTokenType.String */);
  135. }
  136. /**
  137. * Calls the callback for every token that intersects the range.
  138. * If the callback returns `false`, iteration stops and `false` is returned.
  139. * Otherwise, `true` is returned.
  140. */
  141. function testTokensInRange(model, range, callback) {
  142. for (let lineNumber = range.startLineNumber; lineNumber <= range.endLineNumber; lineNumber++) {
  143. const lineTokens = model.tokenization.getLineTokens(lineNumber);
  144. const isFirstLine = lineNumber === range.startLineNumber;
  145. const isEndLine = lineNumber === range.endLineNumber;
  146. let tokenIdx = isFirstLine ? lineTokens.findTokenIndexAtOffset(range.startColumn - 1) : 0;
  147. while (tokenIdx < lineTokens.getCount()) {
  148. if (isEndLine) {
  149. const startOffset = lineTokens.getStartOffset(tokenIdx);
  150. if (startOffset > range.endColumn - 1) {
  151. break;
  152. }
  153. }
  154. const callbackResult = callback(lineTokens.getStandardTokenType(tokenIdx));
  155. if (!callbackResult) {
  156. return false;
  157. }
  158. tokenIdx++;
  159. }
  160. }
  161. return true;
  162. }