diff --git a/src/vs/test/utils/servicesTestUtils.ts b/src/vs/test/utils/servicesTestUtils.ts index b311737d6ed..d0317c15d80 100644 --- a/src/vs/test/utils/servicesTestUtils.ts +++ b/src/vs/test/utils/servicesTestUtils.ts @@ -296,44 +296,6 @@ export class TestStorageService extends EventEmitter implements IStorageService } } -export class TestUntitledEditorService implements IUntitledEditorService { - public _serviceBrand: any; - - private _onDidChangeDirty = new Emitter(); - - public get onDidChangeDirty(): Event { - return this._onDidChangeDirty.event; - } - - public get(resource: URI) { - return null; - } - - public getAll() { - return []; - } - - public getDirty() { - return []; - } - - public revertAll(resources?: URI[]): URI[] { - return []; - } - - public isDirty() { - return false; - } - - public createOrGet(resource?: URI) { - return null; - } - - public hasAssociatedFilePath(resource: URI) { - return false; - } -} - export class TestEditorGroupService implements IEditorGroupService { public _serviceBrand: any; diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index ecc6d901344..41bd56c7b1a 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -21,6 +21,7 @@ import {IMode} from 'vs/editor/common/modes'; import {UntitledEditorInput} from 'vs/workbench/common/editor/untitledEditorInput'; import {IFileEditorInput, EncodingMode, IEncodingSupport, asFileEditorInput, getUntitledOrFileResource} from 'vs/workbench/common/editor'; import {IDisposable, combinedDisposable, dispose} from 'vs/base/common/lifecycle'; +import {IUntitledEditorService} from 'vs/workbench/services/untitled/common/untitledEditorService'; import {IMessageService, Severity} from 'vs/platform/message/common/message'; import {IEditorAction, ICommonCodeEditor, IModelContentChangedEvent, IModelOptionsChangedEvent, IModelModeChangedEvent, ICursorPositionChangedEvent} from 'vs/editor/common/editorCommon'; import {OpenGlobalSettingsAction} from 'vs/workbench/browser/actions/openSettings'; @@ -28,7 +29,6 @@ import {ICodeEditor, IDiffEditor} from 'vs/editor/browser/editorBrowser'; import {TrimTrailingWhitespaceAction} from 'vs/editor/contrib/linesOperations/common/linesOperations'; import {EndOfLineSequence, ITokenizedModel, EditorType, ITextModel, IDiffEditorModel, IEditor} from 'vs/editor/common/editorCommon'; import {IndentUsingSpaces, IndentUsingTabs, DetectIndentation, IndentationToSpacesAction, IndentationToTabsAction} from 'vs/editor/contrib/indentation/common/indentation'; -import {EventType, ResourceEvent} from 'vs/workbench/common/events'; import {BaseTextEditor} from 'vs/workbench/browser/parts/editor/textEditor'; import {IEditor as IBaseEditor} from 'vs/platform/editor/common/editor'; import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService'; @@ -43,6 +43,8 @@ import {Selection} from 'vs/editor/common/core/selection'; import {IEditorGroupService} from 'vs/workbench/services/group/common/groupService'; import {TabFocus} from 'vs/editor/common/config/commonEditorConfig'; +import {ITextFileService} from 'vs/workbench/parts/files/common/files'; // TODO@Ben layer breaker + function getCodeEditor(editorWidget: IEditor): ICommonCodeEditor { if (editorWidget) { if (editorWidget.getEditorType() === EditorType.IDiffEditor) { @@ -240,7 +242,9 @@ export class EditorStatus implements IStatusbarItem { @IQuickOpenService private quickOpenService: IQuickOpenService, @IInstantiationService private instantiationService: IInstantiationService, @IEventService private eventService: IEventService, + @IUntitledEditorService private untitledEditorService: IUntitledEditorService, @IModeService private modeService: IModeService, + @ITextFileService private textFileService: ITextFileService, @IConfigurationService private configurationService: IConfigurationService ) { this.toDispose = []; @@ -295,7 +299,8 @@ export class EditorStatus implements IStatusbarItem { } }, this.editorGroupService.onEditorsChanged(() => this.onEditorsChanged()), - this.eventService.addListener2(EventType.RESOURCE_ENCODING_CHANGED, (e: ResourceEvent) => this.onResourceEncodingChange(e.resource)), + this.untitledEditorService.onDidChangeEncoding(r => this.onResourceEncodingChange(r)), + this.textFileService.models.onModelEncodingChanged(e => this.onResourceEncodingChange(e.resource)), TabFocus.onDidChangeTabFocus((e) => this.onTabFocusModeChange()) ); diff --git a/src/vs/workbench/common/editor/untitledEditorInput.ts b/src/vs/workbench/common/editor/untitledEditorInput.ts index 1cf37109845..d0e265ba987 100644 --- a/src/vs/workbench/common/editor/untitledEditorInput.ts +++ b/src/vs/workbench/common/editor/untitledEditorInput.ts @@ -17,6 +17,7 @@ import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace'; import {IModeService} from 'vs/editor/common/services/modeService'; import {IDisposable, dispose} from 'vs/base/common/lifecycle'; import {IEventService} from 'vs/platform/event/common/event'; +import Event, {Emitter} from 'vs/base/common/event'; import {ITextFileService} from 'vs/workbench/parts/files/common/files'; // TODO@Ben layer breaker @@ -33,6 +34,8 @@ export class UntitledEditorInput extends AbstractUntitledEditorInput { private modeId: string; private cachedModel: UntitledEditorModel; + private _onDidModelChangeEncoding: Emitter; + private toUnbind: IDisposable[]; constructor( @@ -52,6 +55,11 @@ export class UntitledEditorInput extends AbstractUntitledEditorInput { this.hasAssociatedFilePath = hasAssociatedFilePath; this.modeId = modeId; this.toUnbind = []; + this._onDidModelChangeEncoding = new Emitter(); + } + + public get onDidModelChangeEncoding(): Event { + return this._onDidModelChangeEncoding.event; } public getTypeId(): string { @@ -153,8 +161,9 @@ export class UntitledEditorInput extends AbstractUntitledEditorInput { const model = this.instantiationService.createInstance(UntitledEditorModel, content, mime || MIME_TEXT, this.resource, this.hasAssociatedFilePath); - // detect dirty state changes on model and re-emit + // re-emit some events from the model this.toUnbind.push(model.onDidChangeDirty(() => this._onDidChangeDirty.fire())); + this.toUnbind.push(model.onDidChangeEncoding(() => this._onDidModelChangeEncoding.fire())); return model; } @@ -175,6 +184,7 @@ export class UntitledEditorInput extends AbstractUntitledEditorInput { } public dispose(): void { + this._onDidModelChangeEncoding.dispose(); // Listeners dispose(this.toUnbind); diff --git a/src/vs/workbench/common/editor/untitledEditorModel.ts b/src/vs/workbench/common/editor/untitledEditorModel.ts index a21fc4a9140..b0a573577bd 100644 --- a/src/vs/workbench/common/editor/untitledEditorModel.ts +++ b/src/vs/workbench/common/editor/untitledEditorModel.ts @@ -9,11 +9,9 @@ import {TPromise} from 'vs/base/common/winjs.base'; import {EditorModel, IEncodingSupport} from 'vs/workbench/common/editor'; import {StringEditorModel} from 'vs/workbench/common/editor/stringEditorModel'; import URI from 'vs/base/common/uri'; -import {EventType, EndOfLinePreference} from 'vs/editor/common/editorCommon'; -import {EventType as WorkbenchEventType, ResourceEvent} from 'vs/workbench/common/events'; +import {EndOfLinePreference} from 'vs/editor/common/editorCommon'; import {IFilesConfiguration} from 'vs/platform/files/common/files'; import {IConfigurationService} from 'vs/platform/configuration/common/configuration'; -import {IEventService} from 'vs/platform/event/common/event'; import {IModeService} from 'vs/editor/common/services/modeService'; import {IModelService} from 'vs/editor/common/services/modelService'; import {IMode} from 'vs/editor/common/modes'; @@ -26,6 +24,7 @@ export class UntitledEditorModel extends StringEditorModel implements IEncodingS private dirty: boolean; private _onDidChangeDirty: Emitter; + private _onDidChangeEncoding: Emitter; private configuredEncoding: string; private preferredEncoding: string; @@ -39,7 +38,6 @@ export class UntitledEditorModel extends StringEditorModel implements IEncodingS hasAssociatedFilePath: boolean, @IModeService modeService: IModeService, @IModelService modelService: IModelService, - @IEventService private eventService: IEventService, @IConfigurationService private configurationService: IConfigurationService ) { super(value, mime, resource, modeService, modelService); @@ -48,6 +46,7 @@ export class UntitledEditorModel extends StringEditorModel implements IEncodingS this.dirty = hasAssociatedFilePath; // untitled associated to file path are dirty right away this._onDidChangeDirty = new Emitter(); + this._onDidChangeEncoding = new Emitter(); this.registerListeners(); } @@ -56,6 +55,10 @@ export class UntitledEditorModel extends StringEditorModel implements IEncodingS return this._onDidChangeDirty.event; } + public get onDidChangeEncoding(): Event { + return this._onDidChangeEncoding.event; + } + protected getOrCreateMode(modeService: IModeService, mime: string, firstLineText?: string): TPromise { if (isUnspecific(mime)) { return modeService.getOrCreateModeByFilenameOrFirstLine(this.resource.fsPath, firstLineText); // lookup mode via resource path if the provided mime is unspecific @@ -100,7 +103,7 @@ export class UntitledEditorModel extends StringEditorModel implements IEncodingS // Emit if it changed if (oldEncoding !== this.preferredEncoding) { - this.eventService.emit(WorkbenchEventType.RESOURCE_ENCODING_CHANGED, new ResourceEvent(this.resource)); + this._onDidChangeEncoding.fire(); } } @@ -163,5 +166,8 @@ export class UntitledEditorModel extends StringEditorModel implements IEncodingS this.configurationChangeListener.dispose(); this.configurationChangeListener = null; } + + this._onDidChangeDirty.dispose(); + this._onDidChangeEncoding.dispose(); } } \ No newline at end of file diff --git a/src/vs/workbench/common/events.ts b/src/vs/workbench/common/events.ts deleted file mode 100644 index 714f37ac2ac..00000000000 --- a/src/vs/workbench/common/events.ts +++ /dev/null @@ -1,29 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * 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 URI from 'vs/base/common/uri'; -import {Event} from 'vs/base/common/events'; - -/** - * All workbench events are listed here. - */ -export class EventType { - - /** - * Event type for when a resources encoding changes. - */ - static RESOURCE_ENCODING_CHANGED = 'resourceEncodingChanged'; -} - -export class ResourceEvent extends Event { - public resource: URI; - - constructor(resource: URI, originalEvent?: any) { - super(originalEvent); - - this.resource = resource; - } -} \ No newline at end of file diff --git a/src/vs/workbench/parts/files/common/editors/textFileEditorModel.ts b/src/vs/workbench/parts/files/common/editors/textFileEditorModel.ts index 0d27651abd7..b389cbbd8ef 100644 --- a/src/vs/workbench/parts/files/common/editors/textFileEditorModel.ts +++ b/src/vs/workbench/parts/files/common/editors/textFileEditorModel.ts @@ -16,7 +16,6 @@ import diagnostics = require('vs/base/common/diagnostics'); import types = require('vs/base/common/types'); import {IModelContentChangedEvent} from 'vs/editor/common/editorCommon'; import {IMode} from 'vs/editor/common/modes'; -import {EventType as WorkbenchEventType, ResourceEvent} from 'vs/workbench/common/events'; import {ITextFileService, IAutoSaveConfiguration, ModelState, ITextFileEditorModel, ISaveErrorHandler, ISaveParticipant, StateChange} from 'vs/workbench/parts/files/common/files'; import {EncodingMode, EditorModel} from 'vs/workbench/common/editor'; import {BaseTextEditorModel} from 'vs/workbench/common/editor/textEditorModel'; @@ -223,7 +222,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil if (this.preferredEncoding) { this.updatePreferredEncoding(this.contentEncoding); // make sure to reflect the real encoding of the file (never out of sync) } else if (oldEncoding !== this.contentEncoding) { - this.eventService.emit(WorkbenchEventType.RESOURCE_ENCODING_CHANGED, new ResourceEvent(this.resource)); + this._onDidStateChange.fire(StateChange.ENCODING); } // Update Existing Model @@ -644,7 +643,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil this.preferredEncoding = encoding; // Emit - this.eventService.emit(WorkbenchEventType.RESOURCE_ENCODING_CHANGED, new ResourceEvent(this.resource)); + this._onDidStateChange.fire(StateChange.ENCODING); } private isNewEncoding(encoding: string): boolean { diff --git a/src/vs/workbench/parts/files/common/editors/textFileEditorModelManager.ts b/src/vs/workbench/parts/files/common/editors/textFileEditorModelManager.ts index a36a531cba7..70d2f728ab4 100644 --- a/src/vs/workbench/parts/files/common/editors/textFileEditorModelManager.ts +++ b/src/vs/workbench/parts/files/common/editors/textFileEditorModelManager.ts @@ -28,6 +28,7 @@ export class TextFileEditorModelManager implements ITextFileEditorModelManager { private _onModelSaveError: Emitter; private _onModelSaved: Emitter; private _onModelReverted: Emitter; + private _onModelEncodingChanged: Emitter; private mapResourceToDisposeListener: { [resource: string]: IDisposable; }; private mapResourceToStateChangeListener: { [resource: string]: IDisposable; }; @@ -46,11 +47,13 @@ export class TextFileEditorModelManager implements ITextFileEditorModelManager { this._onModelSaveError = new Emitter(); this._onModelSaved = new Emitter(); this._onModelReverted = new Emitter(); + this._onModelEncodingChanged = new Emitter(); this.toUnbind.push(this._onModelDirty); this.toUnbind.push(this._onModelSaveError); this.toUnbind.push(this._onModelSaved); this.toUnbind.push(this._onModelReverted); + this.toUnbind.push(this._onModelEncodingChanged); this.mapResourcePathToModel = Object.create(null); this.mapResourceToDisposeListener = Object.create(null); @@ -151,6 +154,10 @@ export class TextFileEditorModelManager implements ITextFileEditorModelManager { return this._onModelReverted.event; } + public get onModelEncodingChanged(): Event { + return this._onModelEncodingChanged.event; + } + public get(resource: URI): TextFileEditorModel { return this.mapResourcePathToModel[resource.toString()]; } @@ -196,6 +203,9 @@ export class TextFileEditorModelManager implements ITextFileEditorModelManager { case StateChange.REVERTED: this._onModelReverted.fire(event); break; + case StateChange.ENCODING: + this._onModelEncodingChanged.fire(event); + break; } }); } diff --git a/src/vs/workbench/parts/files/common/files.ts b/src/vs/workbench/parts/files/common/files.ts index c6896c3d957..32989913884 100644 --- a/src/vs/workbench/parts/files/common/files.ts +++ b/src/vs/workbench/parts/files/common/files.ts @@ -185,7 +185,8 @@ export enum StateChange { SAVING, SAVE_ERROR, SAVED, - REVERTED + REVERTED, + ENCODING } export class TextFileModelChangeEvent { @@ -262,6 +263,7 @@ export interface ITextFileEditorModelManager { onModelSaveError: Event; onModelSaved: Event; onModelReverted: Event; + onModelEncodingChanged: Event; get(resource: URI): ITextFileEditorModel; diff --git a/src/vs/workbench/parts/files/test/browser/textFileEditorModelManager.test.ts b/src/vs/workbench/parts/files/test/browser/textFileEditorModelManager.test.ts index c4637b9a14a..12c67a17a79 100644 --- a/src/vs/workbench/parts/files/test/browser/textFileEditorModelManager.test.ts +++ b/src/vs/workbench/parts/files/test/browser/textFileEditorModelManager.test.ts @@ -273,6 +273,7 @@ suite('Files - TextFileEditorModelManager', () => { let dirtyCounter = 0; let revertedCounter = 0; let savedCounter = 0; + let encodingCounter = 0; manager.onModelDirty(e => { dirtyCounter++; @@ -289,9 +290,15 @@ suite('Files - TextFileEditorModelManager', () => { assert.equal(e.resource.toString(), resource1.toString()); }); + manager.onModelEncodingChanged(e => { + encodingCounter++; + assert.equal(e.resource.toString(), resource1.toString()); + }); + manager.loadOrCreate(resource1, 'utf8').then(model1 => { return manager.loadOrCreate(resource2, 'utf8').then(model2 => { model1.textEditorModel.setValue('changed'); + model1.updatePreferredEncoding('utf16'); return model1.revert().then(() => { model1.textEditorModel.setValue('changed again'); @@ -304,6 +311,7 @@ suite('Files - TextFileEditorModelManager', () => { assert.equal(dirtyCounter, 2); assert.equal(revertedCounter, 1); assert.equal(savedCounter, 1); + assert.equal(encodingCounter, 2); done(); }); diff --git a/src/vs/workbench/parts/files/test/browser/textFileService.test.ts b/src/vs/workbench/parts/files/test/browser/textFileService.test.ts index 3ec8e7a3943..a6f2ee37a92 100644 --- a/src/vs/workbench/parts/files/test/browser/textFileService.test.ts +++ b/src/vs/workbench/parts/files/test/browser/textFileService.test.ts @@ -13,7 +13,7 @@ import {workbenchInstantiationService, TestLifecycleService, TestTextFileService import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation'; import {TextFileEditorModel} from 'vs/workbench/parts/files/common/editors/textFileEditorModel'; import {ITextFileService} from 'vs/workbench/parts/files/common/files'; -import {ConfirmResult} from 'vs/workbench/common/editor'; +import {ConfirmResult, EncodingMode} from 'vs/workbench/common/editor'; import {IUntitledEditorService} from 'vs/workbench/services/untitled/common/untitledEditorService'; import {UntitledEditorModel} from 'vs/workbench/common/editor/untitledEditorModel'; import {TextFileEditorModelManager} from 'vs/workbench/parts/files/common/editors/textFileEditorModelManager'; @@ -56,6 +56,7 @@ suite('Files - TextFileService', () => { teardown(() => { model.dispose(); (accessor.textFileService.models).clear(); + (accessor.textFileService.models).dispose(); accessor.untitledEditorService.revertAll(); }); diff --git a/src/vs/workbench/services/untitled/common/untitledEditorService.ts b/src/vs/workbench/services/untitled/common/untitledEditorService.ts index 8564b12f46c..60057649a7d 100644 --- a/src/vs/workbench/services/untitled/common/untitledEditorService.ts +++ b/src/vs/workbench/services/untitled/common/untitledEditorService.ts @@ -21,6 +21,11 @@ export interface IUntitledEditorService { */ onDidChangeDirty: Event; + /** + * Events for when untitled editor encodings change. + */ + onDidChangeEncoding: Event; + /** * Returns the untitled editor input matching the provided resource. */ @@ -69,15 +74,21 @@ export class UntitledEditorService implements IUntitledEditorService { private static KNOWN_ASSOCIATED_FILE_PATHS: { [resource: string]: boolean } = Object.create(null); private _onDidChangeDirty: Emitter; + private _onDidChangeEncoding: Emitter; constructor(@IInstantiationService private instantiationService: IInstantiationService) { this._onDidChangeDirty = new Emitter(); + this._onDidChangeEncoding = new Emitter(); } public get onDidChangeDirty(): Event { return this._onDidChangeDirty.event; } + public get onDidChangeEncoding(): Event { + return this._onDidChangeEncoding.event; + } + public get(resource: URI): UntitledEditorInput { return UntitledEditorService.CACHE[resource.toString()]; } @@ -152,16 +163,21 @@ export class UntitledEditorService implements IUntitledEditorService { const input = this.instantiationService.createInstance(UntitledEditorInput, resource, hasAssociatedFilePath, modeId); - const listener = input.onDidChangeDirty(() => { + const dirtyListener = input.onDidChangeDirty(() => { this._onDidChangeDirty.fire(resource); }); + const encodingListener = input.onDidModelChangeEncoding(() => { + this._onDidChangeEncoding.fire(resource); + }); + // 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()]; - listener.dispose(); + dirtyListener.dispose(); + encodingListener.dispose(); }); // Add to cache @@ -181,4 +197,9 @@ export class UntitledEditorService implements IUntitledEditorService { public hasAssociatedFilePath(resource: URI): boolean { return !!UntitledEditorService.KNOWN_ASSOCIATED_FILE_PATHS[resource.toString()]; } + + public dispose(): void { + this._onDidChangeDirty.dispose(); + this._onDidChangeEncoding.dispose(); + } } \ No newline at end of file diff --git a/src/vs/workbench/test/common/editor/untitledEditor.test.ts b/src/vs/workbench/test/common/editor/untitledEditor.test.ts index 858a3da17b8..ab76007dd75 100644 --- a/src/vs/workbench/test/common/editor/untitledEditor.test.ts +++ b/src/vs/workbench/test/common/editor/untitledEditor.test.ts @@ -11,9 +11,10 @@ import {IInstantiationService} from 'vs/platform/instantiation/common/instantiat import {IUntitledEditorService} from 'vs/workbench/services/untitled/common/untitledEditorService'; import {workbenchInstantiationService} from 'vs/test/utils/servicesTestUtils'; import {UntitledEditorModel} from 'vs/workbench/common/editor/untitledEditorModel'; +import {UntitledEditorService} from 'vs/workbench/services/untitled/common/untitledEditorService'; class ServiceAccessor { - constructor(@IUntitledEditorService public untitledEditorService: IUntitledEditorService) { + constructor(@IUntitledEditorService public untitledEditorService: UntitledEditorService) { } } @@ -29,6 +30,7 @@ suite('Workbench - Untitled Editor', () => { teardown(() => { accessor.untitledEditorService.revertAll(); + accessor.untitledEditorService.dispose(); }); test('Untitled Editor Service', function (done) { @@ -122,4 +124,27 @@ suite('Workbench - Untitled Editor', () => { done(); }); }); + + test('encoding change event', function (done) { + const service = accessor.untitledEditorService; + const input = service.createOrGet(); + + let counter = 0; + + service.onDidChangeEncoding(r => { + counter++; + assert.equal(r.toString(), input.getResource().toString()); + }); + + // dirty + input.resolve().then((model: UntitledEditorModel) => { + model.setEncoding('utf16'); + + assert.equal(counter, 1); + + input.dispose(); + + done(); + }); + }); }); \ No newline at end of file