提交 bf7a630d 编写于 作者: B Benjamin Pasero

file model manager: loadOrCreate()

上级 195c04b1
......@@ -17,7 +17,6 @@ import {IEditorRegistry, Extensions, EditorModel, EncodingMode, ConfirmResult, I
import {BinaryEditorModel} from 'vs/workbench/common/editor/binaryEditorModel';
import {IFileOperationResult, FileOperationResult} from 'vs/platform/files/common/files';
import {ITextFileService, BINARY_FILE_EDITOR_ID, FILE_EDITOR_INPUT_ID, FileEditorInput as CommonFileEditorInput, AutoSaveMode, ModelState, EventType as FileEventType, TextFileChangeEvent, IFileEditorDescriptor} from 'vs/workbench/parts/files/common/files';
import {TextFileEditorModel} from 'vs/workbench/parts/files/common/editors/textFileEditorModel';
import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace';
import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation';
import {IDisposable, dispose} from 'vs/base/common/lifecycle';
......@@ -31,9 +30,6 @@ export class FileEditorInput extends CommonFileEditorInput {
// Do ref counting for all inputs that resolved to a model to be able to dispose when count = 0
private static FILE_EDITOR_MODEL_CLIENTS: { [resource: string]: FileEditorInput[]; } = Object.create(null);
// Keep promises that load a file editor model to avoid loading the same model twice
private static FILE_EDITOR_MODEL_LOADERS: { [resource: string]: TPromise<EditorModel>; } = Object.create(null);
private resource: URI;
private mime: string;
private preferredEncoding: string;
......@@ -225,7 +221,6 @@ export class FileEditorInput extends CommonFileEditorInput {
}
public resolve(refresh?: boolean): TPromise<EditorModel> {
let modelPromise: TPromise<EditorModel>;
const resource = this.resource.toString();
// Keep clients who resolved the input to support proper disposal
......@@ -236,39 +231,14 @@ export class FileEditorInput extends CommonFileEditorInput {
FileEditorInput.FILE_EDITOR_MODEL_CLIENTS[resource].push(this);
}
// Check for running loader to ensure the model is only ever loaded once
if (FileEditorInput.FILE_EDITOR_MODEL_LOADERS[resource]) {
return FileEditorInput.FILE_EDITOR_MODEL_LOADERS[resource];
}
// Use Cached Model if present
const cachedModel = this.textFileService.models.get(this.resource);
if (cachedModel instanceof TextFileEditorModel && !refresh) {
modelPromise = TPromise.as<EditorModel>(cachedModel);
}
// Refresh Cached Model if present
else if (cachedModel && refresh) {
modelPromise = cachedModel.load();
FileEditorInput.FILE_EDITOR_MODEL_LOADERS[resource] = modelPromise;
}
// Otherwise Create Model and Load
else {
modelPromise = this.createAndLoadModel();
FileEditorInput.FILE_EDITOR_MODEL_LOADERS[resource] = modelPromise;
}
return this.textFileService.models.loadOrCreate(this.resource, this.preferredEncoding, refresh).then(null, error => {
return modelPromise.then((resolvedModel: TextFileEditorModel | BinaryEditorModel) => {
if (resolvedModel instanceof TextFileEditorModel) {
this.textFileService.models.add(this.resource, resolvedModel); // Store into the text model cache unless this file is binary
// In case of an error that indicates that the file is binary or too large, just return with the binary editor model
if ((<IFileOperationResult>error).fileOperationResult === FileOperationResult.FILE_IS_BINARY || (<IFileOperationResult>error).fileOperationResult === FileOperationResult.FILE_TOO_LARGE) {
return this.instantiationService.createInstance(BinaryEditorModel, this.resource, this.getName()).load();
}
FileEditorInput.FILE_EDITOR_MODEL_LOADERS[resource] = null; // Remove from pending loaders
return resolvedModel;
}, (error) => {
FileEditorInput.FILE_EDITOR_MODEL_LOADERS[resource] = null; // Remove from pending loaders in case of an error
// Bubble any other error up
return TPromise.wrapError(error);
});
}
......@@ -287,28 +257,6 @@ export class FileEditorInput extends CommonFileEditorInput {
return -1;
}
private createAndLoadModel(): TPromise<EditorModel> {
const descriptor = (<IEditorRegistry>Registry.as(Extensions.Editors)).getEditor(this);
if (!descriptor) {
throw new Error('Unable to find an editor in the registry for this input.');
}
// Optimistically create a text model assuming that the file is not binary
const textModel = this.instantiationService.createInstance(TextFileEditorModel, this.resource, this.preferredEncoding);
return textModel.load().then(() => textModel, (error) => {
// In case of an error that indicates that the file is binary or too large, just return with the binary editor model
if ((<IFileOperationResult>error).fileOperationResult === FileOperationResult.FILE_IS_BINARY || (<IFileOperationResult>error).fileOperationResult === FileOperationResult.FILE_TOO_LARGE) {
textModel.dispose();
return this.instantiationService.createInstance(BinaryEditorModel, this.resource, this.getName()).load();
}
// Bubble any other error up
return TPromise.wrapError(error);
});
}
public dispose(): void {
// Listeners
......
......@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import {TPromise} from 'vs/base/common/winjs.base';
import URI from 'vs/base/common/uri';
import {TextFileEditorModel} from 'vs/workbench/parts/files/common/editors/textFileEditorModel';
import {ITextFileEditorModelManager} from 'vs/workbench/parts/files/common/files';
......@@ -12,6 +13,7 @@ import {IEditorGroupService} from 'vs/workbench/services/group/common/groupServi
import {ModelState, ITextFileEditorModel, LocalFileChangeEvent} from 'vs/workbench/parts/files/common/files';
import {ILifecycleService} from 'vs/platform/lifecycle/common/lifecycle';
import {IEventService} from 'vs/platform/event/common/event';
import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation';
import {FileChangesEvent, EventType as CommonFileEventType} from 'vs/platform/files/common/files';
export class TextFileEditorModelManager implements ITextFileEditorModelManager {
......@@ -24,16 +26,19 @@ export class TextFileEditorModelManager implements ITextFileEditorModelManager {
private mapResourceToDisposeListener: { [resource: string]: IDisposable; };
private mapResourcePathToModel: { [resource: string]: TextFileEditorModel; };
private mapResourceToPendingModelLoaders: { [resource: string]: TPromise<TextFileEditorModel>};
constructor(
@ILifecycleService private lifecycleService: ILifecycleService,
@IEventService private eventService: IEventService,
@IInstantiationService private instantiationService: IInstantiationService,
@IEditorGroupService private editorGroupService: IEditorGroupService
) {
this.toUnbind = [];
this.mapResourcePathToModel = Object.create(null);
this.mapResourceToDisposeListener = Object.create(null);
this.mapResourceToPendingModelLoaders = Object.create(null);
this.registerListeners();
}
......@@ -51,6 +56,17 @@ export class TextFileEditorModelManager implements ITextFileEditorModelManager {
this.lifecycleService.onShutdown(this.dispose, this);
}
private onEditorsChanged(): void {
this.disposeUnusedModels();
}
private disposeModelIfPossible(resource: URI): void {
const model = this.get(resource);
if (this.canDispose(model)) {
model.dispose();
}
}
private onLocalFileChange(e: LocalFileChangeEvent): void {
if (e.gotMoved() || e.gotDeleted()) {
this.disposeModelIfPossible(e.getBefore().resource); // dispose models of moved or deleted files
......@@ -82,17 +98,6 @@ export class TextFileEditorModelManager implements ITextFileEditorModelManager {
.forEach(model => this.disposeModelIfPossible(model.getResource()));
}
private onEditorsChanged(): void {
this.disposeUnusedModels();
}
private disposeModelIfPossible(resource: URI): void {
const model = this.get(resource);
if (this.canDispose(model)) {
model.dispose();
}
}
private canDispose(textModel: ITextFileEditorModel): boolean {
if (!textModel) {
return false; // we need data!
......@@ -117,6 +122,56 @@ export class TextFileEditorModelManager implements ITextFileEditorModelManager {
return this.mapResourcePathToModel[resource.toString()];
}
public loadOrCreate(resource: URI, encoding: string, refresh?: boolean): TPromise<TextFileEditorModel> {
// Return early if model is currently being loaded
const pendingLoad = this.mapResourceToPendingModelLoaders[resource.toString()];
if (pendingLoad) {
return pendingLoad;
}
let modelPromise: TPromise<TextFileEditorModel>;
// Model exists
let model = this.get(resource);
if (model) {
if (!refresh) {
modelPromise = TPromise.as(model);
} else {
modelPromise = model.load();
}
}
// Model does not exist
else {
model = this.instantiationService.createInstance(TextFileEditorModel, resource, encoding);
modelPromise = model.load();
}
// Store pending loads to avoid race conditions
this.mapResourceToPendingModelLoaders[resource.toString()] = modelPromise;
return modelPromise.then(model => {
// Make known to manager (if not already known)
this.add(resource, model);
// Remove from pending loads
this.mapResourceToPendingModelLoaders[resource.toString()] = null;
return model;
}, error => {
// Free resources of this invalid model
model.dispose();
// Remove from pending loads
this.mapResourceToPendingModelLoaders[resource.toString()] = null;
return TPromise.wrapError(error);
});
}
public getAll(resource?: URI): TextFileEditorModel[] {
return Object.keys(this.mapResourcePathToModel)
.filter(r => !resource || resource.toString() === r)
......
......@@ -281,7 +281,7 @@ export interface ITextFileEditorModelManager {
getAll(resource?: URI): ITextFileEditorModel[];
add(resource: URI, model: ITextFileEditorModel): void;
loadOrCreate(resource: URI, preferredEncoding: string, refresh?: boolean): TPromise<ITextEditorModel>;
}
export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport {
......
......@@ -54,7 +54,7 @@ suite('Files - TextFileEditorModelManager', () => {
});
test('add, remove, clear, get, getAll', function () {
const manager = instantiationService.createInstance(TextFileEditorModelManager);
const manager: TextFileEditorModelManager = instantiationService.createInstance(TextFileEditorModelManager);
const model1 = new EditorModel();
const model2 = new EditorModel();
......@@ -94,8 +94,33 @@ suite('Files - TextFileEditorModelManager', () => {
assert.strictEqual(0, result.length);
});
test('loadOrCreate', function (done) {
const manager: TextFileEditorModelManager = instantiationService.createInstance(TextFileEditorModelManager);
const resource = URI.file('/test.html');
const encoding = 'utf8';
manager.loadOrCreate(resource, encoding, true).then(model => {
assert.ok(model);
assert.equal(model.getEncoding(), encoding);
assert.equal(manager.get(resource), model);
return manager.loadOrCreate(resource, encoding).then(model2 => {
assert.equal(model2, model);
model.dispose();
return manager.loadOrCreate(resource, encoding).then(model3 => {
assert.notEqual(model3, model2);
assert.equal(manager.get(resource), model3);
done();
});
});
});
});
test('removed from cache when model disposed', function () {
const manager = instantiationService.createInstance(TextFileEditorModelManager);
const manager: TextFileEditorModelManager = instantiationService.createInstance(TextFileEditorModelManager);
const model1 = new EditorModel();
const model2 = new EditorModel();
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册