Merge pull request #129231 from microsoft/hediet/bracketPairColorizer

Performant Bracket Pair Colorization
This commit is contained in:
Henning Dieterichs 2021-08-16 13:32:00 +02:00 committed by GitHub
commit 7aa0f8e754
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 3318 additions and 255 deletions

View file

@ -3274,6 +3274,51 @@ class InlineEditorSuggest extends BaseEditorOption<EditorOption.inlineSuggest, I
//#endregion
//#region bracketPairColorization
export interface IBracketPairColorizationOptions {
/**
* Enable or disable bracket pair colorization.
*/
enabled?: boolean;
}
export type InternalBracketPairColorizationOptions = Readonly<Required<IBracketPairColorizationOptions>>;
/**
* Configuration options for inline suggestions
*/
class BracketPairColorization extends BaseEditorOption<EditorOption.bracketPairColorization, InternalBracketPairColorizationOptions> {
constructor() {
const defaults: InternalBracketPairColorizationOptions = {
enabled: false
};
super(
EditorOption.bracketPairColorization, 'bracketPairColorization', defaults,
{
'editor.bracketPairColorization.enabled': {
type: 'boolean',
default: defaults.enabled,
description: nls.localize('bracketPairColorization.enabled', "Controls whether bracket pair colorization is enabled or not.")
}
}
);
}
public validate(_input: any): InternalBracketPairColorizationOptions {
if (!_input || typeof _input !== 'object') {
return this.defaultValue;
}
const input = _input as IBracketPairColorizationOptions;
return {
enabled: boolean(input.enabled, this.defaultValue.enabled)
};
}
}
//#endregion
//#region suggest
/**
@ -3896,7 +3941,8 @@ export const EDITOR_MODEL_DEFAULTS = {
insertSpaces: true,
detectIndentation: true,
trimAutoWhitespace: true,
largeFileOptimizations: true
largeFileOptimizations: true,
bracketPairColorizationOptions: { enabled: false }
};
/**
@ -3922,6 +3968,7 @@ export const enum EditorOption {
autoIndent,
automaticLayout,
autoSurround,
bracketPairColorization,
codeLens,
codeLensFontFamily,
codeLensFontSize,
@ -4172,6 +4219,7 @@ export const EditorOptions = {
description: nls.localize('autoSurround', "Controls whether the editor should automatically surround selections when typing quotes or brackets.")
}
)),
bracketPairColorization: register(new BracketPairColorization()),
stickyTabStops: register(new EditorBooleanOption(
EditorOption.stickyTabStops, 'stickyTabStops', false,
{ description: nls.localize('stickyTabStops', "Emulate selection behavior of tab characters when using spaces for indentation. Selection will stick to tab stops.") }

View file

@ -17,6 +17,7 @@ import { LanguageId, LanguageIdentifier, FormattingOptions } from 'vs/editor/com
import { ThemeColor } from 'vs/platform/theme/common/themeService';
import { MultilineTokens, MultilineTokens2 } from 'vs/editor/common/model/tokensStore';
import { TextChange } from 'vs/editor/common/model/textChange';
import { equals } from 'vs/base/common/objects';
/**
* Vertical Lane in the overview ruler of the editor.
@ -436,6 +437,7 @@ export class TextModelResolvedOptions {
readonly insertSpaces: boolean;
readonly defaultEOL: DefaultEndOfLine;
readonly trimAutoWhitespace: boolean;
readonly bracketPairColorizationOptions: BracketPairColorizationOptions;
/**
* @internal
@ -446,12 +448,14 @@ export class TextModelResolvedOptions {
insertSpaces: boolean;
defaultEOL: DefaultEndOfLine;
trimAutoWhitespace: boolean;
bracketPairColorizationOptions: BracketPairColorizationOptions;
}) {
this.tabSize = Math.max(1, src.tabSize | 0);
this.indentSize = src.tabSize | 0;
this.insertSpaces = Boolean(src.insertSpaces);
this.defaultEOL = src.defaultEOL | 0;
this.trimAutoWhitespace = Boolean(src.trimAutoWhitespace);
this.bracketPairColorizationOptions = src.bracketPairColorizationOptions;
}
/**
@ -464,6 +468,7 @@ export class TextModelResolvedOptions {
&& this.insertSpaces === other.insertSpaces
&& this.defaultEOL === other.defaultEOL
&& this.trimAutoWhitespace === other.trimAutoWhitespace
&& equals(this.bracketPairColorizationOptions, other.bracketPairColorizationOptions)
);
}
@ -492,6 +497,11 @@ export interface ITextModelCreationOptions {
defaultEOL: DefaultEndOfLine;
isForSimpleWidget: boolean;
largeFileOptimizations: boolean;
bracketPairColorizationOptions: BracketPairColorizationOptions;
}
export interface BracketPairColorizationOptions {
enabled: boolean;
}
export interface ITextModelUpdateOptions {
@ -499,6 +509,7 @@ export interface ITextModelUpdateOptions {
indentSize?: number;
insertSpaces?: boolean;
trimAutoWhitespace?: boolean;
bracketColorizationOptions?: BracketPairColorizationOptions;
}
export class FindMatch {

View file

@ -0,0 +1,480 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { tail } from 'vs/base/common/arrays';
import { DenseKeyProvider, SmallImmutableSet } from './smallImmutableSet';
import { lengthAdd, lengthZero, Length, lengthHash } from './length';
export const enum AstNodeKind {
Text = 0,
Bracket = 1,
Pair = 2,
UnexpectedClosingBracket = 3,
List = 4,
}
export type AstNode = PairAstNode | ListAstNode | BracketAstNode | InvalidBracketAstNode | TextAstNode;
abstract class BaseAstNode {
abstract readonly kind: AstNodeKind;
abstract readonly children: readonly AstNode[];
abstract readonly unopenedBrackets: SmallImmutableSet<number>;
/**
* In case of a list, determines the height of the (2,3) tree.
*/
abstract readonly listHeight: number;
abstract canBeReused(
expectedClosingCategories: SmallImmutableSet<number>,
endLineDidChange: boolean
): boolean;
/**
* Flattenes all lists in this AST. Only for debugging.
*/
abstract flattenLists(): AstNode;
/**
* Creates a deep clone.
*/
abstract clone(): AstNode;
protected _length: Length;
get length(): Length {
return this._length;
}
constructor(length: Length) {
this._length = length;
}
}
export class PairAstNode extends BaseAstNode {
public static create(
category: number,
openingBracket: BracketAstNode,
child: AstNode | null,
closingBracket: BracketAstNode | null
) {
const length = computeLength(openingBracket, child, closingBracket);
const children = new Array(1);
children[0] = openingBracket;
if (child) {
children.push(child);
}
if (closingBracket) {
children.push(closingBracket);
}
return new PairAstNode(length, category, children, child ? child.unopenedBrackets : SmallImmutableSet.getEmpty());
}
get kind(): AstNodeKind.Pair {
return AstNodeKind.Pair;
}
get listHeight() {
return 0;
}
canBeReused(
expectedClosingCategories: SmallImmutableSet<number>,
endLineDidChange: boolean
) {
if (this.closingBracket === null) {
// Unclosed pair ast nodes only
// end at the end of the document
// or when a parent node is closed.
// This could be improved:
// Only return false if some next token is neither "undefined" nor a bracket that closes a parent.
return false;
}
if (expectedClosingCategories.intersects(this.unopenedBrackets)) {
return false;
}
return true;
}
flattenLists(): PairAstNode {
return PairAstNode.create(
this.category,
this.openingBracket.flattenLists(),
this.child && this.child.flattenLists(),
this.closingBracket && this.closingBracket.flattenLists()
);
}
get openingBracket(): BracketAstNode {
return this.children[0] as BracketAstNode;
}
get child(): AstNode | null {
if (this.children.length <= 1) {
return null;
}
if (this.children[1].kind === AstNodeKind.Bracket) {
return null;
}
return this.children[1] || null;
}
get closingBracket(): BracketAstNode | null {
if (this.children.length <= 1) {
return null;
}
if (this.children[1].kind === AstNodeKind.Bracket) {
return this.children[1] || null;
}
return (this.children[2] as BracketAstNode) || null;
}
private constructor(
length: Length,
public readonly category: number,
public readonly children: readonly AstNode[],
public readonly unopenedBrackets: SmallImmutableSet<number>
) {
super(length);
}
clone(): PairAstNode {
return new PairAstNode(
this.length,
this.category,
clone(this.children),
this.unopenedBrackets
);
}
}
function computeLength(openingBracket: BracketAstNode, child: AstNode | null, closingBracket: BracketAstNode | null): Length {
let length = openingBracket.length;
if (child) {
length = lengthAdd(length, child.length);
}
if (closingBracket) {
length = lengthAdd(length, closingBracket.length);
}
return length;
}
export class ListAstNode extends BaseAstNode {
public static create(items: AstNode[]) {
if (items.length === 0) {
return new ListAstNode(lengthZero, 0, items, SmallImmutableSet.getEmpty());
} else {
let length = items[0].length;
let unopenedBrackets = items[0].unopenedBrackets;
for (let i = 1; i < items.length; i++) {
length = lengthAdd(length, items[i].length);
unopenedBrackets = unopenedBrackets.merge(items[i].unopenedBrackets);
}
return new ListAstNode(length, items[0].listHeight + 1, items, unopenedBrackets);
}
}
get kind(): AstNodeKind.List {
return AstNodeKind.List;
}
get children(): readonly AstNode[] {
return this._items;
}
get unopenedBrackets(): SmallImmutableSet<number> {
return this._unopenedBrackets;
}
private constructor(
length: Length,
public readonly listHeight: number,
private readonly _items: AstNode[],
private _unopenedBrackets: SmallImmutableSet<number>
) {
super(length);
}
canBeReused(
expectedClosingCategories: SmallImmutableSet<number>,
endLineDidChange: boolean
): boolean {
if (this._items.length === 0) {
// might not be very helpful
return true;
}
if (expectedClosingCategories.intersects(this.unopenedBrackets)) {
return false;
}
let lastChild: AstNode = this;
while (lastChild.children.length > 0 && lastChild.kind === AstNodeKind.List) {
lastChild = tail(lastChild.children);
}
return lastChild.canBeReused(
expectedClosingCategories,
endLineDidChange
);
}
flattenLists(): ListAstNode {
const items = new Array<AstNode>();
for (const c of this.children) {
const normalized = c.flattenLists();
if (normalized.kind === AstNodeKind.List) {
items.push(...normalized._items);
} else {
items.push(normalized);
}
}
return ListAstNode.create(items);
}
clone(): ListAstNode {
return new ListAstNode(this.length, this.listHeight, clone(this._items), this.unopenedBrackets);
}
private handleChildrenChanged(): void {
const items = this._items;
if (items.length === 0) {
return;
}
let length = items[0].length;
let unopenedBrackets = items[0].unopenedBrackets;
for (let i = 1; i < items.length; i++) {
length = lengthAdd(length, items[i].length);
unopenedBrackets = unopenedBrackets.merge(items[i].unopenedBrackets);
}
this._length = length;
this._unopenedBrackets = unopenedBrackets;
}
/**
* Appends the given node to the end of this (2,3) tree.
* Returns the new root.
*/
append(nodeToAppend: AstNode): AstNode {
const newNode = this._append(nodeToAppend);
if (newNode) {
return ListAstNode.create([this, newNode]);
}
return this;
}
/**
* @returns Additional node after tree
*/
private _append(nodeToAppend: AstNode): AstNode | undefined {
// assert nodeToInsert.listHeight <= tree.listHeight
if (nodeToAppend.listHeight === this.listHeight) {
return nodeToAppend;
}
const lastItem = this._items[this._items.length - 1];
const newNodeAfter = (lastItem.kind === AstNodeKind.List) ? lastItem._append(nodeToAppend) : nodeToAppend;
if (!newNodeAfter) {
this.handleChildrenChanged();
return undefined;
}
// Can we take the element?
if (this._items.length >= 3) {
// assert tree.items.length === 3
// we need to split to maintain (2,3)-tree property.
// Send the third element + the new element to the parent.
const third = this._items.pop()!;
this.handleChildrenChanged();
return ListAstNode.create([third, newNodeAfter]);
} else {
this._items.push(newNodeAfter);
this.handleChildrenChanged();
return undefined;
}
}
/**
* Prepends the given node to the end of this (2,3) tree.
* Returns the new root.
*/
prepend(nodeToPrepend: AstNode): AstNode {
const newNode = this._prepend(nodeToPrepend);
if (newNode) {
return ListAstNode.create([newNode, this]);
}
return this;
}
/**
* @returns Additional node before tree
*/
private _prepend(nodeToPrepend: AstNode): AstNode | undefined {
// assert nodeToInsert.listHeight <= tree.listHeight
if (nodeToPrepend.listHeight === this.listHeight) {
return nodeToPrepend;
}
if (this.kind !== AstNodeKind.List) {
throw new Error('unexpected');
}
const first = this._items[0];
const newNodeBefore = (first.kind === AstNodeKind.List) ? first._prepend(nodeToPrepend) : nodeToPrepend;
if (!newNodeBefore) {
this.handleChildrenChanged();
return undefined;
}
if (this._items.length >= 3) {
// assert this.items.length === 3
// we need to split to maintain (2,3)-this property.
const first = this._items.shift()!;
this.handleChildrenChanged();
return ListAstNode.create([newNodeBefore, first]);
} else {
this._items.unshift(newNodeBefore);
this.handleChildrenChanged();
return undefined;
}
}
}
function clone(arr: readonly AstNode[]): AstNode[] {
const result = new Array<AstNode>(arr.length);
for (let i = 0; i < arr.length; i++) {
result[i] = arr[i].clone();
}
return result;
}
const emptyArray: readonly AstNode[] = [];
export class TextAstNode extends BaseAstNode {
get kind(): AstNodeKind.Text {
return AstNodeKind.Text;
}
get listHeight() {
return 0;
}
get children(): readonly AstNode[] {
return emptyArray;
}
get unopenedBrackets(): SmallImmutableSet<number> {
return SmallImmutableSet.getEmpty();
}
canBeReused(
expectedClosingCategories: SmallImmutableSet<number>,
endLineDidChange: boolean
) {
// Don't reuse text from a line that got changed.
// Otherwise, long brackes might not be detected.
return !endLineDidChange;
}
flattenLists(): TextAstNode {
return this;
}
clone(): TextAstNode {
return this;
}
}
export class BracketAstNode extends BaseAstNode {
private static cacheByLength = new Map<number, BracketAstNode>();
public static create(length: Length): BracketAstNode {
const lengthKey = lengthHash(length);
const cached = BracketAstNode.cacheByLength.get(lengthKey);
if (cached) {
return cached;
}
const node = new BracketAstNode(length);
BracketAstNode.cacheByLength.set(lengthKey, node);
return node;
}
private constructor(length: Length) {
super(length);
}
get kind(): AstNodeKind.Bracket {
return AstNodeKind.Bracket;
}
get listHeight() {
return 0;
}
get children(): readonly AstNode[] {
return emptyArray;
}
get unopenedBrackets(): SmallImmutableSet<number> {
return SmallImmutableSet.getEmpty();
}
canBeReused(
expectedClosingCategories: SmallImmutableSet<number>,
endLineDidChange: boolean
) {
// These nodes could be reused,
// but not in a general way.
// Their parent may be reused.
return false;
}
flattenLists(): BracketAstNode {
return this;
}
clone(): BracketAstNode {
return this;
}
}
export class InvalidBracketAstNode extends BaseAstNode {
get kind(): AstNodeKind.UnexpectedClosingBracket {
return AstNodeKind.UnexpectedClosingBracket;
}
get listHeight() {
return 0;
}
get children(): readonly AstNode[] {
return emptyArray;
}
public readonly unopenedBrackets: SmallImmutableSet<number>;
constructor(category: number, length: Length, denseKeyProvider: DenseKeyProvider<number>) {
super(length);
this.unopenedBrackets = SmallImmutableSet.getEmpty().add(category, denseKeyProvider);
}
canBeReused(
expectedClosingCategories: SmallImmutableSet<number>,
endLineDidChange: boolean
) {
return !expectedClosingCategories.intersects(this.unopenedBrackets);
}
flattenLists(): InvalidBracketAstNode {
return this;
}
clone(): InvalidBracketAstNode {
return this;
}
}

View file

@ -0,0 +1,120 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Length, lengthAdd, lengthDiffNonNegative, lengthLessThanEqual, LengthObj, lengthToObj, toLength } from './length';
export class TextEditInfo {
constructor(
public readonly startOffset: Length,
public readonly endOffset: Length,
public readonly newLength: Length
) {
}
}
export class BeforeEditPositionMapper {
private nextEditIdx = 0;
private deltaOldToNewLineCount = 0;
private deltaOldToNewColumnCount = 0;
private deltaLineIdxInOld = -1;
private readonly edits: readonly TextEditInfoCache[];
/**
* @param edits Must be sorted by offset in ascending order.
*/
constructor(
edits: readonly TextEditInfo[],
private readonly documentLength: Length,
) {
this.edits = edits.map(edit => TextEditInfoCache.from(edit));
}
/**
* @param offset Must be equal to or greater than the last offset this method has been called with.
*/
getOffsetBeforeChange(offset: Length): Length {
this.adjustNextEdit(offset);
return this.translateCurToOld(offset);
}
/**
* @param offset Must be equal to or greater than the last offset this method has been called with.
*/
getDistanceToNextChange(offset: Length): Length {
this.adjustNextEdit(offset);
const nextEdit = this.edits[this.nextEditIdx];
const nextChangeOffset = nextEdit ? this.translateOldToCur(nextEdit.offsetObj) : this.documentLength;
return lengthDiffNonNegative(offset, nextChangeOffset);
}
private translateOldToCur(oldOffsetObj: LengthObj): Length {
if (oldOffsetObj.lineCount === this.deltaLineIdxInOld) {
return toLength(oldOffsetObj.lineCount + this.deltaOldToNewLineCount, oldOffsetObj.columnCount + this.deltaOldToNewColumnCount);
} else {
return toLength(oldOffsetObj.lineCount + this.deltaOldToNewLineCount, oldOffsetObj.columnCount);
}
}
private translateCurToOld(newOffset: Length): Length {
const offsetObj = lengthToObj(newOffset);
if (offsetObj.lineCount - this.deltaOldToNewLineCount === this.deltaLineIdxInOld) {
return toLength(offsetObj.lineCount - this.deltaOldToNewLineCount, offsetObj.columnCount - this.deltaOldToNewColumnCount);
} else {
return toLength(offsetObj.lineCount - this.deltaOldToNewLineCount, offsetObj.columnCount);
}
}
private adjustNextEdit(offset: Length) {
while (this.nextEditIdx < this.edits.length) {
const nextEdit = this.edits[this.nextEditIdx];
// After applying the edit, what is its end offset (considering all previous edits)?
const nextEditEndOffsetInCur = this.translateOldToCur(nextEdit.endOffsetAfterObj);
if (lengthLessThanEqual(nextEditEndOffsetInCur, offset)) {
// We are after the edit, skip it
this.nextEditIdx++;
const nextEditEndOffsetInCurObj = lengthToObj(nextEditEndOffsetInCur);
// Before applying the edit, what is its end offset (considering all previous edits)?
const nextEditEndOffsetBeforeInCurObj = lengthToObj(this.translateOldToCur(nextEdit.endOffsetBeforeObj));
const lineDelta = nextEditEndOffsetInCurObj.lineCount - nextEditEndOffsetBeforeInCurObj.lineCount;
this.deltaOldToNewLineCount += lineDelta;
const previousColumnDelta = this.deltaLineIdxInOld === nextEdit.endOffsetBeforeObj.lineCount ? this.deltaOldToNewColumnCount : 0;
const columnDelta = nextEditEndOffsetInCurObj.columnCount - nextEditEndOffsetBeforeInCurObj.columnCount;
this.deltaOldToNewColumnCount = previousColumnDelta + columnDelta;
this.deltaLineIdxInOld = nextEdit.endOffsetBeforeObj.lineCount;
} else {
// We are in or before the edit.
break;
}
}
}
}
class TextEditInfoCache {
static from(edit: TextEditInfo): TextEditInfoCache {
return new TextEditInfoCache(edit.startOffset, edit.endOffset, edit.newLength);
}
public readonly endOffsetBeforeObj: LengthObj;
public readonly endOffsetAfterObj: LengthObj;
public readonly offsetObj: LengthObj;
constructor(
startOffset: Length,
endOffset: Length,
textLength: Length,
) {
this.endOffsetBeforeObj = lengthToObj(endOffset);
this.endOffsetAfterObj = lengthToObj(lengthAdd(startOffset, textLength));
this.offsetObj = lengthToObj(startOffset);
}
}

View file

@ -0,0 +1,295 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Color } from 'vs/base/common/color';
import { Emitter } from 'vs/base/common/event';
import { Disposable, DisposableStore, IDisposable, IReference, MutableDisposable } from 'vs/base/common/lifecycle';
import { Range } from 'vs/editor/common/core/range';
import { IModelDecoration } from 'vs/editor/common/model';
import { DenseKeyProvider } from 'vs/editor/common/model/bracketPairColorizer/smallImmutableSet';
import { DecorationProvider } from 'vs/editor/common/model/decorationProvider';
import { TextModel } from 'vs/editor/common/model/textModel';
import { IModelContentChangedEvent } from 'vs/editor/common/model/textModelEvents';
import { LanguageId } from 'vs/editor/common/modes';
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
import {
editorBracketHighlightingForeground1, editorBracketHighlightingForeground2, editorBracketHighlightingForeground3, editorBracketHighlightingForeground4, editorBracketHighlightingForeground5, editorBracketHighlightingForeground6
} from 'vs/editor/common/view/editorColorRegistry';
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { AstNode, AstNodeKind } from './ast';
import { TextEditInfo } from './beforeEditPositionMapper';
import { LanguageAgnosticBracketTokens } from './brackets';
import { Length, lengthAdd, lengthGreaterThanEqual, lengthLessThanEqual, lengthOfString, lengthsToRange, lengthZero, positionToLength, toLength } from './length';
import { parseDocument } from './parser';
import { FastTokenizer, TextBufferTokenizer } from './tokenizer';
export class BracketPairColorizer extends Disposable implements DecorationProvider {
private readonly didChangeDecorationsEmitter = new Emitter<void>();
private readonly cache = this._register(new MutableDisposable<IReference<BracketPairColorizerImpl>>());
private tokenizationState: TokenizationState = 'uninitialized';
get isDocumentSupported() {
const maxSupportedDocumentLength = /* max lines */ 50_000 * /* average column count */ 100;
return this.textModel.getValueLength() <= maxSupportedDocumentLength;
}
constructor(private readonly textModel: TextModel) {
super();
this._register(LanguageConfigurationRegistry.onDidChange((e) => {
if (this.cache.value?.object.didLanguageChange(e.languageIdentifier.id)) {
this.cache.clear();
this.updateCache();
}
}));
this._register(textModel.onDidChangeOptions(e => {
this.cache.clear();
this.updateCache();
}));
this._register(textModel.onDidChangeAttached(() => {
this.updateCache();
}));
this._register(textModel.onDidChangeTokens(({ ranges, backgroundTokenizationCompleted }) => {
if (backgroundTokenizationCompleted || this.tokenizationState === 'completed') {
this.tokenizationState = 'completed';
} else {
this.tokenizationState = 'inProgress';
}
}));
}
private updateCache() {
const options = this.textModel.getOptions().bracketPairColorizationOptions;
if (this.textModel.isAttachedToEditor() && this.isDocumentSupported && options.enabled) {
if (!this.cache.value) {
const store = new DisposableStore();
this.cache.value = createDisposableRef(store.add(new BracketPairColorizerImpl(this.textModel, this.tokenizationState)), store);
store.add(this.cache.value.object.onDidChangeDecorations(e => this.didChangeDecorationsEmitter.fire(e)));
this.didChangeDecorationsEmitter.fire();
}
} else {
this.cache.clear();
this.didChangeDecorationsEmitter.fire();
}
}
handleContentChanged(change: IModelContentChangedEvent) {
this.cache.value?.object.handleContentChanged(change);
}
getDecorationsInRange(range: Range, ownerId?: number, filterOutValidation?: boolean): IModelDecoration[] {
if (ownerId === undefined) {
return [];
}
return this.cache.value?.object.getDecorationsInRange(range, ownerId, filterOutValidation) || [];
}
getAllDecorations(ownerId?: number, filterOutValidation?: boolean): IModelDecoration[] {
if (ownerId === undefined) {
return [];
}
return this.cache.value?.object.getAllDecorations(ownerId, filterOutValidation) || [];
}
onDidChangeDecorations(listener: () => void): IDisposable {
return this.didChangeDecorationsEmitter.event(listener);
}
}
function createDisposableRef<T>(object: T, disposable?: IDisposable): IReference<T> {
return {
object,
dispose: () => disposable?.dispose(),
};
}
type TokenizationState = 'uninitialized' | 'inProgress' | 'completed';
class BracketPairColorizerImpl extends Disposable implements DecorationProvider {
private readonly didChangeDecorationsEmitter = new Emitter<void>();
private readonly colorProvider = new ColorProvider();
/*
There are two trees:
* The initial tree that has no token information and is used for performant initial bracket colorization.
* The tree that used token information to detect bracket pairs.
To prevent flickering, we only switch from the initial tree to tree with token information
when tokenization completes.
Since the text can be edited while background tokenization is in progress, we need to update both trees.
*/
private initialAstWithoutTokens: AstNode | undefined;
private astWithTokens: AstNode | undefined;
private readonly brackets = new LanguageAgnosticBracketTokens([]);
private readonly denseKeyProvider = new DenseKeyProvider<number>();
public didLanguageChange(languageId: LanguageId): boolean {
return this.brackets.didLanguageChange(languageId);
}
constructor(private readonly textModel: TextModel, tokenizationState: TokenizationState) {
super();
this._register(textModel.onDidChangeTokens(({ ranges, backgroundTokenizationCompleted }) => {
if (backgroundTokenizationCompleted) {
// Clear the initial tree as we can use the tree with token information now.
this.initialAstWithoutTokens = undefined;
}
const edits = ranges.map(r =>
new TextEditInfo(
toLength(r.fromLineNumber - 1, 0),
toLength(r.toLineNumber, 0),
toLength(r.toLineNumber - r.fromLineNumber + 1, 0)
)
);
this.astWithTokens = this.parseDocumentFromTextBuffer(edits, this.astWithTokens);
if (!this.initialAstWithoutTokens) {
this.didChangeDecorationsEmitter.fire();
}
}));
if (tokenizationState === 'uninitialized') {
// There are no token information yet
const brackets = this.brackets.getSingleLanguageBracketTokens(this.textModel.getLanguageIdentifier().id);
const tokenizer = new FastTokenizer(this.textModel.getValue(), brackets);
this.initialAstWithoutTokens = parseDocument(tokenizer, [], undefined, this.denseKeyProvider);
this.astWithTokens = this.initialAstWithoutTokens.clone();
} else if (tokenizationState === 'completed') {
// Skip the initial ast, as there is no flickering.
// Directly create the tree with token information.
this.initialAstWithoutTokens = undefined;
this.astWithTokens = this.parseDocumentFromTextBuffer([], undefined);
} else if (tokenizationState === 'inProgress') {
this.initialAstWithoutTokens = this.parseDocumentFromTextBuffer([], undefined);
this.astWithTokens = this.initialAstWithoutTokens.clone();
}
}
handleContentChanged(change: IModelContentChangedEvent) {
const edits = change.changes.map(c => {
const range = Range.lift(c.range);
return new TextEditInfo(
positionToLength(range.getStartPosition()),
positionToLength(range.getEndPosition()),
lengthOfString(c.text)
);
}).reverse();
this.astWithTokens = this.parseDocumentFromTextBuffer(edits, this.astWithTokens);
if (this.initialAstWithoutTokens) {
this.initialAstWithoutTokens = this.parseDocumentFromTextBuffer(edits, this.initialAstWithoutTokens);
}
}
/**
* @pure (only if isPure = true)
*/
private parseDocumentFromTextBuffer(edits: TextEditInfo[], previousAst: AstNode | undefined): AstNode {
// Is much faster if `isPure = false`.
const isPure = false;
const previousAstClone = isPure ? previousAst?.clone() : previousAst;
const tokenizer = new TextBufferTokenizer(this.textModel, this.brackets);
const result = parseDocument(tokenizer, edits, previousAstClone, this.denseKeyProvider);
return result;
}
getBracketsInRange(range: Range): BracketInfo[] {
const startOffset = toLength(range.startLineNumber - 1, range.startColumn - 1);
const endOffset = toLength(range.endLineNumber - 1, range.endColumn - 1);
const result = new Array<BracketInfo>();
const node = this.initialAstWithoutTokens || this.astWithTokens!;
collectBrackets(node, lengthZero, node.length, startOffset, endOffset, result);
return result;
}
getDecorationsInRange(range: Range, ownerId?: number, filterOutValidation?: boolean): IModelDecoration[] {
const result = new Array<IModelDecoration>();
const bracketsInRange = this.getBracketsInRange(range);
for (const bracket of bracketsInRange) {
result.push({
id: `bracket${bracket.hash()}`,
options: { description: 'BracketPairColorization', inlineClassName: this.colorProvider.getInlineClassName(bracket) },
ownerId: 0,
range: bracket.range
});
}
return result;
}
getAllDecorations(ownerId?: number, filterOutValidation?: boolean): IModelDecoration[] {
return this.getDecorationsInRange(new Range(1, 1, this.textModel.getLineCount(), 1), ownerId, filterOutValidation);
}
readonly onDidChangeDecorations = this.didChangeDecorationsEmitter.event;
}
function collectBrackets(node: AstNode, nodeOffsetStart: Length, nodeOffsetEnd: Length, startOffset: Length, endOffset: Length, result: BracketInfo[], level: number = 0): void {
if (node.kind === AstNodeKind.Bracket) {
const range = lengthsToRange(nodeOffsetStart, nodeOffsetEnd);
result.push(new BracketInfo(range, level - 1));
}
else {
if (node.kind === AstNodeKind.Pair) {
level++;
}
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);
}
nodeOffsetStart = nodeOffsetEnd;
}
}
}
export class BracketInfo {
constructor(
public readonly range: Range,
/** 0-based level */
public readonly level: number
) { }
hash(): string {
return `${this.range.toString()}-${this.level}`;
}
}
class ColorProvider {
getInlineClassName(bracket: BracketInfo): string {
return this.getInlineClassNameOfLevel(bracket.level);
}
getInlineClassNameOfLevel(level: number): string {
// To support a dynamic amount of colors up to 6 colors,
// we use a number that is a lcm of all numbers from 1 to 6.
return `bracket-highlighting-${level % 30}`;
}
}
registerThemingParticipant((theme, collector) => {
const colors = [
editorBracketHighlightingForeground1,
editorBracketHighlightingForeground2,
editorBracketHighlightingForeground3,
editorBracketHighlightingForeground4,
editorBracketHighlightingForeground5,
editorBracketHighlightingForeground6
];
const colorProvider = new ColorProvider();
let colorValues = colors
.map(c => theme.getColor(c))
.filter((c): c is Color => !!c)
.filter(c => !c.isTransparent());
for (let level = 0; level < 30; level++) {
const color = colorValues[level % colorValues.length];
collector.addRule(`.monaco-editor .${colorProvider.getInlineClassNameOfLevel(level)} { color: ${color}; }`);
}
});

View file

@ -0,0 +1,120 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { escapeRegExpCharacters } from 'vs/base/common/strings';
import { LanguageId } from 'vs/editor/common/modes';
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
import { BracketAstNode } from './ast';
import { toLength } from './length';
import { Token, TokenKind } from './tokenizer';
export class BracketTokens {
static createFromLanguage(languageId: LanguageId, customBracketPairs: readonly [string, string][]): BracketTokens {
const brackets = [...(LanguageConfigurationRegistry.getBracketsSupport(languageId)?.brackets || [])];
const tokens = new BracketTokens();
let idxOffset = 0;
for (const pair of brackets) {
const brackets = [
...pair.open.map((value, idx) => ({ value, kind: TokenKind.OpeningBracket, idx: idx + idxOffset })),
...pair.close.map((value, idx) => ({ value, kind: TokenKind.ClosingBracket, idx: idx + idxOffset })),
];
idxOffset += Math.max(pair.open.length, pair.close.length);
for (const bracket of brackets) {
tokens.addBracket(languageId, bracket.value, bracket.kind, bracket.idx);
}
}
for (const pair of customBracketPairs) {
idxOffset++;
tokens.addBracket(languageId, pair[0], TokenKind.OpeningBracket, idxOffset);
tokens.addBracket(languageId, pair[1], TokenKind.ClosingBracket, idxOffset);
}
return tokens;
}
private hasRegExp = false;
private _regExpGlobal: RegExp | null = null;
private readonly map = new Map<string, Token>();
private addBracket(languageId: LanguageId, value: string, kind: TokenKind, idx: number): void {
const length = toLength(0, value.length);
this.map.set(value,
new Token(
length,
kind,
// A language can have at most 1000 bracket pairs.
languageId * 1000 + idx,
languageId,
BracketAstNode.create(length)
)
);
}
getRegExpStr(): string | null {
if (this.isEmpty) {
return null;
} else {
const keys = [...this.map.keys()];
keys.sort();
keys.reverse();
return keys.map(k => escapeRegExpCharacters(k)).join('|');
}
}
/**
* Returns null if there is no such regexp (because there are no brackets).
*/
get regExpGlobal(): RegExp | null {
if (!this.hasRegExp) {
const regExpStr = this.getRegExpStr();
this._regExpGlobal = regExpStr ? new RegExp(regExpStr, 'g') : null;
this.hasRegExp = true;
}
return this._regExpGlobal;
}
getToken(value: string): Token | undefined {
return this.map.get(value);
}
get isEmpty(): boolean {
return this.map.size === 0;
}
}
export class LanguageAgnosticBracketTokens {
private readonly languageIdToBracketTokens: Map<LanguageId, BracketTokens> = new Map();
constructor(private readonly customBracketPairs: readonly [string, string][]) {
}
public didLanguageChange(languageId: LanguageId): boolean {
const existing = this.languageIdToBracketTokens.get(languageId);
if (!existing) {
return false;
}
const newRegExpStr = BracketTokens.createFromLanguage(languageId, this.customBracketPairs).getRegExpStr();
return existing.getRegExpStr() !== newRegExpStr;
}
getSingleLanguageBracketTokens(languageId: LanguageId): BracketTokens {
let singleLanguageBracketTokens = this.languageIdToBracketTokens.get(languageId);
if (!singleLanguageBracketTokens) {
singleLanguageBracketTokens = BracketTokens.createFromLanguage(languageId, this.customBracketPairs);
this.languageIdToBracketTokens.set(languageId, singleLanguageBracketTokens);
}
return singleLanguageBracketTokens;
}
getToken(value: string, languageId: LanguageId): Token | undefined {
const singleLanguageBracketTokens = this.getSingleLanguageBracketTokens(languageId);
return singleLanguageBracketTokens.getToken(value);
}
}

View file

@ -0,0 +1,92 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { AstNode, ListAstNode } from './ast';
/**
* Concatenates a list of (2,3) AstNode's into a single (2,3) AstNode.
* This mutates the items of the input array!
*/
export function concat23Trees(items: AstNode[]): AstNode | null {
if (items.length === 0) {
return null;
}
if (items.length === 1) {
return items[0];
}
if (allItemsHaveSameHeight(items)) {
return concatFast(items);
}
return concatSlow(items);
}
/**
* @param items must be non empty.
*/
function allItemsHaveSameHeight(items: AstNode[]): boolean {
const firstHeight = items[0].listHeight;
for (const item of items) {
if (item.listHeight !== firstHeight) {
return false;
}
}
return true;
}
function concatFast(items: AstNode[]): AstNode | null {
let length = items.length;
// All trees have same height, just create parent nodes.
while (length > 1) {
const newLength = length >> 1;
// Ideally, due to the slice, not a lot of memory is wasted.
const newItems = new Array<AstNode>(newLength);
for (let i = 0; i < newLength; i++) {
const j = i << 1;
newItems[i] = ListAstNode.create(items.slice(j, (j + 3 === length) ? length : j + 2));
}
length = newLength;
items = newItems;
}
return items[0];
}
function heightDiff(node1: AstNode, node2: AstNode): number {
return Math.abs(node1.listHeight - node2.listHeight);
}
function concatSlow(items: AstNode[]): AstNode | null {
// The items might not have the same height.
// We merge all items by using a binary concat operator.
let first = items[0];
let second = items[1];
for (let i = 2; i < items.length; i++) {
const item = items[i];
// Prefer concatenating smaller trees, as the runtime of concat depends on the tree height.
if (heightDiff(first, second) <= heightDiff(second, item)) {
first = concat(first, second);
second = item;
} else {
second = concat(second, item);
}
}
const result = concat(first, second);
return result;
}
function concat(node1: AstNode, node2: AstNode): AstNode {
if (node1.listHeight === node2.listHeight) {
return ListAstNode.create([node1, node2]);
}
else if (node1.listHeight > node2.listHeight) {
// node1 is the tree we want to insert into
return (node1 as ListAstNode).append(node2);
} else {
return (node2 as ListAstNode).prepend(node1);
}
}

View file

@ -0,0 +1,230 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { splitLines } from 'vs/base/common/strings';
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
/**
* Represents a non-negative length in terms of line and column count.
* Prefer using {@link Length}.
*/
export class LengthObj {
public static zero = new LengthObj(0, 0);
public static lengthDiffNonNegative(start: LengthObj, end: LengthObj): LengthObj {
if (end.isLessThan(start)) {
return LengthObj.zero;
}
if (start.lineCount === end.lineCount) {
return new LengthObj(0, end.columnCount - start.columnCount);
} else {
return new LengthObj(end.lineCount - start.lineCount, end.columnCount);
}
}
constructor(
public readonly lineCount: number,
public readonly columnCount: number
) { }
public isZero() {
return this.lineCount === 0 && this.columnCount === 0;
}
public toLength(): Length {
return toLength(this.lineCount, this.columnCount);
}
public isLessThan(other: LengthObj): boolean {
if (this.lineCount !== other.lineCount) {
return this.lineCount < other.lineCount;
}
return this.columnCount < other.columnCount;
}
public isGreaterThan(other: LengthObj): boolean {
if (this.lineCount !== other.lineCount) {
return this.lineCount > other.lineCount;
}
return this.columnCount > other.columnCount;
}
public equals(other: LengthObj): boolean {
return this.lineCount === other.lineCount && this.columnCount === other.columnCount;
}
public compare(other: LengthObj): number {
if (this.lineCount !== other.lineCount) {
return this.lineCount - other.lineCount;
}
return this.columnCount - other.columnCount;
}
public add(other: LengthObj): LengthObj {
if (other.lineCount === 0) {
return new LengthObj(this.lineCount, this.columnCount + other.columnCount);
} else {
return new LengthObj(this.lineCount + other.lineCount, other.columnCount);
}
}
toString() {
return `${this.lineCount},${this.columnCount}`;
}
}
/**
* The end must be greater than or equal to the start.
*/
export function lengthDiff(startLineCount: number, startColumnCount: number, endLineCount: number, endColumnCount: number): Length {
return (startLineCount !== endLineCount)
? toLength(endLineCount - startLineCount, endColumnCount)
: toLength(0, endColumnCount - startColumnCount);
}
/**
* Represents a non-negative length in terms of line and column count.
* Does not allocate.
*/
export type Length = { _brand: 'Length' };
export const lengthZero = 0 as any as Length;
export function lengthIsZero(length: Length): boolean {
return length as any as number === 0;
}
/*
* We have 52 bits available in a JS number.
* We use the upper 26 bits to store the line and the lower 26 bits to store the column.
*
* Set boolean to `true` when debugging, so that debugging is easier.
*/
const factor = /* is debug: */ false ? 100000 : 2 ** 26;
export function toLength(lineCount: number, columnCount: number): Length {
// llllllllllllllllllllllllllcccccccccccccccccccccccccc (52 bits)
// line count (26 bits) column count (26 bits)
// If there is no overflow (all values/sums below 2^26 = 67108864),
// we have `toLength(lns1, cols1) + toLength(lns2, cols2) = toLength(lns1 + lns2, cols1 + cols2)`.
return (lineCount * factor + columnCount) as any as Length;
}
export function lengthToObj(length: Length): LengthObj {
const l = length as any as number;
const lineCount = Math.floor(l / factor);
const columnCount = l - lineCount * factor;
return new LengthObj(lineCount, columnCount);
}
export function lengthGetLineCount(length: Length): number {
return Math.floor(length as any as number / factor);
}
/**
* Returns the amount of columns of the given length, assuming that it does not span any line.
*/
export function lengthGetColumnCountIfZeroLineCount(length: Length): number {
return length as any as number;
}
// [10 lines, 5 cols] + [ 0 lines, 3 cols] = [10 lines, 8 cols]
// [10 lines, 5 cols] + [20 lines, 3 cols] = [30 lines, 3 cols]
export function lengthAdd(length1: Length, length2: Length): Length;
export function lengthAdd(l1: any, l2: any): Length {
return ((l2 < factor)
? (l1 + l2) // l2 is the amount of columns (zero line count). Keep the column count from l1.
: (l1 - (l1 % factor) + l2)); // l1 - (l1 % factor) equals toLength(l1.lineCount, 0)
}
/**
* Returns a non negative length `result` such that `lengthAdd(length1, result) = length2`, or zero if such length does not exist.
*/
export function lengthDiffNonNegative(length1: Length, length2: Length): Length {
const l1 = length1 as any as number;
const l2 = length2 as any as number;
const diff = l2 - l1;
if (diff <= 0) {
// line-count of length1 is higher than line-count of length2
// or they are equal and column-count of length1 is higher than column-count of length2
return lengthZero;
}
const lineCount1 = Math.floor(l1 / factor);
const lineCount2 = Math.floor(l2 / factor);
const colCount2 = l2 - lineCount2 * factor;
if (lineCount1 === lineCount2) {
const colCount1 = l1 - lineCount1 * factor;
return toLength(0, colCount2 - colCount1);
} else {
return toLength(lineCount2 - lineCount1, colCount2);
}
}
export function lengthLessThan(length1: Length, length2: Length): boolean {
// First, compare line counts, then column counts.
return (length1 as any as number) < (length2 as any as number);
}
export function lengthLessThanEqual(length1: Length, length2: Length): boolean {
return (length1 as any as number) <= (length2 as any as number);
}
export function lengthGreaterThanEqual(length1: Length, length2: Length): boolean {
return (length1 as any as number) >= (length2 as any as number);
}
export function lengthToPosition(length: Length): Position {
const l = length as any as number;
const lineCount = Math.floor(l / factor);
const colCount = l - lineCount * factor;
return new Position(lineCount + 1, colCount + 1);
}
export function positionToLength(position: Position): Length {
return toLength(position.lineNumber - 1, position.column - 1);
}
export function lengthsToRange(lengthStart: Length, lengthEnd: Length): Range {
const l = lengthStart as any as number;
const lineCount = Math.floor(l / factor);
const colCount = l - lineCount * factor;
const l2 = lengthEnd as any as number;
const lineCount2 = Math.floor(l2 / factor);
const colCount2 = l2 - lineCount2 * factor;
return new Range(lineCount + 1, colCount + 1, lineCount2 + 1, colCount2 + 1);
}
export function lengthCompare(length1: Length, length2: Length): number {
const l1 = length1 as any as number;
const l2 = length2 as any as number;
return l1 - l2;
}
export function lengthOfString(str: string): Length {
const lines = splitLines(str);
return toLength(lines.length - 1, lines[lines.length - 1].length);
}
export function lengthOfStringObj(str: string): LengthObj {
const lines = splitLines(str);
return new LengthObj(lines.length - 1, lines[lines.length - 1].length);
}
/**
* Computes a numeric hash of the given length.
*/
export function lengthHash(length: Length): number {
return length as any;
}

View file

@ -0,0 +1,123 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { AstNode } from './ast';
import { lengthAdd, lengthZero, Length, lengthLessThan } from './length';
/**
* Allows to efficiently find a longest child at a given offset in a fixed node.
* The requested offsets must increase monotonously.
*/
export class NodeReader {
private readonly nextNodes: AstNode[];
private readonly offsets: Length[];
private readonly idxs: number[];
private lastOffset: Length = lengthZero;
constructor(node: AstNode) {
this.nextNodes = [node];
this.offsets = [lengthZero];
this.idxs = [];
}
/**
* Returns the longest node at `offset` that satisfies the predicate.
* @param offset must be greater than or equal to the last offset this method has been called with!
*/
readLongestNodeAt(offset: Length, predicate: (node: AstNode) => boolean): AstNode | undefined {
if (lengthLessThan(offset, this.lastOffset)) {
throw new Error('Invalid offset');
}
this.lastOffset = offset;
// Find the longest node of all those that are closest to the current offset.
while (true) {
const curNode = lastOrUndefined(this.nextNodes);
if (!curNode) {
return undefined;
}
const curNodeOffset = lastOrUndefined(this.offsets)!;
if (lengthLessThan(offset, curNodeOffset)) {
// The next best node is not here yet.
// The reader must advance before a cached node is hit.
return undefined;
}
if (lengthLessThan(curNodeOffset, offset)) {
// The reader is ahead of the current node.
if (lengthAdd(curNodeOffset, curNode.length) <= offset) {
// The reader is after the end of the current node.
this.nextNodeAfterCurrent();
} else {
// The reader is somewhere in the current node.
if (curNode.children.length > 0) {
// Go to the first child and repeat.
this.nextNodes.push(curNode.children[0]);
this.offsets.push(curNodeOffset);
this.idxs.push(0);
} else {
// We don't have children
this.nextNodeAfterCurrent();
}
}
} else {
// readerOffsetBeforeChange === curNodeOffset
if (predicate(curNode)) {
this.nextNodeAfterCurrent();
return curNode;
} else {
// look for shorter node
if (curNode.children.length === 0) {
// There is no shorter node.
this.nextNodeAfterCurrent();
return undefined;
} else {
// Descend into first child & repeat.
this.nextNodes.push(curNode.children[0]);
this.offsets.push(curNodeOffset);
this.idxs.push(0);
}
}
}
}
}
// Navigates to the longest node that continues after the current node.
private nextNodeAfterCurrent(): void {
while (true) {
const currentOffset = lastOrUndefined(this.offsets);
const currentNode = lastOrUndefined(this.nextNodes);
this.nextNodes.pop();
this.offsets.pop();
if (this.idxs.length === 0) {
// We just popped the root node, there is no next node.
break;
}
// Parent is not undefined, because idxs is not empty
const parent = lastOrUndefined(this.nextNodes)!;
this.idxs[this.idxs.length - 1]++;
const parentIdx = this.idxs[this.idxs.length - 1];
if (parentIdx < parent.children.length) {
this.nextNodes.push(parent.children[parentIdx]);
this.offsets.push(lengthAdd(currentOffset!, currentNode!.length));
break;
} else {
this.idxs.pop();
}
// We fully consumed the parent.
// Current node is now parent, so call nextNodeAfterCurrent again
}
}
}
function lastOrUndefined<T>(arr: readonly T[]): T | undefined {
return arr.length > 0 ? arr[arr.length - 1] : undefined;
}

View file

@ -0,0 +1,153 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { AstNode, AstNodeKind, BracketAstNode, InvalidBracketAstNode, ListAstNode, PairAstNode, TextAstNode } from './ast';
import { BeforeEditPositionMapper, TextEditInfo } from './beforeEditPositionMapper';
import { DenseKeyProvider, SmallImmutableSet } from './smallImmutableSet';
import { lengthGetLineCount, lengthIsZero, lengthLessThanEqual } from './length';
import { concat23Trees } from './concat23Trees';
import { NodeReader } from './nodeReader';
import { Tokenizer, TokenKind } from './tokenizer';
export function parseDocument(tokenizer: Tokenizer, edits: TextEditInfo[], oldNode: AstNode | undefined, denseKeyProvider: DenseKeyProvider<number>): AstNode {
const parser = new Parser(tokenizer, edits, oldNode, denseKeyProvider);
return parser.parseDocument();
}
class Parser {
private readonly oldNodeReader?: NodeReader;
private readonly positionMapper: BeforeEditPositionMapper;
private _itemsConstructed: number = 0;
private _itemsFromCache: number = 0;
/**
* Reports how many nodes were constructed in the last parse operation.
*/
get nodesConstructed() {
return this._itemsConstructed;
}
/**
* Reports how many nodes were reused in the last parse operation.
*/
get nodesReused() {
return this._itemsFromCache;
}
constructor(
private readonly tokenizer: Tokenizer,
edits: TextEditInfo[],
oldNode: AstNode | undefined,
private readonly denseKeyProvider: DenseKeyProvider<number>,
) {
this.oldNodeReader = oldNode ? new NodeReader(oldNode) : undefined;
this.positionMapper = new BeforeEditPositionMapper(edits, tokenizer.length);
}
parseDocument(): AstNode {
this._itemsConstructed = 0;
this._itemsFromCache = 0;
let result = this.parseList(SmallImmutableSet.getEmpty());
if (!result) {
result = ListAstNode.create([]);
}
return result;
}
private parseList(
expectedClosingCategories: SmallImmutableSet<number>,
): AstNode | null {
const items = new Array<AstNode>();
while (true) {
const token = this.tokenizer.peek();
if (
!token ||
(token.kind === TokenKind.ClosingBracket &&
expectedClosingCategories.has(token.category, this.denseKeyProvider))
) {
break;
}
const child = this.parseChild(expectedClosingCategories);
if (child.kind === AstNodeKind.List && child.children.length === 0) {
continue;
}
items.push(child);
}
const result = concat23Trees(items);
return result;
}
private parseChild(
expectingClosingCategories: SmallImmutableSet<number>,
): AstNode {
if (this.oldNodeReader) {
const maxCacheableLength = this.positionMapper.getDistanceToNextChange(this.tokenizer.offset);
if (!lengthIsZero(maxCacheableLength)) {
const cachedNode = this.oldNodeReader.readLongestNodeAt(this.positionMapper.getOffsetBeforeChange(this.tokenizer.offset), curNode => {
if (!lengthLessThanEqual(curNode.length, maxCacheableLength)) {
return false;
}
const endLineDidChange = lengthGetLineCount(curNode.length) === lengthGetLineCount(maxCacheableLength);
const canBeReused = curNode.canBeReused(expectingClosingCategories, endLineDidChange);
return canBeReused;
});
if (cachedNode) {
this._itemsFromCache++;
this.tokenizer.skip(cachedNode.length);
return cachedNode;
}
}
}
this._itemsConstructed++;
const token = this.tokenizer.read()!;
switch (token.kind) {
case TokenKind.ClosingBracket:
return new InvalidBracketAstNode(token.category, token.length, this.denseKeyProvider);
case TokenKind.Text:
return token.astNode as TextAstNode;
case TokenKind.OpeningBracket:
const set = expectingClosingCategories.add(token.category, this.denseKeyProvider);
const child = this.parseList(set);
const nextToken = this.tokenizer.peek();
if (
nextToken &&
nextToken.kind === TokenKind.ClosingBracket &&
nextToken.category === token.category
) {
this.tokenizer.read();
return PairAstNode.create(
token.category,
token.astNode as BracketAstNode,
child,
nextToken.astNode as BracketAstNode
);
} else {
return PairAstNode.create(
token.category,
token.astNode as BracketAstNode,
child,
null
);
}
default:
throw new Error('unexpected');
}
}
}

View file

@ -0,0 +1,146 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
const emptyArr = new Array<number>();
/**
* Represents an immutable set that works best for a small number of elements (less than 32).
* It uses bits to encode element membership efficiently.
*/
export class SmallImmutableSet<T> {
private static cache = new Array<SmallImmutableSet<any>>(129);
private static create<T>(items: number, additionalItems: readonly number[]): SmallImmutableSet<T> {
if (items <= 128 && additionalItems.length === 0) {
// We create a cache of 128=2^7 elements to cover all sets with up to 7 (dense) elements.
let cached = SmallImmutableSet.cache[items];
if (!cached) {
cached = new SmallImmutableSet(items, additionalItems);
SmallImmutableSet.cache[items] = cached;
}
return cached;
}
return new SmallImmutableSet(items, additionalItems);
}
private static empty = SmallImmutableSet.create<any>(0, emptyArr);
public static getEmpty<T>(): SmallImmutableSet<T> {
return this.empty;
}
private constructor(
private readonly items: number,
private readonly additionalItems: readonly number[]
) {
}
public add(value: T, keyProvider: DenseKeyProvider<T>): SmallImmutableSet<T> {
const key = keyProvider.getKey(value);
let idx = key >> 5; // divided by 32
if (idx === 0) {
// fast path
const newItem = (1 << key) | this.items;
if (newItem === this.items) {
return this;
}
return SmallImmutableSet.create(newItem, this.additionalItems);
}
idx--;
const newItems = this.additionalItems.slice(0);
while (newItems.length < idx) {
newItems.push(0);
}
newItems[idx] |= 1 << (key & 31);
return SmallImmutableSet.create(this.items, newItems);
}
public has(value: T, keyProvider: DenseKeyProvider<T>): boolean {
const key = keyProvider.getKey(value);
let idx = key >> 5; // divided by 32
if (idx === 0) {
// fast path
return (this.items & (1 << key)) !== 0;
}
idx--;
return ((this.additionalItems[idx] || 0) & (1 << (key & 31))) !== 0;
}
public merge(other: SmallImmutableSet<T>): SmallImmutableSet<T> {
const merged = this.items | other.items;
if (this.additionalItems === emptyArr && other.additionalItems === emptyArr) {
// fast path
if (merged === this.items) {
return this;
}
if (merged === other.items) {
return other;
}
return SmallImmutableSet.create(merged, emptyArr);
}
// This can be optimized, but it's not a common case
const newItems = new Array<number>();
for (let i = 0; i < Math.max(this.additionalItems.length, other.additionalItems.length); i++) {
const item1 = this.additionalItems[i] || 0;
const item2 = other.additionalItems[i] || 0;
newItems.push(item1 | item2);
}
return SmallImmutableSet.create(merged, newItems);
}
public intersects(other: SmallImmutableSet<T>): boolean {
if ((this.items & other.items) !== 0) {
return true;
}
for (let i = 0; i < Math.min(this.additionalItems.length, other.additionalItems.length); i++) {
if ((this.additionalItems[i] & other.additionalItems[i]) !== 0) {
return true;
}
}
return false;
}
public equals(other: SmallImmutableSet<T>): boolean {
if (this.items !== other.items) {
return false;
}
if (this.additionalItems.length !== other.additionalItems.length) {
return false;
}
for (let i = 0; i < this.additionalItems.length; i++) {
if (this.additionalItems[i] !== other.additionalItems[i]) {
return false;
}
}
return true;
}
}
/**
* Assigns values a unique incrementing key.
*/
export class DenseKeyProvider<T> {
private readonly items = new Map<T, number>();
getKey(value: T): number {
let existing = this.items.get(value);
if (existing === undefined) {
existing = this.items.size;
this.items.set(value, existing);
}
return existing;
}
}

View file

@ -0,0 +1,349 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { NotSupportedError } from 'vs/base/common/errors';
import { LineTokens } from 'vs/editor/common/core/lineTokens';
import { ITextModel } from 'vs/editor/common/model';
import { LanguageId, StandardTokenType, TokenMetadata } from 'vs/editor/common/modes';
import { BracketAstNode, TextAstNode } from './ast';
import { BracketTokens, LanguageAgnosticBracketTokens } from './brackets';
import { lengthGetColumnCountIfZeroLineCount, Length, lengthAdd, lengthDiff, lengthToObj, lengthZero, toLength } from './length';
export interface Tokenizer {
readonly offset: Length;
readonly length: Length;
read(): Token | null;
peek(): Token | null;
skip(length: Length): void;
getText(): string;
}
export const enum TokenKind {
Text = 0,
OpeningBracket = 1,
ClosingBracket = 2,
}
export class Token {
constructor(
readonly length: Length,
readonly kind: TokenKind,
readonly category: number,
readonly languageId: LanguageId,
readonly astNode: BracketAstNode | TextAstNode | undefined,
) { }
}
export class TextBufferTokenizer implements Tokenizer {
private readonly textBufferLineCount: number;
private readonly textBufferLastLineLength: number;
private readonly reader = new NonPeekableTextBufferTokenizer(this.textModel, this.bracketTokens);
constructor(
private readonly textModel: ITextModel,
private readonly bracketTokens: LanguageAgnosticBracketTokens
) {
this.textBufferLineCount = textModel.getLineCount();
this.textBufferLastLineLength = textModel.getLineLength(this.textBufferLineCount);
}
private _offset: Length = lengthZero;
get offset() {
return this._offset;
}
get length() {
return toLength(this.textBufferLineCount, this.textBufferLastLineLength);
}
getText() {
return this.textModel.getValue();
}
skip(length: Length): void {
this.didPeek = false;
this._offset = lengthAdd(this._offset, length);
const obj = lengthToObj(this._offset);
this.reader.setPosition(obj.lineCount, obj.columnCount);
}
private didPeek = false;
private peeked: Token | null = null;
read(): Token | null {
let token: Token | null;
if (this.peeked) {
this.didPeek = false;
token = this.peeked;
} else {
token = this.reader.read();
}
if (token) {
this._offset = lengthAdd(this._offset, token.length);
}
return token;
}
peek(): Token | null {
if (!this.didPeek) {
this.peeked = this.reader.read();
this.didPeek = true;
}
return this.peeked;
}
}
/**
* Does not support peek.
*/
class NonPeekableTextBufferTokenizer {
private readonly textBufferLineCount: number;
private readonly textBufferLastLineLength: number;
constructor(private readonly textModel: ITextModel, private readonly bracketTokens: LanguageAgnosticBracketTokens) {
this.textBufferLineCount = textModel.getLineCount();
this.textBufferLastLineLength = textModel.getLineLength(this.textBufferLineCount);
}
private lineIdx = 0;
private line: string | null = null;
private lineCharOffset = 0;
private lineTokens: LineTokens | null = null;
private lineTokenOffset = 0;
public setPosition(lineIdx: number, column: number): void {
// We must not jump into a token!
if (lineIdx === this.lineIdx) {
this.lineCharOffset = column;
this.lineTokenOffset = this.lineCharOffset === 0 ? 0 : this.lineTokens!.findTokenIndexAtOffset(this.lineCharOffset);
} else {
this.lineIdx = lineIdx;
this.lineCharOffset = column;
this.line = null;
}
this.peekedToken = null;
}
/** Must be a zero line token. The end of the document cannot be peeked. */
private peekedToken: Token | null = null;
public read(): Token | null {
if (this.peekedToken) {
const token = this.peekedToken;
this.peekedToken = null;
this.lineCharOffset += lengthGetColumnCountIfZeroLineCount(token.length);
return token;
}
if (this.lineIdx > this.textBufferLineCount - 1 || (this.lineIdx === this.textBufferLineCount - 1 && this.lineCharOffset >= this.textBufferLastLineLength)) {
// We are after the end
return null;
}
if (this.line === null) {
this.lineTokens = this.textModel.getLineTokens(this.lineIdx + 1);
this.line = this.lineTokens.getLineContent();
this.lineTokenOffset = this.lineCharOffset === 0 ? 0 : this.lineTokens!.findTokenIndexAtOffset(this.lineCharOffset);
}
const startLineIdx = this.lineIdx;
const startLineCharOffset = this.lineCharOffset;
// limits the length of text tokens.
// If text tokens get too long, incremental updates will be slow
let lengthHeuristic = 0;
while (lengthHeuristic < 1000) {
const lineTokens = this.lineTokens!;
const tokenCount = lineTokens.getCount();
let peekedBracketToken: Token | null = null;
if (this.lineTokenOffset < tokenCount) {
let tokenMetadata = lineTokens.getMetadata(this.lineTokenOffset);
while (this.lineTokenOffset + 1 < tokenCount && tokenMetadata === lineTokens.getMetadata(this.lineTokenOffset + 1)) {
// Skip tokens that are identical.
// Sometimes, (bracket) identifiers are split up into multiple tokens.
this.lineTokenOffset++;
}
const isOther = TokenMetadata.getTokenType(tokenMetadata) === StandardTokenType.Other;
const endOffset = lineTokens.getEndOffset(this.lineTokenOffset);
// Is there a bracket token next? Only consume text.
if (isOther && endOffset !== this.lineCharOffset) {
const languageId = lineTokens.getLanguageId(this.lineTokenOffset);
const text = this.line.substring(this.lineCharOffset, endOffset);
const brackets = this.bracketTokens.getSingleLanguageBracketTokens(languageId);
const regexp = brackets.regExpGlobal;
if (regexp) {
regexp.lastIndex = 0;
const match = regexp.exec(text);
if (match) {
peekedBracketToken = brackets.getToken(match[0])!;
if (peekedBracketToken) {
// Consume leading text of the token
this.lineCharOffset += match.index;
}
}
}
}
lengthHeuristic += endOffset - this.lineCharOffset;
if (peekedBracketToken) {
// Don't skip the entire token, as a single token could contain multiple brackets.
if (startLineIdx !== this.lineIdx || startLineCharOffset !== this.lineCharOffset) {
// There is text before the bracket
this.peekedToken = peekedBracketToken;
break;
} else {
// Consume the peeked token
this.lineCharOffset += lengthGetColumnCountIfZeroLineCount(peekedBracketToken.length);
return peekedBracketToken;
}
} else {
// Skip the entire token, as the token contains no brackets at all.
this.lineTokenOffset++;
this.lineCharOffset = endOffset;
}
} else {
if (this.lineIdx === this.textBufferLineCount - 1) {
break;
}
this.lineIdx++;
this.lineTokens = this.textModel.getLineTokens(this.lineIdx + 1);
this.lineTokenOffset = 0;
this.line = this.lineTokens.getLineContent();
this.lineCharOffset = 0;
lengthHeuristic++;
}
}
const length = lengthDiff(startLineIdx, startLineCharOffset, this.lineIdx, this.lineCharOffset);
return new Token(length, TokenKind.Text, -1, -1, new TextAstNode(length));
}
}
export class FastTokenizer implements Tokenizer {
private _offset: Length = lengthZero;
private readonly tokens: readonly Token[];
private idx = 0;
constructor(private readonly text: string, brackets: BracketTokens) {
const regExpStr = brackets.getRegExpStr();
const regexp = regExpStr ? new RegExp(brackets.getRegExpStr() + '|\n', 'g') : null;
const tokens: Token[] = [];
let match: RegExpExecArray | null;
let curLineCount = 0;
let lastLineBreakOffset = 0;
let lastTokenEndOffset = 0;
let lastTokenEndLine = 0;
const smallTextTokens0Line = new Array<Token>();
for (let i = 0; i < 60; i++) {
smallTextTokens0Line.push(
new Token(
toLength(0, i), TokenKind.Text, -1, -1,
new TextAstNode(toLength(0, i))
)
);
}
const smallTextTokens1Line = new Array<Token>();
for (let i = 0; i < 60; i++) {
smallTextTokens1Line.push(
new Token(
toLength(1, i), TokenKind.Text, -1, -1,
new TextAstNode(toLength(1, i))
)
);
}
if (regexp) {
regexp.lastIndex = 0;
while ((match = regexp.exec(text)) !== null) {
const curOffset = match.index;
const value = match[0];
if (value === '\n') {
curLineCount++;
lastLineBreakOffset = curOffset + 1;
} else {
if (lastTokenEndOffset !== curOffset) {
let token: Token;
if (lastTokenEndLine === curLineCount) {
const colCount = curOffset - lastTokenEndOffset;
if (colCount < smallTextTokens0Line.length) {
token = smallTextTokens0Line[colCount];
} else {
const length = toLength(0, colCount);
token = new Token(length, TokenKind.Text, -1, -1, new TextAstNode(length));
}
} else {
const lineCount = curLineCount - lastTokenEndLine;
const colCount = curOffset - lastLineBreakOffset;
if (lineCount === 1 && colCount < smallTextTokens1Line.length) {
token = smallTextTokens1Line[colCount];
} else {
const length = toLength(lineCount, colCount);
token = new Token(length, TokenKind.Text, -1, -1, new TextAstNode(length));
}
}
tokens.push(token);
}
// value is matched by regexp, so the token must exist
tokens.push(brackets.getToken(value)!);
lastTokenEndOffset = curOffset + value.length;
lastTokenEndLine = curLineCount;
}
}
}
const offset = text.length;
if (lastTokenEndOffset !== offset) {
const length = (lastTokenEndLine === curLineCount)
? toLength(0, offset - lastTokenEndOffset)
: toLength(curLineCount - lastTokenEndLine, offset - lastLineBreakOffset);
tokens.push(new Token(length, TokenKind.Text, -1, -1, new TextAstNode(length)));
}
this.length = toLength(curLineCount, offset - lastLineBreakOffset);
this.tokens = tokens;
}
get offset(): Length {
return this._offset;
}
readonly length: Length;
read(): Token | null {
return this.tokens[this.idx++] || null;
}
peek(): Token | null {
return this.tokens[this.idx] || null;
}
skip(length: Length): void {
throw new NotSupportedError();
}
getText(): string {
return this.text;
}
}

View file

@ -0,0 +1,29 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IDisposable } from 'vs/base/common/lifecycle';
import { Range } from 'vs/editor/common/core/range';
import { IModelDecoration } from 'vs/editor/common/model';
export interface DecorationProvider {
/**
* Gets all the decorations in a range as an array. Only `startLineNumber` and `endLineNumber` from `range` are used for filtering.
* So for now it returns all the decorations on the same line as `range`.
* @param range The range to search in
* @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).
* @return An array with the decorations
*/
getDecorationsInRange(range: Range, ownerId?: number, filterOutValidation?: boolean): IModelDecoration[];
/**
* Gets all the decorations as an array.
* @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[];
onDidChangeDecorations(listener: () => void): IDisposable;
}

View file

@ -40,6 +40,8 @@ import { Constants } from 'vs/base/common/uint';
import { PieceTreeTextBuffer } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer';
import { listenStream } from 'vs/base/common/stream';
import { ArrayQueue } from 'vs/base/common/arrays';
import { BracketPairColorizer } from 'vs/editor/common/model/bracketPairColorizer/bracketPairColorizer';
import { DecorationProvider } from 'vs/editor/common/model/decorationProvider';
function createTextBufferBuilder() {
return new PieceTreeTextBufferBuilder();
@ -192,6 +194,7 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati
defaultEOL: model.DefaultEndOfLine.LF,
trimAutoWhitespace: EDITOR_MODEL_DEFAULTS.trimAutoWhitespace,
largeFileOptimizations: EDITOR_MODEL_DEFAULTS.largeFileOptimizations,
bracketPairColorizationOptions: EDITOR_MODEL_DEFAULTS.bracketPairColorizationOptions,
};
public static resolveOptions(textBuffer: model.ITextBuffer, options: model.ITextModelCreationOptions): model.TextModelResolvedOptions {
@ -202,7 +205,8 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati
indentSize: guessedIndentation.tabSize, // TODO@Alex: guess indentSize independent of tabSize
insertSpaces: guessedIndentation.insertSpaces,
trimAutoWhitespace: options.trimAutoWhitespace,
defaultEOL: options.defaultEOL
defaultEOL: options.defaultEOL,
bracketPairColorizationOptions: options.bracketPairColorizationOptions,
});
}
@ -211,7 +215,8 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati
indentSize: options.indentSize,
insertSpaces: options.insertSpaces,
trimAutoWhitespace: options.trimAutoWhitespace,
defaultEOL: options.defaultEOL
defaultEOL: options.defaultEOL,
bracketPairColorizationOptions: options.bracketPairColorizationOptions,
});
}
@ -289,6 +294,7 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati
private _lastDecorationId: number;
private _decorations: { [decorationId: string]: IntervalNode; };
private _decorationsTree: DecorationsTrees;
private readonly _decorationProvider: DecorationProvider;
//#endregion
//#region Tokenization
@ -299,6 +305,8 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati
private readonly _tokenization: TextModelTokenization;
//#endregion
private readonly _bracketPairColorizer;
constructor(
source: string | model.ITextBufferFactory,
creationOptions: model.ITextModelCreationOptions,
@ -375,6 +383,15 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati
this._tokens = new TokensStore();
this._tokens2 = new TokensStore2();
this._tokenization = new TextModelTokenization(this);
this._bracketPairColorizer = this._register(new BracketPairColorizer(this));
this._decorationProvider = this._bracketPairColorizer;
this._register(this._decorationProvider.onDidChangeDecorations(() => {
this._onDidChangeDecorations.beginDeferredEmit();
this._onDidChangeDecorations.fire();
this._onDidChangeDecorations.endDeferredEmit();
}));
}
public override dispose(): void {
@ -410,6 +427,7 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati
}
private _emitContentChangedEvent(rawChange: ModelRawContentChangedEvent, change: IModelContentChangedEvent): void {
this._bracketPairColorizer.handleContentChanged(change);
if (this._isDisposing) {
// Do not confuse listeners by emitting any event after disposing
return;
@ -621,13 +639,15 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati
let indentSize = (typeof _newOpts.indentSize !== 'undefined') ? _newOpts.indentSize : this._options.indentSize;
let insertSpaces = (typeof _newOpts.insertSpaces !== 'undefined') ? _newOpts.insertSpaces : this._options.insertSpaces;
let trimAutoWhitespace = (typeof _newOpts.trimAutoWhitespace !== 'undefined') ? _newOpts.trimAutoWhitespace : this._options.trimAutoWhitespace;
let bracketPairColorizationOptions = (typeof _newOpts.bracketColorizationOptions !== 'undefined') ? _newOpts.bracketColorizationOptions : this._options.bracketPairColorizationOptions;
let newOpts = new model.TextModelResolvedOptions({
tabSize: tabSize,
indentSize: indentSize,
insertSpaces: insertSpaces,
defaultEOL: this._options.defaultEOL,
trimAutoWhitespace: trimAutoWhitespace
trimAutoWhitespace: trimAutoWhitespace,
bracketPairColorizationOptions,
});
if (this._options.equals(newOpts)) {
@ -1713,7 +1733,6 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati
if (lineNumber < 1 || lineNumber > this.getLineCount()) {
return [];
}
return this.getLinesDecorations(lineNumber, lineNumber, ownerId, filterOutValidation);
}
@ -1722,12 +1741,19 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati
let startLineNumber = Math.min(lineCount, Math.max(1, _startLineNumber));
let endLineNumber = Math.min(lineCount, Math.max(1, _endLineNumber));
let endColumn = this.getLineMaxColumn(endLineNumber);
return this._getDecorationsInRange(new Range(startLineNumber, 1, endLineNumber, endColumn), ownerId, filterOutValidation);
const range = new Range(startLineNumber, 1, endLineNumber, endColumn);
const decorations = this._getDecorationsInRange(range, ownerId, filterOutValidation);
decorations.push(...this._decorationProvider.getDecorationsInRange(range, ownerId, filterOutValidation));
return decorations;
}
public getDecorationsInRange(range: IRange, ownerId: number = 0, filterOutValidation: boolean = false): model.IModelDecoration[] {
let validatedRange = this.validateRange(range);
return this._getDecorationsInRange(validatedRange, ownerId, filterOutValidation);
const decorations = this._getDecorationsInRange(validatedRange, ownerId, filterOutValidation);
decorations.push(...this._decorationProvider.getDecorationsInRange(validatedRange, ownerId, filterOutValidation));
return decorations;
}
public getOverviewRulerDecorations(ownerId: number = 0, filterOutValidation: boolean = false): model.IModelDecoration[] {
@ -1747,7 +1773,9 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati
}
public getAllDecorations(ownerId: number = 0, filterOutValidation: boolean = false): model.IModelDecoration[] {
return this._decorationsTree.getAll(this, ownerId, filterOutValidation, false);
const result = this._decorationsTree.getAll(this, ownerId, filterOutValidation, false);
result.push(...this._decorationProvider.getAllDecorations(ownerId, filterOutValidation));
return result;
}
private _getDecorationsInRange(filterRange: Range, filterOwnerId: number, filterOutValidation: boolean): model.IModelDecoration[] {
@ -1916,7 +1944,7 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati
this._tokens.setTokens(this._languageIdentifier.id, lineNumber - 1, this._buffer.getLineLength(lineNumber), tokens, false);
}
public setTokens(tokens: MultilineTokens[]): void {
public setTokens(tokens: MultilineTokens[], backgroundTokenizationCompleted: boolean = false): void {
if (tokens.length === 0) {
return;
}
@ -1951,6 +1979,7 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati
this._emitModelTokensChangedEvent({
tokenizationSupportChanged: false,
semanticTokensApplied: false,
backgroundTokenizationCompleted,
ranges: ranges
});
}
@ -1962,6 +1991,7 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati
this._emitModelTokensChangedEvent({
tokenizationSupportChanged: false,
semanticTokensApplied: tokens !== null,
backgroundTokenizationCompleted: false,
ranges: [{ fromLineNumber: 1, toLineNumber: this.getLineCount() }]
});
}
@ -1983,6 +2013,7 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati
this._emitModelTokensChangedEvent({
tokenizationSupportChanged: false,
semanticTokensApplied: true,
backgroundTokenizationCompleted: false,
ranges: [{ fromLineNumber: changedRange.startLineNumber, toLineNumber: changedRange.endLineNumber }]
});
}
@ -1998,6 +2029,7 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati
this._emitModelTokensChangedEvent({
tokenizationSupportChanged: true,
semanticTokensApplied: false,
backgroundTokenizationCompleted: false,
ranges: [{
fromLineNumber: 1,
toLineNumber: this._buffer.getLineCount()
@ -2011,6 +2043,7 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati
this._emitModelTokensChangedEvent({
tokenizationSupportChanged: false,
semanticTokensApplied: false,
backgroundTokenizationCompleted: false,
ranges: [{ fromLineNumber: 1, toLineNumber: this.getLineCount() }]
});
}

View file

@ -89,6 +89,10 @@ export interface IModelDecorationsChangedEvent {
export interface IModelTokensChangedEvent {
readonly tokenizationSupportChanged: boolean;
readonly semanticTokensApplied: boolean;
/**
* Indicates that future token updates are not caused by the background tokenization any more.
*/
readonly backgroundTokenizationCompleted: boolean;
readonly ranges: {
/**
* The start of the range (inclusive)

View file

@ -266,10 +266,13 @@ export class TextModelTokenization extends Disposable {
}
}
private _revalidateTokensNow(toLineNumber: number = this._textModel.getLineCount()): void {
private _revalidateTokensNow(): void {
const textModelLastLineNumber = this._textModel.getLineCount();
const MAX_ALLOWED_TIME = 1;
const builder = new MultilineTokensBuilder();
const sw = StopWatch.create(false);
let tokenizedLineNumber = -1;
while (this._hasLinesToTokenize()) {
if (sw.elapsed() > MAX_ALLOWED_TIME) {
@ -277,15 +280,15 @@ export class TextModelTokenization extends Disposable {
break;
}
const tokenizedLineNumber = this._tokenizeOneInvalidLine(builder);
tokenizedLineNumber = this._tokenizeOneInvalidLine(builder);
if (tokenizedLineNumber >= toLineNumber) {
if (tokenizedLineNumber >= textModelLastLineNumber) {
break;
}
}
this._beginBackgroundTokenization();
this._textModel.setTokens(builder.tokens);
this._textModel.setTokens(builder.tokens, tokenizedLineNumber >= textModelLastLineNumber);
}
public tokenizeViewport(startLineNumber: number, endLineNumber: number): void {

View file

@ -30,6 +30,7 @@ import { EditStackElement, isEditStackElement } from 'vs/editor/common/model/edi
import { Schemas } from 'vs/base/common/network';
import { SemanticTokensProviderStyling, toMultilineTokens2 } from 'vs/editor/common/services/semanticTokensProviderStyling';
import { getDocumentSemanticTokens, isSemanticTokens, isSemanticTokensEdits } from 'vs/editor/common/services/getSemanticTokens';
import { equals } from 'vs/base/common/objects';
export interface IEditorSemanticHighlightingOptions {
enabled: true | false | 'configuredByTheme';
@ -101,6 +102,7 @@ interface IRawEditorConfig {
trimAutoWhitespace?: any;
creationOptions?: any;
largeFileOptimizations?: any;
bracketPairColorization?: any;
}
interface IRawConfig {
@ -233,6 +235,12 @@ export class ModelServiceImpl extends Disposable implements IModelService {
if (config.editor && typeof config.editor.largeFileOptimizations !== 'undefined') {
largeFileOptimizations = (config.editor.largeFileOptimizations === 'false' ? false : Boolean(config.editor.largeFileOptimizations));
}
let bracketPairColorizationOptions = EDITOR_MODEL_DEFAULTS.bracketPairColorizationOptions;
if (config.editor?.bracketPairColorization && typeof config.editor.bracketPairColorization === 'object') {
bracketPairColorizationOptions = {
enabled: !!config.editor.bracketPairColorization.enabled
};
}
return {
isForSimpleWidget: isForSimpleWidget,
@ -242,7 +250,8 @@ export class ModelServiceImpl extends Disposable implements IModelService {
detectIndentation: detectIndentation,
defaultEOL: newDefaultEOL,
trimAutoWhitespace: trimAutoWhitespace,
largeFileOptimizations: largeFileOptimizations
largeFileOptimizations: largeFileOptimizations,
bracketPairColorizationOptions
};
}
@ -304,6 +313,7 @@ export class ModelServiceImpl extends Disposable implements IModelService {
&& (currentOptions.tabSize === newOptions.tabSize)
&& (currentOptions.indentSize === newOptions.indentSize)
&& (currentOptions.trimAutoWhitespace === newOptions.trimAutoWhitespace)
&& equals(currentOptions.bracketPairColorizationOptions, newOptions.bracketPairColorizationOptions)
) {
// Same indent opts, no need to touch the model
return;
@ -312,14 +322,16 @@ export class ModelServiceImpl extends Disposable implements IModelService {
if (newOptions.detectIndentation) {
model.detectIndentation(newOptions.insertSpaces, newOptions.tabSize);
model.updateOptions({
trimAutoWhitespace: newOptions.trimAutoWhitespace
trimAutoWhitespace: newOptions.trimAutoWhitespace,
bracketColorizationOptions: newOptions.bracketPairColorizationOptions
});
} else {
model.updateOptions({
insertSpaces: newOptions.insertSpaces,
tabSize: newOptions.tabSize,
indentSize: newOptions.indentSize,
trimAutoWhitespace: newOptions.trimAutoWhitespace
trimAutoWhitespace: newOptions.trimAutoWhitespace,
bracketColorizationOptions: newOptions.bracketPairColorizationOptions
});
}
}

View file

@ -179,125 +179,126 @@ export enum EditorOption {
autoIndent = 9,
automaticLayout = 10,
autoSurround = 11,
codeLens = 12,
codeLensFontFamily = 13,
codeLensFontSize = 14,
colorDecorators = 15,
columnSelection = 16,
comments = 17,
contextmenu = 18,
copyWithSyntaxHighlighting = 19,
cursorBlinking = 20,
cursorSmoothCaretAnimation = 21,
cursorStyle = 22,
cursorSurroundingLines = 23,
cursorSurroundingLinesStyle = 24,
cursorWidth = 25,
disableLayerHinting = 26,
disableMonospaceOptimizations = 27,
domReadOnly = 28,
dragAndDrop = 29,
emptySelectionClipboard = 30,
extraEditorClassName = 31,
fastScrollSensitivity = 32,
find = 33,
fixedOverflowWidgets = 34,
folding = 35,
foldingStrategy = 36,
foldingHighlight = 37,
foldingImportsByDefault = 38,
unfoldOnClickAfterEndOfLine = 39,
fontFamily = 40,
fontInfo = 41,
fontLigatures = 42,
fontSize = 43,
fontWeight = 44,
formatOnPaste = 45,
formatOnType = 46,
glyphMargin = 47,
gotoLocation = 48,
hideCursorInOverviewRuler = 49,
highlightActiveIndentGuide = 50,
hover = 51,
inDiffEditor = 52,
inlineSuggest = 53,
letterSpacing = 54,
lightbulb = 55,
lineDecorationsWidth = 56,
lineHeight = 57,
lineNumbers = 58,
lineNumbersMinChars = 59,
linkedEditing = 60,
links = 61,
matchBrackets = 62,
minimap = 63,
mouseStyle = 64,
mouseWheelScrollSensitivity = 65,
mouseWheelZoom = 66,
multiCursorMergeOverlapping = 67,
multiCursorModifier = 68,
multiCursorPaste = 69,
occurrencesHighlight = 70,
overviewRulerBorder = 71,
overviewRulerLanes = 72,
padding = 73,
parameterHints = 74,
peekWidgetDefaultFocus = 75,
definitionLinkOpensInPeek = 76,
quickSuggestions = 77,
quickSuggestionsDelay = 78,
readOnly = 79,
renameOnType = 80,
renderControlCharacters = 81,
renderIndentGuides = 82,
renderFinalNewline = 83,
renderLineHighlight = 84,
renderLineHighlightOnlyWhenFocus = 85,
renderValidationDecorations = 86,
renderWhitespace = 87,
revealHorizontalRightPadding = 88,
roundedSelection = 89,
rulers = 90,
scrollbar = 91,
scrollBeyondLastColumn = 92,
scrollBeyondLastLine = 93,
scrollPredominantAxis = 94,
selectionClipboard = 95,
selectionHighlight = 96,
selectOnLineNumbers = 97,
showFoldingControls = 98,
showUnused = 99,
snippetSuggestions = 100,
smartSelect = 101,
smoothScrolling = 102,
stickyTabStops = 103,
stopRenderingLineAfter = 104,
suggest = 105,
suggestFontSize = 106,
suggestLineHeight = 107,
suggestOnTriggerCharacters = 108,
suggestSelection = 109,
tabCompletion = 110,
tabIndex = 111,
unusualLineTerminators = 112,
useShadowDOM = 113,
useTabStops = 114,
wordSeparators = 115,
wordWrap = 116,
wordWrapBreakAfterCharacters = 117,
wordWrapBreakBeforeCharacters = 118,
wordWrapColumn = 119,
wordWrapOverride1 = 120,
wordWrapOverride2 = 121,
wrappingIndent = 122,
wrappingStrategy = 123,
showDeprecated = 124,
inlayHints = 125,
editorClassName = 126,
pixelRatio = 127,
tabFocusMode = 128,
layoutInfo = 129,
wrappingInfo = 130
bracketPairColorization = 12,
codeLens = 13,
codeLensFontFamily = 14,
codeLensFontSize = 15,
colorDecorators = 16,
columnSelection = 17,
comments = 18,
contextmenu = 19,
copyWithSyntaxHighlighting = 20,
cursorBlinking = 21,
cursorSmoothCaretAnimation = 22,
cursorStyle = 23,
cursorSurroundingLines = 24,
cursorSurroundingLinesStyle = 25,
cursorWidth = 26,
disableLayerHinting = 27,
disableMonospaceOptimizations = 28,
domReadOnly = 29,
dragAndDrop = 30,
emptySelectionClipboard = 31,
extraEditorClassName = 32,
fastScrollSensitivity = 33,
find = 34,
fixedOverflowWidgets = 35,
folding = 36,
foldingStrategy = 37,
foldingHighlight = 38,
foldingImportsByDefault = 39,
unfoldOnClickAfterEndOfLine = 40,
fontFamily = 41,
fontInfo = 42,
fontLigatures = 43,
fontSize = 44,
fontWeight = 45,
formatOnPaste = 46,
formatOnType = 47,
glyphMargin = 48,
gotoLocation = 49,
hideCursorInOverviewRuler = 50,
highlightActiveIndentGuide = 51,
hover = 52,
inDiffEditor = 53,
inlineSuggest = 54,
letterSpacing = 55,
lightbulb = 56,
lineDecorationsWidth = 57,
lineHeight = 58,
lineNumbers = 59,
lineNumbersMinChars = 60,
linkedEditing = 61,
links = 62,
matchBrackets = 63,
minimap = 64,
mouseStyle = 65,
mouseWheelScrollSensitivity = 66,
mouseWheelZoom = 67,
multiCursorMergeOverlapping = 68,
multiCursorModifier = 69,
multiCursorPaste = 70,
occurrencesHighlight = 71,
overviewRulerBorder = 72,
overviewRulerLanes = 73,
padding = 74,
parameterHints = 75,
peekWidgetDefaultFocus = 76,
definitionLinkOpensInPeek = 77,
quickSuggestions = 78,
quickSuggestionsDelay = 79,
readOnly = 80,
renameOnType = 81,
renderControlCharacters = 82,
renderIndentGuides = 83,
renderFinalNewline = 84,
renderLineHighlight = 85,
renderLineHighlightOnlyWhenFocus = 86,
renderValidationDecorations = 87,
renderWhitespace = 88,
revealHorizontalRightPadding = 89,
roundedSelection = 90,
rulers = 91,
scrollbar = 92,
scrollBeyondLastColumn = 93,
scrollBeyondLastLine = 94,
scrollPredominantAxis = 95,
selectionClipboard = 96,
selectionHighlight = 97,
selectOnLineNumbers = 98,
showFoldingControls = 99,
showUnused = 100,
snippetSuggestions = 101,
smartSelect = 102,
smoothScrolling = 103,
stickyTabStops = 104,
stopRenderingLineAfter = 105,
suggest = 106,
suggestFontSize = 107,
suggestLineHeight = 108,
suggestOnTriggerCharacters = 109,
suggestSelection = 110,
tabCompletion = 111,
tabIndex = 112,
unusualLineTerminators = 113,
useShadowDOM = 114,
useTabStops = 115,
wordSeparators = 116,
wordWrap = 117,
wordWrapBreakAfterCharacters = 118,
wordWrapBreakBeforeCharacters = 119,
wordWrapColumn = 120,
wordWrapOverride1 = 121,
wordWrapOverride2 = 122,
wrappingIndent = 123,
wrappingStrategy = 124,
showDeprecated = 125,
inlayHints = 126,
editorClassName = 127,
pixelRatio = 128,
tabFocusMode = 129,
layoutInfo = 130,
wrappingInfo = 131
}
/**

View file

@ -52,6 +52,13 @@ export const overviewRulerError = registerColor('editorOverviewRuler.errorForegr
export const overviewRulerWarning = registerColor('editorOverviewRuler.warningForeground', { dark: editorWarningForeground, light: editorWarningForeground, hc: editorWarningBorder }, nls.localize('overviewRuleWarning', 'Overview ruler marker color for warnings.'));
export const overviewRulerInfo = registerColor('editorOverviewRuler.infoForeground', { dark: editorInfoForeground, light: editorInfoForeground, hc: editorInfoBorder }, nls.localize('overviewRuleInfo', 'Overview ruler marker color for infos.'));
export const editorBracketHighlightingForeground1 = registerColor('editorBracketHighlight.foreground1', { dark: '#FFD700', light: '#9A6D03', hc: '#FFD700' }, nls.localize('editorBracketHighlightForeground1', 'Foreground color of brackets (1).'));
export const editorBracketHighlightingForeground2 = registerColor('editorBracketHighlight.foreground2', { dark: '#DA70D6', light: '#C433BF', hc: '#DA70D6' }, nls.localize('editorBracketHighlightForeground2', 'Foreground color of brackets (2).'));
export const editorBracketHighlightingForeground3 = registerColor('editorBracketHighlight.foreground3', { dark: '#87CEFA', light: '#0879BF', hc: '#87CEFA' }, nls.localize('editorBracketHighlightForeground3', 'Foreground color of brackets (3).'));
export const editorBracketHighlightingForeground4 = registerColor('editorBracketHighlight.foreground4', { dark: '#00000000', light: '#00000000', hc: '#00000000' }, nls.localize('editorBracketHighlightForeground4', 'Foreground color of brackets (4).'));
export const editorBracketHighlightingForeground5 = registerColor('editorBracketHighlight.foreground5', { dark: '#00000000', light: '#00000000', hc: '#00000000' }, nls.localize('editorBracketHighlightForeground5', 'Foreground color of brackets (5).'));
export const editorBracketHighlightingForeground6 = registerColor('editorBracketHighlight.foreground6', { dark: '#00000000', light: '#00000000', hc: '#00000000' }, nls.localize('editorBracketHighlightForeground6', 'Foreground color of brackets (6).'));
// contains all color rules that used to defined in editor/browser/widget/editor.css
registerThemingParticipant((theme, collector) => {
const background = theme.getColor(editorBackground);

View file

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { URI } from 'vs/base/common/uri';
import { DefaultEndOfLine, ITextModelCreationOptions } from 'vs/editor/common/model';
import { BracketPairColorizationOptions, DefaultEndOfLine, ITextModelCreationOptions } from 'vs/editor/common/model';
import { TextModel } from 'vs/editor/common/model/textModel';
import { LanguageIdentifier } from 'vs/editor/common/modes';
import { TestDialogService } from 'vs/platform/dialogs/test/common/testDialogService';
@ -26,6 +26,7 @@ export interface IRelaxedTextModelCreationOptions {
defaultEOL?: DefaultEndOfLine;
isForSimpleWidget?: boolean;
largeFileOptimizations?: boolean;
bracketColorizationOptions?: BracketPairColorizationOptions;
}
export function createTextModel(text: string, _options: IRelaxedTextModelCreationOptions = TextModel.DEFAULT_CREATION_OPTIONS, languageIdentifier: LanguageIdentifier | null = null, uri: URI | null = null): TextModel {
@ -38,6 +39,7 @@ export function createTextModel(text: string, _options: IRelaxedTextModelCreatio
defaultEOL: (typeof _options.defaultEOL === 'undefined' ? TextModel.DEFAULT_CREATION_OPTIONS.defaultEOL : _options.defaultEOL),
isForSimpleWidget: (typeof _options.isForSimpleWidget === 'undefined' ? TextModel.DEFAULT_CREATION_OPTIONS.isForSimpleWidget : _options.isForSimpleWidget),
largeFileOptimizations: (typeof _options.largeFileOptimizations === 'undefined' ? TextModel.DEFAULT_CREATION_OPTIONS.largeFileOptimizations : _options.largeFileOptimizations),
bracketPairColorizationOptions: (typeof _options.bracketColorizationOptions === 'undefined' ? TextModel.DEFAULT_CREATION_OPTIONS.bracketPairColorizationOptions : _options.bracketColorizationOptions),
};
const dialogService = new TestDialogService();
const notificationService = new TestNotificationService();

View file

@ -0,0 +1,420 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import assert = require('assert');
import { splitLines } from 'vs/base/common/strings';
import { Position } from 'vs/editor/common/core/position';
import { IRange, Range } from 'vs/editor/common/core/range';
import { BeforeEditPositionMapper, TextEditInfo } from 'vs/editor/common/model/bracketPairColorizer/beforeEditPositionMapper';
import { Length, lengthOfString, lengthToObj, lengthToPosition, toLength } from 'vs/editor/common/model/bracketPairColorizer/length';
suite('Bracket Pair Colorizer - BeforeEditPositionMapper', () => {
test('Single-Line 1', () => {
assert.deepStrictEqual(
compute(
[
'0123456789',
],
[
new TextEdit(toLength(0, 4), toLength(0, 7), 'xy')
]
),
[
'0 1 2 3 x y 7 8 9 ', // The line
'0 0 0 0 0 0 0 0 0 0 ', // the old line numbers
'0 1 2 3 4 5 7 8 9 10 ', // the old columns
'0 0 0 0 0 0 0 0 0 0 ', // line count until next change
'4 3 2 1 0 0 3 2 1 0 ', // column count until next change
]
);
});
test('Single-Line 2', () => {
assert.deepStrictEqual(
compute(
[
'0123456789',
],
[
new TextEdit(toLength(0, 2), toLength(0, 4), 'xxxx'),
new TextEdit(toLength(0, 6), toLength(0, 6), 'yy')
]
),
[
'0 1 x x x x 4 5 y y 6 7 8 9 ',
'0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ',
'0 1 2 3 4 5 4 5 6 7 6 7 8 9 10 ',
'0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ',
'2 1 0 0 0 0 2 1 0 0 4 3 2 1 0 ',
]
);
});
test('Multi-Line Replace 1', () => {
assert.deepStrictEqual(
compute(
[
'₀₁₂₃₄₅₆₇₈₉',
'0123456789',
'⁰¹²³⁴⁵⁶⁷⁸⁹',
],
[
new TextEdit(toLength(0, 3), toLength(1, 3), 'xy'),
]
),
[
'₀ ₁ ₂ x y 3 4 5 6 7 8 9 ',
'0 0 0 0 0 1 1 1 1 1 1 1 1 ',
'0 1 2 3 4 3 4 5 6 7 8 9 10 ',
'0 0 0 0 0 1 1 1 1 1 1 1 1 ',
'3 2 1 0 0 10 10 10 10 10 10 10 10 ',
// ------------------
'⁰ ¹ ² ³ ⁴ ⁵ ⁶ ⁷ ⁸ ⁹ ',
'2 2 2 2 2 2 2 2 2 2 2 ',
'0 1 2 3 4 5 6 7 8 9 10 ',
'0 0 0 0 0 0 0 0 0 0 0 ',
'10 9 8 7 6 5 4 3 2 1 0 ',
]
);
});
test('Multi-Line Replace 2', () => {
assert.deepStrictEqual(
compute(
[
'₀₁₂₃₄₅₆₇₈₉',
'012345678',
'⁰¹²³⁴⁵⁶⁷⁸⁹',
],
[
new TextEdit(toLength(0, 3), toLength(1, 0), 'ab'),
new TextEdit(toLength(1, 5), toLength(1, 7), 'c'),
]
),
[
'₀ ₁ ₂ a b 0 1 2 3 4 c 7 8 ',
'0 0 0 0 0 1 1 1 1 1 1 1 1 1 ',
'0 1 2 3 4 0 1 2 3 4 5 7 8 9 ',
'0 0 0 0 0 0 0 0 0 0 0 1 1 1 ',
'3 2 1 0 0 5 4 3 2 1 0 10 10 10 ',
// ------------------
'⁰ ¹ ² ³ ⁴ ⁵ ⁶ ⁷ ⁸ ⁹ ',
'2 2 2 2 2 2 2 2 2 2 2 ',
'0 1 2 3 4 5 6 7 8 9 10 ',
'0 0 0 0 0 0 0 0 0 0 0 ',
'10 9 8 7 6 5 4 3 2 1 0 ',
]
);
});
test('Multi-Line Replace 3', () => {
assert.deepStrictEqual(
compute(
[
'₀₁₂₃₄₅₆₇₈₉',
'012345678',
'⁰¹²³⁴⁵⁶⁷⁸⁹',
],
[
new TextEdit(toLength(0, 3), toLength(1, 0), 'ab'),
new TextEdit(toLength(1, 5), toLength(1, 7), 'c'),
new TextEdit(toLength(1, 8), toLength(2, 4), 'd'),
]
),
[
'₀ ₁ ₂ a b 0 1 2 3 4 c 7 d ⁴ ⁵ ⁶ ⁷ ⁸ ⁹ ',
'0 0 0 0 0 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 ',
'0 1 2 3 4 0 1 2 3 4 5 7 8 4 5 6 7 8 9 10 ',
'0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ',
'3 2 1 0 0 5 4 3 2 1 0 1 0 6 5 4 3 2 1 0 ',
]
);
});
test('Multi-Line Insert 1', () => {
assert.deepStrictEqual(
compute(
[
'012345678',
],
[
new TextEdit(toLength(0, 3), toLength(0, 5), 'a\nb'),
]
),
[
'0 1 2 a ',
'0 0 0 0 0 ',
'0 1 2 3 4 ',
'0 0 0 0 0 ',
'3 2 1 0 0 ',
// ------------------
'b 5 6 7 8 ',
'1 0 0 0 0 0 ',
'0 5 6 7 8 9 ',
'0 0 0 0 0 0 ',
'0 4 3 2 1 0 ',
]
);
});
test('Multi-Line Insert 2', () => {
assert.deepStrictEqual(
compute(
[
'012345678',
],
[
new TextEdit(toLength(0, 3), toLength(0, 5), 'a\nb'),
new TextEdit(toLength(0, 7), toLength(0, 8), 'x\ny'),
]
),
[
'0 1 2 a ',
'0 0 0 0 0 ',
'0 1 2 3 4 ',
'0 0 0 0 0 ',
'3 2 1 0 0 ',
// ------------------
'b 5 6 x ',
'1 0 0 0 0 ',
'0 5 6 7 8 ',
'0 0 0 0 0 ',
'0 2 1 0 0 ',
// ------------------
'y 8 ',
'1 0 0 ',
'0 8 9 ',
'0 0 0 ',
'0 1 0 ',
]
);
});
test('Multi-Line Replace/Insert 1', () => {
assert.deepStrictEqual(
compute(
[
'₀₁₂₃₄₅₆₇₈₉',
'012345678',
'⁰¹²³⁴⁵⁶⁷⁸⁹',
],
[
new TextEdit(toLength(0, 3), toLength(1, 1), 'aaa\nbbb'),
]
),
[
'₀ ₁ ₂ a a a ',
'0 0 0 0 0 0 0 ',
'0 1 2 3 4 5 6 ',
'0 0 0 0 0 0 0 ',
'3 2 1 0 0 0 0 ',
// ------------------
'b b b 1 2 3 4 5 6 7 8 ',
'1 1 1 1 1 1 1 1 1 1 1 1 ',
'0 1 2 1 2 3 4 5 6 7 8 9 ',
'0 0 0 1 1 1 1 1 1 1 1 1 ',
'0 0 0 10 10 10 10 10 10 10 10 10 ',
// ------------------
'⁰ ¹ ² ³ ⁴ ⁵ ⁶ ⁷ ⁸ ⁹ ',
'2 2 2 2 2 2 2 2 2 2 2 ',
'0 1 2 3 4 5 6 7 8 9 10 ',
'0 0 0 0 0 0 0 0 0 0 0 ',
'10 9 8 7 6 5 4 3 2 1 0 ',
]
);
});
test('Multi-Line Replace/Insert 2', () => {
assert.deepStrictEqual(
compute(
[
'₀₁₂₃₄₅₆₇₈₉',
'012345678',
'⁰¹²³⁴⁵⁶⁷⁸⁹',
],
[
new TextEdit(toLength(0, 3), toLength(1, 1), 'aaa\nbbb'),
new TextEdit(toLength(1, 5), toLength(1, 5), 'x\ny'),
new TextEdit(toLength(1, 7), toLength(2, 4), 'k\nl'),
]
),
[
'₀ ₁ ₂ a a a ',
'0 0 0 0 0 0 0 ',
'0 1 2 3 4 5 6 ',
'0 0 0 0 0 0 0 ',
'3 2 1 0 0 0 0 ',
// ------------------
'b b b 1 2 3 4 x ',
'1 1 1 1 1 1 1 1 1 ',
'0 1 2 1 2 3 4 5 6 ',
'0 0 0 0 0 0 0 0 0 ',
'0 0 0 4 3 2 1 0 0 ',
// ------------------
'y 5 6 k ',
'2 1 1 1 1 ',
'0 5 6 7 8 ',
'0 0 0 0 0 ',
'0 2 1 0 0 ',
// ------------------
'l ⁴ ⁵ ⁶ ⁷ ⁸ ⁹ ',
'2 2 2 2 2 2 2 2 ',
'0 4 5 6 7 8 9 10 ',
'0 0 0 0 0 0 0 0 ',
'0 6 5 4 3 2 1 0 ',
]
);
});
});
/** @pure */
function compute(inputArr: string[], edits: TextEdit[]): string[] {
const newLines = splitLines(applyLineColumnEdits(inputArr.join('\n'), edits.map(e => ({
text: e.newText,
range: Range.fromPositions(lengthToPosition(e.startOffset), lengthToPosition(e.endOffset))
}))));
const mapper = new BeforeEditPositionMapper(edits, lengthOfString(newLines.join('\n')));
const result = new Array<string>();
let lineIdx = 0;
for (const line of newLines) {
let lineLine = '';
let colLine = '';
let lineStr = '';
let colDist = '';
let lineDist = '';
for (let colIdx = 0; colIdx <= line.length; colIdx++) {
const before = mapper.getOffsetBeforeChange(toLength(lineIdx, colIdx));
const beforeObj = lengthToObj(before);
if (colIdx < line.length) {
lineStr += rightPad(line[colIdx], 3);
}
lineLine += rightPad('' + beforeObj.lineCount, 3);
colLine += rightPad('' + beforeObj.columnCount, 3);
const dist = lengthToObj(mapper.getDistanceToNextChange(toLength(lineIdx, colIdx)));
lineDist += rightPad('' + dist.lineCount, 3);
colDist += rightPad('' + dist.columnCount, 3);
}
result.push(lineStr);
result.push(lineLine);
result.push(colLine);
result.push(lineDist);
result.push(colDist);
lineIdx++;
}
return result;
}
export class TextEdit extends TextEditInfo {
constructor(
startOffset: Length,
endOffset: Length,
public readonly newText: string
) {
super(
startOffset,
endOffset,
lengthOfString(newText)
);
}
}
class PositionOffsetTransformer {
private readonly lineStartOffsetByLineIdx: number[];
constructor(text: string) {
this.lineStartOffsetByLineIdx = [];
this.lineStartOffsetByLineIdx.push(0);
for (let i = 0; i < text.length; i++) {
if (text.charAt(i) === '\n') {
this.lineStartOffsetByLineIdx.push(i + 1);
}
}
}
getOffset(position: Position): number {
return this.lineStartOffsetByLineIdx[position.lineNumber - 1] + position.column - 1;
}
}
function applyLineColumnEdits(text: string, edits: { range: IRange, text: string }[]): string {
const transformer = new PositionOffsetTransformer(text);
const offsetEdits = edits.map(e => {
const range = Range.lift(e.range);
return ({
startOffset: transformer.getOffset(range.getStartPosition()),
endOffset: transformer.getOffset(range.getEndPosition()),
text: e.text
});
});
offsetEdits.sort((a, b) => b.startOffset - a.startOffset);
for (const edit of offsetEdits) {
text = text.substring(0, edit.startOffset) + edit.text + text.substring(edit.endOffset);
}
return text;
}
function rightPad(str: string, len: number): string {
while (str.length < len) {
str += ' ';
}
return str;
}

View file

@ -0,0 +1,90 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import assert = require('assert');
import { AstNode, AstNodeKind, ListAstNode, TextAstNode } from 'vs/editor/common/model/bracketPairColorizer/ast';
import { toLength } from 'vs/editor/common/model/bracketPairColorizer/length';
import { concat23Trees } from 'vs/editor/common/model/bracketPairColorizer/concat23Trees';
suite('Bracket Pair Colorizer - mergeItems', () => {
test('Clone', () => {
const tree = ListAstNode.create([
new TextAstNode(toLength(1, 1)),
new TextAstNode(toLength(1, 1)),
]);
assert.ok(equals(tree, tree.clone()));
});
function equals(node1: AstNode, node2: AstNode): boolean {
if (node1.length !== node2.length) {
return false;
}
if (node1.children.length !== node2.children.length) {
return false;
}
for (let i = 0; i < node1.children.length; i++) {
if (!equals(node1.children[i], node2.children[i])) {
return false;
}
}
if (!node1.unopenedBrackets.equals(node2.unopenedBrackets)) {
return false;
}
if (node1.kind === AstNodeKind.Pair && node2.kind === AstNodeKind.Pair) {
return node1.category === node2.category;
} else if (node1.kind === node2.kind) {
return true;
}
return false;
}
function testMerge(lists: AstNode[]) {
const node = (concat23Trees(lists.map(l => l.clone())) || ListAstNode.create([])).flattenLists();
// This trivial merge does not maintain the (2,3) tree invariant.
const referenceNode = ListAstNode.create(lists).flattenLists();
assert.ok(equals(node, referenceNode), 'merge23Trees failed');
}
test('Empty List', () => {
testMerge([]);
});
test('Same Height Lists', () => {
const textNode = new TextAstNode(toLength(1, 1));
const tree = ListAstNode.create([textNode.clone(), textNode.clone()]);
testMerge([tree.clone(), tree.clone(), tree.clone(), tree.clone(), tree.clone()]);
});
test('Different Height Lists 1', () => {
const textNode = new TextAstNode(toLength(1, 1));
const tree1 = ListAstNode.create([textNode.clone(), textNode.clone()]);
const tree2 = ListAstNode.create([tree1.clone(), tree1.clone()]);
testMerge([tree1, tree2]);
});
test('Different Height Lists 2', () => {
const textNode = new TextAstNode(toLength(1, 1));
const tree1 = ListAstNode.create([textNode.clone(), textNode.clone()]);
const tree2 = ListAstNode.create([tree1.clone(), tree1.clone()]);
testMerge([tree2, tree1]);
});
test('Different Height Lists 3', () => {
const textNode = new TextAstNode(toLength(1, 1));
const tree1 = ListAstNode.create([textNode.clone(), textNode.clone()]);
const tree2 = ListAstNode.create([tree1.clone(), tree1.clone()]);
testMerge([tree2, tree1, tree1, tree2, tree2]);
});
});

View file

@ -0,0 +1,60 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import assert = require('assert');
import { Length, lengthAdd, lengthDiffNonNegative, lengthToObj, toLength } from 'vs/editor/common/model/bracketPairColorizer/length';
suite('Bracket Pair Colorizer - Length', () => {
function toStr(length: Length): string {
return lengthToObj(length).toString();
}
test('Basic', () => {
const l1 = toLength(100, 10);
assert.strictEqual(lengthToObj(l1).lineCount, 100);
assert.strictEqual(lengthToObj(l1).columnCount, 10);
assert.deepStrictEqual(toStr(lengthAdd(l1, toLength(100, 10))), '200,10');
assert.deepStrictEqual(toStr(lengthAdd(l1, toLength(0, 10))), '100,20');
});
test('lengthDiffNonNeg', () => {
assert.deepStrictEqual(
toStr(
lengthDiffNonNegative(
toLength(100, 10),
toLength(100, 20))
),
'0,10'
);
assert.deepStrictEqual(
toStr(
lengthDiffNonNegative(
toLength(100, 10),
toLength(101, 20))
),
'1,20'
);
assert.deepStrictEqual(
toStr(
lengthDiffNonNegative(
toLength(101, 30),
toLength(101, 20))
),
'0,0'
);
assert.deepStrictEqual(
toStr(
lengthDiffNonNegative(
toLength(102, 10),
toLength(101, 20))
),
'0,0'
);
});
});

View file

@ -0,0 +1,47 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import assert = require('assert');
import { DenseKeyProvider, SmallImmutableSet } from 'vs/editor/common/model/bracketPairColorizer/smallImmutableSet';
suite('Bracket Pair Colorizer - ImmutableSet', () => {
test('Basic', () => {
const keyProvider = new DenseKeyProvider<string>();
const empty = SmallImmutableSet.getEmpty<string>();
const items1 = empty.add('item1', keyProvider);
const items12 = items1.add('item2', keyProvider);
const items2 = empty.add('item2', keyProvider);
const items21 = items2.add('item1', keyProvider);
const items3 = empty.add('item3', keyProvider);
assert.strictEqual(items12.intersects(items1), true);
assert.strictEqual(items12.has('item1', keyProvider), true);
assert.strictEqual(items12.intersects(items3), false);
assert.strictEqual(items12.has('item3', keyProvider), false);
assert.strictEqual(items21.equals(items12), true);
assert.strictEqual(items21.equals(items2), false);
});
test('Many Elements', () => {
const keyProvider = new DenseKeyProvider<string>();
let set = SmallImmutableSet.getEmpty<string>();
for (let i = 0; i < 100; i++) {
keyProvider.getKey(`item${i}`);
if (i % 2 === 0) {
set = set.add(`item${i}`, keyProvider);
}
}
for (let i = 0; i < 100; i++) {
assert.strictEqual(set.has(`item${i}`, keyProvider), i % 2 === 0);
}
});
});

View file

@ -0,0 +1,171 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import assert = require('assert');
import { DisposableStore } from 'vs/base/common/lifecycle';
import { TokenizationResult2 } from 'vs/editor/common/core/token';
import { LanguageAgnosticBracketTokens } from 'vs/editor/common/model/bracketPairColorizer/brackets';
import { Length, lengthAdd, lengthsToRange, lengthZero } from 'vs/editor/common/model/bracketPairColorizer/length';
import { TextBufferTokenizer, Token, Tokenizer, TokenKind } from 'vs/editor/common/model/bracketPairColorizer/tokenizer';
import { TextModel } from 'vs/editor/common/model/textModel';
import { IState, ITokenizationSupport, LanguageId, LanguageIdentifier, MetadataConsts, StandardTokenType, TokenizationRegistry } from 'vs/editor/common/modes';
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
import { createTextModel } from 'vs/editor/test/common/editorTestUtils';
suite('Bracket Pair Colorizer - Tokenizer', () => {
test('Basic', () => {
const mode1 = new LanguageIdentifier('testMode1', 2);
const tStandard = (text: string) => new TokenInfo(text, mode1.id, StandardTokenType.Other);
const tComment = (text: string) => new TokenInfo(text, mode1.id, StandardTokenType.Comment);
const document = new TokenizedDocument([
tStandard(' { } '), tStandard('be'), tStandard('gin end'), tStandard('\n'),
tStandard('hello'), tComment('{'), tStandard('}'),
]);
const disposableStore = new DisposableStore();
disposableStore.add(TokenizationRegistry.register(mode1.language, document.getTokenizationSupport()));
disposableStore.add(LanguageConfigurationRegistry.register(mode1, {
brackets: [['{', '}'], ['[', ']'], ['(', ')']],
}));
const brackets = new LanguageAgnosticBracketTokens([['begin', 'end']]);
const model = createTextModel(document.getText(), {}, mode1);
model.forceTokenization(model.getLineCount());
const tokens = readAllTokens(new TextBufferTokenizer(model, brackets));
assert.deepStrictEqual(toArr(tokens, model), [
{ category: -1, kind: 'Text', languageId: -1, text: ' ', },
{ category: 2000, kind: 'OpeningBracket', languageId: 2, text: '{', },
{ category: -1, kind: 'Text', languageId: -1, text: ' ', },
{ category: 2000, kind: 'ClosingBracket', languageId: 2, text: '}', },
{ category: -1, kind: 'Text', languageId: -1, text: ' ', },
{ category: 2004, kind: 'OpeningBracket', languageId: 2, text: 'begin', },
{ category: -1, kind: 'Text', languageId: -1, text: ' ', },
{ category: 2004, kind: 'ClosingBracket', languageId: 2, text: 'end', },
{ category: -1, kind: 'Text', languageId: -1, text: '\nhello{', },
{ category: 2000, kind: 'ClosingBracket', languageId: 2, text: '}', }
]);
disposableStore.dispose();
});
});
function readAllTokens(tokenizer: Tokenizer): Token[] {
const tokens = new Array<Token>();
while (true) {
const token = tokenizer.read();
if (!token) {
break;
}
tokens.push(token);
}
return tokens;
}
function toArr(tokens: Token[], model: TextModel): any[] {
const result = new Array<any>();
let offset = lengthZero;
for (const token of tokens) {
result.push(tokenToObj(token, offset, model));
offset = lengthAdd(offset, token.length);
}
return result;
}
function tokenToObj(token: Token, offset: Length, model: TextModel): any {
return {
text: model.getValueInRange(lengthsToRange(offset, lengthAdd(offset, token.length))),
category: token.category,
kind: {
[TokenKind.ClosingBracket]: 'ClosingBracket',
[TokenKind.OpeningBracket]: 'OpeningBracket',
[TokenKind.Text]: 'Text',
}[token.kind],
languageId: token.languageId,
};
}
class TokenizedDocument {
private readonly tokensByLine: readonly TokenInfo[][];
constructor(tokens: TokenInfo[]) {
const tokensByLine = new Array<TokenInfo[]>();
let curLine = new Array<TokenInfo>();
for (const token of tokens) {
const lines = token.text.split('\n');
let first = true;
while (lines.length > 0) {
if (!first) {
tokensByLine.push(curLine);
curLine = new Array<TokenInfo>();
} else {
first = false;
}
if (lines[0].length > 0) {
curLine.push(token.withText(lines[0]));
}
lines.pop();
}
}
tokensByLine.push(curLine);
this.tokensByLine = tokensByLine;
}
getText() {
return this.tokensByLine.map(t => t.map(t => t.text).join('')).join('\n');
}
getTokenizationSupport(): ITokenizationSupport {
class State implements IState {
constructor(public readonly lineNumber: number) { }
clone(): IState {
return new State(this.lineNumber);
}
equals(other: IState): boolean {
return this.lineNumber === (other as State).lineNumber;
}
}
return {
getInitialState: () => new State(0),
tokenize: () => { throw new Error('Method not implemented.'); },
tokenize2: (line: string, hasEOL: boolean, state: IState, offsetDelta: number): TokenizationResult2 => {
const state2 = state as State;
const tokens = this.tokensByLine[state2.lineNumber];
const arr = new Array<number>();
let offset = 0;
for (const t of tokens) {
arr.push(offset, t.getMetadata());
offset += t.text.length;
}
return new TokenizationResult2(new Uint32Array(arr), new State(state2.lineNumber + 1));
}
};
}
}
class TokenInfo {
constructor(public readonly text: string, public readonly languageId: LanguageId, public readonly tokenType: StandardTokenType) { }
getMetadata(): number {
return (
(this.languageId << MetadataConsts.LANGUAGEID_OFFSET)
| (this.tokenType << MetadataConsts.TOKEN_TYPE_OFFSET)
) >>> 0;
}
withText(text: string): TokenInfo {
return new TokenInfo(text, this.languageId, this.tokenType);
}
}

255
src/vs/monaco.d.ts vendored
View file

@ -1638,6 +1638,11 @@ declare namespace monaco.editor {
readonly insertSpaces: boolean;
readonly defaultEOL: DefaultEndOfLine;
readonly trimAutoWhitespace: boolean;
readonly bracketPairColorizationOptions: BracketPairColorizationOptions;
}
export interface BracketPairColorizationOptions {
enabled: boolean;
}
export interface ITextModelUpdateOptions {
@ -1645,6 +1650,7 @@ declare namespace monaco.editor {
indentSize?: number;
insertSpaces?: boolean;
trimAutoWhitespace?: boolean;
bracketColorizationOptions?: BracketPairColorizationOptions;
}
export class FindMatch {
@ -3873,6 +3879,15 @@ declare namespace monaco.editor {
export type InternalInlineSuggestOptions = Readonly<Required<IInlineSuggestOptions>>;
export interface IBracketPairColorizationOptions {
/**
* Enable or disable bracket pair colorization.
*/
enabled?: boolean;
}
export type InternalBracketPairColorizationOptions = Readonly<Required<IBracketPairColorizationOptions>>;
/**
* Configuration options for editor suggest widget
*/
@ -4081,125 +4096,126 @@ declare namespace monaco.editor {
autoIndent = 9,
automaticLayout = 10,
autoSurround = 11,
codeLens = 12,
codeLensFontFamily = 13,
codeLensFontSize = 14,
colorDecorators = 15,
columnSelection = 16,
comments = 17,
contextmenu = 18,
copyWithSyntaxHighlighting = 19,
cursorBlinking = 20,
cursorSmoothCaretAnimation = 21,
cursorStyle = 22,
cursorSurroundingLines = 23,
cursorSurroundingLinesStyle = 24,
cursorWidth = 25,
disableLayerHinting = 26,
disableMonospaceOptimizations = 27,
domReadOnly = 28,
dragAndDrop = 29,
emptySelectionClipboard = 30,
extraEditorClassName = 31,
fastScrollSensitivity = 32,
find = 33,
fixedOverflowWidgets = 34,
folding = 35,
foldingStrategy = 36,
foldingHighlight = 37,
foldingImportsByDefault = 38,
unfoldOnClickAfterEndOfLine = 39,
fontFamily = 40,
fontInfo = 41,
fontLigatures = 42,
fontSize = 43,
fontWeight = 44,
formatOnPaste = 45,
formatOnType = 46,
glyphMargin = 47,
gotoLocation = 48,
hideCursorInOverviewRuler = 49,
highlightActiveIndentGuide = 50,
hover = 51,
inDiffEditor = 52,
inlineSuggest = 53,
letterSpacing = 54,
lightbulb = 55,
lineDecorationsWidth = 56,
lineHeight = 57,
lineNumbers = 58,
lineNumbersMinChars = 59,
linkedEditing = 60,
links = 61,
matchBrackets = 62,
minimap = 63,
mouseStyle = 64,
mouseWheelScrollSensitivity = 65,
mouseWheelZoom = 66,
multiCursorMergeOverlapping = 67,
multiCursorModifier = 68,
multiCursorPaste = 69,
occurrencesHighlight = 70,
overviewRulerBorder = 71,
overviewRulerLanes = 72,
padding = 73,
parameterHints = 74,
peekWidgetDefaultFocus = 75,
definitionLinkOpensInPeek = 76,
quickSuggestions = 77,
quickSuggestionsDelay = 78,
readOnly = 79,
renameOnType = 80,
renderControlCharacters = 81,
renderIndentGuides = 82,
renderFinalNewline = 83,
renderLineHighlight = 84,
renderLineHighlightOnlyWhenFocus = 85,
renderValidationDecorations = 86,
renderWhitespace = 87,
revealHorizontalRightPadding = 88,
roundedSelection = 89,
rulers = 90,
scrollbar = 91,
scrollBeyondLastColumn = 92,
scrollBeyondLastLine = 93,
scrollPredominantAxis = 94,
selectionClipboard = 95,
selectionHighlight = 96,
selectOnLineNumbers = 97,
showFoldingControls = 98,
showUnused = 99,
snippetSuggestions = 100,
smartSelect = 101,
smoothScrolling = 102,
stickyTabStops = 103,
stopRenderingLineAfter = 104,
suggest = 105,
suggestFontSize = 106,
suggestLineHeight = 107,
suggestOnTriggerCharacters = 108,
suggestSelection = 109,
tabCompletion = 110,
tabIndex = 111,
unusualLineTerminators = 112,
useShadowDOM = 113,
useTabStops = 114,
wordSeparators = 115,
wordWrap = 116,
wordWrapBreakAfterCharacters = 117,
wordWrapBreakBeforeCharacters = 118,
wordWrapColumn = 119,
wordWrapOverride1 = 120,
wordWrapOverride2 = 121,
wrappingIndent = 122,
wrappingStrategy = 123,
showDeprecated = 124,
inlayHints = 125,
editorClassName = 126,
pixelRatio = 127,
tabFocusMode = 128,
layoutInfo = 129,
wrappingInfo = 130
bracketPairColorization = 12,
codeLens = 13,
codeLensFontFamily = 14,
codeLensFontSize = 15,
colorDecorators = 16,
columnSelection = 17,
comments = 18,
contextmenu = 19,
copyWithSyntaxHighlighting = 20,
cursorBlinking = 21,
cursorSmoothCaretAnimation = 22,
cursorStyle = 23,
cursorSurroundingLines = 24,
cursorSurroundingLinesStyle = 25,
cursorWidth = 26,
disableLayerHinting = 27,
disableMonospaceOptimizations = 28,
domReadOnly = 29,
dragAndDrop = 30,
emptySelectionClipboard = 31,
extraEditorClassName = 32,
fastScrollSensitivity = 33,
find = 34,
fixedOverflowWidgets = 35,
folding = 36,
foldingStrategy = 37,
foldingHighlight = 38,
foldingImportsByDefault = 39,
unfoldOnClickAfterEndOfLine = 40,
fontFamily = 41,
fontInfo = 42,
fontLigatures = 43,
fontSize = 44,
fontWeight = 45,
formatOnPaste = 46,
formatOnType = 47,
glyphMargin = 48,
gotoLocation = 49,
hideCursorInOverviewRuler = 50,
highlightActiveIndentGuide = 51,
hover = 52,
inDiffEditor = 53,
inlineSuggest = 54,
letterSpacing = 55,
lightbulb = 56,
lineDecorationsWidth = 57,
lineHeight = 58,
lineNumbers = 59,
lineNumbersMinChars = 60,
linkedEditing = 61,
links = 62,
matchBrackets = 63,
minimap = 64,
mouseStyle = 65,
mouseWheelScrollSensitivity = 66,
mouseWheelZoom = 67,
multiCursorMergeOverlapping = 68,
multiCursorModifier = 69,
multiCursorPaste = 70,
occurrencesHighlight = 71,
overviewRulerBorder = 72,
overviewRulerLanes = 73,
padding = 74,
parameterHints = 75,
peekWidgetDefaultFocus = 76,
definitionLinkOpensInPeek = 77,
quickSuggestions = 78,
quickSuggestionsDelay = 79,
readOnly = 80,
renameOnType = 81,
renderControlCharacters = 82,
renderIndentGuides = 83,
renderFinalNewline = 84,
renderLineHighlight = 85,
renderLineHighlightOnlyWhenFocus = 86,
renderValidationDecorations = 87,
renderWhitespace = 88,
revealHorizontalRightPadding = 89,
roundedSelection = 90,
rulers = 91,
scrollbar = 92,
scrollBeyondLastColumn = 93,
scrollBeyondLastLine = 94,
scrollPredominantAxis = 95,
selectionClipboard = 96,
selectionHighlight = 97,
selectOnLineNumbers = 98,
showFoldingControls = 99,
showUnused = 100,
snippetSuggestions = 101,
smartSelect = 102,
smoothScrolling = 103,
stickyTabStops = 104,
stopRenderingLineAfter = 105,
suggest = 106,
suggestFontSize = 107,
suggestLineHeight = 108,
suggestOnTriggerCharacters = 109,
suggestSelection = 110,
tabCompletion = 111,
tabIndex = 112,
unusualLineTerminators = 113,
useShadowDOM = 114,
useTabStops = 115,
wordSeparators = 116,
wordWrap = 117,
wordWrapBreakAfterCharacters = 118,
wordWrapBreakBeforeCharacters = 119,
wordWrapColumn = 120,
wordWrapOverride1 = 121,
wordWrapOverride2 = 122,
wrappingIndent = 123,
wrappingStrategy = 124,
showDeprecated = 125,
inlayHints = 126,
editorClassName = 127,
pixelRatio = 128,
tabFocusMode = 129,
layoutInfo = 130,
wrappingInfo = 131
}
export const EditorOptions: {
acceptSuggestionOnCommitCharacter: IEditorOption<EditorOption.acceptSuggestionOnCommitCharacter, boolean>;
@ -4214,6 +4230,7 @@ declare namespace monaco.editor {
autoIndent: IEditorOption<EditorOption.autoIndent, EditorAutoIndentStrategy>;
automaticLayout: IEditorOption<EditorOption.automaticLayout, boolean>;
autoSurround: IEditorOption<EditorOption.autoSurround, 'languageDefined' | 'never' | 'quotes' | 'brackets'>;
bracketPairColorization: IEditorOption<EditorOption.bracketPairColorization, any>;
stickyTabStops: IEditorOption<EditorOption.stickyTabStops, boolean>;
codeLens: IEditorOption<EditorOption.codeLens, boolean>;
codeLensFontFamily: IEditorOption<EditorOption.codeLensFontFamily, string>;