diff --git a/src/sql/base/browser/ui/panel/panel.ts b/src/sql/base/browser/ui/panel/panel.ts index e2680be62d..1167895a7c 100644 --- a/src/sql/base/browser/ui/panel/panel.ts +++ b/src/sql/base/browser/ui/panel/panel.ts @@ -36,7 +36,6 @@ export interface IPanelTab { interface IInternalPanelTab extends IPanelTab { header: HTMLElement; label: HTMLElement; - dispose(): void; } const defaultOptions: IPanelOptions = { @@ -143,8 +142,6 @@ export class TabbedPanel extends Disposable implements IThemable { this.tabList.appendChild(tabHeaderElement); tab.header = tabHeaderElement; tab.label = tabLabel; - tab.dispose = () => { }; - this._register(tab); } public showTab(id: PanelTabIdentifier): void { diff --git a/src/sql/base/browser/ui/table/asyncDataView.ts b/src/sql/base/browser/ui/table/asyncDataView.ts index 9299f10e35..5777dc2f36 100644 --- a/src/sql/base/browser/ui/table/asyncDataView.ts +++ b/src/sql/base/browser/ui/table/asyncDataView.ts @@ -3,11 +3,14 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { IDisposableDataProvider } from 'sql/base/browser/ui/table/interfaces'; + export interface IObservableCollection { getLength(): number; at(index: number): T; getRange(start: number, end: number): T[]; setCollectionChangedCallback(callback: (change: CollectionChange, startIndex: number, count: number) => void): void; + dispose(): void; } export interface IGridDataRow { @@ -24,24 +27,27 @@ class LoadCancellationToken { } class DataWindow { - private _dataSourceLength: number; private _data: TData[]; private _length: number = 0; private _offsetFromDataSource: number = -1; - private loadFunction: (offset: number, count: number) => Thenable; private lastLoadCancellationToken: LoadCancellationToken; - private loadCompleteCallback: (start: number, end: number) => void; - private placeholderItemGenerator: (index: number) => TData; - constructor(dataSourceLength: number, - loadFunction: (offset: number, count: number) => Thenable, - placeholderItemGenerator: (index: number) => TData, - loadCompleteCallback: (start: number, end: number) => void) { - this._dataSourceLength = dataSourceLength; - this.loadFunction = loadFunction; - this.placeholderItemGenerator = placeholderItemGenerator; - this.loadCompleteCallback = loadCompleteCallback; + constructor( + private loadFunction: (offset: number, count: number) => Thenable, + private placeholderItemGenerator: (index: number) => TData, + private loadCompleteCallback: (start: number, end: number) => void + ) { + } + + dispose() { + this._data = undefined; + this.loadFunction = undefined; + this.placeholderItemGenerator = undefined; + this.loadCompleteCallback = undefined; + if (this.lastLoadCancellationToken) { + this.lastLoadCancellationToken.isCancelled = true; + } } getStartIndex(): number { @@ -76,10 +82,9 @@ class DataWindow { return; } - let cancellationToken = new LoadCancellationToken(); - this.lastLoadCancellationToken = cancellationToken; + this.lastLoadCancellationToken = new LoadCancellationToken(); this.loadFunction(offset, length).then(data => { - if (!cancellationToken.isCancelled) { + if (!this.lastLoadCancellationToken.isCancelled) { this._data = data; this.loadCompleteCallback(this._offsetFromDataSource, this._offsetFromDataSource + this._length); } @@ -97,10 +102,12 @@ export class VirtualizedCollection implements IObservableCollection void; - constructor(windowSize: number, + constructor( + windowSize: number, length: number, loadFn: (offset: number, count: number) => Thenable, - private _placeHolderGenerator: (index: number) => TData) { + private _placeHolderGenerator: (index: number) => TData + ) { this._windowSize = windowSize; this._length = length; @@ -110,9 +117,15 @@ export class VirtualizedCollection implements IObservableCollection void): void { @@ -197,7 +210,7 @@ export class VirtualizedCollection implements IObservableCollection implements Slick.DataProvider { +export class AsyncDataProvider implements IDisposableDataProvider { constructor(private dataRows: IObservableCollection) { } @@ -212,4 +225,8 @@ export class AsyncDataProvider implements Slick.Data public getRange(start: number, end: number): TData[] { return !this.dataRows ? undefined : this.dataRows.getRange(start, end); } + + dispose() { + this.dataRows.dispose(); + } } diff --git a/src/sql/base/browser/ui/table/interfaces.ts b/src/sql/base/browser/ui/table/interfaces.ts new file mode 100644 index 0000000000..3cb0f916e7 --- /dev/null +++ b/src/sql/base/browser/ui/table/interfaces.ts @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IListStyles } from 'vs/base/browser/ui/list/listWidget'; +import { Color } from 'vs/base/common/color'; + +export interface IDisposableDataProvider extends Slick.DataProvider { + dispose(): void; +} + +export interface ITableMouseEvent { + anchor: HTMLElement | { x: number, y: number }; + cell?: { row: number, cell: number }; +} + +export interface ITableStyles extends IListStyles { + tableHeaderBackground?: Color; + tableHeaderForeground?: Color; +} + +export interface ITableSorter { + sort(args: Slick.OnSortEventArgs); +} + +export interface ITableConfiguration { + dataProvider?: IDisposableDataProvider | Array; + columns?: Slick.Column[]; + sorter?: ITableSorter; +} diff --git a/src/sql/base/browser/ui/table/table.ts b/src/sql/base/browser/ui/table/table.ts index 12ea8923b6..56cd790bb6 100644 --- a/src/sql/base/browser/ui/table/table.ts +++ b/src/sql/base/browser/ui/table/table.ts @@ -5,28 +5,18 @@ import 'vs/css!./media/table'; import { TableDataView } from './tableDataView'; +import { IDisposableDataProvider, ITableSorter, ITableMouseEvent, ITableConfiguration, ITableStyles } from 'sql/base/browser/ui/table/interfaces'; import { IThemable } from 'vs/platform/theme/common/styler'; -import { IListStyles } from 'vs/base/browser/ui/list/listWidget'; import * as DOM from 'vs/base/browser/dom'; -import { Color } from 'vs/base/common/color'; import { mixin } from 'vs/base/common/objects'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IDisposable } from 'vs/base/common/lifecycle'; import { Orientation } from 'vs/base/browser/ui/splitview/splitview'; import { Widget } from 'vs/base/browser/ui/widget'; import { isArray, isBoolean } from 'vs/base/common/types'; import { Event, Emitter } from 'vs/base/common/event'; import { range } from 'vs/base/common/arrays'; - -export interface ITableMouseEvent { - anchor: HTMLElement | { x: number, y: number }; - cell?: { row: number, cell: number }; -} - -export interface ITableStyles extends IListStyles { - tableHeaderBackground?: Color; - tableHeaderForeground?: Color; -} +import { $ } from 'vs/base/browser/builder'; function getDefaultOptions(): Slick.GridOptions { return >{ @@ -35,23 +25,13 @@ function getDefaultOptions(): Slick.GridOptions { }; } -export interface ITableSorter { - sort(args: Slick.OnSortEventArgs); -} - -export interface ITableConfiguration { - dataProvider?: Slick.DataProvider | Array; - columns?: Slick.Column[]; - sorter?: ITableSorter; -} - export class Table extends Widget implements IThemable, IDisposable { private styleElement: HTMLStyleElement; private idPrefix: string; private _grid: Slick.Grid; private _columns: Slick.Column[]; - private _data: Slick.DataProvider; + private _data: IDisposableDataProvider; private _sorter: ITableSorter; private _autoscroll: boolean; @@ -60,8 +40,6 @@ export class Table extends Widget implements IThemabl private _classChangeTimeout: number; - private _disposables: IDisposable[] = []; - private _onContextMenu = new Emitter(); public readonly onContextMenu: Event = this._onContextMenu.event; @@ -76,6 +54,8 @@ export class Table extends Widget implements IThemabl this._data = configuration.dataProvider; } + this._register(this._data); + if (configuration && configuration.columns) { this._columns = configuration.columns; } else { @@ -117,6 +97,12 @@ export class Table extends Widget implements IThemabl }); } + this._register({ + dispose: () => { + this._grid.destroy(); + } + }); + this.mapMouseEvent(this._grid.onContextMenu, this._onContextMenu); this.mapMouseEvent(this._grid.onClick, this._onClick); } @@ -131,7 +117,8 @@ export class Table extends Widget implements IThemabl } public dispose() { - dispose(this._disposables); + $(this._container).dispose(); + super.dispose(); } public invalidateRows(rows: number[], keepEditor: boolean) { @@ -166,7 +153,7 @@ export class Table extends Widget implements IThemabl this._grid.setData(this._data, true); } - getData(): Slick.DataProvider { + getData(): IDisposableDataProvider { return this._data; } diff --git a/src/sql/base/browser/ui/table/tableDataView.ts b/src/sql/base/browser/ui/table/tableDataView.ts index 25682b6e1c..0507a18d8b 100644 --- a/src/sql/base/browser/ui/table/tableDataView.ts +++ b/src/sql/base/browser/ui/table/tableDataView.ts @@ -9,6 +9,8 @@ import { Event, Emitter } from 'vs/base/common/event'; import { TPromise } from 'vs/base/common/winjs.base'; import * as types from 'vs/base/common/types'; +import { IDisposableDataProvider } from 'sql/base/browser/ui/table/interfaces'; + export interface IFindPosition { col: number; row: number; @@ -20,7 +22,7 @@ function defaultSort(args: Slick.OnSortEventArgs, data: Array): Array (a[field] === b[field] ? 0 : (a[field] > b[field] ? 1 : -1)) * sign); } -export class TableDataView implements Slick.DataProvider { +export class TableDataView implements IDisposableDataProvider { private _data: Array; private _findArray: Array; private _findObs: Observable; @@ -154,4 +156,10 @@ export class TableDataView implements Slick.DataProvi get findCount(): number { return types.isUndefinedOrNull(this._findArray) ? 0 : this._findArray.length; } + + dispose() { + this._data = undefined; + this._findArray = undefined; + this._findObs = undefined; + } } diff --git a/src/sql/parts/query/common/queryEditorService.ts b/src/sql/parts/query/common/queryEditorService.ts index 7701b7f1f9..33cbffd8c7 100644 --- a/src/sql/parts/query/common/queryEditorService.ts +++ b/src/sql/parts/query/common/queryEditorService.ts @@ -32,9 +32,6 @@ export interface IQueryEditorService { // Creates new edit data session newEditDataEditor(schemaName: string, tableName: string, queryString: string): Promise; - // Clears any QueryEditor data for the given URI held by this service - onQueryInputClosed(uri: string): void; - /** * Handles updating of SQL files on a save as event. These need special consideration * due to query results and other information being tied to the URI of the file diff --git a/src/sql/parts/query/common/queryInput.ts b/src/sql/parts/query/common/queryInput.ts index 50ee9f2b94..7b26ea1d5d 100644 --- a/src/sql/parts/query/common/queryInput.ts +++ b/src/sql/parts/query/common/queryInput.ts @@ -116,11 +116,11 @@ export class QueryInput extends EditorInput implements IEncodingSupport, IConnec } if (this._configurationService) { - this._configurationService.onDidChangeConfiguration(e => { + this._toDispose.push(this._configurationService.onDidChangeConfiguration(e => { if (e.affectedKeys.includes('sql.showConnectionInfoInTitle')) { this._onDidChangeLabel.fire(); } - }); + })); } this.onDisconnect(); @@ -196,17 +196,17 @@ export class QueryInput extends EditorInput implements IEncodingSupport, IConnec // State update funtions public runQuery(selection: ISelectionData, executePlanOptions?: ExecutionPlanOptions): void { - this._queryModelService.runQuery(this.uri, selection, this.uri, this, executePlanOptions); + this._queryModelService.runQuery(this.uri, selection, this, executePlanOptions); this.showQueryResultsEditor(); } public runQueryStatement(selection: ISelectionData): void { - this._queryModelService.runQueryStatement(this.uri, selection, this.uri, this); + this._queryModelService.runQueryStatement(this.uri, selection, this); this.showQueryResultsEditor(); } public runQueryString(text: string): void { - this._queryModelService.runQueryString(this.uri, text, this.uri, this); + this._queryModelService.runQueryString(this.uri, text, this); this.showQueryResultsEditor(); } @@ -276,7 +276,6 @@ export class QueryInput extends EditorInput implements IEncodingSupport, IConnec // Clean up functions public dispose(): void { - this._queryModelService.disposeQuery(this.uri); this._sql.dispose(); this._results.dispose(); this._toDispose = dispose(this._toDispose); @@ -285,7 +284,7 @@ export class QueryInput extends EditorInput implements IEncodingSupport, IConnec } public close(): void { - this._queryEditorService.onQueryInputClosed(this.uri); + this._queryModelService.disposeQuery(this.uri); this._connectionManagementService.disconnectEditor(this, true); this._sql.close(); diff --git a/src/sql/parts/query/common/queryManagement.ts b/src/sql/parts/query/common/queryManagement.ts index 7f8baa1c87..65ea8ff449 100644 --- a/src/sql/parts/query/common/queryManagement.ts +++ b/src/sql/parts/query/common/queryManagement.ts @@ -215,6 +215,7 @@ export class QueryManagementService implements IQueryManagementService { }); } public disposeQuery(ownerUri: string): Thenable { + this._queryRunners.delete(ownerUri); return this._runAction(ownerUri, (runner) => { return runner.disposeQuery(ownerUri); }); diff --git a/src/sql/parts/query/common/queryResultsInput.ts b/src/sql/parts/query/common/queryResultsInput.ts index e826cd86e0..096a3d12b3 100644 --- a/src/sql/parts/query/common/queryResultsInput.ts +++ b/src/sql/parts/query/common/queryResultsInput.ts @@ -29,6 +29,13 @@ export class ResultsViewState { constructor(@IConfigurationService private configurationService: IConfigurationService) { } + + dispose() { + this.gridPanelState.dispose(); + this.messagePanelState.dispose(); + this.chartState.dispose(); + this.queryPlanState.dispose(); + } } /** @@ -50,7 +57,11 @@ export class QueryResultsInput extends EditorInput { public readonly onRestoreViewStateEmitter = new Emitter(); public readonly onSaveViewStateEmitter = new Emitter(); - public readonly state = new ResultsViewState(this.configurationService); + private _state = new ResultsViewState(this.configurationService); + + public get state(): ResultsViewState { + return this._state; + } constructor(private _uri: string, @IConfigurationService private configurationService: IConfigurationService @@ -60,6 +71,12 @@ export class QueryResultsInput extends EditorInput { this._hasBootstrapped = false; } + close() { + this.state.dispose(); + this._state = undefined; + super.close(); + } + getTypeId(): string { return QueryResultsInput.ID; } diff --git a/src/sql/parts/query/editor/charting/chartTab.ts b/src/sql/parts/query/editor/charting/chartTab.ts index f3e5a1d199..911ff0dc38 100644 --- a/src/sql/parts/query/editor/charting/chartTab.ts +++ b/src/sql/parts/query/editor/charting/chartTab.ts @@ -33,4 +33,8 @@ export class ChartTab implements IPanelTab { public dispose() { this.view.dispose(); } + + public clear() { + this.view.clear(); + } } diff --git a/src/sql/parts/query/editor/charting/chartView.ts b/src/sql/parts/query/editor/charting/chartView.ts index 0ae7471d44..842ecad986 100644 --- a/src/sql/parts/query/editor/charting/chartView.ts +++ b/src/sql/parts/query/editor/charting/chartView.ts @@ -35,6 +35,10 @@ export class ChartState { options: IInsightOptions = { type: ChartType.Bar }; + + dispose() { + + } } declare class Proxy { @@ -134,6 +138,15 @@ export class ChartView extends Disposable implements IPanelView { this.buildOptions(); } + public clear() { + + } + + public dispose() { + dispose(this.optionDisposables); + super.dispose(); + } + render(container: HTMLElement): void { if (!this.container) { this.container = $('div.chart-parent-container'); diff --git a/src/sql/parts/query/editor/gridPanel.ts b/src/sql/parts/query/editor/gridPanel.ts index ee2f2e0b47..098ce82117 100644 --- a/src/sql/parts/query/editor/gridPanel.ts +++ b/src/sql/parts/query/editor/gridPanel.ts @@ -7,7 +7,7 @@ import { attachTableStyler } from 'sql/common/theme/styler'; import QueryRunner from 'sql/parts/query/execution/queryRunner'; import { VirtualizedCollection, AsyncDataProvider } from 'sql/base/browser/ui/table/asyncDataView'; -import { Table, ITableStyles, ITableMouseEvent } from 'sql/base/browser/ui/table/table'; +import { Table } from 'sql/base/browser/ui/table/table'; import { ScrollableSplitView } from 'sql/base/browser/ui/scrollableSplitview/scrollableSplitview'; import { MouseWheelSupport } from 'sql/base/browser/ui/table/plugins/mousewheelTableScroll.plugin'; import { AutoColumnSize } from 'sql/base/browser/ui/table/plugins/autoSizeColumns.plugin'; @@ -19,6 +19,7 @@ import { escape } from 'sql/base/common/strings'; import { hyperLinkFormatter, textFormatter } from 'sql/parts/grid/services/sharedServices'; import { CopyKeybind } from 'sql/base/browser/ui/table/plugins/copyKeybind.plugin'; import { AdditionalKeyBindings } from 'sql/base/browser/ui/table/plugins/additionalKeyBindings.plugin'; +import { ITableStyles, ITableMouseEvent } from 'sql/base/browser/ui/table/interfaces'; import * as sqlops from 'sqlops'; import * as pretty from 'pretty-data'; @@ -61,6 +62,10 @@ export class GridPanelState { public tableStates: GridTableState[] = []; public scrollPosition: number; public collapsed = false; + + dispose() { + dispose(this.tableStates); + } } export interface IGridTableState { @@ -68,14 +73,14 @@ export interface IGridTableState { maximized: boolean; } -export class GridTableState { +export class GridTableState extends Disposable { private _maximized: boolean; - private _onMaximizedChange = new Emitter(); + private _onMaximizedChange = this._register(new Emitter()); public onMaximizedChange: Event = this._onMaximizedChange.event; - private _onCanBeMaximizedChange = new Emitter(); + private _onCanBeMaximizedChange = this._register(new Emitter()); public onCanBeMaximizedChange: Event = this._onCanBeMaximizedChange.event; private _canBeMaximized: boolean; @@ -86,6 +91,7 @@ export class GridTableState { public activeCell: Slick.Cell; constructor(public readonly resultId: number, public readonly batchId: number) { + super(); } public get canBeMaximized(): boolean { @@ -217,13 +223,13 @@ export class GridPanel extends ViewletPanel { } let table = this.instantiationService.createInstance(GridTable, this.runner, set); table.state = tableState; - tableState.onMaximizedChange(e => { + this.tableDisposable.push(tableState.onMaximizedChange(e => { if (e) { this.maximizeTable(table.id); } else { this.minimizeTables(); } - }); + })); this.tableDisposable.push(attachTableStyler(table, this.themeService)); tables.push(table); @@ -238,11 +244,17 @@ export class GridPanel extends ViewletPanel { this.tables = this.tables.concat(tables); } + public clear() { + this.reset(); + } + private reset() { for (let i = this.splitView.length - 1; i >= 0; i--) { this.splitView.removeView(i); } dispose(this.tables); + dispose(this.tableDisposable); + this.tableDisposable = []; this.tables = []; this.maximizedGrid = undefined; @@ -293,6 +305,15 @@ export class GridPanel extends ViewletPanel { public get state(): GridPanelState { return this._state; } + + public dispose() { + dispose(this.queryRunnerDisposables); + dispose(this.tableDisposable); + dispose(this.tables); + this.tableDisposable = undefined; + this.tables = undefined; + super.dispose(); + } } class GridTable extends Disposable implements IView { @@ -444,9 +465,9 @@ class GridTable extends Disposable implements IView { private setupState() { // change actionbar on maximize change - this.state.onMaximizedChange(this.rebuildActionBar, this); + this._register(this.state.onMaximizedChange(this.rebuildActionBar, this)); - this.state.onCanBeMaximizedChange(this.rebuildActionBar, this); + this._register(this.state.onCanBeMaximizedChange(this.rebuildActionBar, this)); if (this.state.scrollPosition) { // most of the time this won't do anything @@ -656,6 +677,8 @@ class GridTable extends Disposable implements IView { public dispose() { $(this.container).destroy(); + this.table.dispose(); + this.actionBar.dispose(); super.dispose(); } } diff --git a/src/sql/parts/query/editor/messagePanel.ts b/src/sql/parts/query/editor/messagePanel.ts index 231ff1b9a6..9a375ca4ac 100644 --- a/src/sql/parts/query/editor/messagePanel.ts +++ b/src/sql/parts/query/editor/messagePanel.ts @@ -7,6 +7,7 @@ import 'vs/css!./media/messagePanel'; import { IMessagesActionContext, SelectAllMessagesAction, CopyMessagesAction } from './actions'; import QueryRunner from 'sql/parts/query/execution/queryRunner'; +import { QueryInput } from 'sql/parts/query/common/queryInput'; import { IResultMessage, ISelectionData } from 'sqlops'; @@ -28,8 +29,6 @@ import { $ } from 'vs/base/browser/builder'; import { isArray, isUndefinedOrNull } from 'vs/base/common/types'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IEditor } from 'vs/editor/common/editorCommon'; -import { QueryInput } from 'sql/parts/query/common/queryInput'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; export interface IResultMessageIntern extends IResultMessage { @@ -71,6 +70,10 @@ export class MessagePanelState { this.collapsed = !messagesOpenedSettings; } } + + dispose() { + + } } export class MessagePanel extends ViewletPanel { @@ -102,6 +105,7 @@ export class MessagePanel extends ViewletPanel { renderer: this.renderer, controller: this.controller }, { keyboardSupport: false, horizontalScrollMode: ScrollbarVisibility.Auto }); + this.disposables.push(this.tree); this.tree.onDidScroll(e => { if (this.state) { this.state.scrollPosition = this.tree.getScrollPosition(); @@ -117,7 +121,7 @@ export class MessagePanel extends ViewletPanel { protected renderBody(container: HTMLElement): void { this.container.style.width = '100%'; this.container.style.height = '100%'; - attachListStyler(this.tree, this.themeService); + this.disposables.push(attachListStyler(this.tree, this.themeService)); container.appendChild(this.container); this.tree.setInput(this.model); } @@ -193,9 +197,19 @@ export class MessagePanel extends ViewletPanel { } this.setExpanded(!this.state.collapsed); } + public get state(): MessagePanelState { return this._state; } + + public clear() { + this.reset(); + } + + public dispose() { + dispose(this.queryRunnerDisposables); + super.dispose(); + } } class MessageDataSource implements IDataSource { diff --git a/src/sql/parts/query/editor/queryEditor.ts b/src/sql/parts/query/editor/queryEditor.ts index 4135438f43..767cb5570d 100644 --- a/src/sql/parts/query/editor/queryEditor.ts +++ b/src/sql/parts/query/editor/queryEditor.ts @@ -478,11 +478,11 @@ export class QueryEditor extends BaseEditor { this.setTaskbarContent(); - this._configurationService.onDidChangeConfiguration(e => { + this._toDispose.push(this._configurationService.onDidChangeConfiguration(e => { if (e.affectedKeys.includes('workbench.enablePreviewFeatures')) { this.setTaskbarContent(); } - }); + })); } private setTaskbarContent(): void { diff --git a/src/sql/parts/query/editor/queryResultsEditor.ts b/src/sql/parts/query/editor/queryResultsEditor.ts index 2b66039600..0dc51277c5 100644 --- a/src/sql/parts/query/editor/queryResultsEditor.ts +++ b/src/sql/parts/query/editor/queryResultsEditor.ts @@ -90,7 +90,6 @@ export class QueryResultsEditor extends BaseEditor { public static ID: string = 'workbench.editor.queryResultsEditor'; public static AngularSelectorString: string = 'slickgrid-container.slickgridContainer'; protected _rawOptions: BareResultsGridInfo; - protected _input: QueryResultsInput; private resultsView: QueryResultsView; private styleSheet = DOM.createStyleSheet(); @@ -104,17 +103,17 @@ export class QueryResultsEditor extends BaseEditor { ) { super(QueryResultsEditor.ID, telemetryService, themeService); this._rawOptions = BareResultsGridInfo.createFromRawSettings(this._configurationService.getValue('resultsGrid'), getZoomLevel()); - this._configurationService.onDidChangeConfiguration(e => { + this._register(this._configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('resultsGrid')) { this._rawOptions = BareResultsGridInfo.createFromRawSettings(this._configurationService.getValue('resultsGrid'), getZoomLevel()); this.applySettings(); } - }); + })); this.applySettings(); } public get input(): QueryResultsInput { - return this._input; + return this._input as QueryResultsInput; } private applySettings() { @@ -133,10 +132,16 @@ export class QueryResultsEditor extends BaseEditor { this.styleSheet.remove(); parent.appendChild(this.styleSheet); if (!this.resultsView) { - this.resultsView = new QueryResultsView(parent, this._instantiationService, this._queryModelService); + this.resultsView = this._register(new QueryResultsView(parent, this._instantiationService, this._queryModelService)); } } + dispose() { + this.styleSheet.remove(); + this.styleSheet = undefined; + super.dispose(); + } + layout(dimension: DOM.Dimension): void { this.resultsView.layout(dimension); } @@ -147,6 +152,11 @@ export class QueryResultsEditor extends BaseEditor { return TPromise.wrap(null); } + clearInput() { + this.resultsView.clearInput(); + super.clearInput(); + } + public chart(dataId: { batchId: number, resultId: number }) { this.resultsView.chartData(dataId); } @@ -154,11 +164,4 @@ export class QueryResultsEditor extends BaseEditor { public showQueryPlan(xml: string) { this.resultsView.showPlan(xml); } - - public dispose(): void { - super.dispose(); - if (this.resultsView) { - this.resultsView.dispose(); - } - } } diff --git a/src/sql/parts/query/editor/queryResultsView.ts b/src/sql/parts/query/editor/queryResultsView.ts index 1272e27ad3..57c3ad5106 100644 --- a/src/sql/parts/query/editor/queryResultsView.ts +++ b/src/sql/parts/query/editor/queryResultsView.ts @@ -111,6 +111,15 @@ class ResultsView extends Disposable implements IPanelView { } } + dispose() { + super.dispose(); + } + + public clear() { + this.gridPanel.clear(); + this.messagePanel.clear(); + } + remove(): void { this.container.remove(); } @@ -151,6 +160,10 @@ class ResultsTab implements IPanelTab { public dispose() { dispose(this.view); } + + public clear() { + this.view.clear(); + } } export class QueryResultsView extends Disposable { @@ -221,8 +234,11 @@ export class QueryResultsView extends Disposable { } } - public dispose() { - this._panelView.dispose(); + clearInput() { + this._input = undefined; + this.resultsTab.clear(); + this.qpTab.clear(); + this.chartTab.clear(); } public get input(): QueryResultsInput { @@ -264,4 +280,8 @@ export class QueryResultsView extends Disposable { this._panelView.removeTab(this.qpTab.identifier); } } + + public dispose() { + super.dispose(); + } } diff --git a/src/sql/parts/query/execution/queryModel.ts b/src/sql/parts/query/execution/queryModel.ts index 6049954a20..a48b995bf5 100644 --- a/src/sql/parts/query/execution/queryModel.ts +++ b/src/sql/parts/query/execution/queryModel.ts @@ -35,9 +35,9 @@ export interface IQueryModelService { getConfig(): Promise<{ [key: string]: any }>; getShortcuts(): Promise; getQueryRows(uri: string, rowStart: number, numberOfRows: number, batchId: number, resultId: number): Thenable; - runQuery(uri: string, selection: ISelectionData, title: string, queryInput: QueryInput, runOptions?: ExecutionPlanOptions): void; - runQueryStatement(uri: string, selection: ISelectionData, title: string, queryInput: QueryInput): void; - runQueryString(uri: string, selection: string, title: string, queryInput: QueryInput); + runQuery(uri: string, selection: ISelectionData, queryInput: QueryInput, runOptions?: ExecutionPlanOptions): void; + runQueryStatement(uri: string, selection: ISelectionData, queryInput: QueryInput): void; + runQueryString(uri: string, selection: string, queryInput: QueryInput); cancelQuery(input: QueryRunner | string): void; disposeQuery(uri: string): void; isRunningQuery(uri: string): boolean; diff --git a/src/sql/parts/query/execution/queryModelService.ts b/src/sql/parts/query/execution/queryModelService.ts index af062d48f8..3c3e2638af 100644 --- a/src/sql/parts/query/execution/queryModelService.ts +++ b/src/sql/parts/query/execution/queryModelService.ts @@ -209,32 +209,28 @@ export class QueryModelService implements IQueryModelService { /** * Run a query for the given URI with the given text selection */ - public runQuery(uri: string, selection: sqlops.ISelectionData, - title: string, queryInput: QueryInput, runOptions?: sqlops.ExecutionPlanOptions): void { - this.doRunQuery(uri, selection, title, queryInput, false, runOptions); + public runQuery(uri: string, selection: sqlops.ISelectionData, queryInput: QueryInput, runOptions?: sqlops.ExecutionPlanOptions): void { + this.doRunQuery(uri, selection, queryInput, false, runOptions); } /** * Run the current SQL statement for the given URI */ - public runQueryStatement(uri: string, selection: sqlops.ISelectionData, - title: string, queryInput: QueryInput): void { - this.doRunQuery(uri, selection, title, queryInput, true); + public runQueryStatement(uri: string, selection: sqlops.ISelectionData, queryInput: QueryInput): void { + this.doRunQuery(uri, selection, queryInput, true); } /** * Run the current SQL statement for the given URI */ - public runQueryString(uri: string, selection: string, - title: string, queryInput: QueryInput): void { - this.doRunQuery(uri, selection, title, queryInput, true); + public runQueryString(uri: string, selection: string, queryInput: QueryInput): void { + this.doRunQuery(uri, selection, queryInput, true); } /** * Run Query implementation */ - private doRunQuery(uri: string, selection: sqlops.ISelectionData | string, - title: string, queryInput: QueryInput, + private doRunQuery(uri: string, selection: sqlops.ISelectionData | string, queryInput: QueryInput, runCurrentStatement: boolean, runOptions?: sqlops.ExecutionPlanOptions): void { // Reuse existing query runner if it exists let queryRunner: QueryRunner; @@ -256,7 +252,7 @@ export class QueryModelService implements IQueryModelService { } else { // We do not have a query runner for this editor, so create a new one // and map it to the results uri - info = this.initQueryRunner(uri, title); + info = this.initQueryRunner(uri); queryRunner = info.queryRunner; } @@ -277,8 +273,8 @@ export class QueryModelService implements IQueryModelService { } } - private initQueryRunner(uri: string, title: string): QueryInfo { - let queryRunner = this._instantiationService.createInstance(QueryRunner, uri, title); + private initQueryRunner(uri: string): QueryInfo { + let queryRunner = this._instantiationService.createInstance(QueryRunner, uri); let info = new QueryInfo(); queryRunner.addListener(QREvents.RESULT_SET, e => { this._fireQueryEvent(uri, 'resultSet', e); @@ -363,6 +359,10 @@ export class QueryModelService implements IQueryModelService { if (queryRunner) { queryRunner.disposeQuery(); } + // remove our info map + if (this._queryInfoMap.has(ownerUri)) { + this._queryInfoMap.delete(ownerUri); + } } // EDIT DATA METHODS ///////////////////////////////////////////////////// @@ -386,7 +386,7 @@ export class QueryModelService implements IQueryModelService { // We do not have a query runner for this editor, so create a new one // and map it to the results uri - queryRunner = this._instantiationService.createInstance(QueryRunner, ownerUri, ownerUri); + queryRunner = this._instantiationService.createInstance(QueryRunner, ownerUri); queryRunner.addListener(QREvents.RESULT_SET, resultSet => { this._fireQueryEvent(ownerUri, 'resultSet', resultSet); }); diff --git a/src/sql/parts/query/execution/queryRunner.ts b/src/sql/parts/query/execution/queryRunner.ts index 96be349085..cfda2c605d 100644 --- a/src/sql/parts/query/execution/queryRunner.ts +++ b/src/sql/parts/query/execution/queryRunner.ts @@ -20,7 +20,7 @@ import * as nls from 'vs/nls'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import * as types from 'vs/base/common/types'; import { EventEmitter } from 'sql/base/common/eventEmitter'; -import { IDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { Emitter, Event } from 'vs/base/common/event'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -62,7 +62,7 @@ export interface IGridMessage extends sqlops.IResultMessage { * Query Runner class which handles running a query, reports the results to the content manager, * and handles getting more rows from the service layer and disposing when the content is closed. */ -export default class QueryRunner { +export default class QueryRunner extends Disposable { // MEMBER VARIABLES //////////////////////////////////////////////////// private _resultLineOffset: number; private _totalElapsedMilliseconds: number = 0; @@ -76,7 +76,7 @@ export default class QueryRunner { private _planXml = new Deferred(); public get planXml(): Thenable { return this._planXml.promise; } - private _onMessage = new Emitter(); + private _onMessage = this._register(new Emitter()); private _debouncedMessage = debounceEvent(this._onMessage.event, (l, e) => { // on first run if (types.isUndefinedOrNull(l)) { @@ -88,7 +88,7 @@ export default class QueryRunner { private _echoedMessages = echo(this._debouncedMessage.event); public readonly onMessage = this._echoedMessages.event; - private _onResultSet = new Emitter(); + private _onResultSet = this._register(new Emitter()); private _debouncedResultSet = debounceEvent(this._onResultSet.event, (l, e) => { // on first run if (types.isUndefinedOrNull(l)) { @@ -100,16 +100,16 @@ export default class QueryRunner { private _echoedResultSet = echo(this._debouncedResultSet.event); public readonly onResultSet = this._echoedResultSet.event; - private _onQueryStart = new Emitter(); + private _onQueryStart = this._register(new Emitter()); public readonly onQueryStart: Event = this._onQueryStart.event; - private _onQueryEnd = new Emitter(); + private _onQueryEnd = this._register(new Emitter()); public readonly onQueryEnd: Event = this._onQueryEnd.event; - private _onBatchStart = new Emitter(); + private _onBatchStart = this._register(new Emitter()); public readonly onBatchStart: Event = this._onBatchStart.event; - private _onBatchEnd = new Emitter(); + private _onBatchEnd = this._register(new Emitter()); public readonly onBatchEnd: Event = this._onBatchEnd.event; private _queryStartTime: Date; @@ -124,13 +124,14 @@ export default class QueryRunner { // CONSTRUCTOR ///////////////////////////////////////////////////////// constructor( public uri: string, - public title: string, @IQueryManagementService private _queryManagementService: IQueryManagementService, @INotificationService private _notificationService: INotificationService, @IWorkspaceConfigurationService private _workspaceConfigurationService: IWorkspaceConfigurationService, @IClipboardService private _clipboardService: IClipboardService, @IInstantiationService private instantiationService: IInstantiationService - ) { } + ) { + super(); + } get isExecuting(): boolean { return this._isExecuting; @@ -504,10 +505,16 @@ export default class QueryRunner { /** * Disposes the Query from the service client - * @returns A promise that will be rejected if a problem occured */ public disposeQuery(): void { - this._queryManagementService.disposeQuery(this.uri); + this._queryManagementService.disposeQuery(this.uri).then(() => { + this.dispose(); + }); + } + + public dispose() { + this._batchSets = undefined; + super.dispose(); } get totalElapsedMilliseconds(): number { diff --git a/src/sql/parts/query/services/queryEditorService.ts b/src/sql/parts/query/services/queryEditorService.ts index a5e7378884..f11438782c 100644 --- a/src/sql/parts/query/services/queryEditorService.ts +++ b/src/sql/parts/query/services/queryEditorService.ts @@ -152,12 +152,6 @@ export class QueryEditorService implements IQueryEditorService { }); } - /** - * Clears any QueryEditor data for the given URI held by this service - */ - public onQueryInputClosed(uri: string): void { - } - onSaveAsCompleted(oldResource: URI, newResource: URI): void { let oldResourceString: string = oldResource.toString(); diff --git a/src/sql/parts/queryPlan/queryPlan.ts b/src/sql/parts/queryPlan/queryPlan.ts index 737daad64f..c63cb9913c 100644 --- a/src/sql/parts/queryPlan/queryPlan.ts +++ b/src/sql/parts/queryPlan/queryPlan.ts @@ -15,6 +15,9 @@ import { dispose, Disposable } from 'vs/base/common/lifecycle'; export class QueryPlanState { xml: string; + dispose() { + + } } export class QueryPlanTab implements IPanelTab { @@ -29,6 +32,10 @@ export class QueryPlanTab implements IPanelTab { public dispose() { dispose(this.view); } + + public clear() { + this.view.clear(); + } } export class QueryPlanView implements IPanelView { @@ -59,6 +66,12 @@ export class QueryPlanView implements IPanelView { this.container.style.height = dimension.height + 'px'; } + public clear() { + if (this.qp) { + this.qp.xml = undefined; + } + } + public showPlan(xml: string) { if (this.qp) { this.qp.xml = xml; diff --git a/src/sqltest/parts/query/editor/queryEditor.test.ts b/src/sqltest/parts/query/editor/queryEditor.test.ts index 123a07d78e..a923fffe56 100644 --- a/src/sqltest/parts/query/editor/queryEditor.test.ts +++ b/src/sqltest/parts/query/editor/queryEditor.test.ts @@ -141,6 +141,8 @@ suite('SQL QueryEditor Tests', () => { connectionManagementService = TypeMoq.Mock.ofType(ConnectionManagementService, TypeMoq.MockBehavior.Loose, memento.object, undefined); connectionManagementService.callBase = true; connectionManagementService.setup(x => x.isConnected(TypeMoq.It.isAny())).returns(() => false); + connectionManagementService.setup(x => x.disconnectEditor(TypeMoq.It.isAny())).returns(() => void 0); + connectionManagementService.setup(x => x.ensureDefaultLanguageFlavor(TypeMoq.It.isAnyString())).returns(() => void 0); // Create a QueryModelService queryModelService = new QueryModelService(instantiationService.object, notificationService.object); @@ -328,6 +330,9 @@ suite('SQL QueryEditor Tests', () => { queryConnectionService = TypeMoq.Mock.ofType(ConnectionManagementService, TypeMoq.MockBehavior.Loose, memento.object, undefined); queryConnectionService.callBase = true; + queryConnectionService.setup(x => x.disconnectEditor(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => void 0); + queryConnectionService.setup(x => x.ensureDefaultLanguageFlavor(TypeMoq.It.isAnyString())).returns(() => void 0); + // Mock InstantiationService to give us the actions queryActionInstantiationService = TypeMoq.Mock.ofType(InstantiationService, TypeMoq.MockBehavior.Loose); @@ -354,12 +359,13 @@ suite('SQL QueryEditor Tests', () => { let fileInput = new UntitledEditorInput(URI.parse('testUri'), false, '', '', '', instantiationService.object, undefined, undefined, undefined); queryModelService = TypeMoq.Mock.ofType(QueryModelService, TypeMoq.MockBehavior.Loose, undefined, undefined); queryModelService.callBase = true; + queryModelService.setup(x => x.disposeQuery(TypeMoq.It.isAny())).returns(() => void 0); queryInput = new QueryInput( '', fileInput, undefined, undefined, - undefined, + connectionManagementService.object, queryModelService.object, undefined, undefined @@ -395,7 +401,7 @@ suite('SQL QueryEditor Tests', () => { test('Test that we attempt to dispose query when the queryInput is disposed', (done) => { let queryResultsInput = new QueryResultsInput('testUri', configurationService.object); queryInput['_results'] = queryResultsInput; - queryInput.dispose(); + queryInput.close(); queryModelService.verify(x => x.disposeQuery(TypeMoq.It.isAnyString()), TypeMoq.Times.once()); done(); });