diff --git a/src/vs/test/utils/servicesTestUtils.ts b/src/vs/test/utils/servicesTestUtils.ts index c27c62103c6..98be57de9fd 100644 --- a/src/vs/test/utils/servicesTestUtils.ts +++ b/src/vs/test/utils/servicesTestUtils.ts @@ -41,6 +41,7 @@ import {IModeService} from 'vs/editor/common/services/modeService'; import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService'; import {ITextFileService} from 'vs/workbench/parts/files/common/files'; import {IHistoryService} from 'vs/workbench/services/history/common/history'; +import {UntitledEditorEvent} from 'vs/workbench/common/events'; export const TestWorkspace: IWorkspace = { resource: URI.file('C:\\testWorkspace'), @@ -283,6 +284,12 @@ 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; } diff --git a/src/vs/workbench/browser/parts/editor/stringEditor.ts b/src/vs/workbench/browser/parts/editor/stringEditor.ts index f83a5b09106..0ded58c83f2 100644 --- a/src/vs/workbench/browser/parts/editor/stringEditor.ts +++ b/src/vs/workbench/browser/parts/editor/stringEditor.ts @@ -13,7 +13,7 @@ import {TextEditorOptions, EditorModel, EditorInput, EditorOptions} from 'vs/wor import {BaseTextEditorModel} from 'vs/workbench/common/editor/textEditorModel'; import {UntitledEditorInput} from 'vs/workbench/common/editor/untitledEditorInput'; import {BaseTextEditor} from 'vs/workbench/browser/parts/editor/textEditor'; -import {UntitledEditorEvent, EventType} from 'vs/workbench/common/events'; +import {UntitledEditorEvent} from 'vs/workbench/common/events'; import {ITelemetryService} from 'vs/platform/telemetry/common/telemetry'; import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace'; import {IStorageService} from 'vs/platform/storage/common/storage'; @@ -23,6 +23,7 @@ import {IInstantiationService} from 'vs/platform/instantiation/common/instantiat import {IMessageService} from 'vs/platform/message/common/message'; import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService'; import {IThemeService} from 'vs/workbench/services/themes/common/themeService'; +import {IUntitledEditorService} from 'vs/workbench/services/untitled/common/untitledEditorService'; /** * An editor implementation that is capable of showing string inputs or promise inputs that resolve to a string. @@ -43,17 +44,20 @@ export class StringEditor extends BaseTextEditor { @IConfigurationService configurationService: IConfigurationService, @IEventService eventService: IEventService, @IWorkbenchEditorService editorService: IWorkbenchEditorService, - @IThemeService themeService: IThemeService + @IThemeService themeService: IThemeService, + @IUntitledEditorService private untitledEditorService: IUntitledEditorService ) { super(StringEditor.ID, telemetryService, instantiationService, contextService, storageService, messageService, configurationService, eventService, editorService, themeService); this.mapResourceToEditorViewState = Object.create(null); - this.toUnbind.push(this.eventService.addListener2(EventType.UNTITLED_FILE_SAVED, (e: UntitledEditorEvent) => this.onUntitledSavedEvent(e))); + this.toUnbind.push(this.untitledEditorService.onDidChangeDirty(e => this.onUntitledDirtyChange(e))); } - private onUntitledSavedEvent(e: UntitledEditorEvent): void { - delete this.mapResourceToEditorViewState[e.resource.toString()]; + private onUntitledDirtyChange(e: UntitledEditorEvent): void { + if (!this.untitledEditorService.isDirty(e.resource)) { + delete this.mapResourceToEditorViewState[e.resource.toString()]; // untitled file got reverted, so remove view state + } } public getTitle(): string { diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 5419531eeb6..0b82b4a192f 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -121,7 +121,9 @@ export interface IEditorInputFactory { * Each editor input is mapped to an editor that is capable of opening it through the Platform facade. */ export abstract class EditorInput extends EventEmitter implements IEditorInput { + protected _onDidChangeDirty: Emitter; + private disposed: boolean; constructor() { diff --git a/src/vs/workbench/common/editor/untitledEditorInput.ts b/src/vs/workbench/common/editor/untitledEditorInput.ts index a5e88527de7..f63ef3249cd 100644 --- a/src/vs/workbench/common/editor/untitledEditorInput.ts +++ b/src/vs/workbench/common/editor/untitledEditorInput.ts @@ -17,7 +17,6 @@ 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 {EventType as WorkbenchEventType, UntitledEditorEvent} from 'vs/workbench/common/events'; import {ITextFileService} from 'vs/workbench/parts/files/common/files'; // TODO@Ben layer breaker @@ -53,19 +52,6 @@ export class UntitledEditorInput extends AbstractUntitledEditorInput { this.hasAssociatedFilePath = hasAssociatedFilePath; this.modeId = modeId; this.toUnbind = []; - - this.registerListeners(); - } - - private registerListeners(): void { - this.toUnbind.push(this.eventService.addListener2(WorkbenchEventType.UNTITLED_FILE_SAVED, (e: UntitledEditorEvent) => this.onDirtyStateChange(e))); - this.toUnbind.push(this.eventService.addListener2(WorkbenchEventType.UNTITLED_FILE_DIRTY, (e: UntitledEditorEvent) => this.onDirtyStateChange(e))); - } - - private onDirtyStateChange(e: UntitledEditorEvent): void { - if (e.resource.toString() === this.resource.toString()) { - this._onDidChangeDirty.fire(); - } } public getTypeId(): string { @@ -163,7 +149,12 @@ export class UntitledEditorInput extends AbstractUntitledEditorInput { } } - return this.instantiationService.createInstance(UntitledEditorModel, content, mime || MIME_TEXT, this.resource, this.hasAssociatedFilePath); + const model = this.instantiationService.createInstance(UntitledEditorModel, content, mime || MIME_TEXT, this.resource, this.hasAssociatedFilePath); + + // detect dirty state changes on model and re-emit + this.toUnbind.push(model.onDidChangeDirty(() => this._onDidChangeDirty.fire())); + + return model; } public matches(otherInput: any): boolean { diff --git a/src/vs/workbench/common/editor/untitledEditorModel.ts b/src/vs/workbench/common/editor/untitledEditorModel.ts index 711315d13d6..9b53bcbe6a9 100644 --- a/src/vs/workbench/common/editor/untitledEditorModel.ts +++ b/src/vs/workbench/common/editor/untitledEditorModel.ts @@ -10,7 +10,7 @@ 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, UntitledEditorEvent, ResourceEvent} from 'vs/workbench/common/events'; +import {EventType as WorkbenchEventType, ResourceEvent} from 'vs/workbench/common/events'; import {IFilesConfiguration} from 'vs/platform/files/common/files'; import {IConfigurationService} from 'vs/platform/configuration/common/configuration'; import {IEventService} from 'vs/platform/event/common/event'; @@ -18,12 +18,15 @@ import {IModeService} from 'vs/editor/common/services/modeService'; import {IModelService} from 'vs/editor/common/services/modelService'; import {IMode} from 'vs/editor/common/modes'; import {isUnspecific} from 'vs/base/common/mime'; +import Event, {Emitter} from 'vs/base/common/event'; export class UntitledEditorModel extends StringEditorModel implements IEncodingSupport { private textModelChangeListener: IDisposable; private configurationChangeListener: IDisposable; private dirty: boolean; + private _onDidChangeDirty: Emitter; + private configuredEncoding: string; private preferredEncoding: string; @@ -41,9 +44,15 @@ export class UntitledEditorModel extends StringEditorModel implements IEncodingS this.dirty = hasAssociatedFilePath; // untitled associated to file path are dirty right away + this._onDidChangeDirty = new Emitter(); + this.registerListeners(); } + public get onDidChangeDirty(): Event { + return this._onDidChangeDirty.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 @@ -99,7 +108,7 @@ export class UntitledEditorModel extends StringEditorModel implements IEncodingS public revert(): void { this.dirty = false; - this.eventService.emit(WorkbenchEventType.UNTITLED_FILE_SAVED, new UntitledEditorEvent(this.resource)); + this._onDidChangeDirty.fire(); } public load(): TPromise { @@ -115,7 +124,7 @@ export class UntitledEditorModel extends StringEditorModel implements IEncodingS // Emit initial dirty event if we are if (this.dirty) { setTimeout(() => { - this.eventService.emit(WorkbenchEventType.UNTITLED_FILE_DIRTY, new UntitledEditorEvent(this.resource)); + this._onDidChangeDirty.fire(); }, 0 /* prevent race condition between creating model and emitting dirty event */); } @@ -126,7 +135,7 @@ export class UntitledEditorModel extends StringEditorModel implements IEncodingS private onModelContentChanged(): void { if (!this.dirty) { this.dirty = true; - this.eventService.emit(WorkbenchEventType.UNTITLED_FILE_DIRTY, new UntitledEditorEvent(this.resource)); + this._onDidChangeDirty.fire(); } } diff --git a/src/vs/workbench/common/events.ts b/src/vs/workbench/common/events.ts index 3e0492a7bac..516776fba4c 100644 --- a/src/vs/workbench/common/events.ts +++ b/src/vs/workbench/common/events.ts @@ -12,16 +12,6 @@ import {Event} from 'vs/base/common/events'; */ export class EventType { - /** - * Event type for when an untitled file is becoming dirty. - */ - static UNTITLED_FILE_DIRTY = 'untitledFileDirty'; - - /** - * Event type for when an untitled file is saved. - */ - static UNTITLED_FILE_SAVED = 'untitledFileSaved'; - /** * Event type for when a resources encoding changes. */ diff --git a/src/vs/workbench/parts/files/browser/fileActions.ts b/src/vs/workbench/parts/files/browser/fileActions.ts index 427b5e3a8a8..e28ec9cbfbd 100644 --- a/src/vs/workbench/parts/files/browser/fileActions.ts +++ b/src/vs/workbench/parts/files/browser/fileActions.ts @@ -23,7 +23,6 @@ import {Action, IAction} from 'vs/base/common/actions'; import {MessageType, IInputValidator} from 'vs/base/browser/ui/inputbox/inputBox'; import {ITree, IHighlightEvent} from 'vs/base/parts/tree/browser/tree'; import {dispose, IDisposable} from 'vs/base/common/lifecycle'; -import {EventType as WorkbenchEventType} from 'vs/workbench/common/events'; import {LocalFileChangeEvent, VIEWLET_ID, ITextFileService, TextFileChangeEvent, EventType as FileEventType} from 'vs/workbench/parts/files/common/files'; import {IFileService, IFileStat, IImportResult} from 'vs/platform/files/common/files'; import {DiffEditorInput, toDiffLabel} from 'vs/workbench/common/editor/diffEditorInput'; @@ -1565,8 +1564,7 @@ export abstract class BaseSaveAllAction extends BaseActionWithErrorReporting { this.toDispose.push(this.eventService.addListener2(FileEventType.FILE_SAVE_ERROR, (e: TextFileChangeEvent) => this.updateEnablement(true))); if (this.includeUntitled()) { - this.toDispose.push(this.eventService.addListener2(WorkbenchEventType.UNTITLED_FILE_DIRTY, () => this.updateEnablement(true))); - this.toDispose.push(this.eventService.addListener2(WorkbenchEventType.UNTITLED_FILE_SAVED, () => this.updateEnablement(false))); + this.toDispose.push(this.untitledEditorService.onDidChangeDirty(e => this.updateEnablement(this.untitledEditorService.isDirty(e.resource)))); } } diff --git a/src/vs/workbench/parts/files/browser/fileTracker.ts b/src/vs/workbench/parts/files/browser/fileTracker.ts index 9e974c96ee9..ead74bdc580 100644 --- a/src/vs/workbench/parts/files/browser/fileTracker.ts +++ b/src/vs/workbench/parts/files/browser/fileTracker.ts @@ -22,7 +22,7 @@ import {LocalFileChangeEvent, TextFileChangeEvent, VIEWLET_ID, BINARY_FILE_EDITO import {FileChangeType, FileChangesEvent, EventType as CommonFileEventType, IFileService} from 'vs/platform/files/common/files'; import {FileEditorInput} from 'vs/workbench/parts/files/common/editors/fileEditorInput'; import {TextFileEditorModel, CACHE} from 'vs/workbench/parts/files/common/editors/textFileEditorModel'; -import {EventType as WorkbenchEventType, UntitledEditorEvent} from 'vs/workbench/common/events'; +import {UntitledEditorEvent} from 'vs/workbench/common/events'; import {IEditorGroupService} from 'vs/workbench/services/group/common/groupService'; import {IUntitledEditorService} from 'vs/workbench/services/untitled/common/untitledEditorService'; import {ILifecycleService} from 'vs/platform/lifecycle/common/lifecycle'; @@ -78,8 +78,7 @@ export class FileTracker implements IWorkbenchContribution { // Update editors and inputs from local changes and saves this.toUnbind.push(this.editorGroupService.onEditorsChanged(() => this.onEditorsChanged())); - this.toUnbind.push(this.eventService.addListener2(WorkbenchEventType.UNTITLED_FILE_SAVED, (e: UntitledEditorEvent) => this.onUntitledEditorSaved(e))); - this.toUnbind.push(this.eventService.addListener2(WorkbenchEventType.UNTITLED_FILE_DIRTY, (e: UntitledEditorEvent) => this.onUntitledEditorDirty(e))); + this.toUnbind.push(this.untitledEditorService.onDidChangeDirty(e => this.onUntitledDidChangeDirty(e))); this.toUnbind.push(this.eventService.addListener2(FileEventType.FILE_DIRTY, (e: TextFileChangeEvent) => this.onTextFileDirty(e))); this.toUnbind.push(this.eventService.addListener2(FileEventType.FILE_SAVE_ERROR, (e: TextFileChangeEvent) => this.onTextFileSaveError(e))); this.toUnbind.push(this.eventService.addListener2(FileEventType.FILE_SAVED, (e: TextFileChangeEvent) => this.onTextFileSaved(e))); @@ -151,12 +150,10 @@ export class FileTracker implements IWorkbenchContribution { } } - private onUntitledEditorDirty(e: UntitledEditorEvent): void { - this.updateActivityBadge(); - } + private onUntitledDidChangeDirty(e: UntitledEditorEvent): void { + const gotDirty = this.untitledEditorService.isDirty(e.resource); - private onUntitledEditorSaved(e: UntitledEditorEvent): void { - if (this.lastDirtyCount > 0) { + if (gotDirty || this.lastDirtyCount > 0) { this.updateActivityBadge(); } } diff --git a/src/vs/workbench/parts/files/electron-browser/macIntegration.ts b/src/vs/workbench/parts/files/electron-browser/macIntegration.ts index 9d3471ccde4..ede524c39f6 100644 --- a/src/vs/workbench/parts/files/electron-browser/macIntegration.ts +++ b/src/vs/workbench/parts/files/electron-browser/macIntegration.ts @@ -9,11 +9,12 @@ import {IWorkbenchContribution} from 'vs/workbench/common/contributions'; import {TextFileChangeEvent, EventType as FileEventType, ITextFileService, AutoSaveMode} from 'vs/workbench/parts/files/common/files'; import {platform, Platform} from 'vs/base/common/platform'; import {IWindowService} from 'vs/workbench/services/window/electron-browser/windowService'; -import {EventType as WorkbenchEventType} from 'vs/workbench/common/events'; import {IEventService} from 'vs/platform/event/common/event'; import {ILifecycleService} from 'vs/platform/lifecycle/common/lifecycle'; import {IDisposable, dispose} from 'vs/base/common/lifecycle'; import {ipcRenderer as ipc} from 'electron'; +import {UntitledEditorEvent} from 'vs/workbench/common/events'; +import {IUntitledEditorService} from 'vs/workbench/services/untitled/common/untitledEditorService'; export class MacIntegration implements IWorkbenchContribution { private isDocumentedEdited: boolean; @@ -23,7 +24,8 @@ export class MacIntegration implements IWorkbenchContribution { @IEventService private eventService: IEventService, @ITextFileService private textFileService: ITextFileService, @ILifecycleService private lifecycleService: ILifecycleService, - @IWindowService private windowService: IWindowService + @IWindowService private windowService: IWindowService, + @IUntitledEditorService private untitledEditorService: IUntitledEditorService ) { this.toUnbind = []; this.isDocumentedEdited = false; @@ -34,8 +36,7 @@ export class MacIntegration implements IWorkbenchContribution { private registerListeners(): void { // Local text file changes - this.toUnbind.push(this.eventService.addListener2(WorkbenchEventType.UNTITLED_FILE_SAVED, () => this.onUntitledSavedEvent())); - this.toUnbind.push(this.eventService.addListener2(WorkbenchEventType.UNTITLED_FILE_DIRTY, () => this.onUntitledDirtyEvent())); + this.toUnbind.push(this.untitledEditorService.onDidChangeDirty(e => this.onUntitledDidChangeDirty(e))); this.toUnbind.push(this.eventService.addListener2(FileEventType.FILE_DIRTY, (e: TextFileChangeEvent) => this.onTextFileDirty(e))); this.toUnbind.push(this.eventService.addListener2(FileEventType.FILE_SAVED, (e: TextFileChangeEvent) => this.onTextFileSaved(e))); this.toUnbind.push(this.eventService.addListener2(FileEventType.FILE_SAVE_ERROR, (e: TextFileChangeEvent) => this.onTextFileSaveError(e))); @@ -45,14 +46,10 @@ export class MacIntegration implements IWorkbenchContribution { this.lifecycleService.onShutdown(this.dispose, this); } - private onUntitledDirtyEvent(): void { - if (!this.isDocumentedEdited) { - this.updateDocumentEdited(); - } - } + private onUntitledDidChangeDirty(e: UntitledEditorEvent): void { + const gotDirty = this.untitledEditorService.isDirty(e.resource); - private onUntitledSavedEvent(): void { - if (this.isDocumentedEdited) { + if ((!this.isDocumentedEdited && gotDirty) || (this.isDocumentedEdited && !gotDirty)) { this.updateDocumentEdited(); } } diff --git a/src/vs/workbench/parts/output/browser/outputPanel.ts b/src/vs/workbench/parts/output/browser/outputPanel.ts index c6f1c1fffd1..04b47236682 100644 --- a/src/vs/workbench/parts/output/browser/outputPanel.ts +++ b/src/vs/workbench/parts/output/browser/outputPanel.ts @@ -24,6 +24,7 @@ import {SwitchOutputAction, SwitchOutputActionItem, ClearOutputAction} from 'vs/ import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace'; import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService'; import {IThemeService} from 'vs/workbench/services/themes/common/themeService'; +import {IUntitledEditorService} from 'vs/workbench/services/untitled/common/untitledEditorService'; export class OutputPanel extends StringEditor { @@ -40,10 +41,11 @@ export class OutputPanel extends StringEditor { @IEventService eventService: IEventService, @IWorkbenchEditorService editorService: IWorkbenchEditorService, @IThemeService themeService: IThemeService, - @IOutputService private outputService: IOutputService + @IOutputService private outputService: IOutputService, + @IUntitledEditorService untitledEditorService: IUntitledEditorService ) { super(telemetryService, instantiationService, contextService, storageService, - messageService, configurationService, eventService, editorService, themeService); + messageService, configurationService, eventService, editorService, themeService, untitledEditorService); this.toDispose = []; } diff --git a/src/vs/workbench/parts/search/browser/searchViewlet.ts b/src/vs/workbench/parts/search/browser/searchViewlet.ts index 38137f340b2..8e86cdf15fc 100644 --- a/src/vs/workbench/parts/search/browser/searchViewlet.ts +++ b/src/vs/workbench/parts/search/browser/searchViewlet.ts @@ -25,7 +25,7 @@ import {ITree} from 'vs/base/parts/tree/browser/tree'; import {Tree} from 'vs/base/parts/tree/browser/treeImpl'; import {Scope} from 'vs/workbench/common/memento'; import {OpenGlobalSettingsAction} from 'vs/workbench/browser/actions/openSettings'; -import {UntitledEditorEvent, EventType as WorkbenchEventType} from 'vs/workbench/common/events'; +import {UntitledEditorEvent} from 'vs/workbench/common/events'; import {IEditorGroupService} from 'vs/workbench/services/group/common/groupService'; import {getOutOfWorkspaceEditorResources} from 'vs/workbench/common/editor'; import {FileChangeType, FileChangesEvent, EventType as FileEventType} from 'vs/platform/files/common/files'; @@ -55,6 +55,7 @@ import { SearchWidget } from 'vs/workbench/parts/search/browser/searchWidget'; import { RefreshAction, CollapseAllAction, ClearSearchResultsAction, ConfigureGlobalExclusionsAction } from 'vs/workbench/parts/search/browser/searchActions'; import { IReplaceService } from 'vs/workbench/parts/search/common/replace'; import Severity from 'vs/base/common/severity'; +import {IUntitledEditorService} from 'vs/workbench/services/untitled/common/untitledEditorService'; export class SearchViewlet extends Viewlet { @@ -102,7 +103,8 @@ export class SearchViewlet extends Viewlet { @ISearchService private searchService: ISearchService, @IContextKeyService private contextKeyService: IContextKeyService, @IKeybindingService private keybindingService: IKeybindingService, - @IReplaceService private replaceService: IReplaceService + @IReplaceService private replaceService: IReplaceService, + @IUntitledEditorService private untitledEditorService: IUntitledEditorService ) { super(VIEWLET_ID, telemetryService); @@ -114,7 +116,7 @@ export class SearchViewlet extends Viewlet { this.viewletSettings = this.getMemento(storageService, Scope.WORKSPACE); this.toUnbind.push(this.eventService.addListener2(FileEventType.FILE_CHANGES, (e) => this.onFilesChanged(e))); - this.toUnbind.push(this.eventService.addListener2(WorkbenchEventType.UNTITLED_FILE_SAVED, (e) => this.onUntitledFileSaved(e))); + this.toUnbind.push(this.untitledEditorService.onDidChangeDirty(e => this.onUntitledDidChangeDirty(e))); this.toUnbind.push(this.configurationService.onDidUpdateConfiguration(e => this.onConfigurationUpdated(e.config))); } @@ -945,15 +947,18 @@ export class SearchViewlet extends Viewlet { return void 0; } - private onUntitledFileSaved(e: UntitledEditorEvent): void { + private onUntitledDidChangeDirty(e: UntitledEditorEvent): void { if (!this.viewModel) { return; } - let matches = this.viewModel.searchResult.matches(); - for (let i = 0, len = matches.length; i < len; i++) { - if (e.resource.toString() === matches[i].resource().toString()) { - this.viewModel.searchResult.remove(matches[i]); + // remove search results from this resource as it got disposed + if (!this.untitledEditorService.isDirty(e.resource)) { + let matches = this.viewModel.searchResult.matches(); + for (let i = 0, len = matches.length; i < len; i++) { + if (e.resource.toString() === matches[i].resource().toString()) { + this.viewModel.searchResult.remove(matches[i]); + } } } } diff --git a/src/vs/workbench/services/untitled/common/untitledEditorService.ts b/src/vs/workbench/services/untitled/common/untitledEditorService.ts index fe049315b4a..0b522fd1960 100644 --- a/src/vs/workbench/services/untitled/common/untitledEditorService.ts +++ b/src/vs/workbench/services/untitled/common/untitledEditorService.ts @@ -9,6 +9,8 @@ import {createDecorator, IInstantiationService} from 'vs/platform/instantiation/ import {EventType} from 'vs/base/common/events'; import arrays = require('vs/base/common/arrays'); import {UntitledEditorInput} from 'vs/workbench/common/editor/untitledEditorInput'; +import Event, {Emitter} from 'vs/base/common/event'; +import {UntitledEditorEvent} from 'vs/workbench/common/events'; export const IUntitledEditorService = createDecorator('untitledEditorService'); @@ -16,6 +18,11 @@ export interface IUntitledEditorService { _serviceBrand: any; + /** + * Events for when untitled editors change (e.g. getting dirty, saved or reverted). + */ + onDidChangeDirty: Event; + /** * Returns the untitled editor input matching the provided resource. */ @@ -57,12 +64,20 @@ export interface IUntitledEditorService { } 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 _onDidChangeDirty: Emitter; + constructor(@IInstantiationService private instantiationService: IInstantiationService) { + this._onDidChangeDirty = new Emitter(); + } + + public get onDidChangeDirty(): Event { + return this._onDidChangeDirty.event; } public get(resource: URI): UntitledEditorInput { @@ -139,10 +154,15 @@ export class UntitledEditorService implements IUntitledEditorService { let input = this.instantiationService.createInstance(UntitledEditorInput, resource, hasAssociatedFilePath, modeId); + const listener = input.onDidChangeDirty(() => { + this._onDidChangeDirty.fire(new UntitledEditorEvent(resource)); + }); + // Remove from cache on dispose input.addOneTimeDisposableListener(EventType.DISPOSE, () => { delete UntitledEditorService.CACHE[input.getResource().toString()]; delete UntitledEditorService.KNOWN_ASSOCIATED_FILE_PATHS[input.getResource().toString()]; + listener.dispose(); }); // Add to cache