Initial VS Code 1.19 source merge (#571)

* Initial 1.19 xcopy

* Fix yarn build

* Fix numerous build breaks

* Next batch of build break fixes

* More build break fixes

* Runtime breaks

* Additional post merge fixes

* Fix windows setup file

* Fix test failures.

* Update license header blocks to refer to source eula
This commit is contained in:
Karl Burtram
2018-01-28 23:37:17 -08:00
committed by GitHub
parent 9a1ac20710
commit 251ae01c3e
8009 changed files with 93378 additions and 35634 deletions

View File

@@ -12,11 +12,9 @@ import { TextModelWithDecorations, ModelDecorationOptions } from 'vs/editor/comm
import * as strings from 'vs/base/common/strings';
import * as arrays from 'vs/base/common/arrays';
import { Selection } from 'vs/editor/common/core/selection';
import { IDisposable } from 'vs/base/common/lifecycle';
import { LanguageIdentifier } from 'vs/editor/common/modes';
import { ITextSource, IRawTextSource, RawTextSource } from 'vs/editor/common/model/textSource';
import { TextModel } from 'vs/editor/common/model/textModel';
import * as textModelEvents from 'vs/editor/common/model/textModelEvents';
import { ITextSource, IRawTextSource } from 'vs/editor/common/model/textSource';
import { ModelRawContentChangedEvent, ModelRawChange, IModelContentChange, ModelRawLineChanged, ModelRawLinesDeleted, ModelRawLinesInserted } from 'vs/editor/common/model/textModelEvents';
export interface IValidatedEditOperation {
sortIndex: number;
@@ -35,17 +33,6 @@ interface IIdentifiedLineEdit extends ILineEdit {
export class EditableTextModel extends TextModelWithDecorations implements editorCommon.IEditableTextModel {
public static createFromString(text: string, options: editorCommon.ITextModelCreationOptions = TextModel.DEFAULT_CREATION_OPTIONS, languageIdentifier: LanguageIdentifier = null): EditableTextModel {
return new EditableTextModel(RawTextSource.fromString(text), options, languageIdentifier);
}
public onDidChangeRawContent(listener: (e: textModelEvents.ModelRawContentChangedEvent) => void): IDisposable {
return this._eventEmitter.addListener(textModelEvents.TextModelEventType.ModelRawContentChanged2, listener);
}
public onDidChangeContent(listener: (e: textModelEvents.IModelContentChangedEvent) => void): IDisposable {
return this._eventEmitter.addListener(textModelEvents.TextModelEventType.ModelContentChanged, listener);
}
private _commandManager: EditStack;
// for extra details about change events:
@@ -93,8 +80,10 @@ export class EditableTextModel extends TextModelWithDecorations implements edito
public pushEditOperations(beforeCursorState: Selection[], editOperations: editorCommon.IIdentifiedSingleEditOperation[], cursorStateComputer: editorCommon.ICursorStateComputer): Selection[] {
try {
this._eventEmitter.beginDeferredEmit();
this._onDidChangeDecorations.beginDeferredEmit();
return this._pushEditOperations(beforeCursorState, editOperations, cursorStateComputer);
} finally {
this._onDidChangeDecorations.endDeferredEmit();
this._eventEmitter.endDeferredEmit();
}
}
@@ -276,10 +265,10 @@ export class EditableTextModel extends TextModelWithDecorations implements edito
public applyEdits(rawOperations: editorCommon.IIdentifiedSingleEditOperation[]): editorCommon.IIdentifiedSingleEditOperation[] {
try {
this._eventEmitter.beginDeferredEmit();
this._acquireDecorationsTracker();
this._onDidChangeDecorations.beginDeferredEmit();
return this._applyEdits(rawOperations);
} finally {
this._releaseDecorationsTracker();
this._onDidChangeDecorations.endDeferredEmit();
this._eventEmitter.endDeferredEmit();
}
}
@@ -472,8 +461,8 @@ export class EditableTextModel extends TextModelWithDecorations implements edito
// Sort operations descending
operations.sort(EditableTextModel._sortOpsDescending);
let rawContentChanges: textModelEvents.ModelRawChange[] = [];
let contentChanges: textModelEvents.IModelContentChange[] = [];
let rawContentChanges: ModelRawChange[] = [];
let contentChanges: IModelContentChange[] = [];
let lineEditsQueue: IIdentifiedLineEdit[] = [];
const queueLineEdit = (lineEdit: IIdentifiedLineEdit) => {
@@ -506,7 +495,7 @@ export class EditableTextModel extends TextModelWithDecorations implements edito
this._lines[currentLineNumber - 1].applyEdits(lineEditsQueue.slice(currentLineNumberStart, i));
this._lineStarts.changeValue(currentLineNumber - 1, this._lines[currentLineNumber - 1].text.length + this._EOL.length);
rawContentChanges.push(
new textModelEvents.ModelRawLineChanged(currentLineNumber, this._lines[currentLineNumber - 1].text)
new ModelRawLineChanged(currentLineNumber, this._lines[currentLineNumber - 1].text)
);
currentLineNumber = lineNumber;
@@ -517,7 +506,7 @@ export class EditableTextModel extends TextModelWithDecorations implements edito
this._lines[currentLineNumber - 1].applyEdits(lineEditsQueue.slice(currentLineNumberStart, lineEditsQueue.length));
this._lineStarts.changeValue(currentLineNumber - 1, this._lines[currentLineNumber - 1].text.length + this._EOL.length);
rawContentChanges.push(
new textModelEvents.ModelRawLineChanged(currentLineNumber, this._lines[currentLineNumber - 1].text)
new ModelRawLineChanged(currentLineNumber, this._lines[currentLineNumber - 1].text)
);
lineEditsQueue = [];
@@ -580,11 +569,11 @@ export class EditableTextModel extends TextModelWithDecorations implements edito
this._lineStarts.changeValue(spliceStartLineNumber - 1, this._lines[spliceStartLineNumber - 1].text.length + this._EOL.length);
rawContentChanges.push(
new textModelEvents.ModelRawLineChanged(spliceStartLineNumber, this._lines[spliceStartLineNumber - 1].text)
new ModelRawLineChanged(spliceStartLineNumber, this._lines[spliceStartLineNumber - 1].text)
);
rawContentChanges.push(
new textModelEvents.ModelRawLinesDeleted(spliceStartLineNumber + 1, spliceStartLineNumber + spliceCnt)
new ModelRawLinesDeleted(spliceStartLineNumber + 1, spliceStartLineNumber + spliceCnt)
);
}
@@ -604,7 +593,7 @@ export class EditableTextModel extends TextModelWithDecorations implements edito
let leftoverLine = this._lines[spliceLineNumber - 1].split(spliceColumn);
this._lineStarts.changeValue(spliceLineNumber - 1, this._lines[spliceLineNumber - 1].text.length + this._EOL.length);
rawContentChanges.push(
new textModelEvents.ModelRawLineChanged(spliceLineNumber, this._lines[spliceLineNumber - 1].text)
new ModelRawLineChanged(spliceLineNumber, this._lines[spliceLineNumber - 1].text)
);
this._invalidateLine(spliceLineNumber - 1);
@@ -625,7 +614,7 @@ export class EditableTextModel extends TextModelWithDecorations implements edito
this._lines[startLineNumber + insertingLinesCnt - 1].append(leftoverLine);
this._lineStarts.changeValue(startLineNumber + insertingLinesCnt - 1, this._lines[startLineNumber + insertingLinesCnt - 1].text.length + this._EOL.length);
rawContentChanges.push(
new textModelEvents.ModelRawLinesInserted(spliceLineNumber + 1, startLineNumber + insertingLinesCnt, newLinesContent.join('\n'))
new ModelRawLinesInserted(spliceLineNumber + 1, startLineNumber + insertingLinesCnt, newLinesContent.join('\n'))
);
}
@@ -647,25 +636,23 @@ export class EditableTextModel extends TextModelWithDecorations implements edito
if (rawContentChanges.length !== 0 || contentChanges.length !== 0) {
this._increaseVersionId();
this._emitModelRawContentChangedEvent(new textModelEvents.ModelRawContentChangedEvent(
rawContentChanges,
this.getVersionId(),
this._isUndoing,
this._isRedoing
));
const e: textModelEvents.IModelContentChangedEvent = {
changes: contentChanges,
eol: this._EOL,
versionId: this.getVersionId(),
isUndoing: this._isUndoing,
isRedoing: this._isRedoing,
isFlush: false
};
this._eventEmitter.emit(textModelEvents.TextModelEventType.ModelContentChanged, e);
this._emitContentChangedEvent(
new ModelRawContentChangedEvent(
rawContentChanges,
this.getVersionId(),
this._isUndoing,
this._isRedoing
),
{
changes: contentChanges,
eol: this._EOL,
versionId: this.getVersionId(),
isUndoing: this._isUndoing,
isRedoing: this._isRedoing,
isFlush: false
}
);
}
this._resetIndentRanges();
}
private _undo(): Selection[] {
@@ -685,10 +672,10 @@ export class EditableTextModel extends TextModelWithDecorations implements edito
public undo(): Selection[] {
try {
this._eventEmitter.beginDeferredEmit();
this._acquireDecorationsTracker();
this._onDidChangeDecorations.beginDeferredEmit();
return this._undo();
} finally {
this._releaseDecorationsTracker();
this._onDidChangeDecorations.endDeferredEmit();
this._eventEmitter.endDeferredEmit();
}
}
@@ -710,10 +697,10 @@ export class EditableTextModel extends TextModelWithDecorations implements edito
public redo(): Selection[] {
try {
this._eventEmitter.beginDeferredEmit();
this._acquireDecorationsTracker();
this._onDidChangeDecorations.beginDeferredEmit();
return this._redo();
} finally {
this._releaseDecorationsTracker();
this._onDidChangeDecorations.endDeferredEmit();
this._eventEmitter.endDeferredEmit();
}
}
@@ -740,7 +727,7 @@ export class EditableTextModel extends TextModelWithDecorations implements edito
});
}
private static _DECORATION_OPTION = ModelDecorationOptions.register({
private static readonly _DECORATION_OPTION = ModelDecorationOptions.register({
stickiness: editorCommon.TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges
});

View File

@@ -1,267 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { ITextModel } from 'vs/editor/common/editorCommon';
import { FoldingMarkers } from 'vs/editor/common/modes/languageConfiguration';
import { computeIndentLevel } from 'vs/editor/common/model/modelLine';
export const MAX_FOLDING_REGIONS = 0xFFFF;
const MAX_FOLDING_REGIONS_FOR_INDENT_LIMIT = 5000;
const MASK_LINE_NUMBER = 0xFFFFFF;
const MASK_INDENT = 0xFF000000;
export class IndentRanges {
private _startIndexes: Uint32Array;
private _endIndexes: Uint32Array;
private _model: ITextModel;
constructor(startIndexes: Uint32Array, endIndexes: Uint32Array, model: ITextModel) {
if (startIndexes.length !== endIndexes.length || startIndexes.length > MAX_FOLDING_REGIONS) {
throw new Error('invalid startIndexes or endIndexes size');
}
this._startIndexes = startIndexes;
this._endIndexes = endIndexes;
this._model = model;
this._computeParentIndices();
}
private _computeParentIndices() {
let parentIndexes = [];
let isInsideLast = (startLineNumber: number, endLineNumber: number) => {
let index = parentIndexes[parentIndexes.length - 1];
return this.getStartLineNumber(index) <= startLineNumber && this.getEndLineNumber(index) >= endLineNumber;
};
for (let i = 0, len = this._startIndexes.length; i < len; i++) {
let startLineNumber = this._startIndexes[i];
let endLineNumber = this._endIndexes[i];
if (startLineNumber > MASK_LINE_NUMBER || endLineNumber > MASK_LINE_NUMBER) {
throw new Error('startLineNumber or endLineNumber must not exceed ' + MASK_LINE_NUMBER);
}
while (parentIndexes.length > 0 && !isInsideLast(startLineNumber, endLineNumber)) {
parentIndexes.pop();
}
let parentIndex = parentIndexes.length > 0 ? parentIndexes[parentIndexes.length - 1] : -1;
parentIndexes.push(i);
this._startIndexes[i] = startLineNumber + ((parentIndex & 0xFF) << 24);
this._endIndexes[i] = endLineNumber + ((parentIndex & 0xFF00) << 16);
}
}
public get length(): number {
return this._startIndexes.length;
}
public getStartLineNumber(index: number): number {
return this._startIndexes[index] & MASK_LINE_NUMBER;
}
public getEndLineNumber(index: number): number {
return this._endIndexes[index] & MASK_LINE_NUMBER;
}
public getParentIndex(index: number) {
let parent = ((this._startIndexes[index] & MASK_INDENT) >>> 24) + ((this._endIndexes[index] & MASK_INDENT) >>> 16);
if (parent === MAX_FOLDING_REGIONS) {
return -1;
}
return parent;
}
public getIndent(index: number) {
const lineNumber = this.getStartLineNumber(index);
const tabSize = this._model.getOptions().tabSize;
const lineContent = this._model.getLineContent(lineNumber);
return computeIndentLevel(lineContent, tabSize);
}
public contains(index: number, line: number) {
return this.getStartLineNumber(index) <= line && this.getEndLineNumber(index) >= line;
}
private findIndex(line: number) {
let low = 0, high = this._startIndexes.length;
if (high === 0) {
return -1; // no children
}
while (low < high) {
let mid = Math.floor((low + high) / 2);
if (line < this.getStartLineNumber(mid)) {
high = mid;
} else {
low = mid + 1;
}
}
return low - 1;
}
public findRange(line: number): number {
let index = this.findIndex(line);
if (index >= 0) {
let endLineNumber = this.getEndLineNumber(index);
if (endLineNumber >= line) {
return index;
}
index = this.getParentIndex(index);
while (index !== -1) {
if (this.contains(index, line)) {
return index;
}
index = this.getParentIndex(index);
}
}
return -1;
}
}
// public only for testing
export class RangesCollector {
private _startIndexes: number[];
private _endIndexes: number[];
private _indentOccurrences: number[];
private _length: number;
private _foldingRegionsLimit: number;
constructor(foldingRegionsLimit: number) {
this._startIndexes = [];
this._endIndexes = [];
this._indentOccurrences = [];
this._length = 0;
this._foldingRegionsLimit = foldingRegionsLimit;
}
public insertFirst(startLineNumber: number, endLineNumber: number, indent: number) {
if (startLineNumber > MASK_LINE_NUMBER || endLineNumber > MASK_LINE_NUMBER) {
return;
}
let index = this._length;
this._startIndexes[index] = startLineNumber;
this._endIndexes[index] = endLineNumber;
this._length++;
if (indent < 1000) {
this._indentOccurrences[indent] = (this._indentOccurrences[indent] || 0) + 1;
}
}
public toIndentRanges(model: ITextModel) {
if (this._length <= this._foldingRegionsLimit) {
// reverse and create arrays of the exact length
let startIndexes = new Uint32Array(this._length);
let endIndexes = new Uint32Array(this._length);
for (let i = this._length - 1, k = 0; i >= 0; i-- , k++) {
startIndexes[k] = this._startIndexes[i];
endIndexes[k] = this._endIndexes[i];
}
return new IndentRanges(startIndexes, endIndexes, model);
} else {
let entries = 0;
let maxIndent = this._indentOccurrences.length;
for (let i = 0; i < this._indentOccurrences.length; i++) {
let n = this._indentOccurrences[i];
if (n) {
if (n + entries > this._foldingRegionsLimit) {
maxIndent = i;
break;
}
entries += n;
}
}
const tabSize = model.getOptions().tabSize;
// reverse and create arrays of the exact length
let startIndexes = new Uint32Array(entries);
let endIndexes = new Uint32Array(entries);
for (let i = this._length - 1, k = 0; i >= 0; i--) {
let startIndex = this._startIndexes[i];
let lineContent = model.getLineContent(startIndex);
let indent = computeIndentLevel(lineContent, tabSize);
if (indent < maxIndent) {
startIndexes[k] = startIndex;
endIndexes[k] = this._endIndexes[i];
k++;
}
}
return new IndentRanges(startIndexes, endIndexes, model);
}
}
}
interface PreviousRegion { indent: number; line: number; marker: boolean; };
export function computeRanges(model: ITextModel, offSide: boolean, markers?: FoldingMarkers, foldingRegionsLimit = MAX_FOLDING_REGIONS_FOR_INDENT_LIMIT): IndentRanges {
const tabSize = model.getOptions().tabSize;
let result = new RangesCollector(foldingRegionsLimit);
let pattern = void 0;
if (markers) {
pattern = new RegExp(`(${markers.start.source})|(?:${markers.end.source})`);
}
let previousRegions: PreviousRegion[] = [];
previousRegions.push({ indent: -1, line: model.getLineCount() + 1, marker: false }); // sentinel, to make sure there's at least one entry
for (let line = model.getLineCount(); line > 0; line--) {
let lineContent = model.getLineContent(line);
let indent = computeIndentLevel(lineContent, tabSize);
let previous = previousRegions[previousRegions.length - 1];
if (indent === -1) {
if (offSide && !previous.marker) {
// for offSide languages, empty lines are associated to the next block
previous.line = line;
}
continue; // only whitespace
}
let m;
if (pattern && (m = lineContent.match(pattern))) {
// folding pattern match
if (m[1]) { // start pattern match
// discard all regions until the folding pattern
let i = previousRegions.length - 1;
while (i > 0 && !previousRegions[i].marker) {
i--;
}
if (i > 0) {
previousRegions.length = i + 1;
previous = previousRegions[i];
// new folding range from pattern, includes the end line
result.insertFirst(line, previous.line, indent);
previous.marker = false;
previous.indent = indent;
previous.line = line;
continue;
} else {
// no end marker found, treat line as a regular line
}
} else { // end pattern match
previousRegions.push({ indent: -2, line, marker: true });
continue;
}
}
if (previous.indent > indent) {
// discard all regions with larger indent
do {
previousRegions.pop();
previous = previousRegions[previousRegions.length - 1];
} while (previous.indent > indent);
// new folding range
let endLineNumber = previous.line - 1;
if (endLineNumber - line >= 1) { // needs at east size 1
result.insertFirst(line, endLineNumber, indent);
}
}
if (previous.indent === indent) {
previous.line = line;
} else { // previous.indent < indent
// new region with a bigger indent
previousRegions.push({ indent, line, marker: false });
}
}
return result.toIndentRanges(model);
}

View File

@@ -12,10 +12,13 @@ import { IModelDecoration } from 'vs/editor/common/editorCommon';
// The red-black tree is based on the "Introduction to Algorithms" by Cormen, Leiserson and Rivest.
//
/**
* The class name sort order must match the severity order. Highest severity last.
*/
export const ClassName = {
EditorInfoDecoration: 'infosquiggly',
EditorWarningDecoration: 'warningsquiggly',
EditorErrorDecoration: 'errorsquiggly'
EditorInfoDecoration: 'squiggly-a-info',
EditorWarningDecoration: 'squiggly-b-warning',
EditorErrorDecoration: 'squiggly-c-error'
};
/**
@@ -29,7 +32,7 @@ export const enum TrackedRangeStickiness {
GrowsOnlyWhenTypingAfter = 3,
}
const enum NodeColor {
export const enum NodeColor {
Black = 0,
Red = 1,
}
@@ -78,7 +81,7 @@ const enum Constants {
MAX_SAFE_DELTA = 1 << 30,
}
function getNodeColor(node: IntervalNode): NodeColor {
export function getNodeColor(node: IntervalNode): NodeColor {
return ((node.metadata & Constants.ColorMask) >>> Constants.ColorOffset);
}
function setNodeColor(node: IntervalNode, color: NodeColor): void {
@@ -185,9 +188,11 @@ export class IntervalNode implements IModelDecoration {
public setOptions(options: ModelDecorationOptions) {
this.options = options;
let className = this.options.className;
setNodeIsForValidation(this, (
this.options.className === ClassName.EditorErrorDecoration
|| this.options.className === ClassName.EditorWarningDecoration
className === ClassName.EditorErrorDecoration
|| className === ClassName.EditorWarningDecoration
|| className === ClassName.EditorInfoDecoration
));
setNodeStickiness(this, <number>this.options.stickiness);
setNodeIsInOverviewRuler(this, this.options.overviewRuler.color ? true : false);
@@ -209,7 +214,7 @@ export class IntervalNode implements IModelDecoration {
}
}
const SENTINEL: IntervalNode = new IntervalNode(null, 0, 0);
export const SENTINEL: IntervalNode = new IntervalNode(null, 0, 0);
SENTINEL.parent = SENTINEL;
SENTINEL.left = SENTINEL;
SENTINEL.right = SENTINEL;
@@ -239,13 +244,6 @@ export class IntervalTree {
return search(this, filterOwnerId, filterOutValidation, cachedVersionId);
}
public count(): number {
if (this.root === SENTINEL) {
return 0;
}
return nodeCount(this);
}
/**
* Will not set `cachedAbsoluteStart` nor `cachedAbsoluteEnd` on the returned nodes!
*/
@@ -314,46 +312,10 @@ export class IntervalTree {
this._normalizeDeltaIfNecessary();
}
public assertInvariants(): void {
assert(getNodeColor(SENTINEL) === NodeColor.Black);
assert(SENTINEL.parent === SENTINEL);
assert(SENTINEL.left === SENTINEL);
assert(SENTINEL.right === SENTINEL);
assert(SENTINEL.start === 0);
assert(SENTINEL.end === 0);
assert(SENTINEL.delta === 0);
assert(this.root.parent === SENTINEL);
assertValidTree(this);
}
public getAllInOrder(): IntervalNode[] {
return search(this, 0, false, 0);
}
public print(): void {
if (this.root === SENTINEL) {
console.log(`~~ empty`);
return;
}
let out: string[] = [];
this._print(this.root, '', 0, out);
console.log(out.join(''));
}
private _print(n: IntervalNode, indent: string, delta: number, out: string[]): void {
out.push(`${indent}[${getNodeColor(n) === NodeColor.Red ? 'R' : 'B'},${n.delta}, ${n.start}->${n.end}, ${n.maxEnd}] : {${delta + n.start}->${delta + n.end}}, maxEnd: ${n.maxEnd + delta}\n`);
if (n.left !== SENTINEL) {
this._print(n.left, indent + ' ', delta, out);
} else {
out.push(`${indent} NIL\n`);
}
if (n.right !== SENTINEL) {
this._print(n.right, indent + ' ', delta + n.delta, out);
} else {
out.push(`${indent} NIL\n`);
}
}
private _normalizeDeltaIfNecessary(): void {
if (!this.requestNormalizeDelta) {
return;
@@ -425,7 +387,7 @@ function adjustMarkerBeforeColumn(markerOffset: number, markerStickToPreviousCha
return true;
}
return markerStickToPreviousCharacter;
};
}
/**
* This is a lot more complicated than strictly necessary to maintain the same behaviour
@@ -646,40 +608,6 @@ function noOverlapReplace(T: IntervalTree, start: number, end: number, textLengt
//#region Searching
function nodeCount(T: IntervalTree): number {
let node = T.root;
let count = 0;
while (node !== SENTINEL) {
if (getNodeIsVisited(node)) {
// going up from this node
setNodeIsVisited(node.left, false);
setNodeIsVisited(node.right, false);
node = node.parent;
continue;
}
if (node.left !== SENTINEL && !getNodeIsVisited(node.left)) {
// go left
node = node.left;
continue;
}
// handle current node
count++;
setNodeIsVisited(node, true);
if (node.right !== SENTINEL && !getNodeIsVisited(node.right)) {
// go right
node = node.right;
continue;
}
}
setNodeIsVisited(T.root, false);
return count;
}
function collectNodesFromOwner(T: IntervalTree, ownerId: number): IntervalNode[] {
let node = T.root;
let result: IntervalNode[] = [];
@@ -1304,66 +1232,10 @@ function recomputeMaxEndWalkToRoot(node: IntervalNode): void {
//#endregion
//#region utils
function intervalCompare(aStart: number, aEnd: number, bStart: number, bEnd: number): number {
export function intervalCompare(aStart: number, aEnd: number, bStart: number, bEnd: number): number {
if (aStart === bStart) {
return aEnd - bEnd;
}
return aStart - bStart;
}
//#endregion
//#region Assertion
function depth(n: IntervalNode): number {
if (n === SENTINEL) {
// The leafs are black
return 1;
}
assert(depth(n.left) === depth(n.right));
return (getNodeColor(n) === NodeColor.Black ? 1 : 0) + depth(n.left);
}
function assertValidNode(n: IntervalNode, delta): void {
if (n === SENTINEL) {
return;
}
let l = n.left;
let r = n.right;
if (getNodeColor(n) === NodeColor.Red) {
assert(getNodeColor(l) === NodeColor.Black);
assert(getNodeColor(r) === NodeColor.Black);
}
let expectedMaxEnd = n.end;
if (l !== SENTINEL) {
assert(intervalCompare(l.start + delta, l.end + delta, n.start + delta, n.end + delta) <= 0);
expectedMaxEnd = Math.max(expectedMaxEnd, l.maxEnd);
}
if (r !== SENTINEL) {
assert(intervalCompare(n.start + delta, n.end + delta, r.start + delta + n.delta, r.end + delta + n.delta) <= 0);
expectedMaxEnd = Math.max(expectedMaxEnd, r.maxEnd + n.delta);
}
assert(n.maxEnd === expectedMaxEnd);
assertValidNode(l, delta);
assertValidNode(r, delta + n.delta);
}
function assertValidTree(tree: IntervalTree): void {
if (tree.root === SENTINEL) {
return;
}
assert(getNodeColor(tree.root) === NodeColor.Black);
assert(depth(tree.root.left) === depth(tree.root.right));
assertValidNode(tree.root, 0);
}
function assert(condition: boolean): void {
if (!condition) {
throw new Error('Assertion violation');
}
}
//#endregion

View File

@@ -5,15 +5,12 @@
'use strict';
import URI from 'vs/base/common/uri';
import {
IModel, ITextModelCreationOptions
} from 'vs/editor/common/editorCommon';
import Event, { Emitter } from 'vs/base/common/event';
import { IModel, ITextModelCreationOptions } from 'vs/editor/common/editorCommon';
import { EditableTextModel } from 'vs/editor/common/model/editableTextModel';
import { TextModel } from 'vs/editor/common/model/textModel';
import { IDisposable } from 'vs/base/common/lifecycle';
import { LanguageIdentifier } from 'vs/editor/common/modes';
import { IRawTextSource, RawTextSource } from 'vs/editor/common/model/textSource';
import * as textModelEvents from 'vs/editor/common/model/textModelEvents';
// The hierarchy is:
// Model -> EditableTextModel -> TextModelWithDecorations -> TextModelWithTokens -> TextModel
@@ -22,21 +19,9 @@ var MODEL_ID = 0;
export class Model extends EditableTextModel implements IModel {
public onDidChangeDecorations(listener: (e: textModelEvents.IModelDecorationsChangedEvent) => void): IDisposable {
return this._eventEmitter.addListener(textModelEvents.TextModelEventType.ModelDecorationsChanged, listener);
}
public onDidChangeOptions(listener: (e: textModelEvents.IModelOptionsChangedEvent) => void): IDisposable {
return this._eventEmitter.addListener(textModelEvents.TextModelEventType.ModelOptionsChanged, listener);
}
public onWillDispose(listener: () => void): IDisposable {
return this._eventEmitter.addListener(textModelEvents.TextModelEventType.ModelDispose, listener);
}
public onDidChangeLanguage(listener: (e: textModelEvents.IModelLanguageChangedEvent) => void): IDisposable {
return this._eventEmitter.addListener(textModelEvents.TextModelEventType.ModelLanguageChanged, listener);
}
public onDidChangeLanguageConfiguration(listener: (e: textModelEvents.IModelLanguageConfigurationChangedEvent) => void): IDisposable {
return this._eventEmitter.addListener(textModelEvents.TextModelEventType.ModelLanguageConfigurationChanged, listener);
}
private readonly _onWillDispose: Emitter<void> = this._register(new Emitter<void>());
public readonly onWillDispose: Event<void> = this._onWillDispose.event;
public static createFromString(text: string, options: ITextModelCreationOptions = TextModel.DEFAULT_CREATION_OPTIONS, languageIdentifier: LanguageIdentifier = null, uri: URI = null): Model {
return new Model(RawTextSource.fromString(text), options, languageIdentifier, uri);
}
@@ -62,13 +47,9 @@ export class Model extends EditableTextModel implements IModel {
this._attachedEditorCount = 0;
}
public destroy(): void {
this.dispose();
}
public dispose(): void {
this._isDisposing = true;
this._eventEmitter.emit(textModelEvents.TextModelEventType.ModelDispose);
this._onWillDispose.fire();
super.dispose();
this._isDisposing = false;
}

View File

@@ -4,8 +4,8 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import { OrderGuaranteeEventEmitter, BulkListenerCallback } from 'vs/base/common/eventEmitter';
import * as strings from 'vs/base/common/strings';
import Event, { Emitter } from 'vs/base/common/event';
import { Position, IPosition } from 'vs/editor/common/core/position';
import { Range, IRange } from 'vs/editor/common/core/range';
import { Selection } from 'vs/editor/common/core/selection';
@@ -16,8 +16,8 @@ import { EDITOR_MODEL_DEFAULTS } from 'vs/editor/common/config/editorOptions';
import { PrefixSumComputer } from 'vs/editor/common/viewModel/prefixSumComputer';
import { TextModelSearch, SearchParams } from 'vs/editor/common/model/textModelSearch';
import { TextSource, ITextSource, IRawTextSource, RawTextSource } from 'vs/editor/common/model/textSource';
import { IDisposable } from 'vs/base/common/lifecycle';
import * as textModelEvents from 'vs/editor/common/model/textModelEvents';
import { IModelContentChangedEvent, ModelRawContentChangedEvent, ModelRawFlush, ModelRawEOLChanged, IModelOptionsChangedEvent, InternalModelContentChangeEvent } from 'vs/editor/common/model/textModelEvents';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
const LIMIT_FIND_COUNT = 999;
export const LONG_LINE_BOUNDARY = 10000;
@@ -27,10 +27,10 @@ export interface ITextModelCreationData {
readonly options: editorCommon.TextModelResolvedOptions;
}
export class TextModel implements editorCommon.ITextModel {
private static MODEL_SYNC_LIMIT = 50 * 1024 * 1024; // 50 MB
private static MODEL_TOKENIZATION_LIMIT = 20 * 1024 * 1024; // 20 MB
private static MANY_MANY_LINES = 300 * 1000; // 300K lines
export class TextModel extends Disposable implements editorCommon.ITextModel {
private static readonly MODEL_SYNC_LIMIT = 50 * 1024 * 1024; // 50 MB
private static readonly MODEL_TOKENIZATION_LIMIT = 20 * 1024 * 1024; // 20 MB
private static readonly MANY_MANY_LINES = 300 * 1000; // 300K lines
public static DEFAULT_CREATION_OPTIONS: editorCommon.ITextModelCreationOptions = {
tabSize: EDITOR_MODEL_DEFAULTS.tabSize,
@@ -71,11 +71,17 @@ export class TextModel implements editorCommon.ITextModel {
};
}
public addBulkListener(listener: BulkListenerCallback): IDisposable {
return this._eventEmitter.addBulkListener(listener);
}
private readonly _onDidChangeOptions: Emitter<IModelOptionsChangedEvent> = this._register(new Emitter<IModelOptionsChangedEvent>());
public readonly onDidChangeOptions: Event<IModelOptionsChangedEvent> = this._onDidChangeOptions.event;
protected readonly _eventEmitter: OrderGuaranteeEventEmitter;
protected readonly _eventEmitter: DidChangeContentEmitter = this._register(new DidChangeContentEmitter());
public onDidChangeRawContent(listener: (e: ModelRawContentChangedEvent) => void): IDisposable {
return this._eventEmitter.event((e: InternalModelContentChangeEvent) => listener(e.rawContentChangedEvent));
}
public onDidChangeContent(listener: (e: IModelContentChangedEvent) => void): IDisposable {
return this._eventEmitter.event((e: InternalModelContentChangeEvent) => listener(e.contentChangedEvent));
}
/*protected*/ _lines: IModelLine[];
protected _EOL: string;
@@ -97,7 +103,7 @@ export class TextModel implements editorCommon.ITextModel {
protected readonly _isTooLargeForTokenization: boolean;
constructor(rawTextSource: IRawTextSource, creationOptions: editorCommon.ITextModelCreationOptions) {
this._eventEmitter = new OrderGuaranteeEventEmitter();
super();
const textModelData = TextModel.resolveCreationData(rawTextSource, creationOptions);
@@ -167,7 +173,7 @@ export class TextModel implements editorCommon.ITextModel {
let e = this._options.createChangeEvent(newOpts);
this._options = newOpts;
this._eventEmitter.emit(textModelEvents.TextModelEventType.ModelOptionsChanged, e);
this._onDidChangeOptions.fire(e);
}
public detectIndentation(defaultInsertSpaces: boolean, defaultTabSize: number): void {
@@ -296,11 +302,11 @@ export class TextModel implements editorCommon.ITextModel {
this._EOL = null;
this._BOM = null;
this._eventEmitter.dispose();
super.dispose();
}
private _emitContentChanged2(startLineNumber: number, startColumn: number, endLineNumber: number, endColumn: number, rangeLength: number, text: string, isUndoing: boolean, isRedoing: boolean, isFlush: boolean): void {
const e: textModelEvents.IModelContentChangedEvent = {
private _createContentChanged2(startLineNumber: number, startColumn: number, endLineNumber: number, endColumn: number, rangeLength: number, text: string, isUndoing: boolean, isRedoing: boolean, isFlush: boolean): IModelContentChangedEvent {
return {
changes: [{
range: new Range(startLineNumber, startColumn, endLineNumber, endColumn),
rangeLength: rangeLength,
@@ -312,9 +318,6 @@ export class TextModel implements editorCommon.ITextModel {
isRedoing: isRedoing,
isFlush: isFlush
};
if (!this._isDisposing) {
this._eventEmitter.emit(textModelEvents.TextModelEventType.ModelContentChanged, e);
}
}
protected _resetValue(newValue: ITextSource): void {
@@ -364,18 +367,17 @@ export class TextModel implements editorCommon.ITextModel {
this._resetValue(newValue);
this._emitModelRawContentChangedEvent(
new textModelEvents.ModelRawContentChangedEvent(
this._emitContentChangedEvent(
new ModelRawContentChangedEvent(
[
new textModelEvents.ModelRawFlush()
new ModelRawFlush()
],
this._versionId,
false,
false
)
),
this._createContentChanged2(1, 1, endLineNumber, endColumn, oldModelValueLength, this.getValue(), false, false, true)
);
this._emitContentChanged2(1, 1, endLineNumber, endColumn, oldModelValueLength, this.getValue(), false, false, true);
}
public getValue(eol?: editorCommon.EndOfLinePreference, preserveBOM: boolean = false): string {
@@ -519,18 +521,17 @@ export class TextModel implements editorCommon.ITextModel {
this._increaseVersionId();
this._onAfterEOLChange();
this._emitModelRawContentChangedEvent(
new textModelEvents.ModelRawContentChangedEvent(
this._emitContentChangedEvent(
new ModelRawContentChangedEvent(
[
new textModelEvents.ModelRawEOLChanged()
new ModelRawEOLChanged()
],
this._versionId,
false,
false
)
),
this._createContentChanged2(1, 1, endLineNumber, endColumn, oldModelValueLength, this.getValue(), false, false, false)
);
this._emitContentChanged2(1, 1, endLineNumber, endColumn, oldModelValueLength, this.getValue(), false, false, false);
}
public getLineMinColumn(lineNumber: number): number {
@@ -573,17 +574,6 @@ export class TextModel implements editorCommon.ITextModel {
return result + 2;
}
public validateLineNumber(lineNumber: number): number {
this._assertNotDisposed();
if (lineNumber < 1) {
lineNumber = 1;
}
if (lineNumber > this._lines.length) {
lineNumber = this._lines.length;
}
return lineNumber;
}
/**
* Validates `range` is within buffer bounds, but allows it to sit in between surrogate pairs, etc.
* Will try to not allocate if possible.
@@ -750,12 +740,12 @@ export class TextModel implements editorCommon.ITextModel {
return new Range(1, 1, lineCount, this.getLineMaxColumn(lineCount));
}
protected _emitModelRawContentChangedEvent(e: textModelEvents.ModelRawContentChangedEvent): void {
protected _emitContentChangedEvent(rawChange: ModelRawContentChangedEvent, change: IModelContentChangedEvent): void {
if (this._isDisposing) {
// Do not confuse listeners by emitting any event after disposing
return;
}
this._eventEmitter.emit(textModelEvents.TextModelEventType.ModelRawContentChanged2, e);
this._eventEmitter.fire(new InternalModelContentChangeEvent(rawChange, change));
}
private _constructLines(textSource: ITextSource): void {
@@ -820,3 +810,39 @@ export class TextModel implements editorCommon.ITextModel {
return TextModelSearch.findPreviousMatch(this, new SearchParams(searchString, isRegex, matchCase, wordSeparators), searchStart, captureMatches);
}
}
export class DidChangeContentEmitter extends Disposable {
private readonly _actual: Emitter<InternalModelContentChangeEvent> = this._register(new Emitter<InternalModelContentChangeEvent>());
public readonly event: Event<InternalModelContentChangeEvent> = this._actual.event;
private _deferredCnt: number;
private _deferredEvents: InternalModelContentChangeEvent[];
constructor() {
super();
this._deferredCnt = 0;
this._deferredEvents = [];
}
public beginDeferredEmit(): void {
this._deferredCnt++;
}
public endDeferredEmit(): void {
this._deferredCnt--;
if (this._deferredCnt === 0) {
while (this._deferredEvents.length > 0) {
this._actual.fire(this._deferredEvents.shift());
}
}
}
public fire(e: InternalModelContentChangeEvent): void {
if (this._deferredCnt > 0) {
this._deferredEvents.push(e);
return;
}
this._actual.fire(e);
}
}

View File

@@ -7,20 +7,6 @@
import { IRange } from 'vs/editor/common/core/range';
/**
* @internal
*/
export const TextModelEventType = {
ModelDispose: 'modelDispose',
ModelTokensChanged: 'modelTokensChanged',
ModelLanguageChanged: 'modelLanguageChanged',
ModelOptionsChanged: 'modelOptionsChanged',
ModelContentChanged: 'contentChanged',
ModelRawContentChanged2: 'rawContentChanged2',
ModelDecorationsChanged: 'decorationsChanged',
ModelLanguageConfigurationChanged: 'modelLanguageConfigurationChanged'
};
/**
* An event describing that the current mode associated with a model has changed.
*/
@@ -249,3 +235,13 @@ export class ModelRawContentChangedEvent {
return false;
}
}
/**
* @internal
*/
export class InternalModelContentChangeEvent {
constructor(
public readonly rawContentChangedEvent: ModelRawContentChangedEvent,
public readonly contentChangedEvent: IModelContentChangedEvent,
) { }
}

View File

@@ -5,17 +5,19 @@
'use strict';
import { onUnexpectedError } from 'vs/base/common/errors';
import { IMarkdownString, markedStringsEquals } from 'vs/base/common/htmlContent';
import { IMarkdownString } from 'vs/base/common/htmlContent';
import * as strings from 'vs/base/common/strings';
import { CharCode } from 'vs/base/common/charCode';
import Event, { Emitter } from 'vs/base/common/event';
import { Range, IRange } from 'vs/editor/common/core/range';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { TextModelWithTokens } from 'vs/editor/common/model/textModelWithTokens';
import { LanguageIdentifier } from 'vs/editor/common/modes';
import { ITextSource, IRawTextSource } from 'vs/editor/common/model/textSource';
import * as textModelEvents from 'vs/editor/common/model/textModelEvents';
import { IModelDecorationsChangedEvent } from 'vs/editor/common/model/textModelEvents';
import { ThemeColor } from 'vs/platform/theme/common/themeService';
import { IntervalNode, IntervalTree, recomputeMaxEnd, getNodeIsInOverviewRuler } from 'vs/editor/common/model/intervalTree';
import { Disposable } from 'vs/base/common/lifecycle';
let _INSTANCE_COUNT = 0;
/**
@@ -36,14 +38,15 @@ function nextInstanceId(): string {
export class TextModelWithDecorations extends TextModelWithTokens implements editorCommon.ITextModelWithDecorations {
protected readonly _onDidChangeDecorations: DidChangeDecorationsEmitter = this._register(new DidChangeDecorationsEmitter());
public readonly onDidChangeDecorations: Event<IModelDecorationsChangedEvent> = this._onDidChangeDecorations.event;
/**
* Used to workaround broken clients that might attempt using a decoration id generated by a different model.
* It is not globally unique in order to limit it to one character.
*/
private readonly _instanceId: string;
private _lastDecorationId: number;
private _currentDecorationsTrackerCnt: number;
private _currentDecorationsTrackerDidChange: boolean;
private _decorations: { [decorationId: string]: IntervalNode; };
private _decorationsTree: DecorationsTrees;
@@ -52,8 +55,6 @@ export class TextModelWithDecorations extends TextModelWithTokens implements edi
this._instanceId = nextInstanceId();
this._lastDecorationId = 0;
this._currentDecorationsTrackerCnt = 0;
this._currentDecorationsTrackerDidChange = false;
this._decorations = Object.create(null);
this._decorationsTree = new DecorationsTrees();
}
@@ -73,30 +74,10 @@ export class TextModelWithDecorations extends TextModelWithTokens implements edi
this._decorationsTree = new DecorationsTrees();
}
_getTrackedRangesCount(): number {
return this._decorationsTree.count();
}
// --- END TrackedRanges
protected _acquireDecorationsTracker(): void {
if (this._currentDecorationsTrackerCnt === 0) {
this._currentDecorationsTrackerDidChange = false;
}
this._currentDecorationsTrackerCnt++;
}
protected _releaseDecorationsTracker(): void {
this._currentDecorationsTrackerCnt--;
if (this._currentDecorationsTrackerCnt === 0) {
if (this._currentDecorationsTrackerDidChange) {
this._emitModelDecorationsChangedEvent();
}
}
}
protected _adjustDecorationsForEdit(offset: number, length: number, textLength: number, forceMoveMarkers: boolean): void {
this._currentDecorationsTrackerDidChange = true;
this._onDidChangeDecorations.fire();
this._decorationsTree.acceptReplace(offset, length, textLength, forceMoveMarkers);
}
@@ -138,31 +119,29 @@ export class TextModelWithDecorations extends TextModelWithTokens implements edi
this._assertNotDisposed();
try {
this._eventEmitter.beginDeferredEmit();
this._acquireDecorationsTracker();
this._onDidChangeDecorations.beginDeferredEmit();
return this._changeDecorations(ownerId, callback);
} finally {
this._releaseDecorationsTracker();
this._eventEmitter.endDeferredEmit();
this._onDidChangeDecorations.endDeferredEmit();
}
}
private _changeDecorations<T>(ownerId: number, callback: (changeAccessor: editorCommon.IModelDecorationsChangeAccessor) => T): T {
let changeAccessor: editorCommon.IModelDecorationsChangeAccessor = {
addDecoration: (range: IRange, options: editorCommon.IModelDecorationOptions): string => {
this._currentDecorationsTrackerDidChange = true;
this._onDidChangeDecorations.fire();
return this._deltaDecorationsImpl(ownerId, [], [{ range: range, options: options }])[0];
},
changeDecoration: (id: string, newRange: IRange): void => {
this._currentDecorationsTrackerDidChange = true;
this._onDidChangeDecorations.fire();
this._changeDecorationImpl(id, newRange);
},
changeDecorationOptions: (id: string, options: editorCommon.IModelDecorationOptions) => {
this._currentDecorationsTrackerDidChange = true;
this._onDidChangeDecorations.fire();
this._changeDecorationOptionsImpl(id, _normalizeOptions(options));
},
removeDecoration: (id: string): void => {
this._currentDecorationsTrackerDidChange = true;
this._onDidChangeDecorations.fire();
this._deltaDecorationsImpl(ownerId, [id], []);
},
deltaDecorations: (oldDecorations: string[], newDecorations: editorCommon.IModelDeltaDecoration[]): string[] => {
@@ -170,7 +149,7 @@ export class TextModelWithDecorations extends TextModelWithTokens implements edi
// nothing to do
return [];
}
this._currentDecorationsTrackerDidChange = true;
this._onDidChangeDecorations.fire();
return this._deltaDecorationsImpl(ownerId, oldDecorations, newDecorations);
}
};
@@ -199,13 +178,11 @@ export class TextModelWithDecorations extends TextModelWithTokens implements edi
}
try {
this._eventEmitter.beginDeferredEmit();
this._acquireDecorationsTracker();
this._currentDecorationsTrackerDidChange = true;
this._onDidChangeDecorations.beginDeferredEmit();
this._onDidChangeDecorations.fire();
return this._deltaDecorationsImpl(ownerId, oldDecorations, newDecorations);
} finally {
this._releaseDecorationsTracker();
this._eventEmitter.endDeferredEmit();
this._onDidChangeDecorations.endDeferredEmit();
}
}
@@ -312,13 +289,6 @@ export class TextModelWithDecorations extends TextModelWithTokens implements edi
return this._ensureNodesHaveRanges(result);
}
private _emitModelDecorationsChangedEvent(): void {
if (!this._isDisposing) {
let e: textModelEvents.IModelDecorationsChangedEvent = {};
this._eventEmitter.emit(textModelEvents.TextModelEventType.ModelDecorationsChanged, e);
}
}
private _getDecorationsInRange(filterRange: Range, filterOwnerId: number, filterOutValidation: boolean): IntervalNode[] {
const startOffset = this._lineStarts.getAccumulatedValue(filterRange.startLineNumber - 2) + filterRange.startColumn - 1;
const endOffset = this._lineStarts.getAccumulatedValue(filterRange.endLineNumber - 2) + filterRange.endColumn - 1;
@@ -479,12 +449,6 @@ class DecorationsTrees {
}
}
public count(): number {
const c0 = this._decorationsTree0.count();
const c1 = this._decorationsTree1.count();
return c0 + c1;
}
public collectNodesFromOwner(ownerId: number): IntervalNode[] {
const r0 = this._decorationsTree0.collectNodesFromOwner(ownerId);
const r1 = this._decorationsTree1.collectNodesFromOwner(ownerId);
@@ -559,15 +523,6 @@ export class ModelDecorationOverviewRulerOptions implements editorCommon.IModelD
this.position = options.position;
}
}
public equals(other: ModelDecorationOverviewRulerOptions): boolean {
return (
this.color === other.color
&& this.darkColor === other.darkColor
&& this.hcColor === other.hcColor
&& this.position === other.position
);
}
}
let lastStaticId = 0;
@@ -615,28 +570,6 @@ export class ModelDecorationOptions implements editorCommon.IModelDecorationOpti
this.beforeContentClassName = options.beforeContentClassName ? cleanClassName(options.beforeContentClassName) : strings.empty;
this.afterContentClassName = options.afterContentClassName ? cleanClassName(options.afterContentClassName) : strings.empty;
}
public equals(other: ModelDecorationOptions): boolean {
if (this.staticId > 0 || other.staticId > 0) {
return this.staticId === other.staticId;
}
return (
this.stickiness === other.stickiness
&& this.className === other.className
&& this.isWholeLine === other.isWholeLine
&& this.showIfCollapsed === other.showIfCollapsed
&& this.glyphMarginClassName === other.glyphMarginClassName
&& this.linesDecorationsClassName === other.linesDecorationsClassName
&& this.marginClassName === other.marginClassName
&& this.inlineClassName === other.inlineClassName
&& this.beforeContentClassName === other.beforeContentClassName
&& this.afterContentClassName === other.afterContentClassName
&& markedStringsEquals(this.hoverMessage, other.hoverMessage)
&& markedStringsEquals(this.glyphMarginHoverMessage, other.glyphMarginHoverMessage)
&& this.overviewRuler.equals(other.overviewRuler)
);
}
}
ModelDecorationOptions.EMPTY = ModelDecorationOptions.register({});
@@ -656,3 +589,36 @@ function _normalizeOptions(options: editorCommon.IModelDecorationOptions): Model
}
return ModelDecorationOptions.createDynamic(options);
}
export class DidChangeDecorationsEmitter extends Disposable {
private readonly _actual: Emitter<IModelDecorationsChangedEvent> = this._register(new Emitter<IModelDecorationsChangedEvent>());
public readonly event: Event<IModelDecorationsChangedEvent> = this._actual.event;
private _deferredCnt: number;
private _shouldFire: boolean;
constructor() {
super();
this._deferredCnt = 0;
this._shouldFire = false;
}
public beginDeferredEmit(): void {
this._deferredCnt++;
}
public endDeferredEmit(): void {
this._deferredCnt--;
if (this._deferredCnt === 0) {
if (this._shouldFire) {
this._shouldFire = false;
this._actual.fire({});
}
}
}
public fire(): void {
this._shouldFire = true;
}
}

View File

@@ -8,6 +8,7 @@ import * as nls from 'vs/nls';
import { onUnexpectedError } from 'vs/base/common/errors';
import { IDisposable } from 'vs/base/common/lifecycle';
import { StopWatch } from 'vs/base/common/stopwatch';
import Event, { Emitter } from 'vs/base/common/event';
import { Range } from 'vs/editor/common/core/range';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { TextModel } from 'vs/editor/common/model/textModel';
@@ -21,8 +22,7 @@ import { LineTokens, LineToken } from 'vs/editor/common/core/lineTokens';
import { getWordAtText } from 'vs/editor/common/model/wordHelper';
import { TokenizationResult2 } from 'vs/editor/common/core/token';
import { ITextSource, IRawTextSource } from 'vs/editor/common/model/textSource';
import * as textModelEvents from 'vs/editor/common/model/textModelEvents';
import { IndentRanges, computeRanges } from 'vs/editor/common/model/indentRanges';
import { IModelTokensChangedEvent, IModelLanguageChangedEvent, IModelLanguageConfigurationChangedEvent } from 'vs/editor/common/model/textModelEvents';
import { computeIndentLevel } from 'vs/editor/common/model/modelLine';
class ModelTokensChangedEventBuilder {
@@ -50,7 +50,7 @@ class ModelTokensChangedEventBuilder {
}
}
public build(): textModelEvents.IModelTokensChangedEvent {
public build(): IModelTokensChangedEvent {
if (this._ranges.length === 0) {
return null;
}
@@ -62,7 +62,16 @@ class ModelTokensChangedEventBuilder {
export class TextModelWithTokens extends TextModel implements editorCommon.ITokenizedModel {
private static MODE_TOKENIZATION_FAILED_MSG = nls.localize('mode.tokenizationSupportFailed', "The mode has failed while tokenizing the input.");
private static readonly MODE_TOKENIZATION_FAILED_MSG = nls.localize('mode.tokenizationSupportFailed', "The mode has failed while tokenizing the input.");
private readonly _onDidChangeLanguage: Emitter<IModelLanguageChangedEvent> = this._register(new Emitter<IModelLanguageChangedEvent>());
public readonly onDidChangeLanguage: Event<IModelLanguageChangedEvent> = this._onDidChangeLanguage.event;
private readonly _onDidChangeLanguageConfiguration: Emitter<IModelLanguageConfigurationChangedEvent> = this._register(new Emitter<IModelLanguageConfigurationChangedEvent>());
public readonly onDidChangeLanguageConfiguration: Event<IModelLanguageConfigurationChangedEvent> = this._onDidChangeLanguageConfiguration.event;
private readonly _onDidChangeTokens: Emitter<IModelTokensChangedEvent> = this._register(new Emitter<IModelTokensChangedEvent>());
public readonly onDidChangeTokens: Event<IModelTokensChangedEvent> = this._onDidChangeTokens.event;
private _languageIdentifier: LanguageIdentifier;
private _tokenizationListener: IDisposable;
@@ -71,7 +80,6 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke
private _invalidLineStartIndex: number;
private _lastState: IState;
private _indentRanges: IndentRanges;
private _languageRegistryListener: IDisposable;
private _revalidateTokensTimeout: number;
@@ -102,13 +110,11 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke
this._languageRegistryListener = LanguageConfigurationRegistry.onDidChange((e) => {
if (e.languageIdentifier.id === this._languageIdentifier.id) {
this._resetIndentRanges();
this._emitModelLanguageConfigurationEvent({});
this._onDidChangeLanguageConfiguration.fire({});
}
});
this._resetTokenizationState();
this._resetIndentRanges();
}
public dispose(): void {
@@ -128,7 +134,6 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke
super._resetValue(newValue);
// Cancel tokenization, clear all tokens and begin tokenizing
this._resetTokenizationState();
this._resetIndentRanges();
}
protected _resetTokenizationState(): void {
@@ -169,29 +174,19 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke
}
}
private _withModelTokensChangedEventBuilder<T>(callback: (eventBuilder: ModelTokensChangedEventBuilder) => T): T {
let eventBuilder = new ModelTokensChangedEventBuilder();
let result = callback(eventBuilder);
if (!this._isDisposing) {
let e = eventBuilder.build();
if (e) {
this._eventEmitter.emit(textModelEvents.TextModelEventType.ModelTokensChanged, e);
}
}
return result;
}
public forceTokenization(lineNumber: number): void {
if (lineNumber < 1 || lineNumber > this.getLineCount()) {
throw new Error('Illegal value ' + lineNumber + ' for `lineNumber`');
}
this._withModelTokensChangedEventBuilder((eventBuilder) => {
this._updateTokensUntilLine(eventBuilder, lineNumber);
});
const eventBuilder = new ModelTokensChangedEventBuilder();
this._updateTokensUntilLine(eventBuilder, lineNumber);
const e = eventBuilder.build();
if (e) {
this._onDidChangeTokens.fire(e);
}
}
public isCheapToTokenize(lineNumber: number): boolean {
@@ -231,7 +226,7 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke
return;
}
let e: textModelEvents.IModelLanguageChangedEvent = {
let e: IModelLanguageChangedEvent = {
oldLanguage: this._languageIdentifier.language,
newLanguage: languageIdentifier.language
};
@@ -240,7 +235,6 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke
// Cancel tokenization, clear all tokens and begin tokenizing
this._resetTokenizationState();
this._resetIndentRanges();
this.emitModelTokensChangedEvent({
ranges: [{
@@ -248,8 +242,8 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke
toLineNumber: this.getLineCount()
}]
});
this._emitModelModeChangedEvent(e);
this._emitModelLanguageConfigurationEvent({});
this._onDidChangeLanguage.fire(e);
this._onDidChangeLanguageConfiguration.fire({});
}
public getLanguageIdAtPosition(_lineNumber: number, _column: number): LanguageId {
@@ -295,56 +289,60 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke
private _revalidateTokensNow(toLineNumber: number = this._invalidLineStartIndex + 1000000): void {
this._withModelTokensChangedEventBuilder((eventBuilder) => {
const eventBuilder = new ModelTokensChangedEventBuilder();
toLineNumber = Math.min(this._lines.length, toLineNumber);
toLineNumber = Math.min(this._lines.length, toLineNumber);
var MAX_ALLOWED_TIME = 20,
fromLineNumber = this._invalidLineStartIndex + 1,
tokenizedChars = 0,
currentCharsToTokenize = 0,
currentEstimatedTimeToTokenize = 0,
sw = StopWatch.create(false),
elapsedTime: number;
var MAX_ALLOWED_TIME = 20,
fromLineNumber = this._invalidLineStartIndex + 1,
tokenizedChars = 0,
currentCharsToTokenize = 0,
currentEstimatedTimeToTokenize = 0,
sw = StopWatch.create(false),
elapsedTime: number;
// Tokenize at most 1000 lines. Estimate the tokenization speed per character and stop when:
// - MAX_ALLOWED_TIME is reached
// - tokenizing the next line would go above MAX_ALLOWED_TIME
// Tokenize at most 1000 lines. Estimate the tokenization speed per character and stop when:
// - MAX_ALLOWED_TIME is reached
// - tokenizing the next line would go above MAX_ALLOWED_TIME
for (var lineNumber = fromLineNumber; lineNumber <= toLineNumber; lineNumber++) {
elapsedTime = sw.elapsed();
if (elapsedTime > MAX_ALLOWED_TIME) {
// Stop if MAX_ALLOWED_TIME is reached
for (var lineNumber = fromLineNumber; lineNumber <= toLineNumber; lineNumber++) {
elapsedTime = sw.elapsed();
if (elapsedTime > MAX_ALLOWED_TIME) {
// Stop if MAX_ALLOWED_TIME is reached
toLineNumber = lineNumber - 1;
break;
}
// Compute how many characters will be tokenized for this line
currentCharsToTokenize = this._lines[lineNumber - 1].text.length;
if (tokenizedChars > 0) {
// If we have enough history, estimate how long tokenizing this line would take
currentEstimatedTimeToTokenize = (elapsedTime / tokenizedChars) * currentCharsToTokenize;
if (elapsedTime + currentEstimatedTimeToTokenize > MAX_ALLOWED_TIME) {
// Tokenizing this line will go above MAX_ALLOWED_TIME
toLineNumber = lineNumber - 1;
break;
}
// Compute how many characters will be tokenized for this line
currentCharsToTokenize = this._lines[lineNumber - 1].text.length;
if (tokenizedChars > 0) {
// If we have enough history, estimate how long tokenizing this line would take
currentEstimatedTimeToTokenize = (elapsedTime / tokenizedChars) * currentCharsToTokenize;
if (elapsedTime + currentEstimatedTimeToTokenize > MAX_ALLOWED_TIME) {
// Tokenizing this line will go above MAX_ALLOWED_TIME
toLineNumber = lineNumber - 1;
break;
}
}
this._updateTokensUntilLine(eventBuilder, lineNumber);
tokenizedChars += currentCharsToTokenize;
// Skip the lines that got tokenized
lineNumber = Math.max(lineNumber, this._invalidLineStartIndex + 1);
}
elapsedTime = sw.elapsed();
this._updateTokensUntilLine(eventBuilder, lineNumber);
tokenizedChars += currentCharsToTokenize;
if (this._invalidLineStartIndex < this._lines.length) {
this._beginBackgroundTokenization();
}
});
// Skip the lines that got tokenized
lineNumber = Math.max(lineNumber, this._invalidLineStartIndex + 1);
}
elapsedTime = sw.elapsed();
if (this._invalidLineStartIndex < this._lines.length) {
this._beginBackgroundTokenization();
}
const e = eventBuilder.build();
if (e) {
this._onDidChangeTokens.fire(e);
}
}
private _updateTokensUntilLine(eventBuilder: ModelTokensChangedEventBuilder, lineNumber: number): void {
@@ -409,21 +407,9 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke
this._invalidLineStartIndex = Math.max(this._invalidLineStartIndex, endLineIndex + 1);
}
private emitModelTokensChangedEvent(e: textModelEvents.IModelTokensChangedEvent): void {
private emitModelTokensChangedEvent(e: IModelTokensChangedEvent): void {
if (!this._isDisposing) {
this._eventEmitter.emit(textModelEvents.TextModelEventType.ModelTokensChanged, e);
}
}
private _emitModelLanguageConfigurationEvent(e: textModelEvents.IModelLanguageConfigurationChangedEvent): void {
if (!this._isDisposing) {
this._eventEmitter.emit(textModelEvents.TextModelEventType.ModelLanguageConfigurationChanged, e);
}
}
private _emitModelModeChangedEvent(e: textModelEvents.IModelLanguageChangedEvent): void {
if (!this._isDisposing) {
this._eventEmitter.emit(textModelEvents.TextModelEventType.ModelLanguageChanged, e);
this._onDidChangeTokens.fire(e);
}
}
@@ -431,10 +417,10 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke
public getWordAtPosition(_position: IPosition): editorCommon.IWordAtPosition {
this._assertNotDisposed();
let position = this.validatePosition(_position);
let lineContent = this.getLineContent(position.lineNumber);
const position = this.validatePosition(_position);
const lineContent = this.getLineContent(position.lineNumber);
if (this._invalidLineStartIndex <= position.lineNumber) {
if (this._invalidLineStartIndex <= position.lineNumber - 1) {
// this line is not tokenized
return getWordAtText(
position.column,
@@ -444,30 +430,29 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke
);
}
let lineTokens = this._getLineTokens(position.lineNumber);
let offset = position.column - 1;
let token = lineTokens.findTokenAtOffset(offset);
const lineTokens = this._getLineTokens(position.lineNumber);
const offset = position.column - 1;
const token = lineTokens.findTokenAtOffset(offset);
const languageId = token.languageId;
let result = getWordAtText(
position.column,
LanguageConfigurationRegistry.getWordDefinition(token.languageId),
lineContent.substring(token.startOffset, token.endOffset),
token.startOffset
);
if (!result && token.hasPrev && token.startOffset === offset) {
// The position is right at the beginning of `modeIndex`, so try looking at `modeIndex` - 1 too
let prevToken = token.prev();
result = getWordAtText(
position.column,
LanguageConfigurationRegistry.getWordDefinition(prevToken.languageId),
lineContent.substring(prevToken.startOffset, prevToken.endOffset),
prevToken.startOffset
);
// go left until a different language is hit
let startOffset: number;
for (let leftToken = token.clone(); leftToken !== null && leftToken.languageId === languageId; leftToken = leftToken.prev()) {
startOffset = leftToken.startOffset;
}
return result;
// go right until a different language is hit
let endOffset: number;
for (let rightToken = token.clone(); rightToken !== null && rightToken.languageId === languageId; rightToken = rightToken.next()) {
endOffset = rightToken.endOffset;
}
return getWordAtText(
position.column,
LanguageConfigurationRegistry.getWordDefinition(languageId),
lineContent.substring(startOffset, endOffset),
startOffset
);
}
public getWordUntilPosition(position: IPosition): editorCommon.IWordAtPosition {
@@ -516,7 +501,7 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke
let lineTokens = this._getLineTokens(lineNumber);
const lineText = this._lines[lineNumber - 1].text;
const currentToken = lineTokens.findTokenAtOffset(position.column - 1);
let currentToken = lineTokens.findTokenAtOffset(position.column - 1);
if (!currentToken) {
return null;
}
@@ -570,14 +555,14 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke
// If position is in between two tokens, try also looking in the previous token
if (currentToken.hasPrev && currentToken.startOffset === position.column - 1) {
const prevToken = currentToken.prev();
const prevModeBrackets = LanguageConfigurationRegistry.getBracketsSupport(prevToken.languageId);
const searchEndOffset = currentToken.startOffset;
currentToken = currentToken.prev();
const prevModeBrackets = LanguageConfigurationRegistry.getBracketsSupport(currentToken.languageId);
// check that previous token is not to be ignored
if (prevModeBrackets && !ignoreBracketsInToken(prevToken.tokenType)) {
if (prevModeBrackets && !ignoreBracketsInToken(currentToken.tokenType)) {
// limit search in case previous token is very large, there's no need to go beyond `maxBracketLength`
const searchStartOffset = Math.max(prevToken.startOffset, position.column - 1 - prevModeBrackets.maxBracketLength);
const searchEndOffset = currentToken.startOffset;
const searchStartOffset = Math.max(currentToken.startOffset, position.column - 1 - prevModeBrackets.maxBracketLength);
const foundBracket = BracketsUtils.findPrevBracketInToken(prevModeBrackets.reversedRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
// check that we didn't hit a bracket too far away from position
@@ -838,24 +823,6 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke
};
}
protected _resetIndentRanges(): void {
this._indentRanges = null;
}
private _getIndentRanges(): IndentRanges {
if (!this._indentRanges) {
let foldingRules = LanguageConfigurationRegistry.getFoldingRules(this._languageIdentifier.id);
let offSide = foldingRules && foldingRules.offSide;
let markers = foldingRules && foldingRules.markers;
this._indentRanges = computeRanges(this, offSide, markers);
}
return this._indentRanges;
}
public getIndentRanges(): IndentRanges {
return this._getIndentRanges();
}
private _computeIndentLevel(lineIndex: number): number {
return computeIndentLevel(this._lines[lineIndex].text, this._options.tabSize);
}