This commit is contained in:
Alex Dima
2015-11-25 13:21:46 +01:00
parent 5ea34f9dba
commit b57564d7fb
16 changed files with 443 additions and 346 deletions
+12
View File
@@ -1452,6 +1452,18 @@ export interface ITokenizedModel extends ITextModel {
* @param position The position at which to look for a bracket.
*/
matchBracket(position:IPosition, inaccurateResultAcceptable?:boolean): IMatchBracketResult;
/**
* No mode supports allowed on this model because it is simply too large.
* (even tokenization would cause too much memory pressure)
*/
isTooLargeForHavingAMode(): boolean;
/**
* Only basic mode supports allowed on this model because it is simply too large.
* (tokenization is allowed and other basic supports)
*/
isTooLargeForHavingARichMode(): boolean;
}
/**
+31 -34
View File
@@ -16,6 +16,11 @@ import {IResourceService} from 'vs/editor/common/services/resourceService';
import {URL} from 'vs/base/common/network';
import {disposeAll} from 'vs/base/common/lifecycle';
export interface IMirrorModelEvents {
contentChanged: EditorCommon.IModelContentChangedEvent[];
propertiesChanged: EditorCommon.IModelPropertiesChangedEvent;
}
export class AbstractMirrorModel extends TextModelWithTokens implements EditorCommon.IMirrorModel {
_lineStarts:PrefixSumComputer;
@@ -453,51 +458,43 @@ export class MirrorModel extends AbstractMirrorModel implements EditorCommon.IMi
return false;
}
public onEvents(events:{ type:string; }[]) : boolean {
public onEvents(events:IMirrorModelEvents) : boolean {
if (this._isDisposed) {
throw new Error('MirrorModel.onEvents: Model is disposed');
}
var changed = false;
for (var i = 0, len = events.length; i < len; i++) {
var e = events[i];
switch (e.type) {
case EditorCommon.EventType.ModelContentChanged:
var contentChangedEvent = <EditorCommon.IModelContentChangedEvent><any>e;
if (events.propertiesChanged) {
this._extraProperties = events.propertiesChanged.properties;
}
// Force recreating of line starts
this._lineStarts = null;
let changed = false;
for (let i = 0, len = events.contentChanged.length; i < len; i++) {
let contentChangedEvent = events.contentChanged[i];
this._setVersionId(contentChangedEvent.versionId);
switch (contentChangedEvent.changeType) {
case EditorCommon.EventType.ModelContentChangedFlush:
this._onLinesFlushed(<EditorCommon.IModelContentChangedFlushEvent>contentChangedEvent);
changed = true;
break;
// Force recreating of line starts
this._lineStarts = null;
case EditorCommon.EventType.ModelContentChangedLinesDeleted:
this._onLinesDeleted(<EditorCommon.IModelContentChangedLinesDeletedEvent>contentChangedEvent);
changed = true;
break;
case EditorCommon.EventType.ModelContentChangedLinesInserted:
this._onLinesInserted(<EditorCommon.IModelContentChangedLinesInsertedEvent>contentChangedEvent);
changed = true;
break;
case EditorCommon.EventType.ModelContentChangedLineChanged:
this._onLineChanged(<EditorCommon.IModelContentChangedLineChangedEvent>contentChangedEvent);
changed = true;
break;
}
this._setVersionId(contentChangedEvent.versionId);
switch (contentChangedEvent.changeType) {
case EditorCommon.EventType.ModelContentChangedFlush:
this._onLinesFlushed(<EditorCommon.IModelContentChangedFlushEvent>contentChangedEvent);
changed = true;
break;
case EditorCommon.EventType.ModelPropertiesChanged:
this._extraProperties = (<any>e).properties;
case EditorCommon.EventType.ModelContentChangedLinesDeleted:
this._onLinesDeleted(<EditorCommon.IModelContentChangedLinesDeletedEvent>contentChangedEvent);
changed = true;
break;
default:
console.warn('Unknown model event: ' + e.type);
case EditorCommon.EventType.ModelContentChangedLinesInserted:
this._onLinesInserted(<EditorCommon.IModelContentChangedLinesInsertedEvent>contentChangedEvent);
changed = true;
break;
case EditorCommon.EventType.ModelContentChangedLineChanged:
this._onLineChanged(<EditorCommon.IModelContentChangedLineChangedEvent>contentChangedEvent);
changed = true;
break;
}
}
@@ -185,7 +185,7 @@ export class TextModelWithTokens extends TextModel implements EditorCommon.IToke
private static MODE_TOKENIZATION_FAILED_MSG = nls.localize('mode.tokenizationSupportFailed', "The mode has failed while tokenizing the input.");
private static MODEL_SYNC_LIMIT = 5 * 1024 * 1024; // 5 MB
private static MODEL_TOKENIZATION_LIMIT = 50 * 1024 * 1024; // 50 MB
private static MODEL_TOKENIZATION_LIMIT = 20 * 1024 * 1024; // 20 MB
private _shouldAutoTokenize:boolean;
private _mode: Modes.IMode;
@@ -262,12 +262,20 @@ export class TextModelWithTokens extends TextModel implements EditorCommon.IToke
super.dispose();
}
public isTooLargeForHavingAMode(): boolean {
return this._shouldDenyMode;
}
public isTooLargeForHavingARichMode(): boolean {
return this._shouldSimplifyMode;
}
private _massageMode(mode: Modes.IMode): Modes.IMode {
if (this._shouldDenyMode) {
if (this.isTooLargeForHavingAMode()) {
return new NullMode();
}
if (this._shouldSimplifyMode) {
return new SimplifiedMode(mode);
if (this.isTooLargeForHavingARichMode()) {
return mode.toSimplifiedMode();
}
return mode;
}
@@ -856,27 +864,3 @@ export class TextModelWithTokens extends TextModel implements EditorCommon.IToke
return BracketsHelper.matchBracket(this, this.validatePosition(position), inaccurateResultAcceptable);
}
}
class SimplifiedMode implements Modes.IMode {
tokenizationSupport: Modes.ITokenizationSupport;
electricCharacterSupport: Modes.IElectricCharacterSupport;
commentsSupport: Modes.ICommentsSupport;
characterPairSupport: Modes.ICharacterPairSupport;
tokenTypeClassificationSupport: Modes.ITokenTypeClassificationSupport;
private _id: string;
constructor(sourceMode: Modes.IMode) {
this._id = 'vs.editor.modes.simplifiedMode:' + sourceMode.getId();
this.tokenizationSupport = sourceMode.tokenizationSupport;
this.electricCharacterSupport = sourceMode.electricCharacterSupport;
this.commentsSupport = sourceMode.commentsSupport;
this.characterPairSupport = sourceMode.characterPairSupport;
this.tokenTypeClassificationSupport = sourceMode.tokenTypeClassificationSupport;
}
public getId(): string {
return this._id;
}
}
+5
View File
@@ -197,6 +197,11 @@ export interface IMode {
getId(): string;
/**
* Return a mode "similar" to this one that strips any "smart" supports.
*/
toSimplifiedMode(): IMode;
addSupportChangedListener?(callback: (e: EditorCommon.IModeSupportChangedEvent) => void): IDisposable;
/**
@@ -49,6 +49,7 @@ export class AbstractMode<W extends AbstractModeWorker> implements Modes.IMode {
// adapters end
private _eventEmitter = new EventEmitter();
private _simplifiedMode: Modes.IMode;
constructor(
descriptor:Modes.IModeDescriptor,
@@ -73,6 +74,7 @@ export class AbstractMode<W extends AbstractModeWorker> implements Modes.IMode {
this.tokenTypeClassificationSupport = this;
this._workerPiecePromise = null;
this._simplifiedMode = null;
}
public getId(): string {
@@ -86,6 +88,13 @@ export class AbstractMode<W extends AbstractModeWorker> implements Modes.IMode {
}
}
public toSimplifiedMode(): Modes.IMode {
if (!this._simplifiedMode) {
this._simplifiedMode = new SimplifiedMode(this);
}
return this._simplifiedMode;
}
private _getOrCreateWorker(): TPromise<W> {
if (!this._workerPiecePromise) {
var workerDescriptor: AsyncDescriptor2<Modes.IMode, Modes.IWorkerParticipant[], W> = this._getWorkerDescriptor();
@@ -236,7 +245,84 @@ export class AbstractMode<W extends AbstractModeWorker> implements Modes.IMode {
}
}
class SimplifiedMode implements Modes.IMode {
tokenizationSupport: Modes.ITokenizationSupport;
electricCharacterSupport: Modes.IElectricCharacterSupport;
commentsSupport: Modes.ICommentsSupport;
characterPairSupport: Modes.ICharacterPairSupport;
tokenTypeClassificationSupport: Modes.ITokenTypeClassificationSupport;
onEnterSupport: Modes.IOnEnterSupport;
private _sourceMode: Modes.IMode;
private _eventEmitter: EventEmitter;
private _id: string;
constructor(sourceMode: Modes.IMode) {
this._sourceMode = sourceMode;
this._eventEmitter = new EventEmitter();
this._id = 'vs.editor.modes.simplifiedMode:' + sourceMode.getId();
this._assignSupports();
if (this._sourceMode.addSupportChangedListener) {
this._sourceMode.addSupportChangedListener((e) => {
if (e.tokenizationSupport || e.electricCharacterSupport || e.commentsSupport || e.characterPairSupport || e.tokenTypeClassificationSupport || e.onEnterSupport) {
this._assignSupports();
let newEvent = SimplifiedMode._createModeSupportChangedEvent(e);
this._eventEmitter.emit('modeSupportChanged', newEvent);
}
})
}
}
public getId(): string {
return this._id;
}
public toSimplifiedMode(): Modes.IMode {
return this;
}
private _assignSupports(): void {
this.tokenizationSupport = this._sourceMode.tokenizationSupport;
this.electricCharacterSupport = this._sourceMode.electricCharacterSupport;
this.commentsSupport = this._sourceMode.commentsSupport;
this.characterPairSupport = this._sourceMode.characterPairSupport;
this.tokenTypeClassificationSupport = this._sourceMode.tokenTypeClassificationSupport;
this.onEnterSupport = this._sourceMode.onEnterSupport;
}
private static _createModeSupportChangedEvent(originalModeEvent:EditorCommon.IModeSupportChangedEvent): EditorCommon.IModeSupportChangedEvent {
var event = {
codeLensSupport: false,
tokenizationSupport: originalModeEvent.tokenizationSupport,
occurrencesSupport:false,
declarationSupport:false,
typeDeclarationSupport:false,
navigateTypesSupport:false,
referenceSupport:false,
suggestSupport:false,
parameterHintsSupport:false,
extraInfoSupport:false,
outlineSupport:false,
logicalSelectionSupport:false,
formattingSupport:false,
inplaceReplaceSupport:false,
diffSupport:false,
dirtyDiffSupport:false,
emitOutputSupport:false,
linkSupport:false,
configSupport:false,
electricCharacterSupport: originalModeEvent.electricCharacterSupport,
commentsSupport: originalModeEvent.commentsSupport,
characterPairSupport: originalModeEvent.characterPairSupport,
tokenTypeClassificationSupport: originalModeEvent.tokenTypeClassificationSupport,
quickFixSupport:false,
onEnterSupport: originalModeEvent.onEnterSupport
};
return event;
}
}
export var isDigit:(character:string, base:number)=>boolean = (function () {
+4
View File
@@ -91,6 +91,10 @@ export class NullMode implements Modes.IMode {
return NullMode.ID;
}
public toSimplifiedMode(): Modes.IMode {
return this;
}
public getWordDefinition():RegExp {
return NullMode.DEFAULT_WORD_REGEXP;
}
@@ -21,8 +21,6 @@ export interface IModelService {
destroyModel(resource: URL): void;
removeModel(model: EditorCommon.IModel): void;
getModels(): EditorCommon.IModel[];
getModel(resource: URI): EditorCommon.IModel;
+146 -198
View File
@@ -7,7 +7,7 @@
import {IEmitterEvent} from 'vs/base/common/eventEmitter';
import {IModeService} from 'vs/editor/common/services/modeService';
import {IMarker, IMarkerService} from 'vs/platform/markers/common/markers';
import {MirrorModel} from 'vs/editor/common/model/mirrorModel';
import {IMirrorModelEvents, MirrorModel} from 'vs/editor/common/model/mirrorModel';
import {Range} from 'vs/editor/common/core/range';
import EditorCommon = require('vs/editor/common/editorCommon');
import Modes = require('vs/editor/common/modes');
@@ -21,7 +21,7 @@ import URI from 'vs/base/common/uri';
import {URL} from 'vs/base/common/network';
import Severity from 'vs/base/common/severity';
import {EventProvider} from 'vs/base/common/eventProvider';
import {IDisposable} from 'vs/base/common/lifecycle';
import {IDisposable, disposeAll} from 'vs/base/common/lifecycle';
import {TPromise} from 'vs/base/common/winjs.base';
import Errors = require('vs/base/common/errors');
import {anonymize} from 'vs/platform/telemetry/common/telemetry';
@@ -35,53 +35,69 @@ export interface IRawModelData {
modeId:string;
}
class BoundModel implements IDisposable {
function MODEL_ID(resource:URI): string {
return resource.toString();
}
model:EditorCommon.IModel;
toUnbind:Function;
private _decorationIds: string[];
class ModelData implements IDisposable {
model: EditorCommon.IModel;
isSyncedToWorkers: boolean;
constructor(model:EditorCommon.IModel) {
private _markerDecorations: string[];
private _modelEventsListener: IDisposable;
constructor(model: EditorCommon.IModel, eventsHandler:(modelData:ModelData, events:IEmitterEvent[])=>void) {
this.model = model;
this.toUnbind = null;
this.isSyncedToWorkers = false;
this._markerDecorations = [];
this._modelEventsListener = model.addBulkListener2((events) => eventsHandler(this, events));
}
public dispose(): void {
this._decorationIds = this.model.deltaDecorations(this._decorationIds, []);
this._markerDecorations = this.model.deltaDecorations(this._markerDecorations, []);
this._modelEventsListener.dispose();
this._modelEventsListener = null;
this.model = null;
if (this.toUnbind) {
this.toUnbind();
this.toUnbind = null;
}
}
public deltaMarkers(markers:IMarker[]):void {
public getModelId(): string {
return MODEL_ID(this.model.getAssociatedResource());
}
public acceptMarkerDecorations(newDecorations:EditorCommon.IModelDeltaDecoration[]): void {
this._markerDecorations = this.model.deltaDecorations(this._markerDecorations, newDecorations);
}
}
class ModelMarkerHandler {
public static setMarkers(modelData:ModelData, markers:IMarker[]):void {
// Limit to the first 500 errors/warnings
markers = markers.slice(0, 500);
var newModelDecorations = markers.map(marker => {
return <EditorCommon.IModelDeltaDecoration> {
range: this._createDecorationRange(marker),
let newModelDecorations:EditorCommon.IModelDeltaDecoration[] = markers.map((marker) => {
return {
range: this._createDecorationRange(modelData.model, marker),
options: this._createDecorationOption(marker)
};
});
this._decorationIds = this.model.deltaDecorations(this._decorationIds, newModelDecorations);
modelData.acceptMarkerDecorations(newModelDecorations);
}
private _createDecorationRange(rawMarker: IMarker): EditorCommon.IRange {
var marker = this.model.validateRange(new Range(rawMarker.startLineNumber, rawMarker.startColumn, rawMarker.endLineNumber, rawMarker.endColumn));
var ret: EditorCommon.IEditorRange = new Range(marker.startLineNumber, marker.startColumn, marker.endLineNumber, marker.endColumn);
private static _createDecorationRange(model:EditorCommon.IModel, rawMarker: IMarker): EditorCommon.IRange {
let marker = model.validateRange(new Range(rawMarker.startLineNumber, rawMarker.startColumn, rawMarker.endLineNumber, rawMarker.endColumn));
let ret: EditorCommon.IEditorRange = new Range(marker.startLineNumber, marker.startColumn, marker.endLineNumber, marker.endColumn);
if (ret.isEmpty()) {
var word = this.model.getWordAtPosition(ret.getStartPosition());
let word = model.getWordAtPosition(ret.getStartPosition());
if (word) {
ret.startColumn = word.startColumn;
ret.endColumn = word.endColumn;
} else {
var maxColumn = this.model.getLineLastNonWhitespaceColumn(marker.startLineNumber) ||
this.model.getLineMaxColumn(marker.startLineNumber);
let maxColumn = model.getLineLastNonWhitespaceColumn(marker.startLineNumber) ||
model.getLineMaxColumn(marker.startLineNumber);
if (maxColumn === 1) {
// empty line
@@ -96,7 +112,7 @@ class BoundModel implements IDisposable {
}
}
} else if (rawMarker.endColumn === Number.MAX_VALUE && rawMarker.startColumn === 1 && ret.startLineNumber === ret.endLineNumber) {
var minColumn = this.model.getLineFirstNonWhitespaceColumn(rawMarker.startLineNumber);
let minColumn = model.getLineFirstNonWhitespaceColumn(rawMarker.startLineNumber);
if (minColumn < ret.endColumn) {
ret.startColumn = minColumn;
rawMarker.startColumn = minColumn;
@@ -105,7 +121,7 @@ class BoundModel implements IDisposable {
return ret;
}
private _createDecorationOption(marker:IMarker): EditorCommon.IModelDecorationOptions {
private static _createDecorationOption(marker:IMarker): EditorCommon.IModelDecorationOptions {
let className: string;
let color: string;
@@ -151,14 +167,9 @@ class BoundModel implements IDisposable {
}
}
export interface IModelsEvents {
[url:string]: any[];
}
export class ModelServiceImpl implements IModelService {
public serviceId = IModelService;
private _models: {[modelId:string]:BoundModel;};
private _markerService: IMarkerService;
private _markerServiceSubscription: IDisposable;
private _threadService: IThreadService;
@@ -167,9 +178,11 @@ export class ModelServiceImpl implements IModelService {
private _onModelAdded: EventSource<(model: EditorCommon.IModel) => void>;
private _onModelRemoved: EventSource<(model: EditorCommon.IModel) => void>;
private _onModelModeChanged: EventSource<(model: EditorCommon.IModel, oldModeId:string) => void>;
private _accumulatedModelEvents: IModelsEvents;
private _lastSentModelEventsTime: number;
private _sendModelEventsTimerId: number;
/**
* All the models known in the system.
*/
private _models: {[modelId:string]:ModelData;};
constructor(threadService: IThreadService, markerService: IMarkerService) {
this._threadService = threadService;
@@ -185,137 +198,75 @@ export class ModelServiceImpl implements IModelService {
if(this._markerService) {
this._markerServiceSubscription = this._markerService.onMarkerChanged(this._handleMarkerChange, this);
}
this._accumulatedModelEvents = {};
this._lastSentModelEventsTime = -1;
this._sendModelEventsTimerId = -1;
}
public dispose(): void {
if(this._markerServiceSubscription) {
this._markerServiceSubscription.dispose();
}
if (this._sendModelEventsTimerId !== -1) {
clearTimeout(this._sendModelEventsTimerId);
this._sendModelEventsTimerId = -1;
}
}
private _sendModelEvents(url:URL, events:any[]): void {
let modelId = url.toString();
this._accumulatedModelEvents[modelId] = this._accumulatedModelEvents[modelId] || [];
this._accumulatedModelEvents[modelId] = this._accumulatedModelEvents[modelId].concat(events);
this._sendModelEventsNow();
// this._scheduleSendModelEvents();
}
// private _scheduleSendModelEvents(): void {
// if (this._sendModelEventsTimerId !== -1) {
// // sending model events already scheduled
// return;
// }
// let elapsed = Date.now() - this._lastSentModelEventsTime;
// if (elapsed >= 100) {
// // more than 100ms have passed since last model events have been sent => send events now
// this._sendModelEventsNow();
// } else {
// this._sendModelEventsTimerId = setTimeout(() => {
// this._sendModelEventsTimerId = -1;
// this._sendModelEventsNow();
// }, 100 - elapsed);
// }
// }
private _sendModelEventsNow(): void {
this._lastSentModelEventsTime = Date.now();
let sendingEvents = this._accumulatedModelEvents;
this._accumulatedModelEvents = {};
this._workerHelper.$onModelsEvents(sendingEvents);
}
private _handleMarkerChange(changedResources: URI[]): void {
changedResources.forEach(resource => {
var boundModel = this._models[resource.toString()];
if (!boundModel) {
changedResources.forEach((resource) => {
let modelId = MODEL_ID(resource);
let modelData = this._models[modelId];
if (!modelData) {
return;
}
boundModel.deltaMarkers(this._markerService.read({ resource: resource, take: 500 }));
ModelMarkerHandler.setMarkers(modelData, this._markerService.read({ resource: resource, take: 500 }));
});
}
// --- begin IModelService
public createModel(value:string, modeOrPromise:TPromise<Modes.IMode>|Modes.IMode, resource: URL): EditorCommon.IModel {
var model = new Model(value, modeOrPromise, resource);
this.addModel(model);
return model;
}
public addModel(model:EditorCommon.IModel): void {
var modelId = model.getAssociatedResource().toString();
private _createModelData(value:string, modeOrPromise:TPromise<Modes.IMode>|Modes.IMode, resource: URL): ModelData {
// create & save the model
let model = new Model(value, modeOrPromise, resource);
let modelId = MODEL_ID(model.getAssociatedResource());
if (this._models[modelId]) {
// There already exists a model with this id => this is a programmer error
throw new Error('BoundModels: Cannot add model ' + anonymize(modelId) + ' because it already exists!');
throw new Error('ModelService: Cannot add model ' + anonymize(modelId) + ' because it already exists!');
}
var boundModel = new BoundModel(model);
let modelData = new ModelData(model, (modelData, events) => this._onModelEvents(modelData, events));
this._models[modelId] = modelData;
boundModel.toUnbind = model.addBulkListener((events) => this._onModelEvents(modelId, events));
if(this._markerService) {
boundModel.deltaMarkers(this._markerService.read({ resource: model.getAssociatedResource() }));
}
this._models[modelId] = boundModel;
// Create model in workers
this._workerHelper.$createModel(ModelServiceImpl._getBoundModelData(model));
this._onModelAdded.fire(model);
return modelData;
}
public removeModel(model:EditorCommon.IModel): void {
var modelId = model.getAssociatedResource().toString();
if (this._accumulatedModelEvents[modelId]) {
delete this._accumulatedModelEvents[modelId];
}
if (!this._models[modelId]) {
// There is no model with this id => this is a programmer error
throw new Error('BoundModels: Cannot remove model ' + anonymize(modelId) + ' because it doesn\'t exist!');
}
// Dispose model in workers
this._workerHelper.$disposeModel(model.getAssociatedResource());
// this._modelDispose(model.getAssociatedResource());
this._models[modelId].dispose();
delete this._models[modelId];
public createModel(value:string, modeOrPromise:TPromise<Modes.IMode>|Modes.IMode, resource: URL): EditorCommon.IModel {
let modelData = this._createModelData(value, modeOrPromise, resource);
let modelId = modelData.getModelId();
// handle markers (marker service => model)
if (this._markerService) {
var markers = this._markerService.read({ resource: model.getAssociatedResource() }),
owners: { [o: string]: any } = Object.create(null);
markers.forEach(marker => owners[marker.owner] = this);
Object.keys(owners).forEach(owner => this._markerService.changeOne(owner, model.getAssociatedResource(), []));
ModelMarkerHandler.setMarkers(modelData, this._markerService.read({ resource: modelData.model.getAssociatedResource() }));
}
this._onModelRemoved.fire(model);
if (!modelData.model.isTooLargeForHavingARichMode()) {
// send this model to the workers
modelData.isSyncedToWorkers = true;
this._workerHelper.$_acceptNewModel(ModelServiceImpl._getBoundModelData(modelData.model));
}
this._onModelAdded.fire(modelData.model);
return modelData.model;
}
public destroyModel(resource: URL): void {
let model = this.getModel(resource);
if (model) {
model.destroy();
// We need to support that not all models get disposed through this service (i.e. model.dispose() should work!)
let modelData = this._models[MODEL_ID(resource)];
if (!modelData) {
return;
}
modelData.model.dispose();
}
public getModels(): EditorCommon.IModel[] {
var ret: EditorCommon.IModel[] = [];
for (var modelId in this._models) {
let ret: EditorCommon.IModel[] = [];
for (let modelId in this._models) {
if (this._models.hasOwnProperty(modelId)) {
ret.push(this._models[modelId].model);
}
@@ -324,11 +275,12 @@ export class ModelServiceImpl implements IModelService {
}
public getModel(resource: URL): EditorCommon.IModel {
var boundModel = this._models[resource.toString()];
if (boundModel) {
return boundModel.model;
let modelId = MODEL_ID(resource);
let modelData = this._models[modelId];
if (!modelData) {
return null;
}
return null;
return modelData.model;
}
public get onModelAdded(): EventProvider<(model:EditorCommon.IModel)=>void> {
@@ -345,6 +297,30 @@ export class ModelServiceImpl implements IModelService {
// --- end IModelService
private _onModelDisposing(model:EditorCommon.IModel): void {
let modelId = MODEL_ID(model.getAssociatedResource());
let modelData = this._models[modelId];
// TODO@Joh why are we removing markers here?
if (this._markerService) {
var markers = this._markerService.read({ resource: model.getAssociatedResource() }),
owners: { [o: string]: any } = Object.create(null);
markers.forEach(marker => owners[marker.owner] = this);
Object.keys(owners).forEach(owner => this._markerService.changeOne(owner, model.getAssociatedResource(), []));
}
if (modelData.isSyncedToWorkers) {
// Dispose model in workers
this._workerHelper.$_acceptDidDisposeModel(model.getAssociatedResource());
}
delete this._models[modelId];
modelData.dispose();
this._onModelRemoved.fire(model);
}
private static _getBoundModelData(model:EditorCommon.IModel): IRawModelData {
return {
url: model.getAssociatedResource(),
@@ -355,67 +331,47 @@ export class ModelServiceImpl implements IModelService {
};
}
private _onModelEvents(modelId:string, events:IEmitterEvent[]): void {
private _onModelEvents(modelData:ModelData, events:IEmitterEvent[]): void {
let eventsForWorkers: IMirrorModelEvents = { contentChanged: [], propertiesChanged: null };
var resultingEvents:any[] = [],
changed = false,
i:number,
len:number;
for (let i = 0, len = events.length; i < len; i++) {
let e = events[i];
let data = e.getData();
for (i = 0, len = events.length; i < len; i++) {
var e = events[i];
var data = e.getData();
switch (e.getType()) {
case EditorCommon.EventType.ModelDispose:
this.removeModel(this._models[modelId].model);
this._onModelDisposing(modelData.model);
// no more event processing
return;
case EditorCommon.EventType.ModelContentChanged:
switch (data.changeType) {
case EditorCommon.EventType.ModelContentChangedFlush:
resultingEvents.push(this._mixinProperties({ type: e.getType() }, data, ['changeType', 'detail', 'versionId']));
break;
case EditorCommon.EventType.ModelContentChangedLinesDeleted:
resultingEvents.push(this._mixinProperties({ type: e.getType() }, data, ['changeType', 'fromLineNumber', 'toLineNumber', 'versionId']));
break;
case EditorCommon.EventType.ModelContentChangedLinesInserted:
resultingEvents.push(this._mixinProperties({ type: e.getType() }, data, ['changeType', 'fromLineNumber', 'toLineNumber', 'detail', 'versionId']));
break;
case EditorCommon.EventType.ModelContentChangedLineChanged:
resultingEvents.push(this._mixinProperties({ type: e.getType() }, data, ['changeType', 'lineNumber', 'detail', 'versionId']));
break;
if (modelData.isSyncedToWorkers) {
eventsForWorkers.contentChanged.push(<EditorCommon.IModelContentChangedEvent>data);
}
changed = true;
break;
case EditorCommon.EventType.ModelPropertiesChanged:
resultingEvents.push(this._mixinProperties({ type: e.getType() }, data, ['properties']));
if (modelData.isSyncedToWorkers) {
eventsForWorkers.propertiesChanged = <EditorCommon.IModelPropertiesChangedEvent>data;
}
break;
case EditorCommon.EventType.ModelModeChanged:
let modeChangedEvent = <EditorCommon.IModelModeChangedEvent>data;
this._workerHelper.$onModelModeChanged(modelId, modeChangedEvent.oldMode.getId(), modeChangedEvent.newMode.getId());
this._onModelModeChanged.fire(this._models[modelId].model, modeChangedEvent.oldMode.getId());
if (modelData.isSyncedToWorkers) {
// Forward mode change to all the workers
this._workerHelper.$_acceptDidChangeModelMode(modelData.getModelId(), modeChangedEvent.oldMode.getId(), modeChangedEvent.newMode.getId());
}
this._onModelModeChanged.fire(modelData.model, modeChangedEvent.oldMode.getId());
break;
}
}
if (resultingEvents.length > 0) {
if (eventsForWorkers.contentChanged.length > 0 || eventsForWorkers.propertiesChanged) {
// Forward events to all the workers
this._sendModelEvents(this._models[modelId].model.getAssociatedResource(), resultingEvents);
this._workerHelper.$_acceptModelEvents(modelData.getModelId(), eventsForWorkers);
}
}
private _mixinProperties(dst:any, src:any, properties:string[]): any {
for (var i = 0; i < properties.length; i++) {
dst[properties[i]] = src[properties[i]];
}
return dst;
}
}
@Remotable.WorkerContext('ModelServiceWorkerHelper', ThreadAffinity.All)
@@ -432,9 +388,9 @@ export class ModelServiceWorkerHelper {
this._modeService = modeService;
}
public $createModel(data:IRawModelData): TPromise<void> {
public $_acceptNewModel(data:IRawModelData): TPromise<void> {
// Create & insert the mirror model eagerly in the resource service
var mirrorModel = new MirrorModel(this._resourceService, data.versionId, data.value, null, data.url, data.properties);
let mirrorModel = new MirrorModel(this._resourceService, data.versionId, data.value, null, data.url, data.properties);
this._resourceService.insert(mirrorModel.getAssociatedResource(), mirrorModel);
// Block worker execution until the mode is instantiated
@@ -452,8 +408,8 @@ export class ModelServiceWorkerHelper {
});
}
public $onModelModeChanged(modelId:string, oldModeId:string, newModeId:string): TPromise<void> {
var mirrorModel = this._resourceService.get(URI.parse(modelId));
public $_acceptDidChangeModelMode(modelId:string, oldModeId:string, newModeId:string): TPromise<void> {
let mirrorModel = this._resourceService.get(URI.parse(modelId));
// Block worker execution until the mode is instantiated
return this._modeService.getOrCreateMode(newModeId).then((mode) => {
@@ -470,31 +426,23 @@ export class ModelServiceWorkerHelper {
});
}
public $disposeModel(url:URL): void {
var model = <MirrorModel>this._resourceService.get(url);
public $_acceptDidDisposeModel(url:URL): void {
let model = <MirrorModel>this._resourceService.get(url);
this._resourceService.remove(url);
if (model) {
model.dispose();
}
}
public $onModelsEvents(events:IModelsEvents): void {
let missingModels: string[] = [];
Object.keys(events).forEach((strURL:string) => {
var model = <MirrorModel>this._resourceService.get(new URL(strURL));
if (!model) {
missingModels.push(strURL);
return;
}
try {
model.onEvents(events[strURL]);
} catch (err) {
Errors.onUnexpectedError(err);
}
});
if (missingModels.length > 0) {
throw new Error('Received model events for missing models ' + missingModels.map(anonymize).join(' AND '));
public $_acceptModelEvents(modelId: string, events:IMirrorModelEvents): void {
let model = <MirrorModel>this._resourceService.get(new URL(modelId));
if (!model) {
throw new Error('Received model events for missing model ' + anonymize(modelId));
}
try {
model.onEvents(events);
} catch (err) {
Errors.onUnexpectedError(err);
}
}
}
@@ -13,7 +13,7 @@ import {TextModel} from 'vs/editor/common/model/textModel';
import {LineMarker, TextModelWithMarkers} from 'vs/editor/common/model/textModelWithMarkers';
import {ILineMarker} from 'vs/editor/common/model/modelLine';
import {PluginHostDocument} from 'vs/workbench/api/common/pluginHostDocuments';
import {MirrorModel} from 'vs/editor/common/model/mirrorModel';
import {MirrorModel, IMirrorModelEvents} from 'vs/editor/common/model/mirrorModel';
suite('EditorModel - EditableTextModel._getInverseEdits', () => {
@@ -1194,8 +1194,11 @@ suite('EditorModel - EditableTextModel.applyEdits', () => {
console.warn('Model version id did not advance between edits (1)');
}
mirrorModel1PrevVersionId = versionId;
(<any>e).type = EditorCommon.EventType.ModelContentChanged;
mirrorModel1.onEvents(<any[]>[e]);
let mirrorModelEvents:IMirrorModelEvents = {
propertiesChanged: null,
contentChanged: [e]
};
mirrorModel1.onEvents(mirrorModelEvents);
});
model.addListener(EditorCommon.EventType.ModelContentChanged2, (e:EditorCommon.IModelContentChangedEvent2) => {
@@ -21,6 +21,57 @@ function equalRange(left, right) {
assert.ok(true, 'ranges');
};
function contentChangedFlushEvent(detail: EditorCommon.IRawText): EditorCommon.IModelContentChangedFlushEvent {
return {
changeType: EditorCommon.EventType.ModelContentChangedFlush,
isRedoing: false,
isUndoing: false,
versionId: 0,
detail: detail
};
}
function contentChangedLinesDeletedEvent(fromLineNumber: number, toLineNumber: number): EditorCommon.IModelContentChangedLinesDeletedEvent {
return {
changeType: EditorCommon.EventType.ModelContentChangedLinesDeleted,
isRedoing: false,
isUndoing: false,
versionId: 0,
fromLineNumber: fromLineNumber,
toLineNumber: toLineNumber
};
}
function contentChangedLinesInsertedEvent(fromLineNumber: number, toLineNumber: number, detail: string): EditorCommon.IModelContentChangedLinesInsertedEvent {
return {
changeType: EditorCommon.EventType.ModelContentChangedLinesInserted,
isRedoing: false,
isUndoing: false,
versionId: 0,
fromLineNumber: fromLineNumber,
toLineNumber: toLineNumber,
detail: detail
};
}
function contentChangedLineChanged(lineNumber: number, detail: string): EditorCommon.IModelContentChangedLineChangedEvent {
return {
changeType: EditorCommon.EventType.ModelContentChangedLineChanged,
isRedoing: false,
isUndoing: false,
versionId: 0,
lineNumber: lineNumber,
detail: detail
};
}
function mirrorModelEvents(contentChanged:EditorCommon.IModelContentChangedEvent[]): MirrorModel.IMirrorModelEvents {
return {
propertiesChanged: null,
contentChanged: contentChanged
};
}
suite('Editor Model - MirrorModel', () => {
var mirrorModel:MirrorModel.MirrorModel;
@@ -46,19 +97,16 @@ suite('Editor Model - MirrorModel', () => {
assert.equal(mirrorModel.getLineStart(2), 6);
assert.equal(mirrorModel.getLineStart(3), 12);
var e = {
type: EditorCommon.EventType.ModelContentChanged,
changeType: EditorCommon.EventType.ModelContentChangedFlush,
detail: {
lines: [
'foo',
'bar'
],
BOM: '',
EOL: '\n'
}
};
mirrorModel.onEvents([e]);
mirrorModel.onEvents(mirrorModelEvents([contentChangedFlushEvent({
length: -1,
lines: [
'foo',
'bar'
],
BOM: '',
EOL: '\n'
})]));
assert.equal(mirrorModel.getLineStart(1), 0);
assert.equal(mirrorModel.getLineStart(2), 4);
});
@@ -170,14 +218,9 @@ suite('Editor Model - MirrorModel Eventing', () => {
});
test('delete single line', () => {
var e = {
type: EditorCommon.EventType.ModelContentChanged,
changeType: EditorCommon.EventType.ModelContentChangedLinesDeleted,
fromLineNumber: 3,
toLineNumber: 3
};
assert.equal(mirrorModel.getLineContent(3), 'line three');
mirrorModel.onEvents([e]);
mirrorModel.onEvents(mirrorModelEvents([contentChangedLinesDeletedEvent(3, 3)]));
assert.equal(mirrorModel.getLineContent(3), 'line four');
var words = mirrorModel.getAllWords();
@@ -191,13 +234,8 @@ suite('Editor Model - MirrorModel Eventing', () => {
});
test('delete multiple lines', () => {
var e = {
type: EditorCommon.EventType.ModelContentChanged,
changeType: EditorCommon.EventType.ModelContentChangedLinesDeleted,
fromLineNumber: 1,
toLineNumber: 2
};
mirrorModel.onEvents([e]);
mirrorModel.onEvents(mirrorModelEvents([contentChangedLinesDeletedEvent(1, 2)]));
assert.equal(mirrorModel.getLineContent(1), 'line three');
assert.equal(mirrorModel.getLineContent(2), 'line four');
var words = mirrorModel.getAllWords();
@@ -220,13 +258,8 @@ suite('Editor Model - MirrorModel Eventing', () => {
});
test('delete all lines', () => {
var e = {
type: EditorCommon.EventType.ModelContentChanged,
changeType: EditorCommon.EventType.ModelContentChangedLinesDeleted,
fromLineNumber: 1,
toLineNumber: 4
};
mirrorModel.onEvents([e]);
mirrorModel.onEvents(mirrorModelEvents([contentChangedLinesDeletedEvent(1, 4)]));
var words = mirrorModel.getAllWords();
assert.equal(words.length, 0);
var wordsWithRanges = mirrorModel.getAllWordsWithRange();
@@ -234,16 +267,10 @@ suite('Editor Model - MirrorModel Eventing', () => {
});
test('add single lines', () => {
var e = {
type: EditorCommon.EventType.ModelContentChanged,
changeType: EditorCommon.EventType.ModelContentChangedLinesInserted,
fromLineNumber: 1,
toLineNumber: 1,
detail: 'foo bar\nbar foo'
};
assert.equal(mirrorModel.getLineContent(1), 'line one');
mirrorModel.onEvents([e]);
mirrorModel.onEvents(mirrorModelEvents([contentChangedLinesInsertedEvent(1, 1, 'foo bar\nbar foo')]));
assert.equal(mirrorModel.getLineContent(1), 'foo bar');
assert.equal(mirrorModel.getLineContent(2), 'line one');
@@ -257,16 +284,10 @@ suite('Editor Model - MirrorModel Eventing', () => {
test('add multiple lines', () => {
var e = {
type: EditorCommon.EventType.ModelContentChanged,
changeType: EditorCommon.EventType.ModelContentChangedLinesInserted,
fromLineNumber: 1,
toLineNumber: 2,
detail: 'foo bar\nbar foo'
};
assert.equal(mirrorModel.getLineContent(1), 'line one');
mirrorModel.onEvents([e]);
mirrorModel.onEvents(mirrorModelEvents([contentChangedLinesInsertedEvent(1, 2, 'foo bar\nbar foo')]));
assert.equal(mirrorModel.getLineContent(1), 'foo bar');
assert.equal(mirrorModel.getLineContent(2), 'bar foo');
assert.equal(mirrorModel.getLineContent(3), 'line one');
@@ -284,16 +305,11 @@ suite('Editor Model - MirrorModel Eventing', () => {
});
test('change line', () => {
var e = {
type: EditorCommon.EventType.ModelContentChanged,
changeType: EditorCommon.EventType.ModelContentChangedLineChanged,
lineNumber: 1,
detail: 'foobar'
};
assert.equal(mirrorModel.getLineContent(1), 'line one');
var wordsWithRanges = mirrorModel.getAllWordsWithRange();
assert.equal(wordsWithRanges.length, 8);
mirrorModel.onEvents([e]);
mirrorModel.onEvents(mirrorModelEvents([contentChangedLineChanged(1, 'foobar')]));
assert.equal(mirrorModel.getLineContent(1), 'foobar');
wordsWithRanges = mirrorModel.getAllWordsWithRange();
@@ -305,19 +321,17 @@ suite('Editor Model - MirrorModel Eventing', () => {
test('flush model', () => {
assert.equal(mirrorModel.getLineContent(1), 'line one');
assert.equal(mirrorModel.getLineContent(2), 'line two');
var e = {
type: EditorCommon.EventType.ModelContentChanged,
changeType: EditorCommon.EventType.ModelContentChangedFlush,
detail: {
lines: [
'foo',
'bar'
],
BOM: '',
EOL: '\n'
}
};
mirrorModel.onEvents([e]);
mirrorModel.onEvents(mirrorModelEvents([contentChangedFlushEvent({
length: -1,
lines: [
'foo',
'bar'
],
BOM: '',
EOL: '\n'
})]));
assert.equal(mirrorModel.getLineContent(1), 'foo');
assert.equal(mirrorModel.getLineContent(2), 'bar');
var wordsWithRanges = mirrorModel.getAllWordsWithRange();
@@ -84,4 +84,8 @@ class Mode implements modes.IMode {
public getId(): string {
return "testMode";
}
public toSimplifiedMode(): modes.IMode {
return this;
}
}
@@ -44,6 +44,10 @@ export class Mode implements modes.IMode {
public getId(): string {
return "testMode";
}
public toSimplifiedMode(): modes.IMode {
return this;
}
}
@@ -114,6 +118,10 @@ export class SwitchingMode implements modes.IMode {
return this._id;
}
public toSimplifiedMode(): modes.IMode {
return this;
}
public addSupportChangedListener(callback: (e: EditorCommon.IModeSupportChangedEvent) => void): IDisposable {
return EmptyDisposable;
}
+20 -4
View File
@@ -7,6 +7,25 @@
import modes = require('vs/editor/common/modes');
import {Arrays} from 'vs/editor/common/core/arrays';
class SimpleTokenTypeClassificationMode implements modes.IMode {
private _id:string;
public tokenTypeClassificationSupport: modes.ITokenTypeClassificationSupport;
constructor(id:string, tokenTypeClassificationSupport: modes.ITokenTypeClassificationSupport) {
this._id = id;
this.tokenTypeClassificationSupport = tokenTypeClassificationSupport;
}
public getId(): string {
return this._id;
}
public toSimplifiedMode(): modes.IMode {
return this;
}
}
export function createMockMode(id:string, wordRegExp:RegExp = null):modes.IMode {
var tokenTypeClassificationSupport: modes.ITokenTypeClassificationSupport;
if (wordRegExp) {
@@ -14,10 +33,7 @@ export function createMockMode(id:string, wordRegExp:RegExp = null):modes.IMode
getWordDefinition: () => wordRegExp
};
}
return {
getId: () => id,
tokenTypeClassificationSupport: tokenTypeClassificationSupport
};
return new SimpleTokenTypeClassificationMode(id, tokenTypeClassificationSupport);
}
export interface TokenText {
+19 -4
View File
@@ -87,11 +87,28 @@ export interface IOnEnterAsserter {
indentsOutdents(oneLineAboveText:string, beforeText:string, afterText:string): void;
}
class SimpleMode implements modes.IMode {
private _id:string;
constructor(id:string) {
this._id = id;
}
public getId(): string {
return this._id;
}
public toSimplifiedMode(): modes.IMode {
return this;
}
}
export function createOnEnterAsserter(modeId:string, onEnterSupport: modes.IOnEnterSupport): IOnEnterAsserter {
var assertOne = (oneLineAboveText:string, beforeText:string, afterText:string, expected: modes.IndentAction) => {
var model = new Model(
[ oneLineAboveText, beforeText + afterText ].join('\n'),
{ getId: () => modeId }
new SimpleMode(modeId)
);
var actual = onEnterSupport.onEnter(model, { lineNumber: 2, column: beforeText.length + 1 });
if (expected === modes.IndentAction.None) {
@@ -130,9 +147,7 @@ export function executeMonarchTokenizationTests(name:string, language:monarchTyp
var modeService = servicesUtil.createMockModeService();
var tokenizationSupport = monarchLexer.createTokenizationSupport(modeService, {
getId: () => 'mock.mode'
}, lexer);
var tokenizationSupport = monarchLexer.createTokenizationSupport(modeService, new SimpleMode('mock.mode'), lexer);
executeTests(tokenizationSupport, tests);
}
+4
View File
@@ -76,6 +76,10 @@ export class TestingMode implements modes.IMode {
public getId():string {
return 'testing';
}
public toSimplifiedMode(): modes.IMode {
return this;
}
}
export class AbstractIndentingMode extends TestingMode {
@@ -62,7 +62,6 @@ function createModelService(): IModelService {
onModelAdded: undefined,
onModelRemoved: undefined,
onModelModeChanged: undefined,
removeModel: undefined,
destroyModel: undefined
}
}