diff --git a/src/vs/platform/dialogs/browser/dialogService.ts b/src/vs/platform/dialogs/browser/dialogService.ts index 057ed24c053..d5cc82af884 100644 --- a/src/vs/platform/dialogs/browser/dialogService.ts +++ b/src/vs/platform/dialogs/browser/dialogService.ts @@ -12,7 +12,7 @@ import { Dialog } from 'vs/base/browser/ui/dialog/dialog'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { attachDialogStyler } from 'vs/platform/theme/common/styler'; -import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { EventHelper } from 'vs/base/browser/dom'; @@ -70,7 +70,7 @@ export class DialogService implements IDialogService { async show(severity: Severity, message: string, buttons: string[], options?: IDialogOptions): Promise { this.logService.trace('DialogService#show', message); - const dialogDisposables: IDisposable[] = []; + const dialogDisposables = new DisposableStore(); const dialog = new Dialog( this.layoutService.container, message, @@ -84,11 +84,11 @@ export class DialogService implements IDialogService { } }); - dialogDisposables.push(dialog); - dialogDisposables.push(attachDialogStyler(dialog, this.themeService)); + dialogDisposables.add(dialog); + dialogDisposables.add(attachDialogStyler(dialog, this.themeService)); const choice = await dialog.show(); - dispose(dialogDisposables); + dialogDisposables.dispose(); return choice; } diff --git a/src/vs/platform/progress/common/progress.ts b/src/vs/platform/progress/common/progress.ts index cffb0fe749e..ac1259c91d2 100644 --- a/src/vs/platform/progress/common/progress.ts +++ b/src/vs/platform/progress/common/progress.ts @@ -5,7 +5,7 @@ import { createDecorator, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; -import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; +import { toDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle'; import { IAction } from 'vs/base/common/actions'; export const IProgressService = createDecorator('progressService'); @@ -124,15 +124,17 @@ export interface IOperation { stop(): void; } -export class LongRunningOperation { +export class LongRunningOperation extends Disposable { private currentOperationId = 0; - private currentOperationDisposables: IDisposable[] = []; + private readonly currentOperationDisposables = this._register(new DisposableStore()); private currentProgressRunner: IProgressRunner; private currentProgressTimeout: any; constructor( private localProgressService: ILocalProgressService - ) { } + ) { + super(); + } start(progressDelay: number): IOperation { @@ -148,11 +150,9 @@ export class LongRunningOperation { } }, progressDelay); - this.currentOperationDisposables.push( - toDisposable(() => clearTimeout(this.currentProgressTimeout)), - toDisposable(() => newOperationToken.cancel()), - toDisposable(() => this.currentProgressRunner ? this.currentProgressRunner.done() : undefined) - ); + this.currentOperationDisposables.add(toDisposable(() => clearTimeout(this.currentProgressTimeout))); + this.currentOperationDisposables.add(toDisposable(() => newOperationToken.cancel())); + this.currentOperationDisposables.add(toDisposable(() => this.currentProgressRunner ? this.currentProgressRunner.done() : undefined)); return { id: newOperationId, @@ -168,11 +168,7 @@ export class LongRunningOperation { private doStop(operationId: number): void { if (this.currentOperationId === operationId) { - this.currentOperationDisposables = dispose(this.currentOperationDisposables); + this.currentOperationDisposables.clear(); } } - - dispose(): void { - this.currentOperationDisposables = dispose(this.currentOperationDisposables); - } } diff --git a/src/vs/platform/windows/common/windows.ts b/src/vs/platform/windows/common/windows.ts index d288f2f8b84..6110a4b6c70 100644 --- a/src/vs/platform/windows/common/windows.ts +++ b/src/vs/platform/windows/common/windows.ts @@ -13,7 +13,7 @@ import { IRecentlyOpened, IRecent } from 'vs/platform/history/common/history'; import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; import { ExportData } from 'vs/base/common/performance'; import { LogLevel } from 'vs/platform/log/common/log'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { DisposableStore, Disposable } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; @@ -451,13 +451,15 @@ export interface IRunKeybindingInWindowRequest { userSettingsLabel: string; } -export class ActiveWindowManager implements IDisposable { +export class ActiveWindowManager extends Disposable { - private disposables: IDisposable[] = []; + private readonly disposables = this._register(new DisposableStore()); private firstActiveWindowIdPromise: CancelablePromise | undefined; private activeWindowId: number | undefined; constructor(@IWindowsService windowsService: IWindowsService) { + super(); + const onActiveWindowChange = Event.latch(Event.any(windowsService.onWindowOpen, windowsService.onWindowFocus)); onActiveWindowChange(this.setActiveWindow, this, this.disposables); @@ -478,10 +480,7 @@ export class ActiveWindowManager implements IDisposable { async getActiveClientId(): Promise { const id = this.firstActiveWindowIdPromise ? (await this.firstActiveWindowIdPromise) : this.activeWindowId; + return `window:${id}`; } - - dispose() { - this.disposables = dispose(this.disposables); - } } diff --git a/src/vs/platform/windows/electron-main/windowsService.ts b/src/vs/platform/windows/electron-main/windowsService.ts index 07d8e6071a9..ed90804f293 100644 --- a/src/vs/platform/windows/electron-main/windowsService.ts +++ b/src/vs/platform/windows/electron-main/windowsService.ts @@ -5,7 +5,7 @@ import * as nls from 'vs/nls'; import * as os from 'os'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { assign } from 'vs/base/common/objects'; import { URI } from 'vs/base/common/uri'; import product from 'vs/platform/product/node/product'; @@ -23,12 +23,13 @@ import { Schemas } from 'vs/base/common/network'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { isMacintosh, isLinux } from 'vs/base/common/platform'; import { ILogService } from 'vs/platform/log/common/log'; +import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; -export class WindowsService implements IWindowsService, IURLHandler, IDisposable { +export class WindowsService extends Disposable implements IWindowsService, IURLHandler { - _serviceBrand: any; + _serviceBrand: ServiceIdentifier; - private disposables: IDisposable[] = []; + private readonly disposables = this._register(new DisposableStore()); private _activeWindowId: number | undefined; @@ -52,6 +53,8 @@ export class WindowsService implements IWindowsService, IURLHandler, IDisposable @IHistoryMainService private readonly historyService: IHistoryMainService, @ILogService private readonly logService: ILogService ) { + super(); + urlService.registerHandler(this); // remember last active window id @@ -460,8 +463,4 @@ export class WindowsService implements IWindowsService, IURLHandler, IDisposable return undefined; } - - dispose(): void { - this.disposables = dispose(this.disposables); - } } diff --git a/src/vs/workbench/api/common/extHostTreeViews.ts b/src/vs/workbench/api/common/extHostTreeViews.ts index e70b95021c4..ac0c9878b0e 100644 --- a/src/vs/workbench/api/common/extHostTreeViews.ts +++ b/src/vs/workbench/api/common/extHostTreeViews.ts @@ -442,7 +442,7 @@ class ExtHostTreeView extends Disposable { } private createTreeNode(element: T, extensionTreeItem: vscode.TreeItem, parent: TreeNode | Root): TreeNode { - const disposable: DisposableStore = new DisposableStore(); + const disposable = new DisposableStore(); const handle = this.createHandle(element, extensionTreeItem, parent); const icon = this.getLightIconPath(extensionTreeItem); const item = { @@ -464,7 +464,7 @@ class ExtHostTreeView extends Disposable { item, parent, children: undefined, - dispose() { disposable.dispose(); } + dispose(): void { disposable.dispose(); } }; } diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index 3c3f7710d61..9bffa5213e7 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -16,7 +16,7 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IEditorGroupsService, GroupOrientation } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes'; -import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { MenuBarVisibility } from 'vs/platform/windows/common/windows'; import { isWindows, isLinux } from 'vs/base/common/platform'; import { IsMacNativeContext } from 'vs/workbench/browser/contextkeys'; @@ -110,7 +110,7 @@ export class ToggleEditorLayoutAction extends Action { static readonly ID = 'workbench.action.toggleEditorGroupLayout'; static readonly LABEL = nls.localize('flipLayout', "Toggle Vertical/Horizontal Editor Layout"); - private toDispose: IDisposable[]; + private readonly toDispose = this._register(new DisposableStore()); constructor( id: string, @@ -119,8 +119,6 @@ export class ToggleEditorLayoutAction extends Action { ) { super(id, label); - this.toDispose = []; - this.class = 'flip-editor-layout'; this.updateEnablement(); @@ -128,8 +126,8 @@ export class ToggleEditorLayoutAction extends Action { } private registerListeners(): void { - this.toDispose.push(this.editorGroupService.onDidAddGroup(() => this.updateEnablement())); - this.toDispose.push(this.editorGroupService.onDidRemoveGroup(() => this.updateEnablement())); + this.toDispose.add(this.editorGroupService.onDidAddGroup(() => this.updateEnablement())); + this.toDispose.add(this.editorGroupService.onDidRemoveGroup(() => this.updateEnablement())); } private updateEnablement(): void { @@ -142,12 +140,6 @@ export class ToggleEditorLayoutAction extends Action { return Promise.resolve(); } - - dispose(): void { - this.toDispose = dispose(this.toDispose); - - super.dispose(); - } } CommandsRegistry.registerCommand('_workbench.editor.setGroupOrientation', function (accessor: ServicesAccessor, args: [GroupOrientation]) { diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index f9e16f717e4..e06870b2bb7 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; import { EventType, addDisposableListener, addClass, removeClass, isAncestor, getClientArea, position, size, EventHelper } from 'vs/base/browser/dom'; import { onDidChangeFullscreen, isFullscreen, getZoomFactor } from 'vs/base/browser/browser'; @@ -159,7 +159,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi transitionedToCenteredEditorLayout: false, wasSideBarVisible: false, wasPanelVisible: false, - transitionDisposeables: [] as IDisposable[] + transitionDisposeables: new DisposableStore() }, // TODO @misolori remove before shipping stable @@ -582,7 +582,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi toggleZenMode(skipLayout?: boolean, restoring = false): void { this.state.zenMode.active = !this.state.zenMode.active; - this.state.zenMode.transitionDisposeables = dispose(this.state.zenMode.transitionDisposeables); + this.state.zenMode.transitionDisposeables.clear(); const setLineNumbers = (lineNumbers: any) => this.editorService.visibleTextEditorWidgets.forEach(editor => editor.updateOptions({ lineNumbers })); @@ -621,11 +621,11 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi if (config.hideLineNumbers) { setLineNumbers('off'); - this.state.zenMode.transitionDisposeables.push(this.editorService.onDidVisibleEditorsChange(() => setLineNumbers('off'))); + this.state.zenMode.transitionDisposeables.add(this.editorService.onDidVisibleEditorsChange(() => setLineNumbers('off'))); } if (config.hideTabs && this.editorGroupService.partOptions.showTabs) { - this.state.zenMode.transitionDisposeables.push(this.editorGroupService.enforcePartOptions({ showTabs: false })); + this.state.zenMode.transitionDisposeables.add(this.editorGroupService.enforcePartOptions({ showTabs: false })); } if (config.centerLayout) { diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts index 79d64736026..24d8fe70c7f 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts @@ -147,7 +147,7 @@ export class GlobalActivityActionViewItem extends ActivityActionViewItem { private showContextMenu(): void { const globalActivityActions: IAction[] = []; const globalActivityMenu = this.menuService.createMenu(MenuId.GlobalActivity, this.contextKeyService); - createAndFillInActionBarActions(globalActivityMenu, undefined, { primary: [], secondary: globalActivityActions }); + const actionsDisposable = createAndFillInActionBarActions(globalActivityMenu, undefined, { primary: [], secondary: globalActivityActions }); const containerPosition = DOM.getDomNodePagePosition(this.container); const location = { x: containerPosition.left + containerPosition.width / 2, y: containerPosition.top }; @@ -156,7 +156,7 @@ export class GlobalActivityActionViewItem extends ActivityActionViewItem { getActions: () => globalActivityActions, onHide: () => { globalActivityMenu.dispose(); - dispose(globalActivityActions); + dispose(actionsDisposable); } }); } diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index 175a5a6c935..d602a8ed768 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -15,7 +15,7 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IBadge } from 'vs/workbench/services/activity/common/activity'; import { IWorkbenchLayoutService, Parts, Position as SideBarPosition } from 'vs/workbench/services/layout/browser/layoutService'; import { IInstantiationService, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; -import { IDisposable, toDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IDisposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { ToggleActivityBarVisibilityAction } from 'vs/workbench/browser/actions/layoutActions'; import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; import { ACTIVITY_BAR_BACKGROUND, ACTIVITY_BAR_BORDER, ACTIVITY_BAR_FOREGROUND, ACTIVITY_BAR_BADGE_BACKGROUND, ACTIVITY_BAR_BADGE_FOREGROUND, ACTIVITY_BAR_DRAG_AND_DROP_BACKGROUND, ACTIVITY_BAR_INACTIVE_FOREGROUND } from 'vs/workbench/common/theme'; @@ -121,15 +121,13 @@ export class ActivitybarPart extends Part implements IActivityBarService { this._register(this.viewletService.onDidViewletClose(viewlet => this.compositeBar.deactivateComposite(viewlet.getId()))); // Extension registration - let disposables: IDisposable[] = []; + let disposables = this._register(new DisposableStore()); this._register(this.extensionService.onDidRegisterExtensions(() => { - disposables = dispose(disposables); + disposables.clear(); this.onDidRegisterExtensions(); this.compositeBar.onDidChange(() => this.saveCachedViewlets(), this, disposables); this.storageService.onDidChangeStorage(e => this.onDidStorageChange(e), this, disposables); })); - - this._register(toDisposable(() => dispose(disposables))); } private onDidRegisterExtensions(): void { @@ -172,6 +170,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { private showGlobalActivity(badge: IBadge, clazz?: string): IDisposable { this.globalActivityAction.setBadge(badge, clazz); + return toDisposable(() => this.globalActivityAction.setBadge(undefined)); } diff --git a/src/vs/workbench/browser/parts/compositeBar.ts b/src/vs/workbench/browser/parts/compositeBar.ts index 4743b7e053e..69f53744b05 100644 --- a/src/vs/workbench/browser/parts/compositeBar.ts +++ b/src/vs/workbench/browser/parts/compositeBar.ts @@ -201,6 +201,7 @@ export class CompositeBar extends Widget implements ICompositeBar { const activity: ICompositeActivity = { badge, clazz, priority }; this.model.addActivity(compositeId, activity); + return toDisposable(() => this.model.removeActivity(compositeId, activity)); } diff --git a/src/vs/workbench/browser/parts/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositeBarActions.ts index 34a0685818c..fe21d315410 100644 --- a/src/vs/workbench/browser/parts/compositeBarActions.ts +++ b/src/vs/workbench/browser/parts/compositeBarActions.ts @@ -8,7 +8,7 @@ import { Action } from 'vs/base/common/actions'; import * as dom from 'vs/base/browser/dom'; import { BaseActionViewItem, IBaseActionViewItemOptions, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { dispose, IDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle'; +import { dispose, toDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; import { TextBadge, NumberBadge, IBadge, IconBadge, ProgressBadge } from 'vs/workbench/services/activity/common/activity'; @@ -130,7 +130,7 @@ export class ActivityActionViewItem extends BaseActionViewItem { protected options: IActivityActionViewItemOptions; private badgeContent: HTMLElement; - private badgeDisposable: IDisposable = Disposable.None; + private readonly badgeDisposable = this._register(new MutableDisposable()); private mouseUpTimeout: any; constructor( @@ -236,8 +236,7 @@ export class ActivityActionViewItem extends BaseActionViewItem { const badge = action.getBadge(); const clazz = action.getClass(); - this.badgeDisposable.dispose(); - this.badgeDisposable = Disposable.None; + this.badgeDisposable.clear(); dom.clearNode(this.badgeContent); dom.hide(this.badge); @@ -280,7 +279,7 @@ export class ActivityActionViewItem extends BaseActionViewItem { if (clazz) { dom.addClasses(this.badge, clazz); - this.badgeDisposable = toDisposable(() => dom.removeClasses(this.badge, clazz)); + this.badgeDisposable.value = toDisposable(() => dom.removeClasses(this.badge, clazz)); } } diff --git a/src/vs/workbench/browser/parts/compositePart.ts b/src/vs/workbench/browser/parts/compositePart.ts index 86ea39bde6a..8be3d84e29e 100644 --- a/src/vs/workbench/browser/parts/compositePart.ts +++ b/src/vs/workbench/browser/parts/compositePart.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/compositepart'; import * as nls from 'vs/nls'; import { defaultGenerator } from 'vs/base/common/idGenerator'; -import { IDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; import * as strings from 'vs/base/common/strings'; import { Emitter } from 'vs/base/common/event'; import * as errors from 'vs/base/common/errors'; @@ -68,7 +68,7 @@ export abstract class CompositePart extends Part { private titleLabel: ICompositeTitleLabel; private progressBar: ProgressBar; private contentAreaSize: Dimension; - private telemetryActionsListener: IDisposable | null; + private readonly telemetryActionsListener = this._register(new MutableDisposable()); private currentCompositeOpenToken: string; constructor( @@ -249,13 +249,8 @@ export abstract class CompositePart extends Part { } actionsBinding(); - if (this.telemetryActionsListener) { - this.telemetryActionsListener.dispose(); - this.telemetryActionsListener = null; - } - // Action Run Handling - this.telemetryActionsListener = this.toolBar.actionRunner.onDidRun(e => { + this.telemetryActionsListener.value = this.toolBar.actionRunner.onDidRun(e => { // Check for Error if (e.error && !errors.isPromiseCanceledError(e.error)) { diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index 7f7a5a58e53..ba90f283b0a 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -21,7 +21,7 @@ import { CLOSE_EDITOR_COMMAND_ID, NAVIGATE_ALL_EDITORS_GROUP_PREFIX, MOVE_ACTIVE import { IEditorGroupsService, IEditorGroup, GroupsArrangement, EditorsOrder, GroupLocation, GroupDirection, preferredSideBySideGroupDirection, IFindGroupScope, GroupOrientation, EditorGroupLayout, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { DisposableStore } from 'vs/base/common/lifecycle'; export class ExecuteCommandAction extends Action { @@ -41,7 +41,7 @@ export class ExecuteCommandAction extends Action { } export class BaseSplitEditorAction extends Action { - private toDispose: IDisposable[] = []; + private readonly toDispose = this._register(new DisposableStore()); private direction: GroupDirection; constructor( @@ -62,7 +62,7 @@ export class BaseSplitEditorAction extends Action { } private registerListeners(): void { - this.toDispose.push(this.configurationService.onDidChangeConfiguration(e => { + this.toDispose.add(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('workbench.editor.openSideBySideDirection')) { this.direction = preferredSideBySideGroupDirection(this.configurationService); } @@ -74,12 +74,6 @@ export class BaseSplitEditorAction extends Action { return Promise.resolve(true); } - - dispose(): void { - super.dispose(); - - this.toDispose = dispose(this.toDispose); - } } export class SplitEditorAction extends BaseSplitEditorAction { diff --git a/src/vs/workbench/browser/parts/editor/editorControl.ts b/src/vs/workbench/browser/parts/editor/editorControl.ts index 1f70301f300..a3c50f7011a 100644 --- a/src/vs/workbench/browser/parts/editor/editorControl.ts +++ b/src/vs/workbench/browser/parts/editor/editorControl.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { dispose, Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { EditorInput, EditorOptions } from 'vs/workbench/common/editor'; import { Dimension, show, hide, addClass } from 'vs/base/browser/dom'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -38,7 +38,7 @@ export class EditorControl extends Disposable { private _activeControl: BaseEditor | null; private controls: BaseEditor[] = []; - private activeControlDisposeables: IDisposable[] = []; + private readonly activeControlDisposeables = this._register(new DisposableStore()); private dimension: Dimension; private editorOperation: LongRunningOperation; @@ -139,12 +139,12 @@ export class EditorControl extends Disposable { this._activeControl = control; // Clear out previous active control listeners - this.activeControlDisposeables = dispose(this.activeControlDisposeables); + this.activeControlDisposeables.clear(); // Listen to control changes if (control) { - this.activeControlDisposeables.push(control.onDidSizeConstraintsChange(e => this._onDidSizeConstraintsChange.fire(e))); - this.activeControlDisposeables.push(control.onDidFocus(() => this._onDidFocus.fire())); + this.activeControlDisposeables.add(control.onDidSizeConstraintsChange(e => this._onDidSizeConstraintsChange.fire(e))); + this.activeControlDisposeables.add(control.onDidFocus(() => this._onDidFocus.fire())); } // Indicate that size constraints could have changed due to new editor @@ -228,10 +228,4 @@ export class EditorControl extends Disposable { this._activeControl.layout(this.dimension); } } - - dispose(): void { - this.activeControlDisposeables = dispose(this.activeControlDisposeables); - - super.dispose(); - } } diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index fb450decc71..5aa9e32bb4b 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -24,7 +24,7 @@ import { ILocalProgressService } from 'vs/platform/progress/common/progress'; import { LocalProgressService } from 'vs/workbench/services/progress/browser/localProgressService'; import { localize } from 'vs/nls'; import { isPromiseCanceledError } from 'vs/base/common/errors'; -import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { dispose, MutableDisposable } from 'vs/base/common/lifecycle'; import { Severity, INotificationService, INotificationActions } from 'vs/platform/notification/common/notification'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -216,15 +216,15 @@ export class EditorGroupView extends Themable implements IEditorGroupView { const groupActiveEditorDirtyContextKey = EditorGroupActiveEditorDirtyContext.bindTo(contextKeyServcie); const groupEditorsCountContext = EditorGroupEditorsCountContext.bindTo(contextKeyServcie); - let activeEditorListener: IDisposable; + let activeEditorListener = new MutableDisposable(); const observeActiveEditor = () => { - dispose(activeEditorListener); + activeEditorListener.clear(); const activeEditor = this._group.activeEditor; if (activeEditor) { groupActiveEditorDirtyContextKey.set(activeEditor.isDirty()); - activeEditorListener = activeEditor.onDidChangeDirty(() => groupActiveEditorDirtyContextKey.set(activeEditor.isDirty())); + activeEditorListener.value = activeEditor.onDidChangeDirty(() => groupActiveEditorDirtyContextKey.set(activeEditor.isDirty())); } else { groupActiveEditorDirtyContextKey.set(false); } @@ -321,13 +321,16 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Fill in contributed actions const actions: IAction[] = []; - createAndFillInContextMenuActions(menu, undefined, actions, this.contextMenuService); + const actionsDisposable = createAndFillInContextMenuActions(menu, undefined, actions, this.contextMenuService); // Show it this.contextMenuService.showContextMenu({ getAnchor: () => anchor, getActions: () => actions, - onHide: () => this.focus() + onHide: () => { + this.focus(); + dispose(actionsDisposable); + } }); } diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts index aa10002efb7..810b2aa3ab7 100644 --- a/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -19,7 +19,7 @@ import { distinct, coalesce } from 'vs/base/common/arrays'; import { IEditorGroupsAccessor, IEditorGroupView, getEditorPartOptions, impactsEditorPartOptions, IEditorPartOptionsChangeEvent, IEditorPartCreationOptions } from 'vs/workbench/browser/parts/editor/editor'; import { EditorGroupView } from 'vs/workbench/browser/parts/editor/editorGroupView'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; -import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { assign } from 'vs/base/common/objects'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ISerializedEditorGroup, isSerializedEditorGroup } from 'vs/workbench/common/editor/editorGroup'; @@ -521,13 +521,13 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro this.groupViews.set(groupView.id, groupView); // Track focus - let groupDisposables: IDisposable[] = []; - groupDisposables.push(groupView.onDidFocus(() => { + let groupDisposables = new DisposableStore(); + groupDisposables.add(groupView.onDidFocus(() => { this.doSetGroupActive(groupView); })); // Track editor change - groupDisposables.push(groupView.onDidGroupChange(e => { + groupDisposables.add(groupView.onDidGroupChange(e => { if (e.kind === GroupChangeKind.EDITOR_ACTIVE) { this.updateContainer(); } @@ -535,7 +535,7 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro // Track dispose Event.once(groupView.onWillDispose)(() => { - groupDisposables = dispose(groupDisposables); + dispose(groupDisposables); this.groupViews.delete(groupView.id); this.doUpdateMostRecentActive(groupView); }); diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index f05599325a6..9fc2e6ca564 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -14,7 +14,7 @@ import { Action } from 'vs/base/common/actions'; import { Language } from 'vs/base/common/platform'; import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput'; import { IFileEditorInput, EncodingMode, IEncodingSupport, toResource, SideBySideEditorInput, IEditor as IBaseEditor, IEditorInput, SideBySideEditor, IModeSupport } from 'vs/workbench/common/editor'; -import { IDisposable, dispose, Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, MutableDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { IEditorAction } from 'vs/editor/common/editorCommon'; import { EndOfLineSequence } from 'vs/editor/common/model'; @@ -286,8 +286,8 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { private readonly metadataElement = this._register(new MutableDisposable()); private readonly state = new State(); - private readonly activeEditorListeners: IDisposable[] = []; - private delayedRender: IDisposable | null = null; + private readonly activeEditorListeners = this._register(new DisposableStore()); + private readonly delayedRender = this._register(new MutableDisposable()); private toRender: StateChange | null = null; private screenReaderNotification: INotificationHandle | null = null; private promptedScreenReader: boolean = false; @@ -514,8 +514,9 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { if (!this.toRender) { this.toRender = changed; - this.delayedRender = runAtThisOrScheduleAtNextAnimationFrame(() => { - this.delayedRender = null; + this.delayedRender.value = runAtThisOrScheduleAtNextAnimationFrame(() => { + this.delayedRender.clear(); + const toRender = this.toRender; this.toRender = null; if (toRender) { @@ -576,30 +577,30 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { this.onMetadataChange(activeControl); // Dispose old active editor listeners - dispose(this.activeEditorListeners); + this.activeEditorListeners.clear(); // Attach new listeners to active editor if (activeCodeEditor) { // Hook Listener for Configuration changes - this.activeEditorListeners.push(activeCodeEditor.onDidChangeConfiguration((event: IConfigurationChangedEvent) => { + this.activeEditorListeners.add(activeCodeEditor.onDidChangeConfiguration((event: IConfigurationChangedEvent) => { if (event.accessibilitySupport) { this.onScreenReaderModeChange(activeCodeEditor); } })); // Hook Listener for Selection changes - this.activeEditorListeners.push(activeCodeEditor.onDidChangeCursorPosition((event: ICursorPositionChangedEvent) => { + this.activeEditorListeners.add(activeCodeEditor.onDidChangeCursorPosition((event: ICursorPositionChangedEvent) => { this.onSelectionChange(activeCodeEditor); })); // Hook Listener for mode changes - this.activeEditorListeners.push(activeCodeEditor.onDidChangeModelLanguage((event: IModelLanguageChangedEvent) => { + this.activeEditorListeners.add(activeCodeEditor.onDidChangeModelLanguage((event: IModelLanguageChangedEvent) => { this.onModeChange(activeCodeEditor); })); // Hook Listener for content changes - this.activeEditorListeners.push(activeCodeEditor.onDidChangeModelContent((e) => { + this.activeEditorListeners.add(activeCodeEditor.onDidChangeModelContent((e) => { this.onEOLChange(activeCodeEditor); const selections = activeCodeEditor.getSelections(); @@ -614,7 +615,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { })); // Hook Listener for content options changes - this.activeEditorListeners.push(activeCodeEditor.onDidChangeModelOptions((event: IModelOptionsChangedEvent) => { + this.activeEditorListeners.add(activeCodeEditor.onDidChangeModelOptions((event: IModelOptionsChangedEvent) => { this.onIndentationChange(activeCodeEditor); })); } @@ -637,11 +638,11 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { } binaryEditors.forEach(editor => { - this.activeEditorListeners.push(editor.onMetadataChanged(metadata => { + this.activeEditorListeners.add(editor.onMetadataChanged(metadata => { this.onMetadataChange(activeControl); })); - this.activeEditorListeners.push(editor.onDidOpenInPlace(() => { + this.activeEditorListeners.add(editor.onDidOpenInPlace(() => { this.updateStatusBar(); })); }); @@ -812,15 +813,6 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { return !!activeControl && activeControl === control; } - - dispose(): void { - super.dispose(); - - if (this.delayedRender) { - this.delayedRender.dispose(); - this.delayedRender = null; - } - } } function isWritableCodeEditor(codeEditor: ICodeEditor | undefined): boolean { diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index 7578bda2fc2..667ec403730 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -20,7 +20,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IMenuService } from 'vs/platform/actions/common/actions'; import { TitleControl } from 'vs/workbench/browser/parts/editor/titleControl'; import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; -import { IDisposable, dispose, DisposableStore, combinedDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, DisposableStore, combinedDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import { getOrSet } from 'vs/base/common/map'; @@ -64,7 +64,7 @@ export class TabsTitleControl extends TitleControl { private tabDisposeables: IDisposable[] = []; private dimension: Dimension; - private layoutScheduled?: IDisposable; + private readonly layoutScheduled = this._register(new MutableDisposable()); private blockRevealActiveTab: boolean; constructor( @@ -996,10 +996,10 @@ export class TabsTitleControl extends TitleControl { // The layout of tabs can be an expensive operation because we access DOM properties // that can result in the browser doing a full page layout to validate them. To buffer // this a little bit we try at least to schedule this work on the next animation frame. - if (!this.layoutScheduled) { - this.layoutScheduled = scheduleAtNextAnimationFrame(() => { + if (!this.layoutScheduled.value) { + this.layoutScheduled.value = scheduleAtNextAnimationFrame(() => { this.doLayout(this.dimension); - this.layoutScheduled = undefined; + this.layoutScheduled.clear(); }); } } @@ -1149,8 +1149,7 @@ export class TabsTitleControl extends TitleControl { dispose(): void { super.dispose(); - dispose(this.layoutScheduled); - this.layoutScheduled = undefined; + this.tabDisposeables = dispose(this.tabDisposeables); } } diff --git a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts index 3c7045c94a3..ac833ec7818 100644 --- a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts @@ -22,7 +22,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ITextFileService, TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles'; import { ScrollType, IDiffEditorViewState, IDiffEditorModel } from 'vs/editor/common/editorCommon'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; import { URI } from 'vs/base/common/uri'; import { Event } from 'vs/base/common/event'; @@ -40,7 +40,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor { static readonly ID = TEXT_DIFF_EDITOR_ID; private diffNavigator: DiffNavigator; - private diffNavigatorDisposables: IDisposable[] = []; + private readonly diffNavigatorDisposables = this._register(new DisposableStore()); constructor( @ITelemetryService telemetryService: ITelemetryService, @@ -75,7 +75,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor { async setInput(input: EditorInput, options: EditorOptions, token: CancellationToken): Promise { // Dispose previous diff navigator - this.diffNavigatorDisposables = dispose(this.diffNavigatorDisposables); + this.diffNavigatorDisposables.clear(); // Remember view settings if input changes this.saveTextDiffEditorViewState(this.input); @@ -117,7 +117,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor { this.diffNavigator = new DiffNavigator(diffEditor, { alwaysRevealFirst: !optionsGotApplied && !hasPreviousViewState // only reveal first change if we had no options or viewstate }); - this.diffNavigatorDisposables.push(this.diffNavigator); + this.diffNavigatorDisposables.add(this.diffNavigator); // Readonly flag diffEditor.updateOptions({ readOnly: resolvedDiffEditorModel.isReadonly() }); @@ -238,7 +238,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor { clearInput(): void { // Dispose previous diff navigator - this.diffNavigatorDisposables = dispose(this.diffNavigatorDisposables); + this.diffNavigatorDisposables.clear(); // Keep editor view state in settings to restore when coming back this.saveTextDiffEditorViewState(this.input); @@ -330,10 +330,4 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor { // create a URI that is the Base64 concatenation of original + modified resource return URI.from({ scheme: 'diff', path: `${btoa(original.toString())}${btoa(modified.toString())}` }); } - - dispose(): void { - this.diffNavigatorDisposables = dispose(this.diffNavigatorDisposables); - - super.dispose(); - } } diff --git a/src/vs/workbench/browser/parts/editor/titleControl.ts b/src/vs/workbench/browser/parts/editor/titleControl.ts index c0934ae5f06..6bd8f1f83d7 100644 --- a/src/vs/workbench/browser/parts/editor/titleControl.ts +++ b/src/vs/workbench/browser/parts/editor/titleControl.ts @@ -11,7 +11,7 @@ import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; import { IAction, IRunEvent } from 'vs/base/common/actions'; import * as arrays from 'vs/base/common/arrays'; import { ResolvedKeybinding } from 'vs/base/common/keyCodes'; -import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { dispose, DisposableStore } from 'vs/base/common/lifecycle'; import 'vs/css!./media/titlecontrol'; import { getCodeEditor } from 'vs/editor/browser/editorBrowser'; import { localize } from 'vs/nls'; @@ -56,12 +56,13 @@ export abstract class TitleControl extends Themable { private currentPrimaryEditorActionIds: string[] = []; private currentSecondaryEditorActionIds: string[] = []; + protected editorActionsToolbar: ToolBar; private resourceContext: ResourceContextKey; private editorPinnedContext: IContextKey; - private editorToolBarMenuDisposables: IDisposable[] = []; + private readonly editorToolBarMenuDisposables = this._register(new DisposableStore()); private contextMenu: IMenu; @@ -222,7 +223,7 @@ export abstract class TitleControl extends Themable { const secondary: IAction[] = []; // Dispose previous listeners - this.editorToolBarMenuDisposables = dispose(this.editorToolBarMenuDisposables); + this.editorToolBarMenuDisposables.clear(); // Update contexts this.resourceContext.set(this.group.activeEditor ? withUndefinedAsNull(toResource(this.group.activeEditor, { supportSideBySide: SideBySideEditor.MASTER })) : null); @@ -234,12 +235,12 @@ export abstract class TitleControl extends Themable { const codeEditor = getCodeEditor(activeControl.getControl()); const scopedContextKeyService = codeEditor && codeEditor.invokeWithinContext(accessor => accessor.get(IContextKeyService)) || this.contextKeyService; const titleBarMenu = this.menuService.createMenu(MenuId.EditorTitle, scopedContextKeyService); - this.editorToolBarMenuDisposables.push(titleBarMenu); - this.editorToolBarMenuDisposables.push(titleBarMenu.onDidChange(() => { + this.editorToolBarMenuDisposables.add(titleBarMenu); + this.editorToolBarMenuDisposables.add(titleBarMenu.onDidChange(() => { this.updateEditorActionsToolbar(); // Update editor toolbar whenever contributed actions change })); - createAndFillInActionBarActions(titleBarMenu, { arg: this.resourceContext.get(), shouldForwardArgs: true }, { primary, secondary }); + this.editorToolBarMenuDisposables.add(createAndFillInActionBarActions(titleBarMenu, { arg: this.resourceContext.get(), shouldForwardArgs: true }, { primary, secondary })); } return { primary, secondary }; @@ -306,7 +307,7 @@ export abstract class TitleControl extends Themable { // Fill in contributed actions const actions: IAction[] = []; - createAndFillInContextMenuActions(this.contextMenu, { shouldForwardArgs: true, arg: this.resourceContext.get() }, actions, this.contextMenuService); + const actionsDisposable = createAndFillInContextMenuActions(this.contextMenu, { shouldForwardArgs: true, arg: this.resourceContext.get() }, actions, this.contextMenuService); // Show it this.contextMenuService.showContextMenu({ @@ -322,6 +323,9 @@ export abstract class TitleControl extends Themable { // restore focus to active group this.accessor.activeGroup.focus(); + + // Cleanup + dispose(actionsDisposable); } }); } @@ -373,7 +377,6 @@ export abstract class TitleControl extends Themable { dispose(): void { dispose(this.breadcrumbsControl); this.breadcrumbsControl = undefined; - this.editorToolBarMenuDisposables = dispose(this.editorToolBarMenuDisposables); super.dispose(); } diff --git a/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts b/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts index 707ce0440ea..26b7cebe885 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts @@ -5,7 +5,7 @@ import 'vs/css!./media/notificationsToasts'; import { INotificationsModel, NotificationChangeType, INotificationChangeEvent, INotificationViewItem, NotificationViewItemLabelKind } from 'vs/workbench/common/notifications'; -import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { addClass, removeClass, isAncestor, addDisposableListener, EventType, Dimension } from 'vs/base/browser/dom'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { NotificationsList } from 'vs/workbench/browser/parts/notifications/notificationsList'; @@ -29,7 +29,7 @@ interface INotificationToast { list: NotificationsList; container: HTMLElement; toast: HTMLElement; - disposeables: IDisposable[]; + toDispose: DisposableStore; } enum ToastVisibility { @@ -135,7 +135,7 @@ export class NotificationsToasts extends Themable { // Make Visible addClass(this.notificationsToastsContainer, 'visible'); - const itemDisposeables: IDisposable[] = []; + const itemDisposeables = new DisposableStore(); // Container const notificationToastContainer = document.createElement('div'); @@ -158,12 +158,12 @@ export class NotificationsToasts extends Themable { ariaLabel: localize('notificationsToast', "Notification Toast"), verticalScrollMode: ScrollbarVisibility.Hidden }); - itemDisposeables.push(notificationList); + itemDisposeables.add(notificationList); - const toast: INotificationToast = { item, list: notificationList, container: notificationToastContainer, toast: notificationToast, disposeables: itemDisposeables }; + const toast: INotificationToast = { item, list: notificationList, container: notificationToastContainer, toast: notificationToast, toDispose: itemDisposeables }; this.mapNotificationToToast.set(item, toast); - itemDisposeables.push(toDisposable(() => { + itemDisposeables.add(toDisposable(() => { if (this.isVisible(toast)) { this.notificationsToastsContainer.removeChild(toast.container); } @@ -184,12 +184,12 @@ export class NotificationsToasts extends Themable { this.layoutContainer(maxDimensions.height); // Update when item height changes due to expansion - itemDisposeables.push(item.onDidExpansionChange(() => { + itemDisposeables.add(item.onDidExpansionChange(() => { notificationList.updateNotificationsList(0, 1, [item]); })); // Update when item height potentially changes due to label changes - itemDisposeables.push(item.onDidLabelChange(e => { + itemDisposeables.add(item.onDidLabelChange(e => { if (!item.expanded) { return; // dynamic height only applies to expanded notifications } @@ -215,18 +215,18 @@ export class NotificationsToasts extends Themable { // Animate in addClass(notificationToast, 'notification-fade-in'); - itemDisposeables.push(addDisposableListener(notificationToast, 'transitionend', () => { + itemDisposeables.add(addDisposableListener(notificationToast, 'transitionend', () => { removeClass(notificationToast, 'notification-fade-in'); addClass(notificationToast, 'notification-fade-in-done'); })); } - private purgeNotification(item: INotificationViewItem, notificationToastContainer: HTMLElement, notificationList: NotificationsList, disposables: IDisposable[]): void { + private purgeNotification(item: INotificationViewItem, notificationToastContainer: HTMLElement, notificationList: NotificationsList, disposables: DisposableStore): void { // Track mouse over item let isMouseOverToast = false; - disposables.push(addDisposableListener(notificationToastContainer, EventType.MOUSE_OVER, () => isMouseOverToast = true)); - disposables.push(addDisposableListener(notificationToastContainer, EventType.MOUSE_OUT, () => isMouseOverToast = false)); + disposables.add(addDisposableListener(notificationToastContainer, EventType.MOUSE_OVER, () => isMouseOverToast = true)); + disposables.add(addDisposableListener(notificationToastContainer, EventType.MOUSE_OUT, () => isMouseOverToast = false)); // Install Timers to Purge Notification let purgeTimeoutHandle: any; @@ -248,7 +248,7 @@ export class NotificationsToasts extends Themable { hideAfterTimeout(); } }); - disposables.push(listener); + disposables.add(listener); } } @@ -267,7 +267,7 @@ export class NotificationsToasts extends Themable { hideAfterTimeout(); - disposables.push(toDisposable(() => clearTimeout(purgeTimeoutHandle))); + disposables.add(toDisposable(() => clearTimeout(purgeTimeoutHandle))); } private removeToast(item: INotificationViewItem): void { @@ -280,7 +280,7 @@ export class NotificationsToasts extends Themable { } // Listeners - dispose(notificationToast.disposeables); + dispose(notificationToast.toDispose); // Remove from Map this.mapNotificationToToast.delete(item); @@ -303,7 +303,7 @@ export class NotificationsToasts extends Themable { } private removeToasts(): void { - this.mapNotificationToToast.forEach(toast => dispose(toast.disposeables)); + this.mapNotificationToToast.forEach(toast => dispose(toast.toDispose)); this.mapNotificationToToast.clear(); this.doHide(); diff --git a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts index 918fafdc244..24a1c8ca822 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts @@ -14,7 +14,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { IAction, IActionRunner } from 'vs/base/common/actions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { dispose, DisposableStore, Disposable } from 'vs/base/common/lifecycle'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdown'; import { INotificationViewItem, NotificationViewItem, NotificationViewItemLabelKind, INotificationMessage, ChoiceAction } from 'vs/workbench/common/notifications'; @@ -111,7 +111,7 @@ export class NotificationsListDelegate implements IListVirtualDelegate void; - disposeables: IDisposable[]; + toDispose: DisposableStore; } class NotificationMessageRenderer { @@ -157,7 +157,7 @@ class NotificationMessageRenderer { anchor.href = link.href; if (actionHandler) { - actionHandler.disposeables.push(addDisposableListener(anchor, 'click', () => actionHandler.callback(link.href))); + actionHandler.toDispose.add(addDisposableListener(anchor, EventType.CLICK, () => actionHandler.callback(link.href))); } messageContainer.appendChild(anchor); @@ -194,7 +194,7 @@ export class NotificationRenderer implements IListRenderer { if (action && action instanceof ConfigureNotificationAction) { const item = new DropdownMenuActionViewItem(action, action.configurationActions, this.contextMenuService, undefined, this.actionRunner, undefined, action.class); - data.toDispose.push(item); + data.toDispose.add(item); return item; } @@ -232,7 +232,7 @@ export class NotificationRenderer implements IListRenderer = ['info', 'warning', 'error']; - private inputDisposeables: IDisposable[] = []; + private readonly inputDisposeables = this._register(new DisposableStore()); constructor( private template: INotificationTemplateData, @@ -298,6 +298,8 @@ export class NotificationTemplateRenderer { @IThemeService private readonly themeService: IThemeService, @IKeybindingService private readonly keybindingService: IKeybindingService ) { + super(); + if (!NotificationTemplateRenderer.closeNotificationAction) { NotificationTemplateRenderer.closeNotificationAction = instantiationService.createInstance(ClearNotificationAction, ClearNotificationAction.ID, ClearNotificationAction.LABEL); NotificationTemplateRenderer.expandNotificationAction = instantiationService.createInstance(ExpandNotificationAction, ExpandNotificationAction.ID, ExpandNotificationAction.LABEL); @@ -306,7 +308,7 @@ export class NotificationTemplateRenderer { } setInput(notification: INotificationViewItem): void { - this.inputDisposeables = dispose(this.inputDisposeables); + this.inputDisposeables.clear(); this.render(notification); } @@ -315,7 +317,7 @@ export class NotificationTemplateRenderer { // Container toggleClass(this.template.container, 'expanded', notification.expanded); - this.inputDisposeables.push(addDisposableListener(this.template.container, EventType.MOUSE_UP, e => { + this.inputDisposeables.add(addDisposableListener(this.template.container, EventType.MOUSE_UP, e => { if (e.button === 1 /* Middle Button */) { EventHelper.stop(e); @@ -342,7 +344,7 @@ export class NotificationTemplateRenderer { this.renderProgress(notification); // Label Change Events - this.inputDisposeables.push(notification.onDidLabelChange(event => { + this.inputDisposeables.add(notification.onDidLabelChange(event => { switch (event.kind) { case NotificationViewItemLabelKind.SEVERITY: this.renderSeverity(notification); @@ -368,7 +370,7 @@ export class NotificationTemplateRenderer { clearNode(this.template.message); this.template.message.appendChild(NotificationMessageRenderer.render(notification.message, { callback: link => this.openerService.open(URI.parse(link)), - disposeables: this.inputDisposeables + toDispose: this.inputDisposeables })); const messageOverflows = notification.canCollapse && !notification.expanded && this.template.message.scrollWidth > this.template.message.clientWidth; @@ -393,7 +395,7 @@ export class NotificationTemplateRenderer { if (isNonEmptyArray(notification.actions.secondary)) { const configureNotificationAction = this.instantiationService.createInstance(ConfigureNotificationAction, ConfigureNotificationAction.ID, ConfigureNotificationAction.LABEL, notification.actions.secondary); actions.push(configureNotificationAction); - this.inputDisposeables.push(configureNotificationAction); + this.inputDisposeables.add(configureNotificationAction); } // Expand / Collapse @@ -439,7 +441,7 @@ export class NotificationTemplateRenderer { const action = notification.actions.primary![index]; button.label = action.label; - this.inputDisposeables.push(button.onDidClick(e => { + this.inputDisposeables.add(button.onDidClick(e => { EventHelper.stop(e, true); // Run action @@ -451,10 +453,10 @@ export class NotificationTemplateRenderer { } })); - this.inputDisposeables.push(attachButtonStyler(button, this.themeService)); + this.inputDisposeables.add(attachButtonStyler(button, this.themeService)); }); - this.inputDisposeables.push(buttonGroup); + this.inputDisposeables.add(buttonGroup); } } @@ -506,8 +508,4 @@ export class NotificationTemplateRenderer { return keybinding ? keybinding.getLabel() : null; } - - dispose(): void { - this.inputDisposeables = dispose(this.inputDisposeables); - } } diff --git a/src/vs/workbench/browser/parts/panel/panelActions.ts b/src/vs/workbench/browser/parts/panel/panelActions.ts index 7eee50158c4..904f8a364ba 100644 --- a/src/vs/workbench/browser/parts/panel/panelActions.ts +++ b/src/vs/workbench/browser/parts/panel/panelActions.ts @@ -5,7 +5,7 @@ import 'vs/css!./media/panelpart'; import * as nls from 'vs/nls'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { Action } from 'vs/base/common/actions'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -96,7 +96,7 @@ export class TogglePanelPositionAction extends Action { static readonly MOVE_TO_RIGHT_LABEL = nls.localize('moveToRight', "Move Panel Right"); static readonly MOVE_TO_BOTTOM_LABEL = nls.localize('moveToBottom', "Move Panel to Bottom"); - private toDispose: IDisposable[]; + private readonly toDispose = this._register(new DisposableStore()); constructor( id: string, @@ -106,15 +106,13 @@ export class TogglePanelPositionAction extends Action { ) { super(id, label, layoutService.getPanelPosition() === Position.RIGHT ? 'move-panel-to-bottom' : 'move-panel-to-right'); - this.toDispose = []; - const setClassAndLabel = () => { const positionRight = this.layoutService.getPanelPosition() === Position.RIGHT; this.class = positionRight ? 'move-panel-to-bottom' : 'move-panel-to-right'; this.label = positionRight ? TogglePanelPositionAction.MOVE_TO_BOTTOM_LABEL : TogglePanelPositionAction.MOVE_TO_RIGHT_LABEL; }; - this.toDispose.push(editorGroupsService.onDidLayout(() => setClassAndLabel())); + this.toDispose.add(editorGroupsService.onDidLayout(() => setClassAndLabel())); setClassAndLabel(); } @@ -125,12 +123,6 @@ export class TogglePanelPositionAction extends Action { this.layoutService.setPanelPosition(position === Position.BOTTOM ? Position.RIGHT : Position.BOTTOM); return Promise.resolve(); } - - dispose(): void { - super.dispose(); - - this.toDispose = dispose(this.toDispose); - } } export class ToggleMaximizedPanelAction extends Action { @@ -141,7 +133,7 @@ export class ToggleMaximizedPanelAction extends Action { private static readonly MAXIMIZE_LABEL = nls.localize('maximizePanel', "Maximize Panel Size"); private static readonly RESTORE_LABEL = nls.localize('minimizePanel', "Restore Panel Size"); - private toDispose: IDisposable[]; + private readonly toDispose = this._register(new DisposableStore()); constructor( id: string, @@ -151,9 +143,7 @@ export class ToggleMaximizedPanelAction extends Action { ) { super(id, label, layoutService.isPanelMaximized() ? 'minimize-panel-action' : 'maximize-panel-action'); - this.toDispose = []; - - this.toDispose.push(editorGroupsService.onDidLayout(() => { + this.toDispose.add(editorGroupsService.onDidLayout(() => { const maximized = this.layoutService.isPanelMaximized(); this.class = maximized ? 'minimize-panel-action' : 'maximize-panel-action'; this.label = maximized ? ToggleMaximizedPanelAction.RESTORE_LABEL : ToggleMaximizedPanelAction.MAXIMIZE_LABEL; @@ -168,12 +158,6 @@ export class ToggleMaximizedPanelAction extends Action { this.layoutService.toggleMaximizedPanel(); return Promise.resolve(); } - - dispose(): void { - super.dispose(); - - this.toDispose = dispose(this.toDispose); - } } export class PanelActivityAction extends ActivityAction { diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 80af760a4df..0ac5cb6681c 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -15,7 +15,7 @@ import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { IAction, Action } from 'vs/base/common/actions'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import * as nls from 'vs/nls'; import { EditorInput, toResource, Verbosity, SideBySideEditor } from 'vs/workbench/common/editor'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -75,8 +75,8 @@ export class TitlebarPart extends Part implements ITitleService { private isInactive: boolean; - private properties: ITitleProperties; - private activeEditorListeners: IDisposable[]; + private readonly properties: ITitleProperties = { isPure: true, isAdmin: false }; + private readonly activeEditorListeners = this._register(new DisposableStore()); private titleUpdater: RunOnceScheduler = this._register(new RunOnceScheduler(() => this.doUpdateTitle(), 0)); @@ -96,9 +96,6 @@ export class TitlebarPart extends Part implements ITitleService { ) { super(Parts.TITLEBAR_PART, { hasTitle: false }, themeService, storageService, layoutService); - this.properties = { isPure: true, isAdmin: false }; - this.activeEditorListeners = []; - this.registerListeners(); } @@ -162,8 +159,7 @@ export class TitlebarPart extends Part implements ITitleService { private onActiveEditorChange(): void { // Dispose old listeners - dispose(this.activeEditorListeners); - this.activeEditorListeners = []; + this.activeEditorListeners.clear(); // Calculate New Window Title this.titleUpdater.schedule(); @@ -171,8 +167,8 @@ export class TitlebarPart extends Part implements ITitleService { // Apply listener for dirty and label changes const activeEditor = this.editorService.activeEditor; if (activeEditor instanceof EditorInput) { - this.activeEditorListeners.push(activeEditor.onDidChangeDirty(() => this.titleUpdater.schedule())); - this.activeEditorListeners.push(activeEditor.onDidChangeLabel(() => this.titleUpdater.schedule())); + this.activeEditorListeners.add(activeEditor.onDidChangeDirty(() => this.titleUpdater.schedule())); + this.activeEditorListeners.add(activeEditor.onDidChangeLabel(() => this.titleUpdater.schedule())); } // Represented File Name diff --git a/src/vs/workbench/browser/parts/views/customView.ts b/src/vs/workbench/browser/parts/views/customView.ts index ce0f5ee31b7..5a758eb786f 100644 --- a/src/vs/workbench/browser/parts/views/customView.ts +++ b/src/vs/workbench/browser/parts/views/customView.ts @@ -5,7 +5,7 @@ import 'vs/css!./media/views'; import { Event, Emitter } from 'vs/base/common/event'; -import { IDisposable, dispose, Disposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { IDisposable, Disposable, toDisposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IAction, IActionViewItem, ActionRunner, Action } from 'vs/base/common/actions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -100,14 +100,13 @@ export class CustomTreeViewPanel extends ViewletPanel { } } -class TitleMenus implements IDisposable { +class TitleMenus extends Disposable { - private disposables: IDisposable[] = []; - private titleDisposable: IDisposable = Disposable.None; private titleActions: IAction[] = []; + private readonly titleActionsDisposable = this._register(new MutableDisposable()); private titleSecondaryActions: IAction[] = []; - private _onDidChangeTitle = new Emitter(); + private _onDidChangeTitle = this._register(new Emitter()); get onDidChangeTitle(): Event { return this._onDidChangeTitle.event; } constructor( @@ -115,32 +114,26 @@ class TitleMenus implements IDisposable { @IContextKeyService private readonly contextKeyService: IContextKeyService, @IMenuService private readonly menuService: IMenuService, ) { - if (this.titleDisposable) { - this.titleDisposable.dispose(); - this.titleDisposable = Disposable.None; - } + super(); - const _contextKeyService = this.contextKeyService.createScoped(); - _contextKeyService.createKey('view', id); + const scopedContextKeyService = this._register(this.contextKeyService.createScoped()); + scopedContextKeyService.createKey('view', id); - const titleMenu = this.menuService.createMenu(MenuId.ViewTitle, _contextKeyService); + const titleMenu = this._register(this.menuService.createMenu(MenuId.ViewTitle, scopedContextKeyService)); const updateActions = () => { this.titleActions = []; this.titleSecondaryActions = []; - createAndFillInActionBarActions(titleMenu, undefined, { primary: this.titleActions, secondary: this.titleSecondaryActions }); + this.titleActionsDisposable.value = createAndFillInActionBarActions(titleMenu, undefined, { primary: this.titleActions, secondary: this.titleSecondaryActions }); this._onDidChangeTitle.fire(); }; - const listener = titleMenu.onDidChange(updateActions); + this._register(titleMenu.onDidChange(updateActions)); updateActions(); - this.titleDisposable = toDisposable(() => { - listener.dispose(); - titleMenu.dispose(); - _contextKeyService.dispose(); + this._register(toDisposable(() => { this.titleActions = []; this.titleSecondaryActions = []; - }); + })); } getTitleActions(): IAction[] { @@ -150,10 +143,6 @@ class TitleMenus implements IDisposable { getTitleSecondaryActions(): IAction[] { return this.titleSecondaryActions; } - - dispose(): void { - this.disposables = dispose(this.disposables); - } } class Root implements ITreeItem { diff --git a/src/vs/workbench/common/editor/editorGroup.ts b/src/vs/workbench/common/editor/editorGroup.ts index 0618c153440..7d4ca0c5e52 100644 --- a/src/vs/workbench/common/editor/editorGroup.ts +++ b/src/vs/workbench/common/editor/editorGroup.ts @@ -8,7 +8,7 @@ import { Extensions, IEditorInputFactoryRegistry, EditorInput, toResource, IEdit import { URI } from 'vs/base/common/uri'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; -import { dispose, IDisposable, Disposable } from 'vs/base/common/lifecycle'; +import { dispose, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; import { ResourceMap } from 'vs/base/common/map'; import { coalesce } from 'vs/base/common/arrays'; @@ -270,30 +270,30 @@ export class EditorGroup extends Disposable { } private registerEditorListeners(editor: EditorInput): void { - const unbind: IDisposable[] = []; + const listeners = new DisposableStore(); // Re-emit disposal of editor input as our own event const onceDispose = Event.once(editor.onDispose); - unbind.push(onceDispose(() => { + listeners.add(onceDispose(() => { if (this.indexOf(editor) >= 0) { this._onDidEditorDispose.fire(editor); } })); // Re-Emit dirty state changes - unbind.push(editor.onDidChangeDirty(() => { + listeners.add(editor.onDidChangeDirty(() => { this._onDidEditorBecomeDirty.fire(editor); })); // Re-Emit label changes - unbind.push(editor.onDidChangeLabel(() => { + listeners.add(editor.onDidChangeLabel(() => { this._onDidEditorLabelChange.fire(editor); })); // Clean up dispose listeners once the editor gets closed - unbind.push(this.onDidEditorClose(event => { + listeners.add(this.onDidEditorClose(event => { if (event.editor.matches(editor)) { - dispose(unbind); + dispose(listeners); } })); } diff --git a/src/vs/workbench/common/editor/textEditorModel.ts b/src/vs/workbench/common/editor/textEditorModel.ts index a098901ca66..239fd95cc28 100644 --- a/src/vs/workbench/common/editor/textEditorModel.ts +++ b/src/vs/workbench/common/editor/textEditorModel.ts @@ -9,7 +9,7 @@ import { URI } from 'vs/base/common/uri'; import { ITextEditorModel, IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService'; import { IModeService, ILanguageSelection } from 'vs/editor/common/services/modeService'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { IDisposable } from 'vs/base/common/lifecycle'; +import { MutableDisposable } from 'vs/base/common/lifecycle'; import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; import { withUndefinedAsNull } from 'vs/base/common/types'; @@ -20,7 +20,7 @@ export abstract class BaseTextEditorModel extends EditorModel implements ITextEd protected textEditorModelHandle: URI | null; private createdEditorModel: boolean; - private modelDisposeListener: IDisposable | null; + private readonly modelDisposeListener = this._register(new MutableDisposable()); constructor( @IModelService protected modelService: IModelService, @@ -49,11 +49,7 @@ export abstract class BaseTextEditorModel extends EditorModel implements ITextEd } private registerModelDisposeListener(model: ITextModel): void { - if (this.modelDisposeListener) { - this.modelDisposeListener.dispose(); - } - - this.modelDisposeListener = model.onWillDispose(() => { + this.modelDisposeListener.value = model.onWillDispose(() => { this.textEditorModelHandle = null; // make sure we do not dispose code editor model again this.dispose(); }); @@ -166,10 +162,7 @@ export abstract class BaseTextEditorModel extends EditorModel implements ITextEd } dispose(): void { - if (this.modelDisposeListener) { - this.modelDisposeListener.dispose(); // dispose this first because it will trigger another dispose() otherwise - this.modelDisposeListener = null; - } + this.modelDisposeListener.dispose(); // dispose this first because it will trigger another dispose() otherwise if (this.textEditorModelHandle && this.createdEditorModel) { this.modelService.destroyModel(this.textEditorModelHandle); diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts index 3fffff2d139..6d94222b5b8 100644 --- a/src/vs/workbench/contrib/debug/browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -29,6 +29,7 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; import { createMatches, FuzzyScore } from 'vs/base/common/filters'; import { Event } from 'vs/base/common/event'; +import { dispose } from 'vs/base/common/lifecycle'; const $ = dom.$; @@ -296,14 +297,14 @@ export class CallStackView extends ViewletPanel { this.callStackItemType.reset(); } + const actions: IAction[] = []; + const actionsDisposable = createAndFillInContextMenuActions(this.contributedContextMenu, { arg: this.getContextForContributedActions(element), shouldForwardArgs: true }, actions, this.contextMenuService); + this.contextMenuService.showContextMenu({ getAnchor: () => e.anchor, - getActions: () => { - const actions: IAction[] = []; - createAndFillInContextMenuActions(this.contributedContextMenu, { arg: this.getContextForContributedActions(element), shouldForwardArgs: true }, actions, this.contextMenuService); - return actions; - }, - getActionsContext: () => element + getActions: () => actions, + getActionsContext: () => element, + onHide: () => dispose(actionsDisposable) }); } diff --git a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts index f7a188eb798..1021cb40834 100644 --- a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts +++ b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts @@ -31,7 +31,7 @@ import { IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/editor import { CancellationToken } from 'vs/base/common/cancellation'; import { IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor'; import { createErrorWithActions } from 'vs/base/common/errorsWithActions'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { MutableDisposable } from 'vs/base/common/lifecycle'; /** * An implementation of editor for file system resources. @@ -41,7 +41,7 @@ export class TextFileEditor extends BaseTextEditor { static readonly ID = TEXT_FILE_EDITOR_ID; private restoreViewState: boolean; - private groupListener: IDisposable; + private readonly groupListener = this._register(new MutableDisposable()); constructor( @ITelemetryService telemetryService: ITelemetryService, @@ -99,8 +99,7 @@ export class TextFileEditor extends BaseTextEditor { // React to editors closing to preserve or clear view state. This needs to happen // in the onWillCloseEditor because at that time the editor has not yet // been disposed and we can safely persist the view state still as needed. - dispose(this.groupListener); - this.groupListener = ((group as IEditorGroupView).onWillCloseEditor(e => this.onWillCloseEditorInGroup(e))); + this.groupListener.value = ((group as IEditorGroupView).onWillCloseEditor(e => this.onWillCloseEditorInGroup(e))); } private onWillCloseEditorInGroup(e: IEditorCloseEvent): void { @@ -295,10 +294,4 @@ export class TextFileEditor extends BaseTextEditor { this.saveTextEditorViewState(input.getResource()); } } - - dispose(): void { - dispose(this.groupListener); - - super.dispose(); - } } diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index 957c1bae15c..4fb9af476f5 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -49,6 +49,7 @@ import { values } from 'vs/base/common/map'; import { first } from 'vs/base/common/arrays'; import { withNullAsUndefined } from 'vs/base/common/types'; import { IFileService, FileSystemProviderCapabilities } from 'vs/platform/files/common/files'; +import { dispose } from 'vs/base/common/lifecycle'; export class ExplorerView extends ViewletPanel { static readonly ID: string = 'workbench.explorer.fileView'; @@ -384,20 +385,21 @@ export class ExplorerView extends ViewletPanel { this.setContextKeys(stat); const selection = this.tree.getSelection(); + + const actions: IAction[] = []; + const roots = this.explorerService.roots; // If the click is outside of the elements pass the root resource if there is only one root. If there are multiple roots pass empty object. + const arg = stat instanceof ExplorerItem ? stat.resource : roots.length === 1 ? roots[0].resource : {}; + const actionsDisposable = createAndFillInContextMenuActions(this.contributedContextMenu, { arg, shouldForwardArgs: true }, actions, this.contextMenuService); + this.contextMenuService.showContextMenu({ getAnchor: () => e.anchor, - getActions: () => { - const actions: IAction[] = []; - // If the click is outside of the elements pass the root resource if there is only one root. If there are multiple roots pass empty object. - const roots = this.explorerService.roots; - const arg = stat instanceof ExplorerItem ? stat.resource : roots.length === 1 ? roots[0].resource : {}; - createAndFillInContextMenuActions(this.contributedContextMenu, { arg, shouldForwardArgs: true }, actions, this.contextMenuService); - return actions; - }, + getActions: () => actions, onHide: (wasCancelled?: boolean) => { if (wasCancelled) { this.tree.domFocus(); } + + dispose(actionsDisposable); }, getActionsContext: () => stat && selection && selection.indexOf(stat) >= 0 ? selection.map((fs: ExplorerItem) => fs.resource) diff --git a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts index 239e8f0ceb2..46b7957388c 100644 --- a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts +++ b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts @@ -371,14 +371,14 @@ export class OpenEditorsView extends ViewletPanel { } const element = e.element; + const actions: IAction[] = []; + const actionsDisposable = createAndFillInContextMenuActions(this.contributedContextMenu, { shouldForwardArgs: true, arg: element instanceof OpenEditor ? element.editor.getResource() : {} }, actions, this.contextMenuService); + this.contextMenuService.showContextMenu({ getAnchor: () => e.anchor, - getActions: () => { - const actions: IAction[] = []; - createAndFillInContextMenuActions(this.contributedContextMenu, { shouldForwardArgs: true, arg: element instanceof OpenEditor ? element.editor.getResource() : {} }, actions, this.contextMenuService); - return actions; - }, - getActionsContext: () => element instanceof OpenEditor ? { groupId: element.groupId, editorIndex: element.editorIndex } : { groupId: element.id } + getActions: () => actions, + getActionsContext: () => element instanceof OpenEditor ? { groupId: element.groupId, editorIndex: element.editorIndex } : { groupId: element.id }, + onHide: () => dispose(actionsDisposable) }); } diff --git a/src/vs/workbench/contrib/files/common/dirtyFilesTracker.ts b/src/vs/workbench/contrib/files/common/dirtyFilesTracker.ts index 201452f8d45..49ef8b18505 100644 --- a/src/vs/workbench/contrib/files/common/dirtyFilesTracker.ts +++ b/src/vs/workbench/contrib/files/common/dirtyFilesTracker.ts @@ -10,7 +10,7 @@ import { TextFileModelChangeEvent, ITextFileService, AutoSaveMode, ModelState } import { platform, Platform } from 'vs/base/common/platform'; import { IWindowService } from 'vs/platform/windows/common/windows'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; -import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; @@ -20,7 +20,7 @@ import { IEditorService, ACTIVE_GROUP } from 'vs/workbench/services/editor/commo export class DirtyFilesTracker extends Disposable implements IWorkbenchContribution { private isDocumentedEdited: boolean; private lastDirtyCount: number; - private badgeHandle: IDisposable; + private readonly badgeHandle = this._register(new MutableDisposable()); constructor( @ITextFileService private readonly textFileService: ITextFileService, @@ -126,9 +126,9 @@ export class DirtyFilesTracker extends Disposable implements IWorkbenchContribut private updateActivityBadge(): void { const dirtyCount = this.textFileService.getDirty().length; this.lastDirtyCount = dirtyCount; - dispose(this.badgeHandle); + this.badgeHandle.clear(); if (dirtyCount > 0) { - this.badgeHandle = this.activityService.showActivity(VIEWLET_ID, new NumberBadge(dirtyCount, num => num === 1 ? nls.localize('dirtyFile', "1 unsaved file") : nls.localize('dirtyFiles', "{0} unsaved files", dirtyCount)), 'explorer-viewlet-label'); + this.badgeHandle.value = this.activityService.showActivity(VIEWLET_ID, new NumberBadge(dirtyCount, num => num === 1 ? nls.localize('dirtyFile', "1 unsaved file") : nls.localize('dirtyFiles', "{0} unsaved files", dirtyCount)), 'explorer-viewlet-label'); } } diff --git a/src/vs/workbench/contrib/files/common/files.ts b/src/vs/workbench/contrib/files/common/files.ts index fe6d95e6bcc..e60fda6cf31 100644 --- a/src/vs/workbench/contrib/files/common/files.ts +++ b/src/vs/workbench/contrib/files/common/files.ts @@ -9,7 +9,7 @@ import { IWorkbenchEditorConfiguration, IEditorIdentifier, IEditorInput, toResou import { IFilesConfiguration, FileChangeType, IFileService } from 'vs/platform/files/common/files'; import { ContextKeyExpr, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ITextModelContentProvider } from 'vs/editor/common/services/resolverService'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { ITextModel } from 'vs/editor/common/model'; import { Event } from 'vs/base/common/event'; import { IModelService } from 'vs/editor/common/services/modelService'; @@ -133,15 +133,17 @@ export const SortOrderConfiguration = { export type SortOrder = 'default' | 'mixed' | 'filesFirst' | 'type' | 'modified'; -export class TextFileContentProvider implements ITextModelContentProvider { - private fileWatcherDisposable: IDisposable | undefined; +export class TextFileContentProvider extends Disposable implements ITextModelContentProvider { + private readonly fileWatcherDisposable = this._register(new MutableDisposable()); constructor( @ITextFileService private readonly textFileService: ITextFileService, @IFileService private readonly fileService: IFileService, @IModeService private readonly modeService: IModeService, @IModelService private readonly modelService: IModelService - ) { } + ) { + super(); + } static async open(resource: URI, scheme: string, label: string, editorService: IEditorService, options?: ITextEditorOptions): Promise { await editorService.openEditor({ @@ -167,18 +169,15 @@ export class TextFileContentProvider implements ITextModelContentProvider { const codeEditorModel = await this.resolveEditorModel(resource); // Make sure to keep contents up to date when it changes - if (!this.fileWatcherDisposable) { - this.fileWatcherDisposable = this.fileService.onFileChanges(changes => { + if (!this.fileWatcherDisposable.value) { + this.fileWatcherDisposable.value = this.fileService.onFileChanges(changes => { if (changes.contains(savedFileResource, FileChangeType.UPDATED)) { this.resolveEditorModel(resource, false /* do not create if missing */); // update model when resource changes } }); if (codeEditorModel) { - once(codeEditorModel.onWillDispose)(() => { - dispose(this.fileWatcherDisposable); - this.fileWatcherDisposable = undefined; - }); + once(codeEditorModel.onWillDispose)(() => this.fileWatcherDisposable.clear()); } } @@ -210,11 +209,6 @@ export class TextFileContentProvider implements ITextModelContentProvider { return codeEditorModel; } - - dispose(): void { - dispose(this.fileWatcherDisposable); - this.fileWatcherDisposable = undefined; - } } export class OpenEditor implements IEditorIdentifier { diff --git a/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts b/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts index 67fa33738d1..0904b286f7c 100644 --- a/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts +++ b/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts @@ -30,7 +30,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, IDisposable, toDisposable, dispose } from 'vs/base/common/lifecycle'; import { timeout } from 'vs/base/common/async'; export const ALL_COMMANDS_PREFIX = '>'; @@ -385,6 +385,7 @@ export class CommandsHandler extends QuickOpenHandler implements IDisposable { private readonly commandsHistory: CommandsHistory; private readonly disposables = new DisposableStore(); + private readonly disposeOnClose = new DisposableStore(); private waitedForExtensionsRegistered: boolean; @@ -449,6 +450,7 @@ export class CommandsHandler extends QuickOpenHandler implements IDisposable { const menuActions = menu.getActions().reduce((r, [, actions]) => [...r, ...actions], []).filter(action => action instanceof MenuItemAction) as MenuItemAction[]; const commandEntries = this.menuItemActionsToEntries(menuActions, searchValue); menu.dispose(); + this.disposeOnClose.add(toDisposable(() => dispose(menuActions))); // Concat let entries = [...editorEntries, ...commandEntries]; @@ -591,8 +593,15 @@ export class CommandsHandler extends QuickOpenHandler implements IDisposable { return nls.localize('noCommandsMatching', "No commands matching"); } + onClose(canceled: boolean): void { + super.onClose(canceled); + + this.disposeOnClose.clear(); + } + dispose() { this.disposables.dispose(); + this.disposeOnClose.dispose(); } } diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index 4f26edf3d17..f51fd06cb9d 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -694,14 +694,14 @@ export class SearchView extends ViewletPanel { e.browserEvent.preventDefault(); e.browserEvent.stopPropagation(); + const actions: IAction[] = []; + const actionsDisposable = createAndFillInContextMenuActions(this.contextMenu, { shouldForwardArgs: true }, actions, this.contextMenuService); + this.contextMenuService.showContextMenu({ getAnchor: () => e.anchor, - getActions: () => { - const actions: IAction[] = []; - createAndFillInContextMenuActions(this.contextMenu, { shouldForwardArgs: true }, actions, this.contextMenuService); - return actions; - }, - getActionsContext: () => e.element + getActions: () => actions, + getActionsContext: () => e.element, + onHide: () => dispose(actionsDisposable) }); } diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index 8eaf6da6275..a1001064bfc 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -369,7 +369,7 @@ export class ElectronWindow extends Disposable { const actions: Array = []; // Fill actions into groups respecting order - createAndFillInActionBarActions(this.touchBarMenu, undefined, actions); + this.touchBarDisposables.add(createAndFillInActionBarActions(this.touchBarMenu, undefined, actions)); // Convert into command action multi array const items: ICommandAction[][] = []; diff --git a/src/vs/workbench/services/editor/browser/editorService.ts b/src/vs/workbench/services/editor/browser/editorService.ts index 683b344b9dd..1aff720e6e3 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -21,7 +21,7 @@ import { localize } from 'vs/nls'; import { IEditorGroupsService, IEditorGroup, GroupsOrder, IEditorReplacement, GroupChangeKind, preferredSideBySideGroupDirection } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IResourceEditor, ACTIVE_GROUP_TYPE, SIDE_GROUP_TYPE, SIDE_GROUP, IResourceEditorReplacement, IOpenEditorOverrideHandler, IVisibleEditor, IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { Disposable, IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { coalesce } from 'vs/base/common/arrays'; import { isCodeEditor, isDiffEditor, ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser'; import { IEditorGroupView, IEditorOpeningEvent, EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor'; @@ -118,24 +118,24 @@ export class EditorService extends Disposable implements EditorServiceImpl { } private registerGroupListeners(group: IEditorGroupView): void { - const groupDisposeables: IDisposable[] = []; + const groupDisposeables = new DisposableStore(); - groupDisposeables.push(group.onDidGroupChange(e => { + groupDisposeables.add(group.onDidGroupChange(e => { if (e.kind === GroupChangeKind.EDITOR_ACTIVE) { this.handleActiveEditorChange(group); this._onDidVisibleEditorsChange.fire(); } })); - groupDisposeables.push(group.onDidCloseEditor(event => { + groupDisposeables.add(group.onDidCloseEditor(event => { this._onDidCloseEditor.fire(event); })); - groupDisposeables.push(group.onWillOpenEditor(event => { + groupDisposeables.add(group.onWillOpenEditor(event => { this.onGroupWillOpenEditor(group, event); })); - groupDisposeables.push(group.onDidOpenEditorFail(editor => { + groupDisposeables.add(group.onDidOpenEditorFail(editor => { this._onDidOpenEditorFail.fire({ editor, groupId: group.id }); })); diff --git a/src/vs/workbench/services/files/common/fileService.ts b/src/vs/workbench/services/files/common/fileService.ts index 8631b019133..fd0a0d9f4e9 100644 --- a/src/vs/workbench/services/files/common/fileService.ts +++ b/src/vs/workbench/services/files/common/fileService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable, IDisposable, toDisposable, dispose } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, toDisposable, dispose, DisposableStore } from 'vs/base/common/lifecycle'; import { IFileService, IResolveFileOptions, FileChangesEvent, FileOperationEvent, IFileSystemProviderRegistrationEvent, IFileSystemProvider, IFileStat, IResolveFileResult, ICreateFileOptions, IFileSystemProviderActivationEvent, FileOperationError, FileOperationResult, FileOperation, FileSystemProviderCapabilities, FileType, toFileSystemProviderErrorCode, FileSystemProviderErrorCode, IStat, IFileStatWithMetadata, IResolveMetadataFileOptions, etag, hasReadWriteCapability, hasFileFolderCopyCapability, hasOpenReadWriteCloseCapability, toFileOperationResult, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileSystemProviderWithFileReadWriteCapability, IResolveFileResultWithMetadata, IWatchOptions, IWriteFileOptions, IReadFileOptions, IFileStreamContent, IFileContent, ETAG_DISABLED } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; @@ -49,10 +49,10 @@ export class FileService extends Disposable implements IFileService { this._onDidChangeFileSystemProviderRegistrations.fire({ added: true, scheme, provider }); // Forward events from provider - const providerDisposables: IDisposable[] = []; - providerDisposables.push(provider.onDidChangeFile(changes => this._onFileChanges.fire(new FileChangesEvent(changes)))); + const providerDisposables = new DisposableStore(); + providerDisposables.add(provider.onDidChangeFile(changes => this._onFileChanges.fire(new FileChangesEvent(changes)))); if (typeof provider.onDidErrorOccur === 'function') { - providerDisposables.push(provider.onDidErrorOccur(error => this._onError.fire(error))); + providerDisposables.add(provider.onDidErrorOccur(error => this._onError.fire(error))); } return toDisposable(() => { diff --git a/src/vs/workbench/services/history/browser/history.ts b/src/vs/workbench/services/history/browser/history.ts index 192ab79c021..5296da44a56 100644 --- a/src/vs/workbench/services/history/browser/history.ts +++ b/src/vs/workbench/services/history/browser/history.ts @@ -13,7 +13,7 @@ import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { FileChangesEvent, IFileService, FileChangeType, FILES_EXCLUDE_CONFIG } from 'vs/platform/files/common/files'; import { Selection } from 'vs/editor/common/core/selection'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { IDisposable, dispose, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { dispose, Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { Registry } from 'vs/platform/registry/common/platform'; import { Event } from 'vs/base/common/event'; @@ -108,8 +108,8 @@ export class HistoryService extends Disposable implements IHistoryService { private readonly activeEditorListeners = this._register(new DisposableStore()); private lastActiveEditor?: IEditorIdentifier; - private readonly editorHistoryListeners: Map = new Map(); - private readonly editorStackListeners: Map = new Map(); + private readonly editorHistoryListeners: Map = new Map(); + private readonly editorStackListeners: Map = new Map(); private stack: IStackEntry[]; private index: number; @@ -475,19 +475,19 @@ export class HistoryService extends Disposable implements IHistoryService { } } - private onEditorDispose(editor: EditorInput, listener: Function, mapEditorToDispose: Map): void { + private onEditorDispose(editor: EditorInput, listener: Function, mapEditorToDispose: Map): void { const toDispose = Event.once(editor.onDispose)(() => listener()); let disposables = mapEditorToDispose.get(editor); if (!disposables) { - disposables = []; + disposables = new DisposableStore(); mapEditorToDispose.set(editor, disposables); } - disposables.push(toDispose); + disposables.add(toDispose); } - private clearOnEditorDispose(editor: IEditorInput | IResourceInput | FileChangesEvent, mapEditorToDispose: Map): void { + private clearOnEditorDispose(editor: IEditorInput | IResourceInput | FileChangesEvent, mapEditorToDispose: Map): void { if (editor instanceof EditorInput) { const disposables = mapEditorToDispose.get(editor); if (disposables) { diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index dc6f69145e7..9a70fd94b6a 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -25,7 +25,7 @@ import { ITextBufferFactory } from 'vs/editor/common/model'; import { hash } from 'vs/base/common/hash'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { isLinux } from 'vs/base/common/platform'; -import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { toDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { ILogService } from 'vs/platform/log/common/log'; import { isEqual, isEqualOrParent, extname, basename, joinPath } from 'vs/base/common/resources'; import { onUnexpectedError } from 'vs/base/common/errors'; @@ -75,7 +75,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil private autoSaveAfterMillies?: number; private autoSaveAfterMilliesEnabled: boolean; - private autoSaveDisposable?: IDisposable; + private readonly autoSaveDisposable = this._register(new MutableDisposable()); private saveSequentializer: SaveSequentializer; private lastSaveAttemptTime: number; @@ -243,7 +243,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } // Cancel any running auto-save - this.cancelPendingAutoSave(); + this.autoSaveDisposable.clear(); // Unset flags const undo = this.setDirty(false); @@ -567,7 +567,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil this.logService.trace(`doAutoSave() - enter for versionId ${versionId}`, this.resource); // Cancel any currently running auto saves to make this the one that succeeds - this.cancelPendingAutoSave(); + this.autoSaveDisposable.clear(); // Create new save timer and store it for disposal as needed const handle = setTimeout(() => { @@ -578,25 +578,18 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } }, this.autoSaveAfterMillies); - this.autoSaveDisposable = toDisposable(() => clearTimeout(handle)); + this.autoSaveDisposable.value = toDisposable(() => clearTimeout(handle)); } - private cancelPendingAutoSave(): void { - if (this.autoSaveDisposable) { - this.autoSaveDisposable.dispose(); - this.autoSaveDisposable = undefined; - } - } - - save(options: ISaveOptions = Object.create(null)): Promise { + async save(options: ISaveOptions = Object.create(null)): Promise { if (!this.isResolved()) { - return Promise.resolve(); + return; } this.logService.trace('save() - enter', this.resource); // Cancel any currently running auto saves to make this the one that succeeds - this.cancelPendingAutoSave(); + this.autoSaveDisposable.clear(); return this.doSave(this.versionId, options); } @@ -1038,8 +1031,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil this.inOrphanMode = false; this.inErrorMode = false; - this.cancelPendingAutoSave(); - super.dispose(); } }