Path: blob/main/src/vs/editor/common/tokens/contiguousTokensStore.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import * as arrays from '../../../base/common/arrays.js';6import { Position } from '../core/position.js';7import { IRange } from '../core/range.js';8import { ContiguousTokensEditing, EMPTY_LINE_TOKENS, toUint32Array } from './contiguousTokensEditing.js';9import { LineTokens } from './lineTokens.js';10import { ILanguageIdCodec } from '../languages.js';11import { LanguageId, FontStyle, ColorId, StandardTokenType, MetadataConsts, TokenMetadata } from '../encodedTokenAttributes.js';12import { ITextModel } from '../model.js';13import { ContiguousMultilineTokens } from './contiguousMultilineTokens.js';1415/**16* Represents contiguous tokens in a text model.17*/18export class ContiguousTokensStore {19private _lineTokens: (Uint32Array | ArrayBuffer | null)[];20private _len: number;21private readonly _languageIdCodec: ILanguageIdCodec;2223constructor(languageIdCodec: ILanguageIdCodec) {24this._lineTokens = [];25this._len = 0;26this._languageIdCodec = languageIdCodec;27}2829public flush(): void {30this._lineTokens = [];31this._len = 0;32}3334get hasTokens(): boolean {35return this._lineTokens.length > 0;36}3738public getTokens(topLevelLanguageId: string, lineIndex: number, lineText: string): LineTokens {39let rawLineTokens: Uint32Array | ArrayBuffer | null = null;40if (lineIndex < this._len) {41rawLineTokens = this._lineTokens[lineIndex];42}4344if (rawLineTokens !== null && rawLineTokens !== EMPTY_LINE_TOKENS) {45return new LineTokens(toUint32Array(rawLineTokens), lineText, this._languageIdCodec);46}4748const lineTokens = new Uint32Array(2);49lineTokens[0] = lineText.length;50lineTokens[1] = getDefaultMetadata(this._languageIdCodec.encodeLanguageId(topLevelLanguageId));51return new LineTokens(lineTokens, lineText, this._languageIdCodec);52}5354private static _massageTokens(topLevelLanguageId: LanguageId, lineTextLength: number, _tokens: Uint32Array | ArrayBuffer | null): Uint32Array | ArrayBuffer {5556const tokens = _tokens ? toUint32Array(_tokens) : null;5758if (lineTextLength === 0) {59let hasDifferentLanguageId = false;60if (tokens && tokens.length > 1) {61hasDifferentLanguageId = (TokenMetadata.getLanguageId(tokens[1]) !== topLevelLanguageId);62}6364if (!hasDifferentLanguageId) {65return EMPTY_LINE_TOKENS;66}67}6869if (!tokens || tokens.length === 0) {70const tokens = new Uint32Array(2);71tokens[0] = lineTextLength;72tokens[1] = getDefaultMetadata(topLevelLanguageId);73return tokens.buffer;74}7576// Ensure the last token covers the end of the text77tokens[tokens.length - 2] = lineTextLength;7879if (tokens.byteOffset === 0 && tokens.byteLength === tokens.buffer.byteLength) {80// Store directly the ArrayBuffer pointer to save an object81return tokens.buffer;82}83return tokens;84}8586private _ensureLine(lineIndex: number): void {87while (lineIndex >= this._len) {88this._lineTokens[this._len] = null;89this._len++;90}91}9293private _deleteLines(start: number, deleteCount: number): void {94if (deleteCount === 0) {95return;96}97if (start + deleteCount > this._len) {98deleteCount = this._len - start;99}100this._lineTokens.splice(start, deleteCount);101this._len -= deleteCount;102}103104private _insertLines(insertIndex: number, insertCount: number): void {105if (insertCount === 0) {106return;107}108const lineTokens: (Uint32Array | ArrayBuffer | null)[] = [];109for (let i = 0; i < insertCount; i++) {110lineTokens[i] = null;111}112this._lineTokens = arrays.arrayInsert(this._lineTokens, insertIndex, lineTokens);113this._len += insertCount;114}115116public setTokens(topLevelLanguageId: string, lineIndex: number, lineTextLength: number, _tokens: Uint32Array | ArrayBuffer | null, checkEquality: boolean): boolean {117const tokens = ContiguousTokensStore._massageTokens(this._languageIdCodec.encodeLanguageId(topLevelLanguageId), lineTextLength, _tokens);118this._ensureLine(lineIndex);119const oldTokens = this._lineTokens[lineIndex];120this._lineTokens[lineIndex] = tokens;121122if (checkEquality) {123return !ContiguousTokensStore._equals(oldTokens, tokens);124}125return false;126}127128private static _equals(_a: Uint32Array | ArrayBuffer | null, _b: Uint32Array | ArrayBuffer | null) {129if (!_a || !_b) {130return !_a && !_b;131}132133const a = toUint32Array(_a);134const b = toUint32Array(_b);135136if (a.length !== b.length) {137return false;138}139for (let i = 0, len = a.length; i < len; i++) {140if (a[i] !== b[i]) {141return false;142}143}144return true;145}146147//#region Editing148149public acceptEdit(range: IRange, eolCount: number, firstLineLength: number): void {150this._acceptDeleteRange(range);151this._acceptInsertText(new Position(range.startLineNumber, range.startColumn), eolCount, firstLineLength);152}153154private _acceptDeleteRange(range: IRange): void {155156const firstLineIndex = range.startLineNumber - 1;157if (firstLineIndex >= this._len) {158return;159}160161if (range.startLineNumber === range.endLineNumber) {162if (range.startColumn === range.endColumn) {163// Nothing to delete164return;165}166167this._lineTokens[firstLineIndex] = ContiguousTokensEditing.delete(this._lineTokens[firstLineIndex], range.startColumn - 1, range.endColumn - 1);168return;169}170171this._lineTokens[firstLineIndex] = ContiguousTokensEditing.deleteEnding(this._lineTokens[firstLineIndex], range.startColumn - 1);172173const lastLineIndex = range.endLineNumber - 1;174let lastLineTokens: Uint32Array | ArrayBuffer | null = null;175if (lastLineIndex < this._len) {176lastLineTokens = ContiguousTokensEditing.deleteBeginning(this._lineTokens[lastLineIndex], range.endColumn - 1);177}178179// Take remaining text on last line and append it to remaining text on first line180this._lineTokens[firstLineIndex] = ContiguousTokensEditing.append(this._lineTokens[firstLineIndex], lastLineTokens);181182// Delete middle lines183this._deleteLines(range.startLineNumber, range.endLineNumber - range.startLineNumber);184}185186private _acceptInsertText(position: Position, eolCount: number, firstLineLength: number): void {187188if (eolCount === 0 && firstLineLength === 0) {189// Nothing to insert190return;191}192193const lineIndex = position.lineNumber - 1;194if (lineIndex >= this._len) {195return;196}197198if (eolCount === 0) {199// Inserting text on one line200this._lineTokens[lineIndex] = ContiguousTokensEditing.insert(this._lineTokens[lineIndex], position.column - 1, firstLineLength);201return;202}203204this._lineTokens[lineIndex] = ContiguousTokensEditing.deleteEnding(this._lineTokens[lineIndex], position.column - 1);205this._lineTokens[lineIndex] = ContiguousTokensEditing.insert(this._lineTokens[lineIndex], position.column - 1, firstLineLength);206207this._insertLines(position.lineNumber, eolCount);208}209210//#endregion211212public setMultilineTokens(tokens: ContiguousMultilineTokens[], textModel: ITextModel): { changes: { fromLineNumber: number; toLineNumber: number }[] } {213if (tokens.length === 0) {214return { changes: [] };215}216217const ranges: { fromLineNumber: number; toLineNumber: number }[] = [];218219for (let i = 0, len = tokens.length; i < len; i++) {220const element = tokens[i];221let minChangedLineNumber = 0;222let maxChangedLineNumber = 0;223let hasChange = false;224for (let lineNumber = element.startLineNumber; lineNumber <= element.endLineNumber; lineNumber++) {225if (hasChange) {226this.setTokens(textModel.getLanguageId(), lineNumber - 1, textModel.getLineLength(lineNumber), element.getLineTokens(lineNumber), false);227maxChangedLineNumber = lineNumber;228} else {229const lineHasChange = this.setTokens(textModel.getLanguageId(), lineNumber - 1, textModel.getLineLength(lineNumber), element.getLineTokens(lineNumber), true);230if (lineHasChange) {231hasChange = true;232minChangedLineNumber = lineNumber;233maxChangedLineNumber = lineNumber;234}235}236}237if (hasChange) {238ranges.push({ fromLineNumber: minChangedLineNumber, toLineNumber: maxChangedLineNumber, });239}240}241242return { changes: ranges };243}244}245246function getDefaultMetadata(topLevelLanguageId: LanguageId): number {247return (248(topLevelLanguageId << MetadataConsts.LANGUAGEID_OFFSET)249| (StandardTokenType.Other << MetadataConsts.TOKEN_TYPE_OFFSET)250| (FontStyle.None << MetadataConsts.FONT_STYLE_OFFSET)251| (ColorId.DefaultForeground << MetadataConsts.FOREGROUND_OFFSET)252| (ColorId.DefaultBackground << MetadataConsts.BACKGROUND_OFFSET)253// If there is no grammar, we just take a guess and try to match brackets.254| (MetadataConsts.BALANCED_BRACKETS_MASK)255) >>> 0;256}257258259