diff --git a/src/sql/base/browser/ui/panel/panel.ts b/src/sql/base/browser/ui/panel/panel.ts index 7fa14bc6e8..172ede6196 100644 --- a/src/sql/base/browser/ui/panel/panel.ts +++ b/src/sql/base/browser/ui/panel/panel.ts @@ -36,6 +36,7 @@ export interface IPanelTab { interface IInternalPanelTab extends IPanelTab { header: Builder; label: Builder; + dispose(): void; } const defaultOptions: IPanelOptions = { @@ -133,6 +134,11 @@ export class TabbedPanel extends Disposable implements IThemable { this.$tabList.append(tabHeaderElement); tab.header = tabHeaderElement; tab.label = tabLabel; + tab.dispose = () => { + tab.header.dispose(); + tab.label.dispose(); + }; + this._register(tab); } public showTab(id: PanelTabIdentifier): void { diff --git a/src/sql/base/common/event.ts b/src/sql/base/common/event.ts new file mode 100644 index 0000000000..c42488a4e5 --- /dev/null +++ b/src/sql/base/common/event.ts @@ -0,0 +1,39 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import { Emitter, Event } from 'vs/base/common/event'; + +/** + * Implementation of vs/base/common/event/echo that is clearable + * Similar to `buffer` but it buffers indefinitely and repeats + * the buffered events to every new listener. + */ +export function echo(event: Event, nextTick = false, buffer: T[] = []): { clear: () => void; event: Event } { + buffer = buffer.slice(); + + event(e => { + buffer.push(e); + emitter.fire(e); + }); + + const flush = (listener: (e: T) => any, thisArgs?: any) => buffer.forEach(e => listener.call(thisArgs, e)); + const clear = () => buffer = []; + + const emitter = new Emitter({ + onListenerDidAdd(emitter, listener: (e: T) => any, thisArgs?: any) { + if (nextTick) { + setTimeout(() => flush(listener, thisArgs)); + } else { + flush(listener, thisArgs); + } + } + }); + + return { + event: emitter.event, + clear + }; +} diff --git a/src/sql/parts/query/editor/gridPanel.ts b/src/sql/parts/query/editor/gridPanel.ts index 998bbc5847..a8ca660fb3 100644 --- a/src/sql/parts/query/editor/gridPanel.ts +++ b/src/sql/parts/query/editor/gridPanel.ts @@ -97,6 +97,7 @@ export class GridPanel extends ViewletPanel { private tables: GridTable[] = []; private tableDisposable: IDisposable[] = []; private queryRunnerDisposables: IDisposable[] = []; + private currentHeight: number; private runner: QueryRunner; @@ -123,6 +124,11 @@ export class GridPanel extends ViewletPanel { protected layoutBody(size: number): void { this.splitView.layout(size); + // if the size hasn't change it won't layout our table so we have to do it manually + if (size === this.currentHeight) { + this.tables.map(e => e.layout()); + } + this.currentHeight = size; } public set queryRunner(runner: QueryRunner) { @@ -229,6 +235,7 @@ class GridTable extends Disposable implements IView { private container = document.createElement('div'); private selectionModel = new CellSelectionModel(); private styles: ITableStyles; + private currentHeight: number; private columns: Slick.Column[]; @@ -349,10 +356,15 @@ class GridTable extends Disposable implements IView { } } - public layout(size: number): void { + public layout(size?: number): void { if (!this.table) { this.build(); } + if (!size) { + size = this.currentHeight; + } else { + this.currentHeight = size; + } this.table.layout( new Dimension( getContentWidth(this.container) - ACTIONBAR_WIDTH, diff --git a/src/sql/parts/query/editor/messagePanel.ts b/src/sql/parts/query/editor/messagePanel.ts index 9d9858eecc..f7bcc08b5d 100644 --- a/src/sql/parts/query/editor/messagePanel.ts +++ b/src/sql/parts/query/editor/messagePanel.ts @@ -109,24 +109,14 @@ export class MessagePanel extends ViewletPanel { this.queryRunnerDisposables = []; this.reset(); this.queryRunnerDisposables.push(runner.onQueryStart(() => this.reset())); - this.queryRunnerDisposables.push(runner.onBatchStart(e => this.onBatchStart(e))); this.queryRunnerDisposables.push(runner.onMessage(e => this.onMessage(e))); - this.queryRunnerDisposables.push(runner.onQueryEnd(e => this.onQueryEnd(e))); } private onMessage(message: IResultMessage | IResultMessage[]) { if (isArray(message)) { - this.model.messages.push(...message.map(c => { - return { - isError: c.isError, - message: c.message - }; - })); + this.model.messages.push(...message); } else { - this.model.messages.push({ - message: message.message, - isError: message.isError - }); + this.model.messages.push(message); } const previousScrollPosition = this.tree.getScrollPosition(); this.tree.refresh(this.model).then(() => { @@ -136,34 +126,6 @@ export class MessagePanel extends ViewletPanel { }); } - private onBatchStart(batch: BatchSummary) { - this.model.messages.push({ - message: localize('query.message.startQuery', 'Started executing query at Line {0}', batch.selection.startLine), - time: new Date(batch.executionStart).toLocaleTimeString(), - selection: batch.selection, - isError: false - }); - const previousScrollPosition = this.tree.getScrollPosition(); - this.tree.refresh(this.model).then(() => { - if (previousScrollPosition === 1) { - this.tree.setScrollPosition(1); - } - }); - } - - private onQueryEnd(elapsedTime: string) { - this.model.totalExecuteMessage = { - message: localize('query.message.executionTime', 'Total execution time: {0}', elapsedTime), - isError: false - }; - const previousScrollPosition = this.tree.getScrollPosition(); - this.tree.refresh(this.model).then(() => { - if (previousScrollPosition === 1) { - this.tree.setScrollPosition(1); - } - }); - } - private reset() { this.model.messages = []; this.model.totalExecuteMessage = undefined; diff --git a/src/sql/parts/query/editor/queryResultsEditor.ts b/src/sql/parts/query/editor/queryResultsEditor.ts index cd08394335..3ec6cac69b 100644 --- a/src/sql/parts/query/editor/queryResultsEditor.ts +++ b/src/sql/parts/query/editor/queryResultsEditor.ts @@ -158,5 +158,8 @@ export class QueryResultsEditor extends BaseEditor { 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 f8223e29ea..cb9a0b92c1 100644 --- a/src/sql/parts/query/editor/queryResultsView.ts +++ b/src/sql/parts/query/editor/queryResultsView.ts @@ -24,6 +24,7 @@ class ResultsView implements IPanelView { private gridPanel: GridPanel; private messagePanel: MessagePanel; private container = document.createElement('div'); + private currentDimension: DOM.Dimension; constructor(instantiationService: IInstantiationService) { this.panelViewlet = instantiationService.createInstance(PanelViewlet, 'resultsView', { showHeaderInTitleWhenSingleView: false }); @@ -45,6 +46,11 @@ class ResultsView implements IPanelView { layout(dimension: DOM.Dimension): void { this.panelViewlet.layout(dimension); + // the grid won't be resize if the height has not changed so we need to do it manually + if (this.currentDimension && dimension.height === this.currentDimension.height) { + this.gridPanel.layout(dimension.height); + } + this.currentDimension = dimension; } remove(): void { @@ -101,8 +107,13 @@ export class QueryResultsView { let queryRunner = this.queryModelService._getQueryInfo(input.uri).queryRunner; this.resultsTab.queryRunner = queryRunner; this.chartTab.queryRunner = queryRunner; + if (!this._panelView.contains(this.resultsTab)) { + this._panelView.pushTab(this.resultsTab); + } + } - this._panelView.pushTab(this.resultsTab); + public dispose() { + this._panelView.dispose(); } public get input(): QueryResultsInput { diff --git a/src/sql/parts/query/execution/queryRunner.ts b/src/sql/parts/query/execution/queryRunner.ts index 6d701c066a..70c6f83b75 100644 --- a/src/sql/parts/query/execution/queryRunner.ts +++ b/src/sql/parts/query/execution/queryRunner.ts @@ -12,6 +12,7 @@ import * as WorkbenchUtils from 'sql/workbench/common/sqlWorkbenchUtils'; import { IQueryManagementService } from 'sql/parts/query/common/queryManagement'; import * as Utils from 'sql/parts/connection/common/utils'; import { SaveFormat } from 'sql/parts/grid/common/interfaces'; +import { echo } from 'sql/base/common/event'; import Severity from 'vs/base/common/severity'; import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; @@ -21,7 +22,7 @@ import * as types from 'vs/base/common/types'; import { EventEmitter } from 'sql/base/common/eventEmitter'; import { IDisposable } from 'vs/base/common/lifecycle'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { Emitter, echo, debounceEvent, Event } from 'vs/base/common/event'; +import { Emitter, debounceEvent, Event } from 'vs/base/common/event'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ResultSerializer } from 'sql/parts/query/common/resultSerializer'; @@ -51,6 +52,10 @@ export interface IEventType { editSessionReady: IEditSessionReadyEvent; } +export interface IGridMessage extends sqlops.IResultMessage { + selection: sqlops.ISelectionData; +} + /* * 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. @@ -68,36 +73,38 @@ export default class QueryRunner { public get isQueryPlan(): boolean { return this._isQueryPlan; } private _onMessage = new Emitter(); - public readonly onMessage = debounceEvent(echo(this._onMessage.event), (l, e) => { + private _echoedMessages = echo(debounceEvent(this._onMessage.event, (l, e) => { // on first run if (types.isUndefinedOrNull(l)) { return [e]; } else { return l.concat(e); } - }); + })); + public readonly onMessage = this._echoedMessages.event; private _onResultSet = new Emitter(); - public readonly onResultSet = debounceEvent(echo(this._onResultSet.event), (l, e) => { + private _echoedResultSet = echo(debounceEvent(this._onResultSet.event, (l, e) => { // on first run if (types.isUndefinedOrNull(l)) { return [e]; } else { return l.concat(e); } - }); + })); + public readonly onResultSet = this._echoedResultSet.event; private _onQueryStart = new Emitter(); - public readonly onQueryStart: Event = echo(this._onQueryStart.event); + public readonly onQueryStart: Event = this._onQueryStart.event; private _onQueryEnd = new Emitter(); - public readonly onQueryEnd: Event = echo(this._onQueryEnd.event); + public readonly onQueryEnd: Event = this._onQueryEnd.event; private _onBatchStart = new Emitter(); - public readonly onBatchStart: Event = echo(this._onBatchStart.event); + public readonly onBatchStart: Event = this._onBatchStart.event; private _onBatchEnd = new Emitter(); - public readonly onBatchEnd: Event = echo(this._onBatchEnd.event); + public readonly onBatchEnd: Event = this._onBatchEnd.event; // CONSTRUCTOR ///////////////////////////////////////////////////////// constructor( @@ -164,6 +171,8 @@ export default class QueryRunner { private doRunQuery(input: string, runCurrentStatement: boolean, runOptions?: sqlops.ExecutionPlanOptions): Thenable; private doRunQuery(input: sqlops.ISelectionData, runCurrentStatement: boolean, runOptions?: sqlops.ExecutionPlanOptions): Thenable; private doRunQuery(input, runCurrentStatement: boolean, runOptions?: sqlops.ExecutionPlanOptions): Thenable { + this._echoedMessages.clear(); + this._echoedResultSet.clear(); let ownerUri = this.uri; this._batchSets = []; this._hasCompleted = false; @@ -235,7 +244,15 @@ export default class QueryRunner { this._eventEmitter.emit(EventType.COMPLETE, Utils.parseNumAsTimeString(this._totalElapsedMilliseconds)); // We're done with this query so shut down any waiting mechanisms + + let message = { + message: nls.localize('query.message.executionTime', 'Total execution time: {0}', this._totalElapsedMilliseconds), + isError: false, + time: undefined + }; + this._onQueryEnd.fire(Utils.parseNumAsTimeString(this._totalElapsedMilliseconds)); + this._onMessage.fire(message); } /** @@ -255,7 +272,15 @@ export default class QueryRunner { // Store the batch this.batchSets[batch.id] = batch; + + let message = { + message: nls.localize('query.message.startQuery', 'Started executing query at Line {0}', batch.selection.startLine), + time: new Date(batch.executionStart).toLocaleTimeString(), + selection: batch.selection, + isError: false + }; this._eventEmitter.emit(EventType.BATCH_START, batch); + this._onMessage.fire(message); this._onBatchStart.fire(batch); }