提交 443a902d 编写于 作者: J Johannes Rieken

introduce the concept fo ContentProvider to the ResourceEditorInput

Instead of just letting the API layer know about content provider we must push it lower because only than we can handle uri's opaque.
上级 1b409e30
......@@ -24,8 +24,8 @@ import * as vscode from 'vscode';
import {WordHelper} from 'vs/editor/common/model/textModelWithTokensHelpers';
import {IFileService} from 'vs/platform/files/common/files';
import {IUntitledEditorService} from 'vs/workbench/services/untitled/common/untitledEditorService';
import {ResourceEditorInput} from 'vs/workbench/common/editor/resourceEditorInput';
import {asWinJsPromise} from 'vs/base/common/async';
import {IModeService} from 'vs/editor/common/services/modeService';
import * as weak from 'weak';
export interface IModelAddedData {
......@@ -134,6 +134,7 @@ export class ExtHostModelService {
throw new Error(`scheme '${scheme}' already registered`);
}
this._documentContentProviders[scheme] = provider;
this._proxy.$registerTextContentProvider(scheme);
let subscription: IDisposable;
if (typeof provider.onDidChange === 'function') {
......@@ -146,6 +147,7 @@ export class ExtHostModelService {
});
}
return new Disposable(() => {
this._proxy.$unregisterTextContentProvider(scheme);
delete this._documentContentProviders[scheme];
if (subscription) {
subscription.dispose();
......@@ -166,8 +168,14 @@ export class ExtHostModelService {
});
}
$isDocumentReferenced(uri: URI): TPromise<boolean> {
return TPromise.as(this.getDocumentData(uri).isDocumentReferenced);
$getUnferencedDocuments(): TPromise<URI[]> {
const result: URI[] = [];
for (let key in this._documentData) {
if (!this._documentData[key].isDocumentReferenced) {
result.push(URI.parse(key));
}
}
return TPromise.as(result);
}
public _acceptModelAdd(initData: IModelAddedData): void {
......@@ -449,7 +457,6 @@ export class ExtHostDocumentData extends MirrorModel2 {
@Remotable.MainContext('MainThreadDocuments')
export class MainThreadDocuments {
private _modelService: IModelService;
private _modeService: IModeService;
private _textFileService: ITextFileService;
private _editorService: IWorkbenchEditorService;
private _fileService: IFileService;
......@@ -458,11 +465,11 @@ export class MainThreadDocuments {
private _modelToDisposeMap: { [modelUrl: string]: IDisposable; };
private _proxy: ExtHostModelService;
private _modelIsSynced: { [modelId: string]: boolean; };
private _resourceContentProvider: { [scheme: string]: IDisposable };
constructor(
@IThreadService threadService: IThreadService,
@IModelService modelService: IModelService,
@IModeService modeService: IModeService,
@IEventService eventService: IEventService,
@ITextFileService textFileService: ITextFileService,
@IWorkbenchEditorService editorService: IWorkbenchEditorService,
......@@ -470,7 +477,6 @@ export class MainThreadDocuments {
@IUntitledEditorService untitledEditorService: IUntitledEditorService
) {
this._modelService = modelService;
this._modeService = modeService;
this._textFileService = textFileService;
this._editorService = editorService;
this._fileService = fileService;
......@@ -493,7 +499,11 @@ export class MainThreadDocuments {
this._proxy._acceptModelDirty(e.getAfter().resource);
}));
const handle = setInterval(() => this._runDocumentCleanup(), 30 * 1000);
this._toDispose.push({ dispose() { clearInterval(handle) } });
this._modelToDisposeMap = Object.create(null);
this._resourceContentProvider = Object.create(null);
}
public dispose(): void {
......@@ -571,14 +581,12 @@ export class MainThreadDocuments {
let promise: TPromise<boolean>;
switch (uri.scheme) {
case 'file':
promise = this._handleFileScheme(uri);
break;
case 'untitled':
promise = this._handleUnititledScheme(uri);
break;
case 'file':
default:
promise = this._handleAnyScheme(uri);
promise = this._handleAsResourceInput(uri);
break;
}
......@@ -591,7 +599,7 @@ export class MainThreadDocuments {
});
}
private _handleFileScheme(uri: URI): TPromise<boolean> {
private _handleAsResourceInput(uri: URI): TPromise<boolean> {
return this._editorService.resolveEditorModel({ resource: uri }).then(model => {
return !!model;
});
......@@ -617,46 +625,46 @@ export class MainThreadDocuments {
// --- virtual document logic
private _handleAnyScheme(uri: URI): TPromise<boolean> {
$registerTextContentProvider(scheme: string): void {
this._resourceContentProvider[scheme] = ResourceEditorInput.registerResourceContentProvider(scheme, {
provideTextContent: (uri: URI): TPromise<string> => {
return this._proxy.$provideTextDocumentContent(uri);
}
});
}
if (this._modelService.getModel(uri)) {
return TPromise.as(true);
$unregisterTextContentProvider(scheme: string): void {
const registration = this._resourceContentProvider[scheme];
if (registration) {
registration.dispose();
}
}
return this._proxy.$provideTextDocumentContent(uri).then(value => {
$onVirtualDocumentChange(uri: URI, value: string): void {
const model = this._modelService.getModel(uri);
if (model) {
model.setValue(value);
}
}
// create document from string
const firstLineText = value.substr(0, 1 + value.search(/\r?\n/));
const mode = this._modeService.getOrCreateModeByFilenameOrFirstLine(uri.fsPath, firstLineText);
const model = this._modelService.createModel(value, mode, uri);
private _runDocumentCleanup(): void {
this._proxy.$getUnferencedDocuments().then(resources => {
// if neither the extension host nor an editor reference this
// document anymore we destroy the model to reclaim memory
const handle = setInterval(() => {
this._editorService.inputToType({ resource: uri }).then(input => {
const toBeDisposed: URI[] = [];
const promises = resources.map(resource => {
return this._editorService.inputToType({ resource }).then(input => {
if (!this._editorService.isVisible(input, true)) {
return this._proxy.$isDocumentReferenced(uri).then(referenced => {
if (!referenced) {
clearInterval(handle);
this._modelService.destroyModel(uri);
toBeDisposed.push(resource);
}
});
}
}, onUnexpectedError);
}, 30 * 1000);
return model;
}).then(() => {
return true;
});
}
$onVirtualDocumentChange(uri: URI, value: string): TPromise<any> {
const model = this._modelService.getModel(uri);
if (model) {
model.setValue(value);
return;
return TPromise.join(promises).then(() => {
for (let resource of toBeDisposed) {
this._modelService.destroyModel(resource);
}
});
}, onUnexpectedError);
}
}
......@@ -7,10 +7,21 @@
import {TPromise} from 'vs/base/common/winjs.base';
import {EditorModel, EditorInput} from 'vs/workbench/common/editor';
import {ResourceEditorModel} from 'vs/workbench/common/editor/resourceEditorModel';
import {IModel} from 'vs/editor/common/editorCommon';
import URI from 'vs/base/common/uri';
import {EventType} from 'vs/base/common/events';
import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation';
import {IModelService} from 'vs/editor/common/services/modelService';
import {IModeService} from 'vs/editor/common/services/modeService';
import {IDisposable} from 'vs/base/common/lifecycle';
/**
*
*/
export interface IResourceEditorContentProvider {
provideTextContent(resource: URI): TPromise<string>;
// onDidChange
}
/**
* A read-only text editor input whos contents are made of the provided resource that points to an existing
......@@ -18,6 +29,57 @@ import {IModelService} from 'vs/editor/common/services/modelService';
*/
export class ResourceEditorInput extends EditorInput {
// --- registry logic
// todo@joh,ben this should maybe be a service that is in charge of loading/resolving a uri from a scheme
private static loadingModels: { [uri: string]: TPromise<IModel> } = Object.create(null);
private static registry: { [scheme: string]: IResourceEditorContentProvider } = Object.create(null);
public static registerResourceContentProvider(scheme: string, provider: IResourceEditorContentProvider): IDisposable {
ResourceEditorInput.registry[scheme] = provider;
return { dispose() { delete ResourceEditorInput.registry[scheme] } };
}
private static getOrCreateModel(modelService: IModelService, modeService: IModeService, resource: URI): TPromise<IModel> {
const model = modelService.getModel(resource);
if (model) {
return TPromise.as(model);
}
let loadingModel = ResourceEditorInput.loadingModels[resource.toString()];
if (!loadingModel) {
// make sure we have a provider this scheme
// the resource uses
const provider = ResourceEditorInput.registry[resource.scheme];
if (!provider) {
return TPromise.wrapError(`No model with uri '${resource}' nor a resolver for the scheme '${resource.scheme}'.`);
}
// load the model-content from the provider and cache
// the loading such that we don't create the same model
// twice
ResourceEditorInput.loadingModels[resource.toString()] = loadingModel = new TPromise<IModel>((resolve, reject) => {
provider.provideTextContent(resource).then(value => {
const firstLineText = value.substr(0, 1 + value.search(/\r?\n/));
const mode = modeService.getOrCreateModeByFilenameOrFirstLine(resource.fsPath, firstLineText);
return modelService.createModel(value, mode, resource);
}).then(resolve, reject);
}, function() {
// no cancellation when caching promises
});
// remove the cached promise 'cos the model is now
// known to the model service (see above)
loadingModel.then(() => delete ResourceEditorInput.loadingModels[resource.toString()], () => delete ResourceEditorInput.loadingModels[resource.toString()]);
}
return loadingModel;
}
public static ID: string = 'workbench.editors.resourceEditorInput';
protected cachedModel: ResourceEditorModel;
......@@ -31,6 +93,7 @@ export class ResourceEditorInput extends EditorInput {
description: string,
resource: URI,
@IModelService protected modelService: IModelService,
@IModeService protected modeService: IModeService,
@IInstantiationService protected instantiationService: IInstantiationService
) {
super();
......@@ -60,6 +123,7 @@ export class ResourceEditorInput extends EditorInput {
}
// Otherwise Create Model and handle dispose event
return ResourceEditorInput.getOrCreateModel(this.modelService, this.modeService, this.resource).then(() => {
let model = this.instantiationService.createInstance(ResourceEditorModel, this.resource);
const unbind = model.addListener(EventType.DISPOSE, () => {
this.cachedModel = null; // make sure we do not dispose model again
......@@ -73,6 +137,7 @@ export class ResourceEditorInput extends EditorInput {
return this.cachedModel;
});
});
}
public matches(otherInput: any): boolean {
......
......@@ -151,14 +151,14 @@ export class FileOnDiskEditorInput extends ResourceEditorInput {
name: string,
description: string,
@IModelService modelService: IModelService,
@IModeService private modeService: IModeService,
@IModeService modeService: IModeService,
@IInstantiationService instantiationService: IInstantiationService,
@IFileService private fileService: IFileService
) {
// We create a new resource URI here that is different from the file resource because we represent the state of
// the file as it is on disk and not as it is (potentially cached) in Code. That allows us to have a different
// model for the left-hand comparision compared to the conflicting one in Code to the right.
super(name, description, URI.create('disk', null, fileResource.fsPath), modelService, instantiationService);
super(name, description, URI.create('disk', null, fileResource.fsPath), modelService, modeService, instantiationService);
this.fileResource = fileResource;
this.mime = mime;
......
......@@ -9,6 +9,7 @@ import URI from 'vs/base/common/uri';
import network = require('vs/base/common/network');
import {guessMimeTypes} from 'vs/base/common/mime';
import {Registry} from 'vs/platform/platform';
import {basename, dirname} from 'vs/base/common/paths';
import types = require('vs/base/common/types');
import {IDiffEditor, ICodeEditor} from 'vs/editor/browser/editorBrowser';
import {ICommonCodeEditor, IModel, EditorType, IEditor as ICommonEditor} from 'vs/editor/common/editorCommon';
......@@ -289,8 +290,12 @@ export class WorkbenchEditorService implements IWorkbenchEditorService {
return this.createFileInput(resourceInput.resource, resourceInput.mime);
}
// Treat an URI as ResourceEditorInput
else if (URI.isURI(resourceInput.resource)) {
return TPromise.as(this.instantiationService.createInstance(ResourceEditorInput, resourceInput.resource.fsPath, undefined, resourceInput.resource));
return TPromise.as(this.instantiationService.createInstance(ResourceEditorInput,
basename(resourceInput.resource.fsPath),
dirname(resourceInput.resource.fsPath),
resourceInput.resource));
}
return TPromise.as<EditorInput>(null);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册