From d01e111fb0e6e27378fcd049bbc9b36d0833d78e Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 10 Dec 2019 09:46:04 +0100 Subject: [PATCH] debt - model reload should happen from model manager --- .../files/node/diskFileSystemProvider.ts | 8 +- .../browser/editors/fileEditorTracker.ts | 78 ++++----------- .../textfile/common/textFileEditorModel.ts | 1 + .../common/textFileEditorModelManager.ts | 95 +++++++++++++------ 4 files changed, 90 insertions(+), 92 deletions(-) diff --git a/src/vs/platform/files/node/diskFileSystemProvider.ts b/src/vs/platform/files/node/diskFileSystemProvider.ts index 0187b5d9bcc..08b595a6bd4 100644 --- a/src/vs/platform/files/node/diskFileSystemProvider.ts +++ b/src/vs/platform/files/node/diskFileSystemProvider.ts @@ -484,15 +484,15 @@ export class DiskFileSystemProvider extends Disposable implements //#region File Watching - private _onDidWatchErrorOccur: Emitter = this._register(new Emitter()); - readonly onDidErrorOccur: Event = this._onDidWatchErrorOccur.event; + private _onDidWatchErrorOccur = this._register(new Emitter()); + readonly onDidErrorOccur = this._onDidWatchErrorOccur.event; private _onDidChangeFile = this._register(new Emitter()); - get onDidChangeFile(): Event { return this._onDidChangeFile.event; } + readonly onDidChangeFile = this._onDidChangeFile.event; private recursiveWatcher: WindowsWatcherService | UnixWatcherService | NsfwWatcherService | undefined; private recursiveFoldersToWatch: { path: string, excludes: string[] }[] = []; - private recursiveWatchRequestDelayer: ThrottledDelayer = this._register(new ThrottledDelayer(0)); + private recursiveWatchRequestDelayer = this._register(new ThrottledDelayer(0)); private recursiveWatcherLogLevelListener: IDisposable | undefined; diff --git a/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts b/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts index 854c0b686c6..2a1f0860a39 100644 --- a/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts +++ b/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts @@ -8,7 +8,7 @@ import { URI } from 'vs/base/common/uri'; import * as resources from 'vs/base/common/resources'; import { IEditorViewState } from 'vs/editor/common/editorCommon'; import { toResource, SideBySideEditorInput, IWorkbenchEditorConfiguration, SideBySideEditor as SideBySideEditorChoice } from 'vs/workbench/common/editor'; -import { ITextFileService, ITextFileEditorModel, TextFileModelChangeEvent, ModelState } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, TextFileModelChangeEvent, ModelState } from 'vs/workbench/services/textfile/common/textfiles'; import { FileOperationEvent, FileOperation, IFileService, FileChangeType, FileChangesEvent } from 'vs/platform/files/common/files'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; @@ -22,17 +22,17 @@ import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { ResourceQueue, timeout } from 'vs/base/common/async'; -import { onUnexpectedError } from 'vs/base/common/errors'; +import { timeout } from 'vs/base/common/async'; import { withNullAsUndefined } from 'vs/base/common/types'; import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; export class FileEditorTracker extends Disposable implements IWorkbenchContribution { - private closeOnFileDelete: boolean = false; - private readonly modelLoadQueue = new ResourceQueue(); + private readonly activeOutOfWorkspaceWatchers = new ResourceMap(); + private closeOnFileDelete: boolean = false; + constructor( @IEditorService private readonly editorService: IEditorService, @ITextFileService private readonly textFileService: ITextFileService, @@ -177,8 +177,18 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut return undefined; } + //#endregion + + //#region File Changes: Close editors of deleted files + + private onFileChanges(e: FileChangesEvent): void { + if (e.gotDeleted()) { + this.handleDeletes(e, true); + } + } + private handleDeletes(arg1: URI | FileChangesEvent, isExternal: boolean, movedTo?: URI): void { - const nonDirtyFileEditors = this.getOpenedFileEditors(false /* non-dirty only */); + const nonDirtyFileEditors = this.getNonDirtyFileEditors(); nonDirtyFileEditors.forEach(async editor => { const resource = editor.getResource(); @@ -227,12 +237,12 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut }); } - private getOpenedFileEditors(dirtyState: boolean): FileEditorInput[] { + private getNonDirtyFileEditors(): FileEditorInput[] { const editors: FileEditorInput[] = []; this.editorService.editors.forEach(editor => { if (editor instanceof FileEditorInput) { - if (!!editor.isDirty() === dirtyState) { + if (!editor.isDirty()) { editors.push(editor); } } else if (editor instanceof SideBySideEditorInput) { @@ -240,13 +250,13 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut const details = editor.details; if (master instanceof FileEditorInput) { - if (!!master.isDirty() === dirtyState) { + if (!master.isDirty()) { editors.push(master); } } if (details instanceof FileEditorInput) { - if (!!details.isDirty() === dirtyState) { + if (!details.isDirty()) { editors.push(details); } } @@ -258,52 +268,6 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut //#endregion - //#region File Changes: Update text models and binary editors - - private onFileChanges(e: FileChangesEvent): void { - - // Handle updates - if (e.gotAdded() || e.gotUpdated()) { - this.handleUpdates(e); - } - - // Handle deletes - if (e.gotDeleted()) { - this.handleDeletes(e, true); - } - } - - private handleUpdates(e: FileChangesEvent): void { - - // Handle updates to text models - this.handleUpdatesToTextModels(e); - } - - private handleUpdatesToTextModels(e: FileChangesEvent): void { - - // Collect distinct (saved) models to update. - // - // Note: we also consider the added event because it could be that a file was added - // and updated right after. - distinct(coalesce([...e.getUpdated(), ...e.getAdded()] - .map(u => this.textFileService.models.get(u.resource))) - .filter(model => model && !model.isDirty()), model => model.resource.toString()) - .forEach(model => this.queueModelLoad(model)); - } - - private queueModelLoad(model: ITextFileEditorModel): void { - - // Load model to update (use a queue to prevent accumulation of loads - // when the load actually takes long. At most we only want the queue - // to have a size of 2 (1 running load and 1 queued load). - const queue = this.modelLoadQueue.queueFor(model.resource); - if (queue.size <= 1) { - queue.queue(() => model.load().then(undefined, onUnexpectedError)); - } - } - - //#endregion - //#region Text File Dirty: Ensure every dirty text file is opened in an editor private onTextFilesDirty(events: ReadonlyArray): void { @@ -396,7 +360,7 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut return model; })), model => model.resource.toString() - ).forEach(model => this.queueModelLoad(model)); + ).forEach(model => model.load()); } } diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index 1ab5da03cf5..fb8822c7f3c 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -63,6 +63,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil static DEFAULT_CONTENT_CHANGE_BUFFER_DELAY = CONTENT_CHANGE_EVENT_BUFFER_DELAY; static DEFAULT_ORPHANED_CHANGE_BUFFER_DELAY = 100; + static WHITELIST_JSON = ['package.json', 'package-lock.json', 'tsconfig.json', 'jsconfig.json', 'bower.json', '.eslintrc.json', 'tslint.json', 'composer.json']; static WHITELIST_WORKSPACE_JSON = ['settings.json', 'extensions.json', 'tasks.json', 'launch.json']; diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts index 13ff5e0fa77..6a8d170d6e5 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts @@ -11,35 +11,39 @@ import { ITextFileEditorModel, ITextFileEditorModelManager, TextFileModelChangeE import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ResourceMap } from 'vs/base/common/map'; +import { IFileService, FileChangesEvent } from 'vs/platform/files/common/files'; +import { distinct, coalesce } from 'vs/base/common/arrays'; +import { ResourceQueue } from 'vs/base/common/async'; +import { onUnexpectedError } from 'vs/base/common/errors'; export class TextFileEditorModelManager extends Disposable implements ITextFileEditorModelManager { - private readonly _onModelDisposed: Emitter = this._register(new Emitter()); - readonly onModelDisposed: Event = this._onModelDisposed.event; + private readonly _onModelDisposed = this._register(new Emitter()); + readonly onModelDisposed = this._onModelDisposed.event; - private readonly _onModelContentChanged: Emitter = this._register(new Emitter()); - readonly onModelContentChanged: Event = this._onModelContentChanged.event; + private readonly _onModelContentChanged = this._register(new Emitter()); + readonly onModelContentChanged = this._onModelContentChanged.event; - private readonly _onModelDirty: Emitter = this._register(new Emitter()); - readonly onModelDirty: Event = this._onModelDirty.event; + private readonly _onModelDirty = this._register(new Emitter()); + readonly onModelDirty = this._onModelDirty.event; - private readonly _onModelSaveError: Emitter = this._register(new Emitter()); - readonly onModelSaveError: Event = this._onModelSaveError.event; + private readonly _onModelSaveError = this._register(new Emitter()); + readonly onModelSaveError = this._onModelSaveError.event; - private readonly _onModelSaved: Emitter = this._register(new Emitter()); - readonly onModelSaved: Event = this._onModelSaved.event; + private readonly _onModelSaved = this._register(new Emitter()); + readonly onModelSaved = this._onModelSaved.event; - private readonly _onModelReverted: Emitter = this._register(new Emitter()); - readonly onModelReverted: Event = this._onModelReverted.event; + private readonly _onModelReverted = this._register(new Emitter()); + readonly onModelReverted = this._onModelReverted.event; - private readonly _onModelEncodingChanged: Emitter = this._register(new Emitter()); - readonly onModelEncodingChanged: Event = this._onModelEncodingChanged.event; + private readonly _onModelEncodingChanged = this._register(new Emitter()); + readonly onModelEncodingChanged = this._onModelEncodingChanged.event; - private readonly _onModelOrphanedChanged: Emitter = this._register(new Emitter()); - readonly onModelOrphanedChanged: Event = this._onModelOrphanedChanged.event; + private readonly _onModelOrphanedChanged = this._register(new Emitter()); + readonly onModelOrphanedChanged = this._onModelOrphanedChanged.event; - private _onModelsDirty: Event | undefined; - get onModelsDirty(): Event { + private _onModelsDirty: Event> | undefined; + get onModelsDirty(): Event> { if (!this._onModelsDirty) { this._onModelsDirty = this.debounce(this.onModelDirty); } @@ -47,8 +51,8 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE return this._onModelsDirty; } - private _onModelsSaveError: Event | undefined; - get onModelsSaveError(): Event { + private _onModelsSaveError: Event> | undefined; + get onModelsSaveError(): Event> { if (!this._onModelsSaveError) { this._onModelsSaveError = this.debounce(this.onModelSaveError); } @@ -56,8 +60,8 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE return this._onModelsSaveError; } - private _onModelsSaved: Event | undefined; - get onModelsSaved(): Event { + private _onModelsSaved: Event> | undefined; + get onModelsSaved(): Event> { if (!this._onModelsSaved) { this._onModelsSaved = this.debounce(this.onModelSaved); } @@ -65,8 +69,8 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE return this._onModelsSaved; } - private _onModelsReverted: Event | undefined; - get onModelsReverted(): Event { + private _onModelsReverted: Event> | undefined; + get onModelsReverted(): Event> { if (!this._onModelsReverted) { this._onModelsReverted = this.debounce(this.onModelReverted); } @@ -74,15 +78,18 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE return this._onModelsReverted; } - private mapResourceToDisposeListener = new ResourceMap(); - private mapResourceToStateChangeListener = new ResourceMap(); - private mapResourceToModelContentChangeListener = new ResourceMap(); - private mapResourceToModel = new ResourceMap(); - private mapResourceToPendingModelLoaders = new ResourceMap>(); + private readonly mapResourceToDisposeListener = new ResourceMap(); + private readonly mapResourceToStateChangeListener = new ResourceMap(); + private readonly mapResourceToModelContentChangeListener = new ResourceMap(); + private readonly mapResourceToModel = new ResourceMap(); + private readonly mapResourceToPendingModelLoaders = new ResourceMap>(); + + private readonly modelLoadQueue = new ResourceQueue(); constructor( @ILifecycleService private readonly lifecycleService: ILifecycleService, - @IInstantiationService private readonly instantiationService: IInstantiationService + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IFileService private readonly fileService: IFileService ) { super(); @@ -91,11 +98,37 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE private registerListeners(): void { + // Update models from file change events + this._register(this.fileService.onFileChanges(e => this.onFileChanges(e))); + // Lifecycle this.lifecycleService.onShutdown(this.dispose, this); } - private debounce(event: Event): Event { + private onFileChanges(e: FileChangesEvent): void { + + // Collect distinct (saved) models to update. + // + // Note: we also consider the added event because it could be that a file was added + // and updated right after. + distinct(coalesce([...e.getUpdated(), ...e.getAdded()] + .map(({ resource }) => this.get(resource))) + .filter(model => model && !model.isDirty()), model => model.resource.toString()) + .forEach(model => this.queueModelLoad(model)); + } + + private queueModelLoad(model: ITextFileEditorModel): void { + + // Load model to update (use a queue to prevent accumulation of loads + // when the load actually takes long. At most we only want the queue + // to have a size of 2 (1 running load and 1 queued load). + const queue = this.modelLoadQueue.queueFor(model.resource); + if (queue.size <= 1) { + queue.queue(() => model.load().then(undefined, onUnexpectedError)); + } + } + + private debounce(event: Event): Event> { return Event.debounce(event, (prev: TextFileModelChangeEvent[], cur: TextFileModelChangeEvent) => { if (!prev) { prev = [cur];