Merge pull request #129231 from microsoft/hediet/bracketPairColorizer
Performant Bracket Pair Colorization
This commit is contained in:
commit
7aa0f8e754
|
@ -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.") }
|
||||
|
|
|
@ -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 {
|
||||
|
|
480
src/vs/editor/common/model/bracketPairColorizer/ast.ts
Normal file
480
src/vs/editor/common/model/bracketPairColorizer/ast.ts
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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}; }`);
|
||||
}
|
||||
});
|
120
src/vs/editor/common/model/bracketPairColorizer/brackets.ts
Normal file
120
src/vs/editor/common/model/bracketPairColorizer/brackets.ts
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
230
src/vs/editor/common/model/bracketPairColorizer/length.ts
Normal file
230
src/vs/editor/common/model/bracketPairColorizer/length.ts
Normal 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;
|
||||
}
|
123
src/vs/editor/common/model/bracketPairColorizer/nodeReader.ts
Normal file
123
src/vs/editor/common/model/bracketPairColorizer/nodeReader.ts
Normal 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;
|
||||
}
|
153
src/vs/editor/common/model/bracketPairColorizer/parser.ts
Normal file
153
src/vs/editor/common/model/bracketPairColorizer/parser.ts
Normal 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');
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
349
src/vs/editor/common/model/bracketPairColorizer/tokenizer.ts
Normal file
349
src/vs/editor/common/model/bracketPairColorizer/tokenizer.ts
Normal 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;
|
||||
}
|
||||
}
|
29
src/vs/editor/common/model/decorationProvider.ts
Normal file
29
src/vs/editor/common/model/decorationProvider.ts
Normal 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;
|
||||
}
|
|
@ -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() }]
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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]);
|
||||
});
|
||||
});
|
|
@ -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'
|
||||
);
|
||||
});
|
||||
});
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
});
|
|
@ -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
255
src/vs/monaco.d.ts
vendored
|
@ -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>;
|
||||
|
|
Loading…
Reference in a new issue