Clean up some editor input debt and lifecycle issues (#24439)

This commit is contained in:
Benjamin Pasero 2017-04-10 19:37:42 +02:00 committed by GitHub
parent ec89a599b3
commit 5ecf0cda9a
33 changed files with 788 additions and 562 deletions

View file

@ -128,12 +128,18 @@ suite('editor tests', () => {
});
return Promise.all([
commands.executeCommand('workbench.action.closeAllEditors'),
delay(800).then(() => commands.executeCommand('workbench.action.closeAllEditors')), // TODO@Ben TODO@Joh this delay is a hack
p
]).then(() => undefined);
});
});
function delay(time) {
return new Promise(function (fulfill) {
setTimeout(fulfill, time);
});
}
test('issue #20867: vscode.window.visibleTextEditors returns closed document 2/2', () => {
const file10Path = join(workspace.rootPath || '', './10linefile.ts');
@ -165,8 +171,11 @@ suite('editor tests', () => {
// hide doesn't what it means because it triggers a close event and because it
// detached the editor. For this test that's what we want.
editors[0].hide();
return p;
delay(800).then(() => { // TODO@Ben TODO@Joh this delay is a hack
editors[0].hide();
return p;
});
});
});

View file

@ -60,7 +60,7 @@ suite('workspace-namespace', () => {
test('openTextDocument', () => {
let len = workspace.textDocuments.length;
return workspace.openTextDocument(join(workspace.rootPath || '', './far.js')).then(doc => {
return workspace.openTextDocument(join(workspace.rootPath || '', './simple.txt')).then(doc => {
assert.ok(doc);
assert.equal(workspace.textDocuments.length, len + 1);
});

View file

@ -0,0 +1 @@
Just a simple file...

View file

@ -69,6 +69,29 @@ export interface IResourceInput extends IBaseResourceInput {
encoding?: string;
}
export interface IUntitledResourceInput extends IBaseResourceInput {
/**
* Optional resource. If the resource is not provided a new untitled file is created.
*/
resource?: URI;
/**
* Optional file path. Using the file resource will associate the file to the untitled resource.
*/
filePath?: string;
/**
* Optional language of the untitled resource.
*/
language?: string;
/**
* Optional contents of the untitled resource.
*/
contents?: string;
}
export interface IResourceDiffInput extends IBaseResourceInput {
/**

View file

@ -11,7 +11,7 @@ import types = require('vs/base/common/types');
import { Builder } from 'vs/base/browser/builder';
import { Registry } from 'vs/platform/platform';
import { Panel } from 'vs/workbench/browser/panel';
import { EditorInput, IFileEditorInput, EditorOptions, IEditorDescriptor, IEditorInputFactory, IEditorRegistry, Extensions } from 'vs/workbench/common/editor';
import { EditorInput, EditorOptions, IEditorDescriptor, IEditorInputFactory, IEditorRegistry, Extensions, IFileInputFactory } from 'vs/workbench/common/editor';
import { IEditor, Position, POSITIONS } from 'vs/platform/editor/common/editor';
import { IInstantiationService, IConstructorSignature0 } from 'vs/platform/instantiation/common/instantiation';
import { SyncDescriptor, AsyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
@ -160,7 +160,7 @@ const INPUT_DESCRIPTORS_PROPERTY = '__$inputDescriptors';
class EditorRegistry implements IEditorRegistry {
private editors: EditorDescriptor[];
private instantiationService: IInstantiationService;
private defaultFileInputDescriptor: AsyncDescriptor<IFileEditorInput>;
private fileInputFactory: IFileInputFactory;
private editorInputFactoryConstructors: { [editorInputId: string]: IConstructorSignature0<IEditorInputFactory> } = Object.create(null);
private editorInputFactoryInstances: { [editorInputId: string]: IEditorInputFactory } = Object.create(null);
@ -283,12 +283,12 @@ class EditorRegistry implements IEditorRegistry {
return inputClasses;
}
public registerDefaultFileInput(editorInputDescriptor: AsyncDescriptor<IFileEditorInput>): void {
this.defaultFileInputDescriptor = editorInputDescriptor;
public registerFileInputFactory(factory: IFileInputFactory): void {
this.fileInputFactory = factory;
}
public getDefaultFileInput(): AsyncDescriptor<IFileEditorInput> {
return this.defaultFileInputDescriptor;
public getFileInputFactory(): IFileInputFactory {
return this.fileInputFactory;
}
public registerEditorInputFactory(editorInputId: string, ctor: IConstructorSignature0<IEditorInputFactory>): void {

View file

@ -8,7 +8,6 @@ import { Registry } from 'vs/platform/platform';
import nls = require('vs/nls');
import URI from 'vs/base/common/uri';
import { Action, IAction } from 'vs/base/common/actions';
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
import { IEditorQuickOpenEntry, IQuickOpenRegistry, Extensions as QuickOpenExtensions, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen';
import { StatusbarItemDescriptor, StatusbarAlignment, IStatusbarRegistry, Extensions as StatusExtensions } from 'vs/workbench/browser/parts/statusbar/statusbar';
import { EditorDescriptor } from 'vs/workbench/browser/parts/editor/baseEditor';
@ -37,6 +36,7 @@ import {
NAVIGATE_IN_GROUP_TWO_PREFIX, ShowEditorsInGroupThreeAction, NAVIGATE_IN_GROUP_THREE_PREFIX, FocusLastEditorInStackAction, OpenNextRecentlyUsedEditorInGroupAction, MoveEditorToPreviousGroupAction, MoveEditorToNextGroupAction, MoveEditorLeftInGroupAction, ClearRecentFilesAction
} from 'vs/workbench/browser/parts/editor/editorActions';
import * as editorCommands from 'vs/workbench/browser/parts/editor/editorCommands';
import { IWorkbenchEditorService } from "vs/workbench/services/editor/common/editorService";
// Register String Editor
Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
@ -101,7 +101,6 @@ interface ISerializedUntitledEditorInput {
class UntitledEditorInputFactory implements IEditorInputFactory {
constructor(
@IUntitledEditorService private untitledEditorService: IUntitledEditorService,
@ITextFileService private textFileService: ITextFileService
) {
}
@ -127,10 +126,15 @@ class UntitledEditorInputFactory implements IEditorInputFactory {
return JSON.stringify(serialized);
}
public deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): EditorInput {
const deserialized: ISerializedUntitledEditorInput = JSON.parse(serializedEditorInput);
public deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): UntitledEditorInput {
return instantiationService.invokeFunction<UntitledEditorInput>(accessor => {
const deserialized: ISerializedUntitledEditorInput = JSON.parse(serializedEditorInput);
const resource = !!deserialized.resourceJSON ? URI.revive(deserialized.resourceJSON) : URI.parse(deserialized.resource);
const filePath = resource.scheme === 'file' ? resource.fsPath : void 0;
const language = deserialized.modeId;
return this.untitledEditorService.createOrGet(!!deserialized.resourceJSON ? URI.revive(deserialized.resourceJSON) : URI.parse(deserialized.resource), deserialized.modeId);
return accessor.get(IWorkbenchEditorService).createInput({ resource, filePath, language }) as UntitledEditorInput;
});
}
}

View file

@ -14,7 +14,7 @@ import { isMacintosh } from 'vs/base/common/platform';
import { MIME_BINARY } from 'vs/base/common/mime';
import { shorten } from 'vs/base/common/labels';
import { ActionRunner, IAction } from 'vs/base/common/actions';
import { Position, IEditorInput, Verbosity } from 'vs/platform/editor/common/editor';
import { Position, IEditorInput, Verbosity, IUntitledResourceInput } from 'vs/platform/editor/common/editor';
import { IEditorGroup, toResource } from 'vs/workbench/common/editor';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { KeyCode } from 'vs/base/common/keyCodes';
@ -23,7 +23,6 @@ import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
import { IMessageService } from 'vs/platform/message/common/message';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@ -66,7 +65,6 @@ export class TabsTitleControl extends TitleControl {
@IInstantiationService instantiationService: IInstantiationService,
@IWorkbenchEditorService editorService: IWorkbenchEditorService,
@IEditorGroupService editorGroupService: IEditorGroupService,
@IUntitledEditorService private untitledEditorService: IUntitledEditorService,
@IContextKeyService contextKeyService: IContextKeyService,
@IKeybindingService keybindingService: IKeybindingService,
@ITelemetryService telemetryService: ITelemetryService,
@ -143,7 +141,7 @@ export class TabsTitleControl extends TitleControl {
const group = this.context;
if (group) {
this.editorService.openEditor(this.untitledEditorService.createOrGet(), { pinned: true, index: group.count /* always at the end */ }).done(null, errors.onUnexpectedError); // untitled are always pinned
this.editorService.openEditor({ options: { pinned: true, index: group.count /* always at the end */ } } as IUntitledResourceInput).done(null, errors.onUnexpectedError); // untitled are always pinned
}
}
}));

View file

@ -14,7 +14,7 @@ import { IEditor, ICommonCodeEditor, IEditorViewState, IEditorOptions as ICodeEd
import { IEditorInput, IEditorModel, IEditorOptions, ITextEditorOptions, IBaseResourceInput, Position, Verbosity } from 'vs/platform/editor/common/editor';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
import { SyncDescriptor, AsyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { IInstantiationService, IConstructorSignature0 } from 'vs/platform/instantiation/common/instantiation';
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
@ -49,6 +49,10 @@ export const TEXT_DIFF_EDITOR_ID = 'workbench.editors.textDiffEditor';
*/
export const BINARY_DIFF_EDITOR_ID = 'workbench.editors.binaryResourceDiffEditor';
export interface IFileInputFactory {
createFileInput(resource: URI, encoding: string, instantiationService: IInstantiationService): IFileEditorInput;
}
export interface IEditorRegistry {
/**
@ -79,20 +83,14 @@ export interface IEditorRegistry {
getEditors(): IEditorDescriptor[];
/**
* Registers the default input to be used for files in the workbench.
*
* @param editorInputDescriptor a descriptor that resolves to an instance of EditorInput that
* should be used to handle file inputs.
* Registers the file input factory to use for file inputs.
*/
registerDefaultFileInput(editorInputDescriptor: AsyncDescriptor<IFileEditorInput>): void;
registerFileInputFactory(factory: IFileInputFactory): void;
/**
* Returns a descriptor of the default input to be used for files in the workbench.
*
* @return a descriptor that resolves to an instance of EditorInput that should be used to handle
* file inputs.
* Returns the file input factory to use for file inputs.
*/
getDefaultFileInput(): AsyncDescriptor<IFileEditorInput>;
getFileInputFactory(): IFileInputFactory;
/**
* Registers a editor input factory for the given editor input to the registry. An editor input factory
@ -329,11 +327,6 @@ export interface IFileEditorInput extends IEditorInput, IEncodingSupport {
*/
getResource(): URI;
/**
* Sets the absolute file resource URI this input is about.
*/
setResource(resource: URI): void;
/**
* Sets the preferred encodingt to use for this input.
*/
@ -851,7 +844,6 @@ export interface IEditorStacksModel {
next(jumpGroups: boolean, cycleAtEnd?: boolean): IEditorIdentifier;
previous(jumpGroups: boolean, cycleAtStart?: boolean): IEditorIdentifier;
isOpen(editor: IEditorInput): boolean;
isOpen(resource: URI): boolean;
toString(): string;
@ -869,8 +861,7 @@ export interface IEditorGroup {
getEditor(resource: URI): IEditorInput;
indexOf(editor: IEditorInput): number;
contains(editor: IEditorInput): boolean;
contains(resource: URI): boolean;
contains(editorOrResource: IEditorInput | URI): boolean;
getEditors(mru?: boolean): IEditorInput[];
isActive(editor: IEditorInput): boolean;

View file

@ -587,14 +587,12 @@ export class EditorGroup implements IEditorGroup {
return -1;
}
public contains(candidate: EditorInput): boolean;
public contains(resource: URI): boolean;
public contains(arg1: any): boolean {
if (arg1 instanceof EditorInput) {
return this.indexOf(arg1) >= 0;
public contains(editorOrResource: EditorInput | URI): boolean {
if (editorOrResource instanceof EditorInput) {
return this.indexOf(editorOrResource) >= 0;
}
const counter = this.mapResourceToEditorCount.get(arg1);
const counter = this.mapResourceToEditorCount.get(editorOrResource);
return typeof counter === 'number' && counter > 0;
}
@ -1174,32 +1172,28 @@ export class EditorStacksModel implements IEditorStacksModel {
private handleOnEditorClosed(event: GroupEvent): void {
const editor = event.editor;
const editorsToClose = [editor];
// Close the editor when it is no longer open in any group
if (!this.isOpen(editor)) {
editor.close();
// Also take care of side by side editor inputs that wrap around 2 editors
if (editor instanceof SideBySideEditorInput) {
[editor.master, editor.details].forEach(editor => {
if (!this.isOpen(editor)) {
editor.close();
}
});
}
// Include both sides of side by side editors when being closed and not opened multiple times
if (editor instanceof SideBySideEditorInput && !this.isOpen(editor)) {
editorsToClose.push(editor.master, editor.details);
}
// Close the editor when it is no longer open in any group including diff editors
editorsToClose.forEach(editorToClose => {
const resource = toResource(editorToClose); // prefer resource to not close right-hand side editors of a diff editor
if (!this.isOpen(resource || editorToClose)) {
editorToClose.close();
}
});
}
public isOpen(resource: URI): boolean;
public isOpen(editor: EditorInput): boolean;
public isOpen(arg1: any): boolean {
return this._groups.some(group => group.contains(arg1));
public isOpen(editorOrResource: URI | EditorInput): boolean {
return this._groups.some(group => group.contains(editorOrResource));
}
public count(resource: URI): number;
public count(editor: EditorInput): number;
public count(arg1: any): number {
return this._groups.filter(group => group.contains(arg1)).length;
public count(editor: EditorInput): number {
return this._groups.filter(group => group.contains(editor)).length;
}
private onShutdown(): void {

View file

@ -20,9 +20,8 @@ export class ResourceEditorInput extends EditorInput {
static ID: string = 'workbench.editors.resourceEditorInput';
protected promise: TPromise<IReference<ResourceEditorModel>>;
protected resource: URI;
private modelReference: TPromise<IReference<ResourceEditorModel>>;
private resource: URI;
private name: string;
private description: string;
@ -39,54 +38,54 @@ export class ResourceEditorInput extends EditorInput {
this.resource = resource;
}
getResource(): URI {
public getResource(): URI {
return this.resource;
}
getTypeId(): string {
public getTypeId(): string {
return ResourceEditorInput.ID;
}
getName(): string {
public getName(): string {
return this.name;
}
setName(name: string): void {
public setName(name: string): void {
if (this.name !== name) {
this.name = name;
this._onDidChangeLabel.fire();
}
}
getDescription(): string {
public getDescription(): string {
return this.description;
}
setDescription(description: string): void {
public setDescription(description: string): void {
if (this.description !== description) {
this.description = description;
this._onDidChangeLabel.fire();
}
}
getTelemetryDescriptor(): { [key: string]: any; } {
public getTelemetryDescriptor(): { [key: string]: any; } {
const descriptor = super.getTelemetryDescriptor();
descriptor['resource'] = telemetryURIDescriptor(this.resource);
return descriptor;
}
resolve(refresh?: boolean): TPromise<ITextEditorModel> {
if (!this.promise) {
this.promise = this.textModelResolverService.createModelReference(this.resource);
public resolve(refresh?: boolean): TPromise<ITextEditorModel> {
if (!this.modelReference) {
this.modelReference = this.textModelResolverService.createModelReference(this.resource);
}
return this.promise.then(ref => {
return this.modelReference.then(ref => {
const model = ref.object;
if (!(model instanceof ResourceEditorModel)) {
ref.dispose();
this.promise = null;
this.modelReference = null;
return TPromise.wrapError(`Unexpected model for ResourceInput: ${this.resource}`); // TODO@Ben eventually also files should be supported, but we guard due to the dangerous dispose of the model in dispose()
}
@ -94,7 +93,7 @@ export class ResourceEditorInput extends EditorInput {
});
}
matches(otherInput: any): boolean {
public matches(otherInput: any): boolean {
if (super.matches(otherInput) === true) {
return true;
}
@ -109,10 +108,10 @@ export class ResourceEditorInput extends EditorInput {
return false;
}
dispose(): void {
if (this.promise) {
this.promise.done(ref => ref.dispose());
this.promise = null;
public dispose(): void {
if (this.modelReference) {
this.modelReference.done(ref => ref.dispose());
this.modelReference = null;
}
super.dispose();

View file

@ -22,14 +22,13 @@ import { Builder, $ } from 'vs/base/browser/builder';
import { IPartService } from 'vs/workbench/services/part/common/partService';
import { AutoSaveConfiguration } from 'vs/platform/files/common/files';
import { toResource } from 'vs/workbench/common/editor';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IWorkbenchEditorService, IResourceInputType } from 'vs/workbench/services/editor/common/editorService';
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
import { IMessageService } from 'vs/platform/message/common/message';
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
import { IWindowsService, IWindowService, IWindowSettings } from 'vs/platform/windows/common/windows';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IWindowIPCService } from 'vs/workbench/services/window/electron-browser/windowService';
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IPath, IOpenFileRequest, IWindowConfiguration } from 'vs/workbench/electron-browser/common';
@ -44,7 +43,7 @@ import { ReloadWindowAction, ToggleDevToolsAction, ShowStartupPerformance, OpenR
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { Position, IResourceInput } from 'vs/platform/editor/common/editor';
import { Position, IResourceInput, IUntitledResourceInput } from 'vs/platform/editor/common/editor';
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
import { KeyboardMapperFactory } from 'vs/workbench/services/keybinding/electron-browser/keybindingService';
import { Themable, EDITOR_DRAG_AND_DROP_BACKGROUND } from 'vs/workbench/common/theme';
@ -91,8 +90,7 @@ export class ElectronWindow extends Themable {
@IViewletService private viewletService: IViewletService,
@IContextMenuService private contextMenuService: IContextMenuService,
@IKeybindingService private keybindingService: IKeybindingService,
@IEnvironmentService private environmentService: IEnvironmentService,
@IUntitledEditorService private untitledEditorService: IUntitledEditorService,
@IEnvironmentService private environmentService: IEnvironmentService
) {
super(themeService);
@ -384,7 +382,7 @@ export class ElectronWindow extends Themable {
}
private onOpenFiles(request: IOpenFileRequest): void {
let inputs: IResourceInput[] = [];
let inputs: IResourceInputType[] = [];
let diffMode = (request.filesToDiff.length === 2);
if (!diffMode && request.filesToOpen) {
@ -404,7 +402,7 @@ export class ElectronWindow extends Themable {
}
}
private openResources(resources: IResourceInput[], diffMode: boolean): TPromise<any> {
private openResources(resources: (IResourceInput | IUntitledResourceInput)[], diffMode: boolean): TPromise<any> {
return this.partService.joinCreation().then(() => {
// In diffMode we open 2 resources as diff
@ -428,14 +426,15 @@ export class ElectronWindow extends Themable {
});
}
private toInputs(paths: IPath[], isNew: boolean): IResourceInput[] {
private toInputs(paths: IPath[], isNew: boolean): IResourceInputType[] {
return paths.map(p => {
let input = <IResourceInput>{
resource: isNew ? this.untitledEditorService.createOrGet(URI.file(p.filePath)).getResource() : URI.file(p.filePath),
options: {
pinned: true
}
};
const resource = URI.file(p.filePath);
let input: IResourceInput | IUntitledResourceInput;
if (isNew) {
input = { filePath: resource.fsPath, options: { pinned: true } } as IUntitledResourceInput;
} else {
input = { resource, options: { pinned: true } } as IResourceInput;
}
if (!isNew && p.lineNumber) {
input.options.selection = {

View file

@ -23,7 +23,7 @@ import { toErrorMessage } from 'vs/base/common/errorMessage';
import { Registry } from 'vs/platform/platform';
import { isWindows, isLinux, isMacintosh } from 'vs/base/common/platform';
import { IOptions } from 'vs/workbench/common/options';
import { Position as EditorPosition, IResourceInput, IResourceDiffInput } from 'vs/platform/editor/common/editor';
import { Position as EditorPosition, IResourceDiffInput, IUntitledResourceInput } from 'vs/platform/editor/common/editor';
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
import { IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/common/editor';
@ -39,7 +39,6 @@ import { IActionBarRegistry, Extensions as ActionBarExtensions } from 'vs/workbe
import { PanelRegistry, Extensions as PanelExtensions } from 'vs/workbench/browser/panel';
import { QuickOpenController } from 'vs/workbench/browser/parts/quickopen/quickOpenController';
import { getServices } from 'vs/platform/instantiation/common/extensions';
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
import { WorkbenchEditorService } from 'vs/workbench/services/editor/browser/editorService';
import { Position, Parts, IPartService, ILayoutOptions } from 'vs/workbench/services/part/common/partService';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
@ -200,7 +199,6 @@ export class Workbench implements IPartService {
options: IOptions,
serviceCollection: ServiceCollection,
@IInstantiationService private instantiationService: IInstantiationService,
@IUntitledEditorService private untitledEditorService: IUntitledEditorService,
@IWorkspaceContextService private contextService: IWorkspaceContextService,
@IStorageService private storageService: IStorageService,
@ILifecycleService private lifecycleService: ILifecycleService,
@ -382,14 +380,14 @@ export class Workbench implements IPartService {
// Otherwise: Open/Create files
else {
const filesToCreateInputs: IResourceInput[] = filesToCreate.map(resourceInput => {
return <IResourceInput>{
resource: this.untitledEditorService.createOrGet(resourceInput.resource).getResource(),
const filesToCreateInputs: IUntitledResourceInput[] = filesToCreate.map(resourceInput => {
return <IUntitledResourceInput>{
filePath: resourceInput.resource.fsPath,
options: { pinned: true }
};
});
return TPromise.as(filesToOpen.concat(filesToCreateInputs));
return TPromise.as([].concat(filesToOpen).concat(filesToCreateInputs));
}
}
@ -400,7 +398,7 @@ export class Workbench implements IPartService {
return TPromise.as([]); // do not open any empty untitled file if we have backups to restore
}
return TPromise.as([<IResourceInput>{ resource: this.untitledEditorService.createOrGet().getResource() }]);
return TPromise.as([<IUntitledResourceInput>{}]);
});
}

View file

@ -14,9 +14,9 @@ import { IPartService } from 'vs/workbench/services/part/common/partService';
import errors = require('vs/base/common/errors');
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
import { ITextModelResolverService } from 'vs/editor/common/services/resolverService';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { Position, IResourceInput } from 'vs/platform/editor/common/editor';
import { Position, IResourceInput, IUntitledResourceInput } from 'vs/platform/editor/common/editor';
import { ITextFileService } from "vs/workbench/services/textfile/common/textfiles";
export class BackupRestorer implements IWorkbenchContribution {
@ -28,7 +28,7 @@ export class BackupRestorer implements IWorkbenchContribution {
@IPartService private partService: IPartService,
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
@IBackupFileService private backupFileService: IBackupFileService,
@ITextModelResolverService private textModelResolverService: ITextModelResolverService,
@ITextFileService private textFileService: ITextFileService,
@IEditorGroupService private groupService: IEditorGroupService
) {
this.restoreBackups();
@ -68,7 +68,7 @@ export class BackupRestorer implements IWorkbenchContribution {
backups.forEach(backup => {
if (stacks.isOpen(backup)) {
if (backup.scheme === 'file') {
restorePromises.push(this.textModelResolverService.createModelReference(backup).then(null, () => unresolved.push(backup)));
restorePromises.push(this.textFileService.models.loadOrCreate(backup).then(null, () => unresolved.push(backup)));
} else if (backup.scheme === 'untitled') {
restorePromises.push(this.untitledEditorService.get(backup).resolve().then(null, () => unresolved.push(backup)));
}
@ -80,30 +80,27 @@ export class BackupRestorer implements IWorkbenchContribution {
return TPromise.join(restorePromises).then(() => unresolved, () => unresolved);
}
private doOpenEditors(inputs: URI[]): TPromise<void> {
private doOpenEditors(resources: URI[]): TPromise<void> {
const stacks = this.groupService.getStacksModel();
const hasOpenedEditors = stacks.groups.length > 0;
return TPromise.join(inputs.map(resource => this.resolveInput(resource))).then(inputs => {
const openEditorsArgs = inputs.map((input, index) => {
return { input, options: { pinned: true, preserveFocus: true, inactive: index > 0 || hasOpenedEditors }, position: Position.ONE };
});
// Open all remaining backups as editors and resolve them to load their backups
return this.editorService.openEditors(openEditorsArgs).then(() => void 0);
const inputs = resources.map(resource => this.resolveInput(resource));
const openEditorsArgs = inputs.map((input, index) => {
return { input, options: { pinned: true, preserveFocus: true, inactive: index > 0 || hasOpenedEditors }, position: Position.ONE };
});
// Open all remaining backups as editors and resolve them to load their backups
return this.editorService.openEditors(openEditorsArgs).then(() => void 0);
}
private resolveInput(resource: URI): TPromise<IResourceInput> {
private resolveInput(resource: URI): IResourceInput | IUntitledResourceInput {
if (resource.scheme === 'untitled' && !BackupRestorer.UNTITLED_REGEX.test(resource.fsPath)) {
// TODO@Ben debt: instead of guessing if an untitled file has an associated file path or not
// this information should be provided by the backup service and stored as meta data within
return TPromise.as({
resource: this.untitledEditorService.createOrGet(URI.file(resource.fsPath)).getResource()
});
return { filePath: resource.fsPath };
}
return TPromise.as({ resource });
return { resource };
}
public getId(): string {

View file

@ -10,7 +10,7 @@ import { editorAction, ServicesAccessor, EditorAction } from 'vs/editor/common/e
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { WorkbenchKeybindingService } from 'vs/workbench/services/keybinding/electron-browser/keybindingService';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
import { IUntitledResourceInput } from "vs/platform/editor/common/editor";
@editorAction
class InspectKeyMap extends EditorAction {
@ -27,11 +27,9 @@ class InspectKeyMap extends EditorAction {
public run(accessor: ServicesAccessor, editor: ICommonCodeEditor): void {
const keybindingService = accessor.get(IKeybindingService);
const editorService = accessor.get(IWorkbenchEditorService);
const untitledEditorService = accessor.get(IUntitledEditorService);
if (keybindingService instanceof WorkbenchKeybindingService) {
const input = untitledEditorService.createOrGet(undefined, null, keybindingService.dumpDebugInfo());
editorService.openEditor(input, { pinned: true });
editorService.openEditor({ contents: keybindingService.dumpDebugInfo(), options: { pinned: true } } as IUntitledResourceInput);
}
}
}

View file

@ -37,14 +37,13 @@ import { IEditorGroupService } from 'vs/workbench/services/group/common/groupSer
import { IQuickOpenService, IFilePickOpenEntry } from 'vs/platform/quickOpen/common/quickOpen';
import { IHistoryService } from 'vs/workbench/services/history/common/history';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { Position, IResourceInput, IEditorInput } from 'vs/platform/editor/common/editor';
import { Position, IResourceInput, IEditorInput, IUntitledResourceInput } from 'vs/platform/editor/common/editor';
import { IInstantiationService, IConstructorSignature2, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { IMessageService, IMessageWithAction, IConfirmation, Severity, CancelAction } from 'vs/platform/message/common/message';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { getCodeEditor } from 'vs/editor/common/services/codeEditorService';
import { IEditorViewState } from 'vs/editor/common/editorCommon';
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
import { ITextModelResolverService } from 'vs/editor/common/services/resolverService';
import { IWindowsService, IWindowService } from 'vs/platform/windows/common/windows';
import { withFocussedFilesExplorer, revealInOSCommand, revealInExplorerCommand, copyPathCommand } from 'vs/workbench/parts/files/browser/fileCommands';
import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry';
@ -277,7 +276,6 @@ class RenameFileAction extends BaseRenameAction {
@IFileService fileService: IFileService,
@IMessageService messageService: IMessageService,
@ITextFileService textFileService: ITextFileService,
@ITextModelResolverService private textModelResolverService: ITextModelResolverService,
@IBackupFileService private backupFileService: IBackupFileService
) {
super(RenameFileAction.ID, nls.localize('rename', "Rename"), element, fileService, messageService, textFileService);
@ -323,7 +321,7 @@ class RenameFileAction extends BaseRenameAction {
// 4.) resolve those that were dirty to load their previous dirty contents from disk
.then(() => {
return TPromise.join(dirtyRenamed.map(t => this.textModelResolverService.createModelReference(t)));
return TPromise.join(dirtyRenamed.map(t => this.textFileService.models.loadOrCreate(t)));
});
}
}
@ -517,16 +515,13 @@ export class GlobalNewUntitledFileAction extends Action {
constructor(
id: string,
label: string,
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
@IUntitledEditorService private untitledEditorService: IUntitledEditorService
@IWorkbenchEditorService private editorService: IWorkbenchEditorService
) {
super(id, label);
}
public run(): TPromise<any> {
const input = this.untitledEditorService.createOrGet();
return this.editorService.openEditor(input, { pinned: true }); // untitled are always pinned
return this.editorService.openEditor({ options: { pinned: true } } as IUntitledResourceInput); // untitled are always pinned
}
}

View file

@ -23,7 +23,7 @@ import { FileEditorInput } from 'vs/workbench/parts/files/common/editors/fileEdi
import { TextFileEditor } from 'vs/workbench/parts/files/browser/editors/textFileEditor';
import { BinaryFileEditor } from 'vs/workbench/parts/files/browser/editors/binaryFileEditor';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { SyncDescriptor, AsyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { IKeybindings } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
@ -96,12 +96,12 @@ Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerEditor(
]
);
// Register default file input handler
// Note: because of service injection, the descriptor needs to have the exact count
// of arguments as the FileEditorInput constructor. Otherwise when creating an
// instance through the instantiation service he will inject the services wrong!
const descriptor = new AsyncDescriptor<IFileEditorInput>('vs/workbench/parts/files/common/editors/fileEditorInput', 'FileEditorInput', /* DO NOT REMOVE */ void 0, /* DO NOT REMOVE */ void 0);
Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerDefaultFileInput(descriptor);
// Register default file input factory
Registry.as<IEditorRegistry>(EditorExtensions.Editors).registerFileInputFactory({
createFileInput: (resource, encoding, instantiationService): IFileEditorInput => {
return instantiationService.createInstance(FileEditorInput, resource, encoding);
}
});
interface ISerializedFileInput {
resource: string;
@ -145,10 +145,14 @@ class FileEditorInputFactory implements IEditorInputFactory {
return JSON.stringify(fileInput);
}
public deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): EditorInput {
const fileInput: ISerializedFileInput = JSON.parse(serializedEditorInput);
public deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): FileEditorInput {
return instantiationService.invokeFunction<FileEditorInput>(accessor => {
const fileInput: ISerializedFileInput = JSON.parse(serializedEditorInput);
const resource = !!fileInput.resourceJSON ? URI.revive(fileInput.resourceJSON) : URI.parse(fileInput.resource);
const encoding = fileInput.encoding;
return instantiationService.createInstance(FileEditorInput, !!fileInput.resourceJSON ? URI.revive(fileInput.resourceJSON) : URI.parse(fileInput.resource), fileInput.encoding);
return accessor.get(IWorkbenchEditorService).createInput({ resource, encoding }) as FileEditorInput;
});
}
}

View file

@ -49,7 +49,6 @@ import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { IMenuService, IMenu, MenuId } from 'vs/platform/actions/common/actions';
import { fillInActions } from 'vs/platform/actions/browser/menuItemActionItem';
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
import { ITextModelResolverService } from 'vs/editor/common/services/resolverService';
import { attachInputBoxStyler } from 'vs/platform/theme/common/styler';
import { IThemeService } from 'vs/platform/theme/common/themeService';
@ -605,7 +604,6 @@ export class FileDragAndDrop implements IDragAndDrop {
@IConfigurationService private configurationService: IConfigurationService,
@IInstantiationService private instantiationService: IInstantiationService,
@ITextFileService private textFileService: ITextFileService,
@ITextModelResolverService private textModelResolverService: ITextModelResolverService,
@IBackupFileService private backupFileService: IBackupFileService
) {
this.toDispose = [];
@ -761,7 +759,7 @@ export class FileDragAndDrop implements IDragAndDrop {
// Success: load all files that are dirty again to restore their dirty contents
// Error: discard any backups created during the process
const onSuccess = () => TPromise.join(dirtyMoved.map(t => this.textModelResolverService.createModelReference(t)));
const onSuccess = () => TPromise.join(dirtyMoved.map(t => this.textFileService.models.loadOrCreate(t)));
const onError = (error?: Error, showError?: boolean) => {
if (showError) {
this.messageService.show(Severity.Error, error);

View file

@ -17,10 +17,11 @@ import { BINARY_FILE_EDITOR_ID, TEXT_FILE_EDITOR_ID, FILE_EDITOR_INPUT_ID } from
import { ITextFileService, AutoSaveMode, ModelState, TextFileModelChangeEvent } from 'vs/workbench/services/textfile/common/textfiles';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IDisposable, dispose, IReference } from 'vs/base/common/lifecycle';
import { telemetryURIDescriptor } from 'vs/platform/telemetry/common/telemetryUtils';
import { Verbosity } from 'vs/platform/editor/common/editor';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { ITextModelResolverService } from "vs/editor/common/services/resolverService";
/**
* A file editor input is the input type for the file editor of file system resources.
@ -30,6 +31,8 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput {
private preferredEncoding: string;
private forceOpenAsBinary: boolean;
private textModelReference: TPromise<IReference<TextFileEditorModel>>;
private name: string;
private description: string;
@ -48,16 +51,15 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput {
@IInstantiationService private instantiationService: IInstantiationService,
@IWorkspaceContextService private contextService: IWorkspaceContextService,
@ITextFileService private textFileService: ITextFileService,
@IEnvironmentService private environmentService: IEnvironmentService
@IEnvironmentService private environmentService: IEnvironmentService,
@ITextModelResolverService private textModelResolverService: ITextModelResolverService
) {
super();
this.toUnbind = [];
if (resource) {
this.setResource(resource);
this.preferredEncoding = preferredEncoding;
}
this.resource = resource;
this.preferredEncoding = preferredEncoding;
this.registerListeners();
}
@ -84,17 +86,6 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput {
}
}
public setResource(resource: URI): void {
this.resource = resource;
// Reset resource dependent properties
this.name = null;
this.description = null;
this.shortTitle = null;
this.mediumTitle = null;
this.longTitle = null;
}
public getResource(): URI {
return this.resource;
}
@ -216,7 +207,18 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput {
}
// Resolve as text
return this.textFileService.models.loadOrCreate(this.resource, this.preferredEncoding, refresh).then(null, error => {
return this.textFileService.models.loadOrCreate(this.resource, { encoding: this.preferredEncoding, reload: refresh }).then(model => {
// TODO@Ben this is a bit ugly, because we first resolve the model and then resolve a model reference. the reason being that binary
// or very large files do not resolve to a text file model but should be opened as binary files without text. First calling into
// loadOrCreate ensures we are not creating model references for these kind of resources.
// In addition we have a bit of payload to take into account (encoding, reload) that the text resolver does not handle yet.
if (!this.textModelReference) {
this.textModelReference = this.textModelResolverService.createModelReference(this.resource);
}
return this.textModelReference.then(ref => ref.object);
}, 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) {
@ -245,6 +247,12 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput {
public dispose(): void {
// Model reference
if (this.textModelReference) {
this.textModelReference.done(ref => ref.dispose());
this.textModelReference = null;
}
// Listeners
this.toUnbind = dispose(this.toUnbind);

View file

@ -9,13 +9,15 @@ import URI from 'vs/base/common/uri';
import { join } from 'vs/base/common/paths';
import { FileEditorInput } from 'vs/workbench/parts/files/common/editors/fileEditorInput';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { workbenchInstantiationService, TestTextFileService } from 'vs/workbench/test/workbenchTestServices';
import { workbenchInstantiationService, TestTextFileService, TestEditorGroupService, createFileInput } from 'vs/workbench/test/workbenchTestServices';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { EncodingMode } from 'vs/workbench/common/editor';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { FileOperationResult, IFileOperationResult } from 'vs/platform/files/common/files';
import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel';
import { Verbosity } from 'vs/platform/editor/common/editor';
import { IEditorGroupService } from "vs/workbench/services/group/common/groupService";
import { IModelService } from "vs/editor/common/services/modelService";
function toResource(path) {
return URI.file(join('C:\\', new Buffer(this.test.fullTitle()).toString('base64'), path));
@ -24,7 +26,9 @@ function toResource(path) {
class ServiceAccessor {
constructor(
@IWorkbenchEditorService public editorService: IWorkbenchEditorService,
@ITextFileService public textFileService: TestTextFileService
@ITextFileService public textFileService: TestTextFileService,
@IModelService public modelService: IModelService,
@IEditorGroupService public editorGroupService: TestEditorGroupService
) {
}
}
@ -182,4 +186,29 @@ suite('Files - FileEditorInput', () => {
done();
});
});
test('disposes model when not open anymore', function (done) {
const resource = toResource.call(this, '/path/index.txt');
const input = createFileInput(instantiationService, resource);
input.resolve().then((model: TextFileEditorModel) => {
const stacks = accessor.editorGroupService.getStacksModel();
const group = stacks.openGroup('group', true);
group.openEditor(input);
accessor.editorGroupService.fireChange();
assert.ok(!model.isDisposed());
group.closeEditor(input);
accessor.editorGroupService.fireChange();
assert.ok(model.isDisposed());
model.dispose();
assert.ok(!accessor.modelService.getModel(model.getResource()));
done();
});
});
});

View file

@ -193,10 +193,10 @@ class EditorInputCache {
resource = URI.file(paths.join(model.getRepositoryRoot(), indexStatus.getRename()));
}
return this.editorService.createInput({ resource });
return TPromise.as(this.editorService.createInput({ resource }));
case Status.BOTH_MODIFIED:
return this.editorService.createInput({ resource });
return TPromise.as(this.editorService.createInput({ resource }));
default:
return TPromise.as(null);

View file

@ -38,7 +38,6 @@ import { IModeService } from 'vs/editor/common/services/modeService';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
import { ITextModelResolverService } from 'vs/editor/common/services/resolverService';
import { ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
@ -461,7 +460,6 @@ export class DefaultPreferencesEditor extends BaseTextEditor {
@IStorageService storageService: IStorageService,
@IConfigurationService configurationService: IConfigurationService,
@IWorkbenchThemeService themeService: IWorkbenchThemeService,
@IUntitledEditorService private untitledEditorService: IUntitledEditorService,
@IPreferencesService private preferencesService: IPreferencesService,
@IModelService private modelService: IModelService,
@IModeService modeService: IModeService,

View file

@ -10,18 +10,19 @@ import network = require('vs/base/common/network');
import { Registry } from 'vs/platform/platform';
import { basename, dirname } from 'vs/base/common/paths';
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
import { EditorInput, EditorOptions, IFileEditorInput, TextEditorOptions, IEditorRegistry, Extensions, SideBySideEditorInput } from 'vs/workbench/common/editor';
import { EditorInput, EditorOptions, TextEditorOptions, IEditorRegistry, Extensions, SideBySideEditorInput, IFileEditorInput, IFileInputFactory } from 'vs/workbench/common/editor';
import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput';
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
import { IWorkbenchEditorService, IResourceInputType } from 'vs/workbench/services/editor/common/editorService';
import { IEditorInput, IEditorOptions, ITextEditorOptions, Position, Direction, IEditor, IResourceInput, IResourceDiffInput, IResourceSideBySideInput } from 'vs/platform/editor/common/editor';
import { IEditorInput, IEditorOptions, ITextEditorOptions, Position, Direction, IEditor, IResourceInput, IResourceDiffInput, IResourceSideBySideInput, IUntitledResourceInput } from 'vs/platform/editor/common/editor';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { AsyncDescriptor0 } from 'vs/platform/instantiation/common/descriptors';
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import nls = require('vs/nls');
import { getPathLabel, IWorkspaceProvider } from 'vs/base/common/labels';
import { ResourceMap } from "vs/base/common/map";
import { once } from "vs/base/common/event";
import { IEnvironmentService } from "vs/platform/environment/common/environment";
export interface IEditorPart {
@ -37,12 +38,16 @@ export interface IEditorPart {
getActiveEditorInput(): IEditorInput;
}
type ICachedEditorInput = ResourceEditorInput | IFileEditorInput;
export class WorkbenchEditorService implements IWorkbenchEditorService {
public _serviceBrand: any;
private static CACHE: ResourceMap<ICachedEditorInput> = new ResourceMap<ICachedEditorInput>();
private editorPart: IEditorPart | IWorkbenchEditorService;
private fileInputDescriptor: AsyncDescriptor0<IFileEditorInput>;
private fileInputFactory: IFileInputFactory;
constructor(
editorPart: IEditorPart | IWorkbenchEditorService,
@ -52,7 +57,7 @@ export class WorkbenchEditorService implements IWorkbenchEditorService {
@IEnvironmentService private environmentService: IEnvironmentService
) {
this.editorPart = editorPart;
this.fileInputDescriptor = Registry.as<IEditorRegistry>(Extensions.Editors).getDefaultFileInput();
this.fileInputFactory = Registry.as<IEditorRegistry>(Extensions.Editors).getFileInputFactory();
}
public getActiveEditor(): IEditor {
@ -117,13 +122,12 @@ export class WorkbenchEditorService implements IWorkbenchEditorService {
// Untyped Text Editor Support (required for code that uses this service below workbench level)
const textInput = <IResourceInputType>input;
return this.createInput(textInput).then(typedInput => {
if (typedInput) {
return this.doOpenEditor(typedInput, TextEditorOptions.from(textInput), arg2);
}
const typedInput = this.createInput(textInput);
if (typedInput) {
return this.doOpenEditor(typedInput, TextEditorOptions.from(textInput), arg2);
}
return TPromise.as<IEditor>(null);
});
return TPromise.as<IEditor>(null);
}
private toOptions(arg1?: any): EditorOptions {
@ -151,39 +155,36 @@ export class WorkbenchEditorService implements IWorkbenchEditorService {
public openEditors(editors: { input: IResourceInputType, position: Position }[]): TPromise<IEditor[]>;
public openEditors(editors: { input: IEditorInput, position: Position, options?: IEditorOptions }[]): TPromise<IEditor[]>;
public openEditors(editors: any[]): TPromise<IEditor[]> {
return TPromise.join(editors.map(editor => this.createInput(editor.input))).then(inputs => {
const typedInputs: { input: IEditorInput, position: Position, options?: EditorOptions }[] = inputs.map((input, index) => {
const options = editors[index].input instanceof EditorInput ? this.toOptions(editors[index].options) : TextEditorOptions.from(editors[index].input);
const inputs = editors.map(editor => this.createInput(editor.input));
const typedInputs: { input: IEditorInput, position: Position, options?: EditorOptions }[] = inputs.map((input, index) => {
const options = editors[index].input instanceof EditorInput ? this.toOptions(editors[index].options) : TextEditorOptions.from(editors[index].input);
return {
input,
options,
position: editors[index].position
};
});
return this.editorPart.openEditors(typedInputs);
return {
input,
options,
position: editors[index].position
};
});
return this.editorPart.openEditors(typedInputs);
}
public replaceEditors(editors: { toReplace: IResourceInputType, replaceWith: IResourceInputType }[], position?: Position): TPromise<BaseEditor[]>;
public replaceEditors(editors: { toReplace: IEditorInput, replaceWith: IEditorInput, options?: IEditorOptions }[], position?: Position): TPromise<BaseEditor[]>;
public replaceEditors(editors: any[], position?: Position): TPromise<BaseEditor[]> {
return TPromise.join(editors.map(editor => this.createInput(editor.toReplace))).then(toReplaceInputs => {
return TPromise.join(editors.map(editor => this.createInput(editor.replaceWith))).then(replaceWithInputs => {
const typedReplacements: { toReplace: IEditorInput, replaceWith: IEditorInput, options?: EditorOptions }[] = editors.map((editor, index) => {
const options = editor.toReplace instanceof EditorInput ? this.toOptions(editor.options) : TextEditorOptions.from(editor.replaceWith);
const toReplaceInputs = editors.map(editor => this.createInput(editor.toReplace));
const replaceWithInputs = editors.map(editor => this.createInput(editor.replaceWith));
const typedReplacements: { toReplace: IEditorInput, replaceWith: IEditorInput, options?: EditorOptions }[] = editors.map((editor, index) => {
const options = editor.toReplace instanceof EditorInput ? this.toOptions(editor.options) : TextEditorOptions.from(editor.replaceWith);
return {
toReplace: toReplaceInputs[index],
replaceWith: replaceWithInputs[index],
options
};
});
return this.editorPart.replaceEditors(typedReplacements, position);
});
return {
toReplace: toReplaceInputs[index],
replaceWith: replaceWithInputs[index],
options
};
});
return this.editorPart.replaceEditors(typedReplacements, position);
}
public closeEditor(position: Position, input: IEditorInput): TPromise<void> {
@ -202,51 +203,48 @@ export class WorkbenchEditorService implements IWorkbenchEditorService {
return this.editorPart.closeAllEditors(except);
}
public createInput(input: IEditorInput): TPromise<EditorInput>;
public createInput(input: IResourceInputType): TPromise<EditorInput>;
public createInput(input: any): TPromise<IEditorInput> {
public createInput(input: IEditorInput): EditorInput;
public createInput(input: IResourceInputType): EditorInput;
public createInput(input: any): IEditorInput {
// Workbench Input Support
if (input instanceof EditorInput) {
return TPromise.as<EditorInput>(input);
return input;
}
// Side by Side Support
const resourceSideBySideInput = <IResourceSideBySideInput>input;
if (resourceSideBySideInput.masterResource && resourceSideBySideInput.detailResource) {
return this.createInput({ resource: resourceSideBySideInput.masterResource }).then(masterInput => {
return this.createInput({ resource: resourceSideBySideInput.detailResource }).then(detailInput => {
return new SideBySideEditorInput(resourceSideBySideInput.label || masterInput.getName(), typeof resourceSideBySideInput.description === 'string' ? resourceSideBySideInput.description : masterInput.getDescription(), detailInput, masterInput);
});
});
const masterInput = this.createInput({ resource: resourceSideBySideInput.masterResource });
const detailInput = this.createInput({ resource: resourceSideBySideInput.detailResource });
return new SideBySideEditorInput(resourceSideBySideInput.label || masterInput.getName(), typeof resourceSideBySideInput.description === 'string' ? resourceSideBySideInput.description : masterInput.getDescription(), detailInput, masterInput);
}
// Diff Editor Support
const resourceDiffInput = <IResourceDiffInput>input;
if (resourceDiffInput.leftResource && resourceDiffInput.rightResource) {
return this.createInput({ resource: resourceDiffInput.leftResource }).then(leftInput => {
return this.createInput({ resource: resourceDiffInput.rightResource }).then(rightInput => {
const label = resourceDiffInput.label || this.toDiffLabel(resourceDiffInput.leftResource, resourceDiffInput.rightResource, this.workspaceContextService, this.environmentService);
const leftInput = this.createInput({ resource: resourceDiffInput.leftResource });
const rightInput = this.createInput({ resource: resourceDiffInput.rightResource });
const label = resourceDiffInput.label || this.toDiffLabel(resourceDiffInput.leftResource, resourceDiffInput.rightResource, this.workspaceContextService, this.environmentService);
return new DiffEditorInput(label, resourceDiffInput.description, leftInput, rightInput);
});
});
return new DiffEditorInput(label, resourceDiffInput.description, leftInput, rightInput);
}
// Base Text Editor Support for inmemory resources
const resourceInput = <IResourceInput>input;
// Untitled file support
if (resourceInput.resource instanceof URI && (resourceInput.resource.scheme === UntitledEditorInput.SCHEMA)) {
return TPromise.as<EditorInput>(this.untitledEditorService.createOrGet(resourceInput.resource));
const untitledInput = <IUntitledResourceInput>input;
if (!untitledInput.resource || typeof untitledInput.filePath === 'string' || (untitledInput.resource instanceof URI && untitledInput.resource.scheme === UntitledEditorInput.SCHEMA)) {
return this.untitledEditorService.createOrGet(untitledInput.filePath ? URI.file(untitledInput.filePath) : untitledInput.resource, untitledInput.language, untitledInput.contents);
}
// Base Text Editor Support for file resources
else if (this.fileInputDescriptor && resourceInput.resource instanceof URI && resourceInput.resource.scheme === network.Schemas.file) {
return this.createFileInput(resourceInput.resource, resourceInput.encoding);
const resourceInput = <IResourceInput>input;
// Files support
if (resourceInput.resource instanceof URI && resourceInput.resource.scheme === network.Schemas.file) {
return this.createOrGet(resourceInput.resource, this.instantiationService, resourceInput.label, resourceInput.description, resourceInput.encoding);
}
// Treat an URI as ResourceEditorInput
// Any other resource
else if (resourceInput.resource instanceof URI) {
const label = resourceInput.label || basename(resourceInput.resource.fsPath);
let description: string;
@ -256,19 +254,38 @@ export class WorkbenchEditorService implements IWorkbenchEditorService {
description = dirname(resourceInput.resource.fsPath);
}
return TPromise.as(this.instantiationService.createInstance(ResourceEditorInput, label, description, resourceInput.resource));
return this.createOrGet(resourceInput.resource, this.instantiationService, label, description);
}
return TPromise.as<EditorInput>(null);
return null;
}
private createFileInput(resource: URI, encoding?: string): TPromise<IFileEditorInput> {
return this.instantiationService.createInstance(this.fileInputDescriptor).then(typedFileInput => {
typedFileInput.setResource(resource);
typedFileInput.setPreferredEncoding(encoding);
private createOrGet(resource: URI, instantiationService: IInstantiationService, label: string, description: string, encoding?: string): ICachedEditorInput {
if (WorkbenchEditorService.CACHE.has(resource)) {
const input = WorkbenchEditorService.CACHE.get(resource);
if (input instanceof ResourceEditorInput) {
input.setName(label);
input.setDescription(description);
} else {
input.setPreferredEncoding(encoding);
}
return typedFileInput;
return input;
}
let input: ICachedEditorInput;
if (resource.scheme === network.Schemas.file) {
input = this.fileInputFactory.createFileInput(resource, encoding, instantiationService);
} else {
input = instantiationService.createInstance(ResourceEditorInput, label, description, resource);
}
WorkbenchEditorService.CACHE.set(resource, input);
once(input.onDispose)(() => {
WorkbenchEditorService.CACHE.delete(resource);
});
return input;
}
private toDiffLabel(res1: URI, res2: URI, context: IWorkspaceProvider, environment: IEnvironmentService): string {

View file

@ -7,11 +7,11 @@
import { TPromise } from 'vs/base/common/winjs.base';
import { createDecorator, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
import { IEditorService, IEditor, IEditorInput, IEditorOptions, ITextEditorOptions, Position, Direction, IResourceInput, IResourceDiffInput, IResourceSideBySideInput } from 'vs/platform/editor/common/editor';
import { IEditorService, IEditor, IEditorInput, IEditorOptions, ITextEditorOptions, Position, Direction, IResourceInput, IResourceDiffInput, IResourceSideBySideInput, IUntitledResourceInput } from 'vs/platform/editor/common/editor';
export const IWorkbenchEditorService = createDecorator<IWorkbenchEditorService>('editorService');
export type IResourceInputType = IResourceInput | IResourceDiffInput | IResourceSideBySideInput;
export type IResourceInputType = IResourceInput | IUntitledResourceInput | IResourceDiffInput | IResourceSideBySideInput;
/**
* The editor service allows to open editors and work on the active
@ -90,5 +90,5 @@ export interface IWorkbenchEditorService extends IEditorService {
/**
* Allows to resolve an untyped input to a workbench typed instanceof editor input
*/
createInput(input: IResourceInputType): TPromise<IEditorInput>;
createInput(input: IResourceInputType): IEditorInput;
}

View file

@ -0,0 +1,296 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as assert from 'assert';
import { Promise, TPromise } from 'vs/base/common/winjs.base';
import paths = require('vs/base/common/paths');
import { Position, Direction, IEditor } from 'vs/platform/editor/common/editor';
import URI from 'vs/base/common/uri';
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
import { EditorInput, EditorOptions, TextEditorOptions } from 'vs/workbench/common/editor';
import { StringEditorInput } from 'vs/workbench/common/editor/stringEditorInput';
import { StringEditorModel } from 'vs/workbench/common/editor/stringEditorModel';
import { FileEditorInput } from 'vs/workbench/parts/files/common/editors/fileEditorInput';
import { workbenchInstantiationService, TestThemeService } from 'vs/workbench/test/workbenchTestServices';
import { DelegatingWorkbenchEditorService, WorkbenchEditorService, IEditorPart } from 'vs/workbench/services/editor/browser/editorService';
import { UntitledEditorInput } from "vs/workbench/common/editor/untitledEditorInput";
let activeEditor: BaseEditor = <any>{
getSelection: function () {
return 'test.selection';
}
};
let openedEditorInput;
let openedEditorOptions;
let openedEditorPosition;
function toResource(path) {
return URI.from({ scheme: 'custom', path });
}
function toFileResource(path) {
return URI.file(paths.join('C:\\', new Buffer(this.test.fullTitle()).toString('base64'), path));
}
class TestEditorPart implements IEditorPart {
private activeInput;
public getId(): string {
return null;
}
public openEditors(args: any[]): Promise {
return TPromise.as([]);
}
public replaceEditors(editors: { toReplace: EditorInput, replaceWith: EditorInput, options?: any }[]): TPromise<IEditor[]> {
return TPromise.as([]);
}
public closeEditors(position: Position, except?: EditorInput, direction?: Direction): TPromise<void> {
return TPromise.as(null);
}
public closeAllEditors(except?: Position): TPromise<void> {
return TPromise.as(null);
}
public closeEditor(position: Position, input: EditorInput): TPromise<void> {
return TPromise.as(null);
}
public openEditor(input?: EditorInput, options?: EditorOptions, sideBySide?: boolean): TPromise<BaseEditor>;
public openEditor(input?: EditorInput, options?: EditorOptions, position?: Position): TPromise<BaseEditor>;
public openEditor(input?: EditorInput, options?: EditorOptions, arg?: any): TPromise<BaseEditor> {
openedEditorInput = input;
openedEditorOptions = options;
openedEditorPosition = arg;
return TPromise.as(activeEditor);
}
public getActiveEditor(): BaseEditor {
return activeEditor;
}
public setActiveEditorInput(input: EditorInput) {
this.activeInput = input;
}
public getActiveEditorInput(): EditorInput {
return this.activeInput;
}
public getVisibleEditors(): IEditor[] {
return [activeEditor];
}
}
suite('WorkbenchEditorService', () => {
test('basics', function () {
let instantiationService = workbenchInstantiationService();
let activeInput: EditorInput = instantiationService.createInstance(FileEditorInput, toFileResource.call(this, '/something.js'), void 0);
let testEditorPart = new TestEditorPart();
testEditorPart.setActiveEditorInput(activeInput);
let service: WorkbenchEditorService = <any>instantiationService.createInstance(<any>WorkbenchEditorService, testEditorPart);
assert.strictEqual(service.getActiveEditor(), activeEditor);
assert.strictEqual(service.getActiveEditorInput(), activeInput);
// Open EditorInput
service.openEditor(activeInput, null).then((editor) => {
assert.strictEqual(openedEditorInput, activeInput);
assert.strictEqual(openedEditorOptions, null);
assert.strictEqual(editor, activeEditor);
assert.strictEqual(service.getVisibleEditors().length, 1);
assert(service.getVisibleEditors()[0] === editor);
});
service.openEditor(activeInput, null, Position.ONE).then((editor) => {
assert.strictEqual(openedEditorInput, activeInput);
assert.strictEqual(openedEditorOptions, null);
assert.strictEqual(editor, activeEditor);
assert.strictEqual(service.getVisibleEditors().length, 1);
assert(service.getVisibleEditors()[0] === editor);
});
// Open Untyped Input (file)
service.openEditor({ resource: toFileResource.call(this, '/index.html'), options: { selection: { startLineNumber: 1, startColumn: 1 } } }).then((editor) => {
assert.strictEqual(editor, activeEditor);
assert(openedEditorInput instanceof FileEditorInput);
let contentInput = <FileEditorInput>openedEditorInput;
assert.strictEqual(contentInput.getResource().fsPath, toFileResource.call(this, '/index.html').fsPath);
assert(openedEditorOptions instanceof TextEditorOptions);
let textEditorOptions = <TextEditorOptions>openedEditorOptions;
assert(textEditorOptions.hasOptionsDefined());
});
// Open Untyped Input (file, encoding)
service.openEditor({ resource: toFileResource.call(this, '/index.html'), encoding: 'utf16le', options: { selection: { startLineNumber: 1, startColumn: 1 } } }).then((editor) => {
assert.strictEqual(editor, activeEditor);
assert(openedEditorInput instanceof FileEditorInput);
let contentInput = <FileEditorInput>openedEditorInput;
assert.equal(contentInput.getPreferredEncoding(), 'utf16le');
});
// Open Untyped Input (untitled)
service.openEditor({ options: { selection: { startLineNumber: 1, startColumn: 1 } } }).then((editor) => {
assert.strictEqual(editor, activeEditor);
assert(openedEditorInput instanceof UntitledEditorInput);
assert(openedEditorOptions instanceof TextEditorOptions);
let textEditorOptions = <TextEditorOptions>openedEditorOptions;
assert(textEditorOptions.hasOptionsDefined());
});
// Open Untyped Input (untitled with contents)
service.openEditor({ contents: 'Hello Untitled', options: { selection: { startLineNumber: 1, startColumn: 1 } } }).then((editor) => {
assert.strictEqual(editor, activeEditor);
assert(openedEditorInput instanceof UntitledEditorInput);
const untitledInput = openedEditorInput as UntitledEditorInput;
untitledInput.resolve().then(model => {
assert.equal(model.getValue(), 'Hello Untitled');
});
});
// Open Untyped Input (untitled with file path)
service.openEditor({ filePath: '/some/path.txt', options: { selection: { startLineNumber: 1, startColumn: 1 } } }).then((editor) => {
assert.strictEqual(editor, activeEditor);
assert(openedEditorInput instanceof UntitledEditorInput);
const untitledInput = openedEditorInput as UntitledEditorInput;
assert.ok(untitledInput.hasAssociatedFilePath);
});
// Resolve Editor Model (Typed EditorInput)
let input = instantiationService.createInstance(StringEditorInput, 'name', 'description', 'hello world', 'text/plain', false);
input.resolve(true).then((model: StringEditorModel) => {
assert(model instanceof StringEditorModel);
assert(model.isResolved());
input.resolve().then((otherModel) => {
assert(model === otherModel);
input.dispose();
});
});
});
test('caching', function () {
let instantiationService = workbenchInstantiationService();
let activeInput: EditorInput = instantiationService.createInstance(FileEditorInput, toFileResource.call(this, '/something.js'), void 0);
let testEditorPart = new TestEditorPart();
testEditorPart.setActiveEditorInput(activeInput);
let service: WorkbenchEditorService = <any>instantiationService.createInstance(<any>WorkbenchEditorService, testEditorPart);
// Cached Input (Files)
const fileResource1 = toFileResource.call(this, '/foo/bar/cache1.js');
const fileInput1 = service.createInput({ resource: fileResource1 });
assert.ok(fileInput1);
const fileResource2 = toFileResource.call(this, '/foo/bar/cache2.js');
const fileInput2 = service.createInput({ resource: fileResource2 });
assert.ok(fileInput2);
assert.notEqual(fileInput1, fileInput2);
const fileInput1Again = service.createInput({ resource: fileResource1 });
assert.equal(fileInput1Again, fileInput1);
fileInput1Again.dispose();
assert.ok(fileInput1.isDisposed());
const fileInput1AgainAndAgain = service.createInput({ resource: fileResource1 });
assert.notEqual(fileInput1AgainAndAgain, fileInput1);
assert.ok(!fileInput1AgainAndAgain.isDisposed());
// Cached Input (Resource)
const resource1 = toResource.call(this, '/foo/bar/cache1.js');
const input1 = service.createInput({ resource: resource1 });
assert.ok(input1);
const resource2 = toResource.call(this, '/foo/bar/cache2.js');
const input2 = service.createInput({ resource: resource2 });
assert.ok(input2);
assert.notEqual(input1, input2);
const input1Again = service.createInput({ resource: resource1 });
assert.equal(input1Again, input1);
input1Again.dispose();
assert.ok(input1.isDisposed());
const input1AgainAndAgain = service.createInput({ resource: resource1 });
assert.notEqual(input1AgainAndAgain, input1);
assert.ok(!input1AgainAndAgain.isDisposed());
});
test('delegate', function (done) {
let instantiationService = workbenchInstantiationService();
let activeInput: EditorInput = instantiationService.createInstance(FileEditorInput, toFileResource.call(this, '/something.js'), void 0);
let testEditorPart = new TestEditorPart();
testEditorPart.setActiveEditorInput(activeInput);
instantiationService.createInstance(<any>WorkbenchEditorService, testEditorPart);
class MyEditor extends BaseEditor {
constructor(id: string) {
super(id, null, new TestThemeService());
}
getId(): string {
return 'myEditor';
}
public layout(): void {
}
public createEditor(): any {
}
}
let ed = instantiationService.createInstance(MyEditor, 'my.editor');
let inp = instantiationService.createInstance(StringEditorInput, 'name', 'description', 'hello world', 'text/plain', false);
let delegate = instantiationService.createInstance(DelegatingWorkbenchEditorService);
delegate.setEditorOpenHandler((input, options?) => {
assert.strictEqual(input, inp);
return TPromise.as(ed);
});
delegate.setEditorCloseHandler((position, input) => {
assert.strictEqual(input, inp);
done();
return TPromise.as(void 0);
});
delegate.openEditor(inp);
delegate.closeEditor(0, inp);
});
});

View file

@ -8,92 +8,16 @@
import * as assert from 'assert';
import { IAction, IActionItem } from 'vs/base/common/actions';
import { Promise, TPromise } from 'vs/base/common/winjs.base';
import paths = require('vs/base/common/paths');
import { IEditorControl, Position, Direction, IEditor } from 'vs/platform/editor/common/editor';
import URI from 'vs/base/common/uri';
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
import { EditorInput, EditorOptions, TextEditorOptions } from 'vs/workbench/common/editor';
import { StringEditorInput } from 'vs/workbench/common/editor/stringEditorInput';
import { StringEditorModel } from 'vs/workbench/common/editor/stringEditorModel';
import { FileEditorInput } from 'vs/workbench/parts/files/common/editors/fileEditorInput';
import { workbenchInstantiationService, TestThemeService } from 'vs/workbench/test/workbenchTestServices';
import { IEditorControl } from 'vs/platform/editor/common/editor';
import { Viewlet, ViewletDescriptor } from 'vs/workbench/browser/viewlet';
import { IPanel } from 'vs/workbench/common/panel';
import { WorkbenchProgressService, ScopedService } from 'vs/workbench/services/progress/browser/progressService';
import { DelegatingWorkbenchEditorService, WorkbenchEditorService, IEditorPart } from 'vs/workbench/services/editor/browser/editorService';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { IViewlet } from 'vs/workbench/common/viewlet';
import { Emitter } from 'vs/base/common/event';
let activeViewlet: Viewlet = <any>{};
let activeEditor: BaseEditor = <any>{
getSelection: function () {
return 'test.selection';
}
};
let openedEditorInput;
let openedEditorOptions;
let openedEditorPosition;
function toResource(path) {
return URI.file(paths.join('C:\\', new Buffer(this.test.fullTitle()).toString('base64'), path));
}
class TestEditorPart implements IEditorPart {
private activeInput;
public getId(): string {
return null;
}
public openEditors(args: any[]): Promise {
return TPromise.as([]);
}
public replaceEditors(editors: { toReplace: EditorInput, replaceWith: EditorInput, options?: any }[]): TPromise<IEditor[]> {
return TPromise.as([]);
}
public closeEditors(position: Position, except?: EditorInput, direction?: Direction): TPromise<void> {
return TPromise.as(null);
}
public closeAllEditors(except?: Position): TPromise<void> {
return TPromise.as(null);
}
public closeEditor(position: Position, input: EditorInput): TPromise<void> {
return TPromise.as(null);
}
public openEditor(input?: EditorInput, options?: EditorOptions, sideBySide?: boolean): TPromise<BaseEditor>;
public openEditor(input?: EditorInput, options?: EditorOptions, position?: Position): TPromise<BaseEditor>;
public openEditor(input?: EditorInput, options?: EditorOptions, arg?: any): TPromise<BaseEditor> {
openedEditorInput = input;
openedEditorOptions = options;
openedEditorPosition = arg;
return TPromise.as(activeEditor);
}
public getActiveEditor(): BaseEditor {
return activeEditor;
}
public setActiveEditorInput(input: EditorInput) {
this.activeInput = input;
}
public getActiveEditorInput(): EditorInput {
return this.activeInput;
}
public getVisibleEditors(): IEditor[] {
return [activeEditor];
}
}
class TestViewletService implements IViewletService {
public _serviceBrand: any;
@ -284,112 +208,7 @@ class TestProgressBar {
}
}
suite('Workbench UI Services', () => {
test('WorkbenchEditorService', function () {
let instantiationService = workbenchInstantiationService();
let activeInput: EditorInput = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/something.js'), void 0);
let testEditorPart = new TestEditorPart();
testEditorPart.setActiveEditorInput(activeInput);
let service: WorkbenchEditorService = <any>instantiationService.createInstance(<any>WorkbenchEditorService, testEditorPart);
assert.strictEqual(service.getActiveEditor(), activeEditor);
assert.strictEqual(service.getActiveEditorInput(), activeInput);
// Open EditorInput
service.openEditor(activeInput, null).then((editor) => {
assert.strictEqual(openedEditorInput, activeInput);
assert.strictEqual(openedEditorOptions, null);
assert.strictEqual(editor, activeEditor);
assert.strictEqual(service.getVisibleEditors().length, 1);
assert(service.getVisibleEditors()[0] === editor);
});
service.openEditor(activeInput, null, Position.ONE).then((editor) => {
assert.strictEqual(openedEditorInput, activeInput);
assert.strictEqual(openedEditorOptions, null);
assert.strictEqual(editor, activeEditor);
assert.strictEqual(service.getVisibleEditors().length, 1);
assert(service.getVisibleEditors()[0] === editor);
});
// Open Untyped Input
service.openEditor({ resource: toResource.call(this, '/index.html'), options: { selection: { startLineNumber: 1, startColumn: 1 } } }).then((editor) => {
assert.strictEqual(editor, activeEditor);
assert(openedEditorInput instanceof FileEditorInput);
let contentInput = <FileEditorInput>openedEditorInput;
assert.strictEqual(contentInput.getResource().fsPath, toResource.call(this, '/index.html').fsPath);
assert(openedEditorOptions instanceof TextEditorOptions);
let textEditorOptions = <TextEditorOptions>openedEditorOptions;
assert(textEditorOptions.hasOptionsDefined());
});
// Resolve Editor Model (Typed EditorInput)
let input = instantiationService.createInstance(StringEditorInput, 'name', 'description', 'hello world', 'text/plain', false);
input.resolve(true).then((model: StringEditorModel) => {
assert(model instanceof StringEditorModel);
assert(model.isResolved());
input.resolve().then((otherModel) => {
assert(model === otherModel);
input.dispose();
});
});
});
test('DelegatingWorkbenchEditorService', function (done) {
let instantiationService = workbenchInstantiationService();
let activeInput: EditorInput = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/something.js'), void 0);
let testEditorPart = new TestEditorPart();
testEditorPart.setActiveEditorInput(activeInput);
instantiationService.createInstance(<any>WorkbenchEditorService, testEditorPart);
class MyEditor extends BaseEditor {
constructor(id: string) {
super(id, null, new TestThemeService());
}
getId(): string {
return 'myEditor';
}
public layout(): void {
}
public createEditor(): any {
}
}
let ed = instantiationService.createInstance(MyEditor, 'my.editor');
let inp = instantiationService.createInstance(StringEditorInput, 'name', 'description', 'hello world', 'text/plain', false);
let delegate = instantiationService.createInstance(DelegatingWorkbenchEditorService);
delegate.setEditorOpenHandler((input, options?) => {
assert.strictEqual(input, inp);
return TPromise.as(ed);
});
delegate.setEditorCloseHandler((position, input) => {
assert.strictEqual(input, inp);
done();
return TPromise.as(void 0);
});
delegate.openEditor(inp);
delegate.closeEditor(0, inp);
});
suite('Progress Service', () => {
test('ScopedService', () => {
let viewletService = new TestViewletService();

View file

@ -9,8 +9,7 @@ import { TPromise } from 'vs/base/common/winjs.base';
import URI from 'vs/base/common/uri';
import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
import { ITextFileEditorModel, ITextFileEditorModelManager, TextFileModelChangeEvent, StateChange } from 'vs/workbench/services/textfile/common/textfiles';
import { ITextFileEditorModel, ITextFileEditorModelManager, TextFileModelChangeEvent, StateChange, IModelLoadOrCreateOptions } from 'vs/workbench/services/textfile/common/textfiles';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ResourceMap } from 'vs/base/common/map';
@ -40,8 +39,7 @@ export class TextFileEditorModelManager implements ITextFileEditorModelManager {
constructor(
@ILifecycleService private lifecycleService: ILifecycleService,
@IInstantiationService private instantiationService: IInstantiationService,
@IEditorGroupService private editorGroupService: IEditorGroupService
@IInstantiationService private instantiationService: IInstantiationService
) {
this.toUnbind = [];
@ -74,62 +72,10 @@ export class TextFileEditorModelManager implements ITextFileEditorModelManager {
private registerListeners(): void {
// Editors changing/closing
this.toUnbind.push(this.editorGroupService.onEditorsChanged(() => this.onEditorsChanged()));
this.toUnbind.push(this.editorGroupService.getStacksModel().onEditorClosed(() => this.onEditorClosed()));
// Lifecycle
this.lifecycleService.onShutdown(this.dispose, this);
}
private onEditorsChanged(): void {
this.disposeUnusedModels();
}
private onEditorClosed(): void {
this.disposeUnusedModels();
}
private disposeUnusedModels(): void {
// To not grow our text file model cache infinitly, we dispose models that
// are not showing up in any opened editor.
// TODO@Ben this is a workaround until we have adopted model references from
// the resolver service (https://github.com/Microsoft/vscode/issues/17888)
this.getAll(void 0, model => this.canDispose(model)).forEach(model => {
model.dispose();
});
}
private canDispose(model: ITextFileEditorModel): boolean {
if (!model) {
return false; // we need data!
}
if (model.isDisposed()) {
return false; // already disposed
}
if (this.mapResourceToPendingModelLoaders.has(model.getResource())) {
return false; // not yet loaded
}
if (model.isDirty()) {
return false; // not saved
}
if (model.textEditorModel && model.textEditorModel.isAttachedToEditor()) {
return false; // never dispose when attached to editor (e.g. viewzones)
}
if (this.editorGroupService.getStacksModel().isOpen(model.getResource())) {
return false; // never dispose when opened inside an editor (e.g. tabs)
}
return true;
}
public get onModelDisposed(): Event<URI> {
return this._onModelDisposed.event;
}
@ -213,7 +159,7 @@ export class TextFileEditorModelManager implements ITextFileEditorModelManager {
return this.mapResourceToModel.get(resource);
}
public loadOrCreate(resource: URI, encoding?: string, refresh?: boolean): TPromise<ITextFileEditorModel> {
public loadOrCreate(resource: URI, options?: IModelLoadOrCreateOptions): TPromise<ITextFileEditorModel> {
// Return early if model is currently being loaded
const pendingLoad = this.mapResourceToPendingModelLoaders.get(resource);
@ -226,7 +172,7 @@ export class TextFileEditorModelManager implements ITextFileEditorModelManager {
// Model exists
let model = this.get(resource);
if (model) {
if (!refresh) {
if (!options || !options.reload) {
modelPromise = TPromise.as(model);
} else {
modelPromise = model.load();
@ -235,7 +181,7 @@ export class TextFileEditorModelManager implements ITextFileEditorModelManager {
// Model does not exist
else {
model = this.instantiationService.createInstance(TextFileEditorModel, resource, encoding);
model = this.instantiationService.createInstance(TextFileEditorModel, resource, options ? options.encoding : void 0);
modelPromise = model.load();
// Install state change listener
@ -376,6 +322,26 @@ export class TextFileEditorModelManager implements ITextFileEditorModelManager {
this.mapResourceToModelContentChangeListener.clear();
}
public disposeModel(model: TextFileEditorModel): void {
if (!model) {
return; // we need data!
}
if (model.isDisposed()) {
return; // already disposed
}
if (this.mapResourceToPendingModelLoaders.has(model.getResource())) {
return; // not yet loaded
}
if (model.isDirty()) {
return; // not saved
}
model.dispose();
}
public dispose(): void {
this.toUnbind = dispose(this.toUnbind);
}

View file

@ -141,6 +141,11 @@ export interface IRawTextContent extends IBaseStat {
encoding: string;
}
export interface IModelLoadOrCreateOptions {
encoding?: string;
reload?: boolean;
}
export interface ITextFileEditorModelManager {
onModelDisposed: Event<URI>;
@ -162,7 +167,9 @@ export interface ITextFileEditorModelManager {
getAll(resource?: URI): ITextFileEditorModel[];
loadOrCreate(resource: URI, preferredEncoding?: string, refresh?: boolean): TPromise<ITextEditorModel>;
loadOrCreate(resource: URI, options?: IModelLoadOrCreateOptions): TPromise<ITextEditorModel>;
disposeModel(model: ITextFileEditorModel): void;
}
export interface IModelSaveOptions {

View file

@ -11,7 +11,7 @@ import { TPromise } from 'vs/base/common/winjs.base';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager';
import { join } from 'vs/base/common/paths';
import { workbenchInstantiationService, TestEditorGroupService, createFileInput, TestFileService } from 'vs/workbench/test/workbenchTestServices';
import { workbenchInstantiationService, TestEditorGroupService, TestFileService } from 'vs/workbench/test/workbenchTestServices';
import { onError } from 'vs/base/test/common/utils';
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel';
@ -108,17 +108,17 @@ suite('Files - TextFileEditorModelManager', () => {
const resource = URI.file('/test.html');
const encoding = 'utf8';
manager.loadOrCreate(resource, encoding, true).done(model => {
manager.loadOrCreate(resource, { encoding, reload: true }).done(model => {
assert.ok(model);
assert.equal(model.getEncoding(), encoding);
assert.equal(manager.get(resource), model);
return manager.loadOrCreate(resource, encoding).then(model2 => {
return manager.loadOrCreate(resource, { encoding }).then(model2 => {
assert.equal(model2, model);
model.dispose();
return manager.loadOrCreate(resource, encoding).then(model3 => {
return manager.loadOrCreate(resource, { encoding }).then(model3 => {
assert.notEqual(model3, model2);
assert.equal(manager.get(resource), model3);
@ -150,34 +150,6 @@ suite('Files - TextFileEditorModelManager', () => {
model3.dispose();
});
test('disposes model when not open anymore', function () {
const manager: TestTextFileEditorModelManager = instantiationService.createInstance(TestTextFileEditorModelManager);
const resource = toResource('/path/index.txt');
const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, resource, 'utf8');
manager.add(resource, model);
const input = createFileInput(instantiationService, resource);
const stacks = accessor.editorGroupService.getStacksModel();
const group = stacks.openGroup('group', true);
group.openEditor(input);
accessor.editorGroupService.fireChange();
assert.ok(!model.isDisposed());
group.closeEditor(input);
accessor.editorGroupService.fireChange();
assert.ok(model.isDisposed());
model.dispose();
assert.ok(!accessor.modelService.getModel(model.getResource()));
manager.dispose();
});
test('events', function (done) {
TextFileEditorModel.DEFAULT_CONTENT_CHANGE_BUFFER_DELAY = 0;
TextFileEditorModel.DEFAULT_ORPHANED_CHANGE_BUFFER_DELAY = 0;
@ -235,11 +207,11 @@ suite('Files - TextFileEditorModelManager', () => {
disposeCounter++;
});
manager.loadOrCreate(resource1, 'utf8').done(model1 => {
manager.loadOrCreate(resource1, { encoding: 'utf8' }).done(model1 => {
accessor.fileService.fireFileChanges(new FileChangesEvent([{ resource: resource1, type: FileChangeType.DELETED }]));
accessor.fileService.fireFileChanges(new FileChangesEvent([{ resource: resource1, type: FileChangeType.ADDED }]));
return manager.loadOrCreate(resource2, 'utf8').then(model2 => {
return manager.loadOrCreate(resource2, { encoding: 'utf8' }).then(model2 => {
model1.textEditorModel.setValue('changed');
model1.updatePreferredEncoding('utf16');
@ -304,8 +276,8 @@ suite('Files - TextFileEditorModelManager', () => {
assert.equal(e[0].resource.toString(), resource1.toString());
});
manager.loadOrCreate(resource1, 'utf8').done(model1 => {
return manager.loadOrCreate(resource2, 'utf8').then(model2 => {
manager.loadOrCreate(resource1, { encoding: 'utf8' }).done(model1 => {
return manager.loadOrCreate(resource2, { encoding: 'utf8' }).then(model2 => {
model1.textEditorModel.setValue('changed');
model1.updatePreferredEncoding('utf16');
@ -342,7 +314,7 @@ suite('Files - TextFileEditorModelManager', () => {
const resource = toResource('/path/index_something.txt');
manager.loadOrCreate(resource, 'utf8').done(model => {
manager.loadOrCreate(resource, { encoding: 'utf8' }).done(model => {
model.dispose();
assert.ok(!manager.get(resource));
@ -352,4 +324,25 @@ suite('Files - TextFileEditorModelManager', () => {
done();
}, error => onError(error, done));
});
test('dispose prevents dirty model from getting disposed', function (done) {
const manager: TestTextFileEditorModelManager = instantiationService.createInstance(TestTextFileEditorModelManager);
const resource = toResource('/path/index_something.txt');
manager.loadOrCreate(resource, { encoding: 'utf8' }).done((model: TextFileEditorModel) => {
model.textEditorModel.setValue('make dirty');
manager.disposeModel(model);
assert.ok(!model.isDisposed());
model.revert(true);
manager.disposeModel(model);
assert.ok(model.isDisposed());
manager.dispose();
done();
}, error => onError(error, done));
});
});

View file

@ -17,29 +17,40 @@ import network = require('vs/base/common/network');
import { ITextModelResolverService, ITextModelContentProvider, ITextEditorModel } from 'vs/editor/common/services/resolverService';
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
import { TextFileEditorModel } from "vs/workbench/services/textfile/common/textFileEditorModel";
class ResourceModelCollection extends ReferenceCollection<TPromise<ITextEditorModel>> {
private providers: { [scheme: string]: ITextModelContentProvider[] } = Object.create(null);
constructor(
@IInstantiationService private instantiationService: IInstantiationService
@IInstantiationService private instantiationService: IInstantiationService,
@ITextFileService private textFileService: ITextFileService
) {
super();
}
createReferencedObject(key: string): TPromise<ITextEditorModel> {
public createReferencedObject(key: string): TPromise<ITextEditorModel> {
const resource = URI.parse(key);
return this.resolveTextModelContent(key)
.then(() => this.instantiationService.createInstance(ResourceEditorModel, resource));
if (resource.scheme === network.Schemas.file) {
return this.textFileService.models.loadOrCreate(resource);
}
return this.resolveTextModelContent(key).then(() => this.instantiationService.createInstance(ResourceEditorModel, resource));
}
destroyReferencedObject(modelPromise: TPromise<ITextEditorModel>): void {
modelPromise.done(model => model.dispose());
public destroyReferencedObject(modelPromise: TPromise<ITextEditorModel>): void {
modelPromise.done(model => {
if (model instanceof TextFileEditorModel) {
this.textFileService.models.disposeModel(model);
} else {
model.dispose();
}
});
}
registerTextModelContentProvider(scheme: string, provider: ITextModelContentProvider): IDisposable {
public registerTextModelContentProvider(scheme: string, provider: ITextModelContentProvider): IDisposable {
const registry = this.providers;
const providers = registry[scheme] || (registry[scheme] = []);
@ -98,7 +109,7 @@ export class TextModelResolverService implements ITextModelResolverService {
this.resourceModelCollection = instantiationService.createInstance(ResourceModelCollection);
}
createModelReference(resource: URI): TPromise<IReference<ITextEditorModel>> {
public createModelReference(resource: URI): TPromise<IReference<ITextEditorModel>> {
const uri = resource.toString();
let promise = this.promiseCache[uri];
@ -112,12 +123,6 @@ export class TextModelResolverService implements ITextModelResolverService {
}
private _createModelReference(resource: URI): TPromise<IReference<ITextEditorModel>> {
// File Schema: use text file service
// TODO ImmortalReference is a hack
if (resource.scheme === network.Schemas.file) {
return this.textFileService.models.loadOrCreate(resource)
.then(model => new ImmortalReference(model));
}
// Untitled Schema: go through cached input
// TODO ImmortalReference is a hack
@ -144,12 +149,13 @@ export class TextModelResolverService implements ITextModelResolverService {
model => ({ object: model, dispose: () => ref.dispose() }),
err => {
ref.dispose();
return TPromise.wrapError(err);
}
);
}
registerTextModelContentProvider(scheme: string, provider: ITextModelContentProvider): IDisposable {
public registerTextModelContentProvider(scheme: string, provider: ITextModelContentProvider): IDisposable {
return this.resourceModelCollection.registerTextModelContentProvider(scheme, provider);
}
}

View file

@ -21,6 +21,7 @@ import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textF
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager';
import { once } from "vs/base/common/event";
class ServiceAccessor {
constructor(
@ -74,6 +75,14 @@ suite('Workbench - TextModelResolverService', () => {
assert.ok(model);
assert.equal(model.getValue(), 'Hello Test');
let disposed = false;
once(model.onDispose)(() => {
disposed = true;
});
input.dispose();
assert.equal(disposed, true);
dispose.dispose();
done();
});
@ -90,7 +99,14 @@ suite('Workbench - TextModelResolverService', () => {
assert.ok(editorModel);
assert.equal(editorModel.getValue(), 'Hello Html');
let disposed = false;
once(model.onDispose)(() => {
disposed = true;
});
ref.dispose();
assert.equal(disposed, true);
});
});
});

View file

@ -11,6 +11,7 @@ import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorIn
import { IFilesConfiguration } from 'vs/platform/files/common/files';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import Event, { Emitter, once } from 'vs/base/common/event';
import { ResourceMap } from 'vs/base/common/map';
export const IUntitledEditorService = createDecorator<IUntitledEditorService>('untitledEditorService');
@ -82,8 +83,8 @@ export class UntitledEditorService implements IUntitledEditorService {
public _serviceBrand: any;
private static CACHE: { [resource: string]: UntitledEditorInput } = Object.create(null);
private static KNOWN_ASSOCIATED_FILE_PATHS: { [resource: string]: boolean } = Object.create(null);
private static CACHE: ResourceMap<UntitledEditorInput> = new ResourceMap<UntitledEditorInput>();
private static KNOWN_ASSOCIATED_FILE_PATHS: ResourceMap<boolean> = new ResourceMap<boolean>();
private _onDidChangeContent: Emitter<URI>;
private _onDidChangeDirty: Emitter<URI>;
@ -117,15 +118,15 @@ export class UntitledEditorService implements IUntitledEditorService {
}
public get(resource: URI): UntitledEditorInput {
return UntitledEditorService.CACHE[resource.toString()];
return UntitledEditorService.CACHE.get(resource);
}
public getAll(resources?: URI[]): UntitledEditorInput[] {
if (resources) {
return arrays.coalesce(resources.map((r) => this.get(r)));
return arrays.coalesce(resources.map(r => this.get(r)));
}
return Object.keys(UntitledEditorService.CACHE).map((key) => UntitledEditorService.CACHE[key]);
return UntitledEditorService.CACHE.values();
}
public revertAll(resources?: URI[], force?: boolean): URI[] {
@ -151,10 +152,9 @@ export class UntitledEditorService implements IUntitledEditorService {
}
public getDirty(): URI[] {
return Object.keys(UntitledEditorService.CACHE)
.map((key) => UntitledEditorService.CACHE[key])
.filter((i) => i.isDirty())
.map((i) => i.getResource());
return UntitledEditorService.CACHE.values()
.filter(i => i.isDirty())
.map(i => i.getResource());
}
public createOrGet(resource?: URI, modeId?: string, initialValue?: string): UntitledEditorInput {
@ -164,13 +164,13 @@ export class UntitledEditorService implements IUntitledEditorService {
resource = this.resourceToUntitled(resource); // ensure we have the right scheme
if (hasAssociatedFilePath) {
UntitledEditorService.KNOWN_ASSOCIATED_FILE_PATHS[resource.toString()] = true; // remember for future lookups
UntitledEditorService.KNOWN_ASSOCIATED_FILE_PATHS.set(resource, true); // remember for future lookups
}
}
// Return existing instance if asked for it
if (resource && UntitledEditorService.CACHE[resource.toString()]) {
return UntitledEditorService.CACHE[resource.toString()];
if (resource && UntitledEditorService.CACHE.has(resource)) {
return UntitledEditorService.CACHE.get(resource);
}
// Create new otherwise
@ -181,11 +181,11 @@ export class UntitledEditorService implements IUntitledEditorService {
if (!resource) {
// Create new taking a resource URI that is not already taken
let counter = Object.keys(UntitledEditorService.CACHE).length + 1;
let counter = UntitledEditorService.CACHE.size + 1;
do {
resource = URI.from({ scheme: UntitledEditorInput.SCHEMA, path: `Untitled-${counter}` });
counter++;
} while (Object.keys(UntitledEditorService.CACHE).indexOf(resource.toString()) >= 0);
} while (UntitledEditorService.CACHE.has(resource));
}
// Look up default language from settings if any
@ -217,8 +217,8 @@ export class UntitledEditorService implements IUntitledEditorService {
// Remove from cache on dispose
const onceDispose = once(input.onDispose);
onceDispose(() => {
delete UntitledEditorService.CACHE[input.getResource().toString()];
delete UntitledEditorService.KNOWN_ASSOCIATED_FILE_PATHS[input.getResource().toString()];
UntitledEditorService.CACHE.delete(input.getResource());
UntitledEditorService.KNOWN_ASSOCIATED_FILE_PATHS.delete(input.getResource());
contentListener.dispose();
dirtyListener.dispose();
encodingListener.dispose();
@ -226,7 +226,7 @@ export class UntitledEditorService implements IUntitledEditorService {
});
// Add to cache
UntitledEditorService.CACHE[resource.toString()] = input;
UntitledEditorService.CACHE.set(resource, input);
return input;
}
@ -240,7 +240,7 @@ export class UntitledEditorService implements IUntitledEditorService {
}
public hasAssociatedFilePath(resource: URI): boolean {
return !!UntitledEditorService.KNOWN_ASSOCIATED_FILE_PATHS[resource.toString()];
return UntitledEditorService.KNOWN_ASSOCIATED_FILE_PATHS.has(resource);
}
public dispose(): void {

View file

@ -145,8 +145,6 @@ class TestFileEditorInput extends EditorInput implements IFileEditorInput {
return other && this.id === other.id && other instanceof TestFileEditorInput;
}
public setResource(r: URI): void {
}
public setEncoding(encoding: string) {
}
@ -1568,11 +1566,10 @@ suite('Editor Stacks Model', () => {
assert.ok(model.isOpen(input1Resource));
assert.ok(group1.contains(input1Resource));
assert.equal(model.count(input1Resource), 1);
assert.equal(model.count(input1), 1);
assert.equal(group1.getEditor(input1Resource), input1);
assert.ok(!group1.getEditor(input1ResourceUpper));
assert.equal(model.count(input1ResourceUpper), 0);
assert.ok(!model.isOpen(input1ResourceUpper));
assert.ok(!group1.contains(input1ResourceUpper));
@ -1585,7 +1582,7 @@ suite('Editor Stacks Model', () => {
assert.ok(!group1.getEditor(input1ResourceUpper));
assert.ok(group2.contains(input1Resource));
assert.equal(group2.getEditor(input1Resource), input1);
assert.equal(model.count(input1Resource), 1);
assert.equal(model.count(input1), 1);
const input1ResourceClone = URI.file('/hello/world.txt');
const input1Clone = input(void 0, false, input1ResourceClone);
@ -1733,6 +1730,72 @@ suite('Editor Stacks Model', () => {
assert.equal(input1.isDisposed(), false);
});
test('Stack - Multiple Editors - Editor Disposed on Close (same input, files)', function () {
const model = create();
const group1 = model.openGroup('group1');
const group2 = model.openGroup('group2');
const input1 = input(void 0, void 0, URI.file('/hello/world.txt'));
group1.openEditor(input1, { pinned: true, active: true });
group2.openEditor(input1, { pinned: true, active: true });
group2.closeEditor(input1);
assert.equal(input1.isDisposed(), false);
group1.closeEditor(input1);
assert.equal(input1.isDisposed(), true);
});
test('Stack - Multiple Editors - Editor Disposed on Close (same input, files, diff)', function () {
const model = create();
const group1 = model.openGroup('group1');
const group2 = model.openGroup('group2');
const input1 = input(void 0, void 0, URI.file('/hello/world.txt'));
const input2 = input(void 0, void 0, URI.file('/hello/world_other.txt'));
const diffInput = new DiffEditorInput('name', 'description', input2, input1);
group1.openEditor(input1, { pinned: true, active: true });
group2.openEditor(diffInput, { pinned: true, active: true });
group1.closeEditor(input1);
assert.equal(input1.isDisposed(), false);
assert.equal(input2.isDisposed(), false);
assert.equal(diffInput.isDisposed(), false);
group2.closeEditor(diffInput);
assert.equal(input1.isDisposed(), true);
assert.equal(input2.isDisposed(), true);
assert.equal(diffInput.isDisposed(), true);
});
test('Stack - Multiple Editors - Editor Disposed on Close (same input, files, diff, close diff)', function () {
const model = create();
const group1 = model.openGroup('group1');
const group2 = model.openGroup('group2');
const input1 = input(void 0, void 0, URI.file('/hello/world.txt'));
const input2 = input(void 0, void 0, URI.file('/hello/world_other.txt'));
const diffInput = new DiffEditorInput('name', 'description', input2, input1);
group1.openEditor(input1, { pinned: true, active: true });
group2.openEditor(diffInput, { pinned: true, active: true });
group2.closeEditor(diffInput);
assert.equal(input1.isDisposed(), false);
assert.equal(input2.isDisposed(), true);
assert.equal(diffInput.isDisposed(), true);
group1.closeEditor(input1);
assert.equal(input1.isDisposed(), true);
});
test('Stack - Multiple Editors - Editor Emits Dirty and Label Changed', function () {
const model = create();

View file

@ -568,8 +568,8 @@ export class TestEditorService implements IWorkbenchEditorService {
return TPromise.as(null);
}
public createInput(input: IResourceInput): TPromise<IEditorInput> {
return TPromise.as(null);
public createInput(input: IResourceInput): IEditorInput {
return null;
}
}