monaco-editor-core
Version:
A browser based code editor
205 lines (204 loc) • 8.98 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as arrays from '../../../base/common/arrays.js';
import { Position } from '../core/position.js';
import { ContiguousTokensEditing, EMPTY_LINE_TOKENS, toUint32Array } from './contiguousTokensEditing.js';
import { LineTokens } from './lineTokens.js';
import { TokenMetadata } from '../encodedTokenAttributes.js';
/**
* Represents contiguous tokens in a text model.
*/
export class ContiguousTokensStore {
constructor(languageIdCodec) {
this._lineTokens = [];
this._len = 0;
this._languageIdCodec = languageIdCodec;
}
flush() {
this._lineTokens = [];
this._len = 0;
}
get hasTokens() {
return this._lineTokens.length > 0;
}
getTokens(topLevelLanguageId, lineIndex, lineText) {
let rawLineTokens = null;
if (lineIndex < this._len) {
rawLineTokens = this._lineTokens[lineIndex];
}
if (rawLineTokens !== null && rawLineTokens !== EMPTY_LINE_TOKENS) {
return new LineTokens(toUint32Array(rawLineTokens), lineText, this._languageIdCodec);
}
const lineTokens = new Uint32Array(2);
lineTokens[0] = lineText.length;
lineTokens[1] = getDefaultMetadata(this._languageIdCodec.encodeLanguageId(topLevelLanguageId));
return new LineTokens(lineTokens, lineText, this._languageIdCodec);
}
static _massageTokens(topLevelLanguageId, lineTextLength, _tokens) {
const tokens = _tokens ? toUint32Array(_tokens) : null;
if (lineTextLength === 0) {
let hasDifferentLanguageId = false;
if (tokens && tokens.length > 1) {
hasDifferentLanguageId = (TokenMetadata.getLanguageId(tokens[1]) !== topLevelLanguageId);
}
if (!hasDifferentLanguageId) {
return EMPTY_LINE_TOKENS;
}
}
if (!tokens || tokens.length === 0) {
const tokens = new Uint32Array(2);
tokens[0] = lineTextLength;
tokens[1] = getDefaultMetadata(topLevelLanguageId);
return tokens.buffer;
}
// Ensure the last token covers the end of the text
tokens[tokens.length - 2] = lineTextLength;
if (tokens.byteOffset === 0 && tokens.byteLength === tokens.buffer.byteLength) {
// Store directly the ArrayBuffer pointer to save an object
return tokens.buffer;
}
return tokens;
}
_ensureLine(lineIndex) {
while (lineIndex >= this._len) {
this._lineTokens[this._len] = null;
this._len++;
}
}
_deleteLines(start, deleteCount) {
if (deleteCount === 0) {
return;
}
if (start + deleteCount > this._len) {
deleteCount = this._len - start;
}
this._lineTokens.splice(start, deleteCount);
this._len -= deleteCount;
}
_insertLines(insertIndex, insertCount) {
if (insertCount === 0) {
return;
}
const lineTokens = [];
for (let i = 0; i < insertCount; i++) {
lineTokens[i] = null;
}
this._lineTokens = arrays.arrayInsert(this._lineTokens, insertIndex, lineTokens);
this._len += insertCount;
}
setTokens(topLevelLanguageId, lineIndex, lineTextLength, _tokens, checkEquality) {
const tokens = ContiguousTokensStore._massageTokens(this._languageIdCodec.encodeLanguageId(topLevelLanguageId), lineTextLength, _tokens);
this._ensureLine(lineIndex);
const oldTokens = this._lineTokens[lineIndex];
this._lineTokens[lineIndex] = tokens;
if (checkEquality) {
return !ContiguousTokensStore._equals(oldTokens, tokens);
}
return false;
}
static _equals(_a, _b) {
if (!_a || !_b) {
return !_a && !_b;
}
const a = toUint32Array(_a);
const b = toUint32Array(_b);
if (a.length !== b.length) {
return false;
}
for (let i = 0, len = a.length; i < len; i++) {
if (a[i] !== b[i]) {
return false;
}
}
return true;
}
//#region Editing
acceptEdit(range, eolCount, firstLineLength) {
this._acceptDeleteRange(range);
this._acceptInsertText(new Position(range.startLineNumber, range.startColumn), eolCount, firstLineLength);
}
_acceptDeleteRange(range) {
const firstLineIndex = range.startLineNumber - 1;
if (firstLineIndex >= this._len) {
return;
}
if (range.startLineNumber === range.endLineNumber) {
if (range.startColumn === range.endColumn) {
// Nothing to delete
return;
}
this._lineTokens[firstLineIndex] = ContiguousTokensEditing.delete(this._lineTokens[firstLineIndex], range.startColumn - 1, range.endColumn - 1);
return;
}
this._lineTokens[firstLineIndex] = ContiguousTokensEditing.deleteEnding(this._lineTokens[firstLineIndex], range.startColumn - 1);
const lastLineIndex = range.endLineNumber - 1;
let lastLineTokens = null;
if (lastLineIndex < this._len) {
lastLineTokens = ContiguousTokensEditing.deleteBeginning(this._lineTokens[lastLineIndex], range.endColumn - 1);
}
// Take remaining text on last line and append it to remaining text on first line
this._lineTokens[firstLineIndex] = ContiguousTokensEditing.append(this._lineTokens[firstLineIndex], lastLineTokens);
// Delete middle lines
this._deleteLines(range.startLineNumber, range.endLineNumber - range.startLineNumber);
}
_acceptInsertText(position, eolCount, firstLineLength) {
if (eolCount === 0 && firstLineLength === 0) {
// Nothing to insert
return;
}
const lineIndex = position.lineNumber - 1;
if (lineIndex >= this._len) {
return;
}
if (eolCount === 0) {
// Inserting text on one line
this._lineTokens[lineIndex] = ContiguousTokensEditing.insert(this._lineTokens[lineIndex], position.column - 1, firstLineLength);
return;
}
this._lineTokens[lineIndex] = ContiguousTokensEditing.deleteEnding(this._lineTokens[lineIndex], position.column - 1);
this._lineTokens[lineIndex] = ContiguousTokensEditing.insert(this._lineTokens[lineIndex], position.column - 1, firstLineLength);
this._insertLines(position.lineNumber, eolCount);
}
//#endregion
setMultilineTokens(tokens, textModel) {
if (tokens.length === 0) {
return { changes: [] };
}
const ranges = [];
for (let i = 0, len = tokens.length; i < len; i++) {
const element = tokens[i];
let minChangedLineNumber = 0;
let maxChangedLineNumber = 0;
let hasChange = false;
for (let lineNumber = element.startLineNumber; lineNumber <= element.endLineNumber; lineNumber++) {
if (hasChange) {
this.setTokens(textModel.getLanguageId(), lineNumber - 1, textModel.getLineLength(lineNumber), element.getLineTokens(lineNumber), false);
maxChangedLineNumber = lineNumber;
}
else {
const lineHasChange = this.setTokens(textModel.getLanguageId(), lineNumber - 1, textModel.getLineLength(lineNumber), element.getLineTokens(lineNumber), true);
if (lineHasChange) {
hasChange = true;
minChangedLineNumber = lineNumber;
maxChangedLineNumber = lineNumber;
}
}
}
if (hasChange) {
ranges.push({ fromLineNumber: minChangedLineNumber, toLineNumber: maxChangedLineNumber, });
}
}
return { changes: ranges };
}
}
function getDefaultMetadata(topLevelLanguageId) {
return ((topLevelLanguageId << 0 /* MetadataConsts.LANGUAGEID_OFFSET */)
| (0 /* StandardTokenType.Other */ << 8 /* MetadataConsts.TOKEN_TYPE_OFFSET */)
| (0 /* FontStyle.None */ << 11 /* MetadataConsts.FONT_STYLE_OFFSET */)
| (1 /* ColorId.DefaultForeground */ << 15 /* MetadataConsts.FOREGROUND_OFFSET */)
| (2 /* ColorId.DefaultBackground */ << 24 /* MetadataConsts.BACKGROUND_OFFSET */)
// If there is no grammar, we just take a guess and try to match brackets.
| (1024 /* MetadataConsts.BALANCED_BRACKETS_MASK */)) >>> 0;
}