From d53f4c55443653a8ff20b1f1034d09ccf324adde Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Mon, 25 Jul 2022 11:20:56 +0200 Subject: [PATCH] Improves performance of bracket pair colorization --- src/vs/base/common/arrays.ts | 70 ++++++ .../browser/viewParts/minimap/minimap.ts | 2 +- src/vs/editor/common/model.ts | 2 +- .../bracketPairsImpl.ts | 56 ++--- .../bracketPairsTree/bracketPairsTree.ts | 213 +++++++++--------- ...colorizedBracketPairsDecorationProvider.ts | 36 +-- .../editor/common/model/decorationProvider.ts | 2 +- .../common/model/guidesTextModelPart.ts | 4 +- src/vs/editor/common/model/textModel.ts | 4 +- src/vs/editor/common/textModelBracketPairs.ts | 7 +- src/vs/editor/common/viewModel.ts | 2 +- .../common/viewModel/viewModelDecorations.ts | 11 +- .../editor/common/viewModel/viewModelImpl.ts | 4 +- .../editor/common/viewModel/viewModelLines.ts | 14 +- .../getBracketPairsInRange.test.ts | 12 +- src/vs/monaco.d.ts | 2 +- 16 files changed, 263 insertions(+), 178 deletions(-) diff --git a/src/vs/base/common/arrays.ts b/src/vs/base/common/arrays.ts index d543fdd8f83a0..0b91b28724702 100644 --- a/src/vs/base/common/arrays.ts +++ b/src/vs/base/common/arrays.ts @@ -829,3 +829,73 @@ export class ArrayQueue { return result; } } + +/** + * This class is faster than an iterator and array for lazy computed data. +*/ +export class CallbackIterable { + public static readonly empty = new CallbackIterable(_callback => { }); + + constructor( + /** + * Calls the callback for every item. + * Stops when the callback returns false. + */ + public readonly iterate: (callback: (item: T) => boolean) => void + ) { + } + + forEach(handler: (item: T) => void) { + this.iterate(item => { handler(item); return true; }); + } + + toArray(): T[] { + const result: T[] = []; + this.iterate(item => { result.push(item); return true; }); + return result; + } + + filter(predicate: (item: T) => boolean): CallbackIterable { + return new CallbackIterable(cb => this.iterate(item => predicate(item) ? cb(item) : true)); + } + + map(mapFn: (item: T) => TResult): CallbackIterable { + return new CallbackIterable(cb => this.iterate(item => cb(mapFn(item)))); + } + + findFirst(predicate: (item: T) => boolean): T | undefined { + let result: T | undefined; + this.iterate(item => { + if (predicate(item)) { + result = item; + return false; + } + return true; + }); + return result; + } + + findLast(predicate: (item: T) => boolean): T | undefined { + let result: T | undefined; + this.iterate(item => { + if (predicate(item)) { + result = item; + } + return true; + }); + return result; + } + + findLastMaxBy(comparator: Comparator): T | undefined { + let result: T | undefined; + let first = true; + this.iterate(item => { + if (first || CompareResult.isGreaterThan(comparator(item, result!))) { + first = false; + result = item; + } + return true; + }); + return result; + } +} diff --git a/src/vs/editor/browser/viewParts/minimap/minimap.ts b/src/vs/editor/browser/viewParts/minimap/minimap.ts index ab06570ed5f1f..12d68b9b527d8 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimap.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimap.ts @@ -1021,7 +1021,7 @@ export class Minimap extends ViewPart implements IMinimapModel { } else { visibleRange = new Range(startLineNumber, 1, endLineNumber, this._context.viewModel.getLineMaxColumn(endLineNumber)); } - const decorations = this._context.viewModel.getDecorationsInViewport(visibleRange); + const decorations = this._context.viewModel.getDecorationsInViewport(visibleRange, true); if (this._samplingState) { const result: ViewModelDecoration[] = []; diff --git a/src/vs/editor/common/model.ts b/src/vs/editor/common/model.ts index 354026fdcb5f2..c847c5ecd6451 100644 --- a/src/vs/editor/common/model.ts +++ b/src/vs/editor/common/model.ts @@ -941,7 +941,7 @@ export interface ITextModel { * @param filterOutValidation If set, it will ignore decorations specific to validation (i.e. warnings, errors). * @return An array with the decorations */ - getDecorationsInRange(range: IRange, ownerId?: number, filterOutValidation?: boolean): IModelDecoration[]; + getDecorationsInRange(range: IRange, ownerId?: number, filterOutValidation?: boolean, onlyMinimapDecorations?: boolean): IModelDecoration[]; /** * Gets all the decorations as an array. diff --git a/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsImpl.ts b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsImpl.ts index d5c012141f605..853b1f1fe8691 100644 --- a/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsImpl.ts +++ b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsImpl.ts @@ -3,20 +3,20 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { CallbackIterable, compareBy } from 'vs/base/common/arrays'; import { Emitter } from 'vs/base/common/event'; import { Disposable, DisposableStore, IDisposable, IReference, MutableDisposable } from 'vs/base/common/lifecycle'; -import { LineTokens } from 'vs/editor/common/tokens/lineTokens'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; -import { BracketPairsTree } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/bracketPairsTree'; -import { BracketInfo, BracketPairInfo, BracketPairWithMinIndentationInfo, IBracketPairsTextModelPart, IFoundBracket } from 'vs/editor/common/textModelBracketPairs'; -import { TextModel } from 'vs/editor/common/model/textModel'; -import { IModelContentChangedEvent, IModelLanguageChangedEvent, IModelOptionsChangedEvent, IModelTokensChangedEvent } from 'vs/editor/common/textModelEvents'; import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { ignoreBracketsInToken } from 'vs/editor/common/languages/supports'; -import { RichEditBrackets, BracketsUtils, RichEditBracket } from 'vs/editor/common/languages/supports/richEditBrackets'; -import { compareBy, findLast, findLastMaxBy } from 'vs/base/common/arrays'; import { LanguageBracketsConfiguration } from 'vs/editor/common/languages/supports/languageBracketsConfiguration'; +import { BracketsUtils, RichEditBracket, RichEditBrackets } from 'vs/editor/common/languages/supports/richEditBrackets'; +import { BracketPairsTree } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/bracketPairsTree'; +import { TextModel } from 'vs/editor/common/model/textModel'; +import { BracketInfo, BracketPairInfo, BracketPairWithMinIndentationInfo, IBracketPairsTextModelPart, IFoundBracket } from 'vs/editor/common/textModelBracketPairs'; +import { IModelContentChangedEvent, IModelLanguageChangedEvent, IModelOptionsChangedEvent, IModelTokensChangedEvent } from 'vs/editor/common/textModelEvents'; +import { LineTokens } from 'vs/editor/common/tokens/lineTokens'; export class BracketPairsTextModelPart extends Disposable implements IBracketPairsTextModelPart { private readonly bracketPairsTree = this._register(new MutableDisposable>()); @@ -102,22 +102,22 @@ export class BracketPairsTextModelPart extends Disposable implements IBracketPai * Returns all bracket pairs that intersect the given range. * The result is sorted by the start position. */ - public getBracketPairsInRange(range: Range): BracketPairInfo[] { + public getBracketPairsInRange(range: Range): CallbackIterable { this.bracketsRequested = true; this.updateBracketPairsTree(); - return this.bracketPairsTree.value?.object.getBracketPairsInRange(range, false) || []; + return this.bracketPairsTree.value?.object.getBracketPairsInRange(range, false) || CallbackIterable.empty; } - public getBracketPairsInRangeWithMinIndentation(range: Range): BracketPairWithMinIndentationInfo[] { + public getBracketPairsInRangeWithMinIndentation(range: Range): CallbackIterable { this.bracketsRequested = true; this.updateBracketPairsTree(); - return this.bracketPairsTree.value?.object.getBracketPairsInRange(range, true) || []; + return this.bracketPairsTree.value?.object.getBracketPairsInRange(range, true) || CallbackIterable.empty; } - public getBracketsInRange(range: Range): BracketInfo[] { + public getBracketsInRange(range: Range): CallbackIterable { this.bracketsRequested = true; this.updateBracketPairsTree(); - return this.bracketPairsTree.value?.object.getBracketsInRange(range) || []; + return this.bracketPairsTree.value?.object.getBracketsInRange(range) || CallbackIterable.empty; } public findMatchingBracketUp(_bracket: string, _position: IPosition, maxDuration?: number): Range | null { @@ -133,7 +133,7 @@ export class BracketPairsTextModelPart extends Disposable implements IBracketPai return null; } - const bracketPair = findLast(this.getBracketPairsInRange(Range.fromPositions(_position, _position)) || [], (b) => + const bracketPair = this.getBracketPairsInRange(Range.fromPositions(_position, _position)).findLast((b) => closingBracketInfo.closes(b.openingBracketInfo) ); @@ -163,7 +163,7 @@ export class BracketPairsTextModelPart extends Disposable implements IBracketPai public matchBracket(position: IPosition, maxDuration?: number): [Range, Range] | null { if (this.canBuildAST) { - const bracketPair = findLastMaxBy( + const bracketPair = this.getBracketPairsInRange( Range.fromPositions(position, position) ).filter( @@ -171,15 +171,15 @@ export class BracketPairsTextModelPart extends Disposable implements IBracketPai item.closingBracketRange !== undefined && (item.openingBracketRange.containsPosition(position) || item.closingBracketRange.containsPosition(position)) - ), - compareBy( - (item) => - item.openingBracketRange.containsPosition(position) - ? item.openingBracketRange - : item.closingBracketRange, - Range.compareRangesUsingStarts - ) - ); + ).findLastMaxBy( + compareBy( + (item) => + item.openingBracketRange.containsPosition(position) + ? item.openingBracketRange + : item.closingBracketRange, + Range.compareRangesUsingStarts + ) + ); if (bracketPair) { return [bracketPair.openingBracketRange, bracketPair.closingBracketRange!]; } @@ -674,10 +674,10 @@ export class BracketPairsTextModelPart extends Disposable implements IBracketPai if (this.canBuildAST) { const range = Range.fromPositions(position); - const bracketPair = findLast( - this.getBracketPairsInRange(Range.fromPositions(position, position)), - (item) => item.closingBracketRange !== undefined && item.range.strictContainsRange(range) - ); + const bracketPair = + this.getBracketPairsInRange(Range.fromPositions(position, position)).findLast( + (item) => item.closingBracketRange !== undefined && item.range.strictContainsRange(range) + ); if (bracketPair) { return [bracketPair.openingBracketRange, bracketPair.closingBracketRange!]; } diff --git a/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/bracketPairsTree.ts b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/bracketPairsTree.ts index 9ea7e35847cb4..e79d4efb21b6c 100644 --- a/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/bracketPairsTree.ts +++ b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/bracketPairsTree.ts @@ -20,6 +20,7 @@ import { DenseKeyProvider } from './smallImmutableSet'; import { FastTokenizer, TextBufferTokenizer } from './tokenizer'; import { BackgroundTokenizationState } from 'vs/editor/common/tokenizationTextModelPart'; import { Position } from 'vs/editor/common/core/position'; +import { CallbackIterable } from 'vs/base/common/arrays'; export class BracketPairsTree extends Disposable { private readonly didChangeEmitter = new Emitter(); @@ -125,26 +126,24 @@ export class BracketPairsTree extends Disposable { return result; } - public getBracketsInRange(range: Range): BracketInfo[] { + public getBracketsInRange(range: Range): CallbackIterable { const startOffset = toLength(range.startLineNumber - 1, range.startColumn - 1); const endOffset = toLength(range.endLineNumber - 1, range.endColumn - 1); - const result = new Array(); - const node = this.initialAstWithoutTokens || this.astWithTokens!; - collectBrackets(node, lengthZero, node.length, startOffset, endOffset, result, 0, new Map()); - return result; + return new CallbackIterable(cb => { + const node = this.initialAstWithoutTokens || this.astWithTokens!; + collectBrackets(node, lengthZero, node.length, startOffset, endOffset, cb, 0, new Map()); + }); } - public getBracketPairsInRange(range: Range, includeMinIndentation: boolean): BracketPairWithMinIndentationInfo[] { - const result = new Array(); - + public getBracketPairsInRange(range: Range, includeMinIndentation: boolean): CallbackIterable { const startLength = positionToLength(range.getStartPosition()); const endLength = positionToLength(range.getEndPosition()); - const node = this.initialAstWithoutTokens || this.astWithTokens!; - const context = new CollectBracketPairsContext(result, includeMinIndentation, this.textModel); - collectBracketPairs(node, lengthZero, node.length, startLength, endLength, context, 0, new Map()); - - return result; + return new CallbackIterable(cb => { + const node = this.initialAstWithoutTokens || this.astWithTokens!; + const context = new CollectBracketPairsContext(cb, includeMinIndentation, this.textModel); + collectBracketPairs(node, lengthZero, node.length, startLength, endLength, context, 0, new Map()); + }); } public getFirstBracketAfter(position: Position): IFoundBracket | null { @@ -219,108 +218,105 @@ function collectBrackets( nodeOffsetEnd: Length, startOffset: Length, endOffset: Length, - result: BracketInfo[], + push: (item: BracketInfo) => boolean, level: number, levelPerBracketType: Map -): void { +): boolean { if (level > 200) { - return; + return true; } - if (node.kind === AstNodeKind.List) { - for (const child of node.children) { - nodeOffsetEnd = lengthAdd(nodeOffsetStart, child.length); - if ( - lengthLessThanEqual(nodeOffsetStart, endOffset) && - lengthGreaterThanEqual(nodeOffsetEnd, startOffset) - ) { - collectBrackets( - child, - nodeOffsetStart, - nodeOffsetEnd, - startOffset, - endOffset, - result, - level, - levelPerBracketType - ); - } - nodeOffsetStart = nodeOffsetEnd; - } - } else if (node.kind === AstNodeKind.Pair) { - let levelPerBracket = 0; - if (levelPerBracketType) { - let existing = levelPerBracketType.get(node.openingBracket.text); - if (existing === undefined) { - existing = 0; + whileLoop: + while (true) { + switch (node.kind) { + case AstNodeKind.List: { + const childCount = node.childrenLength; + for (let i = 0; i < childCount; i++) { + const child = node.getChild(i); + if (!child) { + continue; + } + nodeOffsetEnd = lengthAdd(nodeOffsetStart, child.length); + if ( + lengthLessThanEqual(nodeOffsetStart, endOffset) && + lengthGreaterThanEqual(nodeOffsetEnd, startOffset) + ) { + const childEndsAfterEnd = lengthGreaterThanEqual(nodeOffsetEnd, endOffset); + if (childEndsAfterEnd) { + // No child after this child in the requested window, don't recurse + node = child; + continue whileLoop; + } + + const shouldContinue = collectBrackets(child, nodeOffsetStart, nodeOffsetEnd, startOffset, endOffset, push, level, levelPerBracketType); + if (!shouldContinue) { + return false; + } + } + nodeOffsetStart = nodeOffsetEnd; + } + return true; } - levelPerBracket = existing; - existing++; - levelPerBracketType.set(node.openingBracket.text, existing); - } + case AstNodeKind.Pair: { + let levelPerBracket = 0; + if (levelPerBracketType) { + let existing = levelPerBracketType.get(node.openingBracket.text); + if (existing === undefined) { + existing = 0; + } + levelPerBracket = existing; + existing++; + levelPerBracketType.set(node.openingBracket.text, existing); + } - // Don't use node.children here to improve performance - { - const child = node.openingBracket; - nodeOffsetEnd = lengthAdd(nodeOffsetStart, child.length); - if ( - lengthLessThanEqual(nodeOffsetStart, endOffset) && - lengthGreaterThanEqual(nodeOffsetEnd, startOffset) - ) { - const range = lengthsToRange(nodeOffsetStart, nodeOffsetEnd); - result.push( - new BracketInfo(range, level, levelPerBracket, !node.closingBracket) - ); - } - nodeOffsetStart = nodeOffsetEnd; - } + const childCount = node.childrenLength; + for (let i = 0; i < childCount; i++) { + const child = node.getChild(i); + if (!child) { + continue; + } + nodeOffsetEnd = lengthAdd(nodeOffsetStart, child.length); + if ( + lengthLessThanEqual(nodeOffsetStart, endOffset) && + lengthGreaterThanEqual(nodeOffsetEnd, startOffset) + ) { + const childEndsAfterEnd = lengthGreaterThanEqual(nodeOffsetEnd, endOffset); + if (childEndsAfterEnd) { + // No child after this child in the requested window, don't recurse + node = child; + level++; + continue whileLoop; + } + + const shouldContinue = collectBrackets(child, nodeOffsetStart, nodeOffsetEnd, startOffset, endOffset, push, level + 1, levelPerBracketType); + if (!shouldContinue) { + return false; + } + } + nodeOffsetStart = nodeOffsetEnd; + } - if (node.child) { - const child = node.child; - nodeOffsetEnd = lengthAdd(nodeOffsetStart, child.length); - if ( - lengthLessThanEqual(nodeOffsetStart, endOffset) && - lengthGreaterThanEqual(nodeOffsetEnd, startOffset) - ) { - collectBrackets( - child, - nodeOffsetStart, - nodeOffsetEnd, - startOffset, - endOffset, - result, - level + 1, - levelPerBracketType - ); + levelPerBracketType?.set(node.openingBracket.text, levelPerBracket); + + return true; } - nodeOffsetStart = nodeOffsetEnd; - } - if (node.closingBracket) { - const child = node.closingBracket; - nodeOffsetEnd = lengthAdd(nodeOffsetStart, child.length); - if ( - lengthLessThanEqual(nodeOffsetStart, endOffset) && - lengthGreaterThanEqual(nodeOffsetEnd, startOffset) - ) { + case AstNodeKind.UnexpectedClosingBracket: { const range = lengthsToRange(nodeOffsetStart, nodeOffsetEnd); - result.push(new BracketInfo(range, level, levelPerBracket, false)); + return push(new BracketInfo(range, level - 1, 0, true)); } - nodeOffsetStart = nodeOffsetEnd; + case AstNodeKind.Bracket: { + const range = lengthsToRange(nodeOffsetStart, nodeOffsetEnd); + return push(new BracketInfo(range, level - 1, 0, false)); + } + case AstNodeKind.Text: + return true; } - - levelPerBracketType?.set(node.openingBracket.text, levelPerBracket); - } else if (node.kind === AstNodeKind.UnexpectedClosingBracket) { - const range = lengthsToRange(nodeOffsetStart, nodeOffsetEnd); - result.push(new BracketInfo(range, level - 1, 0, true)); - } else if (node.kind === AstNodeKind.Bracket) { - const range = lengthsToRange(nodeOffsetStart, nodeOffsetEnd); - result.push(new BracketInfo(range, level - 1, 0, false)); } } class CollectBracketPairsContext { constructor( - public readonly result: BracketPairWithMinIndentationInfo[], + public readonly push: (item: BracketPairWithMinIndentationInfo) => boolean, public readonly includeMinIndentation: boolean, public readonly textModel: ITextModel, ) { @@ -336,11 +332,13 @@ function collectBracketPairs( context: CollectBracketPairsContext, level: number, levelPerBracketType: Map -) { +): boolean { if (level > 200) { - return; + return true; } + let shouldContinue = true; + if (node.kind === AstNodeKind.Pair) { let levelPerBracket = 0; if (levelPerBracketType) { @@ -362,7 +360,7 @@ function collectBracketPairs( ); } - context.result.push( + shouldContinue = context.push( new BracketPairWithMinIndentationInfo( lengthsToRange(nodeOffsetStart, nodeOffsetEnd), lengthsToRange(nodeOffsetStart, openingBracketEnd), @@ -380,14 +378,14 @@ function collectBracketPairs( ); nodeOffsetStart = openingBracketEnd; - if (node.child) { + if (shouldContinue && node.child) { const child = node.child; nodeOffsetEnd = lengthAdd(nodeOffsetStart, child.length); if ( lengthLessThanEqual(nodeOffsetStart, endOffset) && lengthGreaterThanEqual(nodeOffsetEnd, startOffset) ) { - collectBracketPairs( + shouldContinue = collectBracketPairs( child, nodeOffsetStart, nodeOffsetEnd, @@ -397,6 +395,9 @@ function collectBracketPairs( level + 1, levelPerBracketType ); + if (!shouldContinue) { + return false; + } } } @@ -411,7 +412,7 @@ function collectBracketPairs( lengthLessThanEqual(childOffset, endOffset) && lengthLessThanEqual(startOffset, curOffset) ) { - collectBracketPairs( + shouldContinue = collectBracketPairs( child, childOffset, curOffset, @@ -421,8 +422,12 @@ function collectBracketPairs( level, levelPerBracketType ); + if (!shouldContinue) { + return false; + } } } } + return shouldContinue; } diff --git a/src/vs/editor/common/model/bracketPairsTextModelPart/colorizedBracketPairsDecorationProvider.ts b/src/vs/editor/common/model/bracketPairsTextModelPart/colorizedBracketPairsDecorationProvider.ts index 1e4d0a0ed0516..21d52586ae386 100644 --- a/src/vs/editor/common/model/bracketPairsTextModelPart/colorizedBracketPairsDecorationProvider.ts +++ b/src/vs/editor/common/model/bracketPairsTextModelPart/colorizedBracketPairsDecorationProvider.ts @@ -42,7 +42,11 @@ export class ColorizedBracketPairsDecorationProvider extends Disposable implemen //#endregion - getDecorationsInRange(range: Range, ownerId?: number, filterOutValidation?: boolean): IModelDecoration[] { + getDecorationsInRange(range: Range, ownerId?: number, filterOutValidation?: boolean, onlyMinimapDecorations?: boolean): IModelDecoration[] { + if (onlyMinimapDecorations) { + // Bracket pair colorization decorations are not rendered in the minimap + return []; + } if (ownerId === undefined) { return []; } @@ -50,22 +54,20 @@ export class ColorizedBracketPairsDecorationProvider extends Disposable implemen return []; } - const result = new Array(); - const bracketsInRange = this.textModel.bracketPairs.getBracketsInRange(range); - for (const bracket of bracketsInRange) { - result.push({ - id: `bracket${bracket.range.toString()}-${bracket.nestingLevel}`, - options: { - description: 'BracketPairColorization', - inlineClassName: this.colorProvider.getInlineClassName( - bracket, - this.colorizationOptions.independentColorPoolPerBracketType - ), - }, - ownerId: 0, - range: bracket.range, - }); - } + const result = this.textModel.bracketPairs.getBracketsInRange(range).map(bracket => + ({ + id: `bracket${bracket.range.toString()}-${bracket.nestingLevel}`, + options: { + description: 'BracketPairColorization', + inlineClassName: this.colorProvider.getInlineClassName( + bracket, + this.colorizationOptions.independentColorPoolPerBracketType + ), + }, + ownerId: 0, + range: bracket.range, + })).toArray(); + return result; } diff --git a/src/vs/editor/common/model/decorationProvider.ts b/src/vs/editor/common/model/decorationProvider.ts index c9ef6e3df0e6c..af8452a6101b0 100644 --- a/src/vs/editor/common/model/decorationProvider.ts +++ b/src/vs/editor/common/model/decorationProvider.ts @@ -23,7 +23,7 @@ export interface DecorationProvider { * @param ownerId If set, it will ignore decorations belonging to other owners. * @param filterOutValidation If set, it will ignore decorations specific to validation (i.e. warnings, errors). */ - getAllDecorations(ownerId?: number, filterOutValidation?: boolean): IModelDecoration[]; + getAllDecorations(ownerId?: number, filterOutValidation?: boolean, onlyMinimapDecorations?: boolean): IModelDecoration[]; onDidChange: Event; } diff --git a/src/vs/editor/common/model/guidesTextModelPart.ts b/src/vs/editor/common/model/guidesTextModelPart.ts index 9e7937f7ad617..8d99b36c0260e 100644 --- a/src/vs/editor/common/model/guidesTextModelPart.ts +++ b/src/vs/editor/common/model/guidesTextModelPart.ts @@ -292,7 +292,7 @@ export class GuidesTextModelPart extends TextModelPart implements IGuidesTextMod endLineNumber, this.textModel.getLineMaxColumn(endLineNumber) ) - ); + ).toArray(); let activeBracketPairRange: Range | undefined = undefined; if (activePosition && bracketPairs.length > 0) { @@ -303,7 +303,7 @@ export class GuidesTextModelPart extends TextModelPart implements IGuidesTextMod ? bracketPairs : this.textModel.bracketPairs.getBracketPairsInRange( Range.fromPositions(activePosition) - ) + ).toArray() ).filter((bp) => Range.strictContainsPosition(bp.range, activePosition)); activeBracketPairRange = findLast( diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index 690a2eab09ae9..305b91eb36936 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -1711,11 +1711,11 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati return decorations; } - public getDecorationsInRange(range: IRange, ownerId: number = 0, filterOutValidation: boolean = false): model.IModelDecoration[] { + public getDecorationsInRange(range: IRange, ownerId: number = 0, filterOutValidation: boolean = false, onlyMinimapDecorations: boolean = false): model.IModelDecoration[] { const validatedRange = this.validateRange(range); const decorations = this._getDecorationsInRange(validatedRange, ownerId, filterOutValidation); - pushMany(decorations, this._decorationProvider.getDecorationsInRange(validatedRange, ownerId, filterOutValidation)); + pushMany(decorations, this._decorationProvider.getDecorationsInRange(validatedRange, ownerId, filterOutValidation, onlyMinimapDecorations)); return decorations; } diff --git a/src/vs/editor/common/textModelBracketPairs.ts b/src/vs/editor/common/textModelBracketPairs.ts index 633b72fdd405d..27df3befbb36d 100644 --- a/src/vs/editor/common/textModelBracketPairs.ts +++ b/src/vs/editor/common/textModelBracketPairs.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { CallbackIterable } from 'vs/base/common/arrays'; import { Event } from 'vs/base/common/event'; import { IPosition } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; @@ -19,15 +20,15 @@ export interface IBracketPairsTextModelPart { * Gets all bracket pairs that intersect the given position. * The result is sorted by the start position. */ - getBracketPairsInRange(range: IRange): BracketPairInfo[]; + getBracketPairsInRange(range: IRange): CallbackIterable; /** * Gets all bracket pairs that intersect the given position. * The result is sorted by the start position. */ - getBracketPairsInRangeWithMinIndentation(range: IRange): BracketPairWithMinIndentationInfo[]; + getBracketPairsInRangeWithMinIndentation(range: IRange): CallbackIterable; - getBracketsInRange(range: IRange): BracketInfo[]; + getBracketsInRange(range: IRange): CallbackIterable; /** * Find the matching bracket of `request` up, counting brackets. diff --git a/src/vs/editor/common/viewModel.ts b/src/vs/editor/common/viewModel.ts index e582f7dc71903..4ed3be68bd6f6 100644 --- a/src/vs/editor/common/viewModel.ts +++ b/src/vs/editor/common/viewModel.ts @@ -40,7 +40,7 @@ export interface IViewModel extends ICursorSimpleModel { onCompositionStart(): void; onCompositionEnd(): void; - getDecorationsInViewport(visibleRange: Range): ViewModelDecoration[]; + getDecorationsInViewport(visibleRange: Range, onlyMinimapDecorations?: boolean): ViewModelDecoration[]; getViewLineRenderingData(visibleRange: Range, lineNumber: number): ViewLineRenderingData; getViewLineData(lineNumber: number): ViewLineData; getMinimapLinesRenderingData(startLineNumber: number, endLineNumber: number, needed: boolean[]): MinimapLinesRenderingData; diff --git a/src/vs/editor/common/viewModel/viewModelDecorations.ts b/src/vs/editor/common/viewModel/viewModelDecorations.ts index 909d9f19bc335..95b7c3bfe30a2 100644 --- a/src/vs/editor/common/viewModel/viewModelDecorations.ts +++ b/src/vs/editor/common/viewModel/viewModelDecorations.ts @@ -36,6 +36,7 @@ export class ViewModelDecorations implements IDisposable { private _cachedModelDecorationsResolver: IDecorationsViewportData | null; private _cachedModelDecorationsResolverViewRange: Range | null; + private _cachedOnlyMinimapDecorations: boolean | null = null; constructor(editorId: number, model: ITextModel, configuration: IEditorConfiguration, linesCollection: IViewModelLines, coordinatesConverter: ICoordinatesConverter) { this.editorId = editorId; @@ -96,18 +97,20 @@ export class ViewModelDecorations implements IDisposable { return r; } - public getDecorationsViewportData(viewRange: Range): IDecorationsViewportData { + public getDecorationsViewportData(viewRange: Range, onlyMinimapDecorations: boolean = false): IDecorationsViewportData { let cacheIsValid = (this._cachedModelDecorationsResolver !== null); cacheIsValid = cacheIsValid && (viewRange.equalsRange(this._cachedModelDecorationsResolverViewRange)); + cacheIsValid = cacheIsValid && (this._cachedOnlyMinimapDecorations === onlyMinimapDecorations); if (!cacheIsValid) { - this._cachedModelDecorationsResolver = this._getDecorationsViewportData(viewRange); + this._cachedModelDecorationsResolver = this._getDecorationsViewportData(viewRange, onlyMinimapDecorations); this._cachedModelDecorationsResolverViewRange = viewRange; + this._cachedOnlyMinimapDecorations = onlyMinimapDecorations; } return this._cachedModelDecorationsResolver!; } - private _getDecorationsViewportData(viewportRange: Range): IDecorationsViewportData { - const modelDecorations = this._linesCollection.getDecorationsInRange(viewportRange, this.editorId, filterValidationDecorations(this.configuration.options)); + private _getDecorationsViewportData(viewportRange: Range, onlyMinimapDecorations: boolean): IDecorationsViewportData { + const modelDecorations = this._linesCollection.getDecorationsInRange(viewportRange, this.editorId, filterValidationDecorations(this.configuration.options), onlyMinimapDecorations); const startLineNumber = viewportRange.startLineNumber; const endLineNumber = viewportRange.endLineNumber; diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts index c55cf3d02d15e..86c8b8ce13064 100644 --- a/src/vs/editor/common/viewModel/viewModelImpl.ts +++ b/src/vs/editor/common/viewModel/viewModelImpl.ts @@ -671,8 +671,8 @@ export class ViewModel extends Disposable implements IViewModel { return result + 2; } - public getDecorationsInViewport(visibleRange: Range): ViewModelDecoration[] { - return this._decorations.getDecorationsViewportData(visibleRange).decorations; + public getDecorationsInViewport(visibleRange: Range, onlyMinimapDecorations: boolean = false): ViewModelDecoration[] { + return this._decorations.getDecorationsViewportData(visibleRange, onlyMinimapDecorations).decorations; } public getInjectedTextAt(viewPosition: Position): InjectedText | null { diff --git a/src/vs/editor/common/viewModel/viewModelLines.ts b/src/vs/editor/common/viewModel/viewModelLines.ts index 86b2010989f20..7d766e8ee9fb6 100644 --- a/src/vs/editor/common/viewModel/viewModelLines.ts +++ b/src/vs/editor/common/viewModel/viewModelLines.ts @@ -45,7 +45,7 @@ export interface IViewModelLines extends IDisposable { getViewLineData(viewLineNumber: number): ViewLineData; getViewLinesData(viewStartLineNumber: number, viewEndLineNumber: number, needed: boolean[]): Array; - getDecorationsInRange(range: Range, ownerId: number, filterOutValidation: boolean): IModelDecoration[]; + getDecorationsInRange(range: Range, ownerId: number, filterOutValidation: boolean, onlyMinimapDecorations: boolean): IModelDecoration[]; getInjectedTextAt(viewPosition: Position): InjectedText | null; @@ -888,14 +888,14 @@ export class ViewModelLinesFromProjectedModel implements IViewModelLines { return this.modelLineProjections[lineIndex].getViewLineNumberOfModelPosition(deltaLineNumber, this.model.getLineMaxColumn(lineIndex + 1)); } - public getDecorationsInRange(range: Range, ownerId: number, filterOutValidation: boolean): IModelDecoration[] { + public getDecorationsInRange(range: Range, ownerId: number, filterOutValidation: boolean, onlyMinimapDecorations: boolean): IModelDecoration[] { const modelStart = this.convertViewPositionToModelPosition(range.startLineNumber, range.startColumn); const modelEnd = this.convertViewPositionToModelPosition(range.endLineNumber, range.endColumn); if (modelEnd.lineNumber - modelStart.lineNumber <= range.endLineNumber - range.startLineNumber) { // most likely there are no hidden lines => fast path // fetch decorations from column 1 to cover the case of wrapped lines that have whole line decorations at column 1 - return this.model.getDecorationsInRange(new Range(modelStart.lineNumber, 1, modelEnd.lineNumber, modelEnd.column), ownerId, filterOutValidation); + return this.model.getDecorationsInRange(new Range(modelStart.lineNumber, 1, modelEnd.lineNumber, modelEnd.column), ownerId, filterOutValidation, onlyMinimapDecorations); } let result: IModelDecoration[] = []; @@ -914,14 +914,14 @@ export class ViewModelLinesFromProjectedModel implements IViewModelLines { // hit invisible line => flush request if (reqStart !== null) { const maxLineColumn = this.model.getLineMaxColumn(modelLineIndex); - result = result.concat(this.model.getDecorationsInRange(new Range(reqStart.lineNumber, reqStart.column, modelLineIndex, maxLineColumn), ownerId, filterOutValidation)); + result = result.concat(this.model.getDecorationsInRange(new Range(reqStart.lineNumber, reqStart.column, modelLineIndex, maxLineColumn), ownerId, filterOutValidation, onlyMinimapDecorations)); reqStart = null; } } } if (reqStart !== null) { - result = result.concat(this.model.getDecorationsInRange(new Range(reqStart.lineNumber, reqStart.column, modelEnd.lineNumber, modelEnd.column), ownerId, filterOutValidation)); + result = result.concat(this.model.getDecorationsInRange(new Range(reqStart.lineNumber, reqStart.column, modelEnd.lineNumber, modelEnd.column), ownerId, filterOutValidation, onlyMinimapDecorations)); reqStart = null; } @@ -1221,8 +1221,8 @@ export class ViewModelLinesFromModelAsIs implements IViewModelLines { return result; } - public getDecorationsInRange(range: Range, ownerId: number, filterOutValidation: boolean): IModelDecoration[] { - return this.model.getDecorationsInRange(range, ownerId, filterOutValidation); + public getDecorationsInRange(range: Range, ownerId: number, filterOutValidation: boolean, onlyMinimapDecorations: boolean): IModelDecoration[] { + return this.model.getDecorationsInRange(range, ownerId, filterOutValidation, onlyMinimapDecorations); } normalizePosition(position: Position, affinity: PositionAffinity): Position { diff --git a/src/vs/editor/test/common/model/bracketPairColorizer/getBracketPairsInRange.test.ts b/src/vs/editor/test/common/model/bracketPairColorizer/getBracketPairsInRange.test.ts index e74219407a6ca..5a631ed3d1037 100644 --- a/src/vs/editor/test/common/model/bracketPairColorizer/getBracketPairsInRange.test.ts +++ b/src/vs/editor/test/common/model/bracketPairColorizer/getBracketPairsInRange.test.ts @@ -36,7 +36,8 @@ suite('Bracket Pair Colorizer - getBracketPairsInRange', () => { assert.deepStrictEqual( model.bracketPairs .getBracketPairsInRange(doc.range(1, 2)) - .map(bracketPairToJSON), + .map(bracketPairToJSON) + .toArray(), [ { level: 0, @@ -68,7 +69,8 @@ suite('Bracket Pair Colorizer - getBracketPairsInRange', () => { assert.deepStrictEqual( model.bracketPairs .getBracketPairsInRange(doc.range(1, 2)) - .map(bracketPairToJSON), + .map(bracketPairToJSON) + .toArray(), [ { level: 0, @@ -94,7 +96,8 @@ suite('Bracket Pair Colorizer - getBracketPairsInRange', () => { assert.deepStrictEqual( model.bracketPairs .getBracketPairsInRange(doc.range(1, 2)) - .map(bracketPairToJSON), + .map(bracketPairToJSON) + .toArray(), [] ); }); @@ -107,7 +110,8 @@ suite('Bracket Pair Colorizer - getBracketPairsInRange', () => { assert.deepStrictEqual( model.bracketPairs .getBracketPairsInRange(doc.range(1, 2)) - .map(bracketPairToJSON), + .map(bracketPairToJSON) + .toArray(), [ { level: 0, diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index f608381f9e474..25bd5f5007875 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -1995,7 +1995,7 @@ declare namespace monaco.editor { * @param filterOutValidation If set, it will ignore decorations specific to validation (i.e. warnings, errors). * @return An array with the decorations */ - getDecorationsInRange(range: IRange, ownerId?: number, filterOutValidation?: boolean): IModelDecoration[]; + getDecorationsInRange(range: IRange, ownerId?: number, filterOutValidation?: boolean, onlyMinimapDecorations?: boolean): IModelDecoration[]; /** * Gets all the decorations as an array. * @param ownerId If set, it will ignore decorations belonging to other owners.