Last of the layering (#5187)

* layer query

* update imports
This commit is contained in:
Anthony Dresser
2019-04-26 15:30:41 -07:00
committed by GitHub
parent bb9c85cd8f
commit ca98ef879d
98 changed files with 101 additions and 173 deletions

View File

@@ -9,7 +9,7 @@ import { IExtHostContext } from 'vs/workbench/api/common/extHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { IConnectionManagementService, IConnectionCompletionOptions, ConnectionType, RunQueryOnConnectionMode } from 'sql/platform/connection/common/connectionManagement';
import { IQueryEditorService } from 'sql/workbench/services/queryEditor/common/queryEditorService';
import { QueryEditor } from 'sql/parts/query/editor/queryEditor';
import { QueryEditor } from 'sql/workbench/parts/query/browser/queryEditor';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { IQueryModelService, IQueryEvent } from 'sql/platform/query/common/queryModel';

View File

@@ -8,8 +8,8 @@ import { IInstantiationService, ServiceIdentifier } from 'vs/platform/instantiat
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
import { URI } from 'vs/base/common/uri';
import { QueryResultsInput } from 'sql/parts/query/common/queryResultsInput';
import { QueryInput } from 'sql/parts/query/common/queryInput';
import { QueryResultsInput } from 'sql/workbench/parts/query/common/queryResultsInput';
import { QueryInput } from 'sql/workbench/parts/query/common/queryInput';
import { IQueryEditorOptions } from 'sql/workbench/services/queryEditor/common/queryEditorService';
import { QueryPlanInput } from 'sql/workbench/parts/queryPlan/common/queryPlanInput';
import { NotebookInput } from 'sql/workbench/parts/notebook/notebookInput';

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import ConnectionConstants = require('sql/platform/connection/common/constants');
import { QueryInput } from 'sql/parts/query/common/queryInput';
import { QueryInput } from 'sql/workbench/parts/query/common/queryInput';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { URI } from 'vs/base/common/uri';

View File

@@ -19,7 +19,7 @@ import { IInsightsConfig } from 'sql/workbench/parts/dashboard/widgets/insights/
import { IInsightsDialogService } from 'sql/workbench/services/insights/common/insightsDialogService';
import { ConnectionManagementInfo } from 'sql/platform/connection/common/connectionManagementInfo';
import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/common/objectExplorerService';
import { QueryInput } from 'sql/parts/query/common/queryInput';
import { QueryInput } from 'sql/workbench/parts/query/common/queryInput';
import { DashboardInput } from 'sql/workbench/parts/dashboard/dashboardInput';
import { ProfilerInput } from 'sql/workbench/parts/profiler/browser/profilerInput';
import { IErrorMessageService } from 'sql/platform/errorMessage/common/errorMessageService';

View File

@@ -0,0 +1,213 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IInsightOptions, IInsight } from './interfaces';
import { Graph } from './graphInsight';
import { QueryEditor } from 'sql/workbench/parts/query/browser/queryEditor';
import { IClipboardService } from 'sql/platform/clipboard/common/clipboardService';
import { IInsightsConfig } from 'sql/workbench/parts/dashboard/widgets/insights/interfaces';
import { resolveCurrentDirectory, getRootPath } from 'sql/platform/node/pathUtilities';
import { localize } from 'vs/nls';
import { Action } from 'vs/base/common/actions';
import { join, normalize } from 'vs/base/common/path';
import { writeFile } from 'vs/base/node/pfs';
import { IWindowsService, IWindowService } from 'vs/platform/windows/common/windows';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { URI } from 'vs/base/common/uri';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
export interface IChartActionContext {
options: IInsightOptions;
insight: IInsight;
}
export class CreateInsightAction extends Action {
public static ID = 'chartview.createInsight';
public static LABEL = localize('createInsightLabel', "Create Insight");
public static ICON = 'createInsight';
constructor(
@IEditorService private editorService: IEditorService,
@INotificationService private notificationService: INotificationService,
@IUntitledEditorService private untitledEditorService: IUntitledEditorService
) {
super(CreateInsightAction.ID, CreateInsightAction.LABEL, CreateInsightAction.ICON);
}
public run(context: IChartActionContext): Promise<boolean> {
let uriString: string = this.getActiveUriString();
if (!uriString) {
this.showError(localize('createInsightNoEditor', 'Cannot create insight as the active editor is not a SQL Editor'));
return Promise.resolve(false);
}
let uri: URI = URI.parse(uriString);
let queryFile: string = uri.fsPath;
let query: string = undefined;
let type = {};
let options = Object.assign({}, context.options);
delete options.type;
type[context.options.type] = options;
// create JSON
let config: IInsightsConfig = {
type,
query,
queryFile
};
let widgetConfig = {
name: localize('myWidgetName', 'My-Widget'),
gridItemConfig: {
sizex: 2,
sizey: 1
},
widget: {
'insights-widget': config
}
};
let input = this.untitledEditorService.createOrGet(undefined, 'json', JSON.stringify(widgetConfig));
return this.editorService.openEditor(input, { pinned: true })
.then(
() => true,
error => {
this.notificationService.notify({
severity: Severity.Error,
message: error
});
return false;
}
);
}
private getActiveUriString(): string {
let editor = this.editorService.activeControl;
if (editor && editor instanceof QueryEditor) {
let queryEditor: QueryEditor = editor;
return queryEditor.uri;
}
return undefined;
}
private showError(errorMsg: string) {
this.notificationService.notify({
severity: Severity.Error,
message: errorMsg
});
}
}
export class CopyAction extends Action {
public static ID = 'chartview.copy';
public static LABEL = localize('copyChartLabel', "Copy as image");
public static ICON = 'copyImage';
constructor(
@IClipboardService private clipboardService: IClipboardService,
@INotificationService private notificationService: INotificationService
) {
super(CopyAction.ID, CopyAction.LABEL, CopyAction.ICON);
}
public run(context: IChartActionContext): Promise<boolean> {
if (context.insight instanceof Graph) {
let data = context.insight.getCanvasData();
if (!data) {
this.showError(localize('chartNotFound', 'Could not find chart to save'));
return Promise.resolve(false);
}
this.clipboardService.writeImageDataUrl(data);
return Promise.resolve(true);
}
return Promise.resolve(false);
}
private showError(errorMsg: string) {
this.notificationService.notify({
severity: Severity.Error,
message: errorMsg
});
}
}
export class SaveImageAction extends Action {
public static ID = 'chartview.saveImage';
public static LABEL = localize('saveImageLabel', "Save as image");
public static ICON = 'saveAsImage';
constructor(
@IWindowsService private windowsService: IWindowsService,
@IWindowService private windowService: IWindowService,
@INotificationService private notificationService: INotificationService,
@IEditorService private editorService: IEditorService,
@IWorkspaceContextService private workspaceContextService: IWorkspaceContextService
) {
super(SaveImageAction.ID, SaveImageAction.LABEL, SaveImageAction.ICON);
}
public run(context: IChartActionContext): Promise<boolean> {
if (context.insight instanceof Graph) {
return this.promptForFilepath().then(filePath => {
let data = (<Graph>context.insight).getCanvasData();
if (!data) {
this.showError(localize('chartNotFound', 'Could not find chart to save'));
return false;
}
if (filePath) {
let buffer = this.decodeBase64Image(data);
writeFile(filePath, buffer).then(undefined, (err) => {
if (err) {
this.showError(err.message);
} else {
let fileUri = URI.file(filePath);
this.windowsService.openExternal(fileUri.toString());
this.notificationService.notify({
severity: Severity.Error,
message: localize('chartSaved', 'Saved Chart to path: {0}', filePath)
});
}
});
}
return true;
});
}
return Promise.resolve(false);
}
private decodeBase64Image(data: string): Buffer {
let matches = data.match(/^data:([A-Za-z-+\/]+);base64,(.+)$/);
return Buffer.from(matches[2], 'base64');
}
private promptForFilepath(): Promise<string> {
let filepathPlaceHolder = resolveCurrentDirectory(this.getActiveUriString(), getRootPath(this.workspaceContextService));
filepathPlaceHolder = join(filepathPlaceHolder, 'chart.png');
return this.windowService.showSaveDialog({
title: localize('chartViewer.saveAsFileTitle', 'Choose Results File'),
defaultPath: normalize(filepathPlaceHolder)
});
}
private showError(errorMsg: string) {
this.notificationService.notify({
severity: Severity.Error,
message: errorMsg
});
}
private getActiveUriString(): string {
let editor = this.editorService.activeControl;
if (editor && editor instanceof QueryEditor) {
let queryEditor: QueryEditor = editor;
return queryEditor.uri;
}
return undefined;
}
}

View File

@@ -0,0 +1,220 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import { Registry } from 'vs/platform/registry/common/platform';
import { Extensions, IInsightRegistry } from 'sql/platform/dashboard/common/insightRegistry';
import { InsightType, IInsightOptions } from './interfaces';
import { DataDirection, ChartType, LegendPosition, DataType } from 'sql/workbench/parts/dashboard/widgets/insights/views/charts/interfaces';
const insightRegistry = Registry.as<IInsightRegistry>(Extensions.InsightContribution);
export enum ControlType {
combo,
numberInput,
input,
checkbox,
dateInput
}
export interface IChartOption {
label: string;
type: ControlType;
configEntry: string;
default: any;
options?: any[];
displayableOptions?: string[];
if?: (options: IInsightOptions) => boolean;
}
export interface IChartOptions {
general: Array<IChartOption>;
[x: string]: Array<IChartOption>;
}
const dataDirectionOption: IChartOption = {
label: localize('dataDirectionLabel', 'Data Direction'),
type: ControlType.combo,
displayableOptions: [localize('verticalLabel', 'Vertical'), localize('horizontalLabel', 'Horizontal')],
options: [DataDirection.Vertical, DataDirection.Horizontal],
configEntry: 'dataDirection',
default: DataDirection.Horizontal
};
const columnsAsLabelsInput: IChartOption = {
label: localize('columnsAsLabelsLabel', 'Use column names as labels'),
type: ControlType.checkbox,
configEntry: 'columnsAsLabels',
default: false,
if: (options: IInsightOptions) => {
return options.dataDirection === DataDirection.Vertical && options.dataType !== DataType.Point;
}
};
const labelFirstColumnInput: IChartOption = {
label: localize('labelFirstColumnLabel', 'Use first column as row label'),
type: ControlType.checkbox,
configEntry: 'labelFirstColumn',
default: false,
if: (options: IInsightOptions) => {
return options.dataDirection === DataDirection.Horizontal && options.dataType !== DataType.Point;
}
};
const legendInput: IChartOption = {
label: localize('legendLabel', 'Legend Position'),
type: ControlType.combo,
options: Object.values(LegendPosition),
configEntry: 'legendPosition',
default: LegendPosition.Top
};
const yAxisLabelInput: IChartOption = {
label: localize('yAxisLabel', 'Y Axis Label'),
type: ControlType.input,
configEntry: 'yAxisLabel',
default: undefined
};
const yAxisMinInput: IChartOption = {
label: localize('yAxisMinVal', 'Y Axis Minimum Value'),
type: ControlType.numberInput,
configEntry: 'yAxisMin',
default: undefined
};
const yAxisMaxInput: IChartOption = {
label: localize('yAxisMaxVal', 'Y Axis Maximum Value'),
type: ControlType.numberInput,
configEntry: 'yAxisMax',
default: undefined
};
const xAxisLabelInput: IChartOption = {
label: localize('xAxisLabel', 'X Axis Label'),
type: ControlType.input,
configEntry: 'xAxisLabel',
default: undefined
};
const xAxisMinInput: IChartOption = {
label: localize('xAxisMinVal', 'X Axis Minimum Value'),
type: ControlType.numberInput,
configEntry: 'xAxisMin',
default: undefined
};
const xAxisMaxInput: IChartOption = {
label: localize('xAxisMaxVal', 'X Axis Maximum Value'),
type: ControlType.numberInput,
configEntry: 'xAxisMax',
default: undefined
};
const xAxisMinDateInput: IChartOption = {
label: localize('xAxisMinDate', 'X Axis Minimum Date'),
type: ControlType.dateInput,
configEntry: 'xAxisMin',
default: undefined
};
const xAxisMaxDateInput: IChartOption = {
label: localize('xAxisMaxDate', 'X Axis Maximum Date'),
type: ControlType.dateInput,
configEntry: 'xAxisMax',
default: undefined
};
const dataTypeInput: IChartOption = {
label: localize('dataTypeLabel', 'Data Type'),
type: ControlType.combo,
options: [DataType.Number, DataType.Point],
displayableOptions: [localize('numberLabel', 'Number'), localize('pointLabel', 'Point')],
configEntry: 'dataType',
default: DataType.Number
};
export const ChartOptions: IChartOptions = {
general: [
{
label: localize('chartTypeLabel', 'Chart Type'),
type: ControlType.combo,
options: insightRegistry.getAllIds(),
configEntry: 'type',
default: ChartType.Bar
}
],
[ChartType.Line]: [
dataTypeInput,
columnsAsLabelsInput,
labelFirstColumnInput,
yAxisLabelInput,
xAxisLabelInput,
legendInput
],
[ChartType.Scatter]: [
legendInput,
yAxisLabelInput,
xAxisLabelInput
],
[ChartType.TimeSeries]: [
legendInput,
yAxisLabelInput,
yAxisMinInput,
yAxisMaxInput,
xAxisLabelInput,
xAxisMinDateInput,
xAxisMaxDateInput,
],
[ChartType.Bar]: [
dataDirectionOption,
columnsAsLabelsInput,
labelFirstColumnInput,
legendInput,
yAxisLabelInput,
yAxisMinInput,
yAxisMaxInput,
xAxisLabelInput
],
[ChartType.HorizontalBar]: [
dataDirectionOption,
columnsAsLabelsInput,
labelFirstColumnInput,
legendInput,
xAxisLabelInput,
xAxisMinInput,
xAxisMaxInput,
yAxisLabelInput
],
[ChartType.Pie]: [
dataDirectionOption,
columnsAsLabelsInput,
labelFirstColumnInput,
legendInput
],
[ChartType.Doughnut]: [
dataDirectionOption,
columnsAsLabelsInput,
labelFirstColumnInput,
legendInput
],
[InsightType.Table]: [],
[InsightType.Count]: [],
[InsightType.Image]: [
{
configEntry: 'encoding',
label: localize('encodingOption', 'Encoding'),
type: ControlType.input,
default: 'hex'
},
{
configEntry: 'imageFormat',
label: localize('imageFormatOption', 'Image Format'),
type: ControlType.input,
default: 'jpeg'
}
]
};

View File

@@ -0,0 +1,37 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IPanelTab } from 'sql/base/browser/ui/panel/panel';
import { ChartView } from './chartView';
import QueryRunner from 'sql/platform/query/common/queryRunner';
import { localize } from 'vs/nls';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
export class ChartTab implements IPanelTab {
public readonly title = localize('chartTabTitle', 'Chart');
public readonly identifier = 'ChartTab';
public readonly view: ChartView;
constructor(@IInstantiationService instantiationService: IInstantiationService) {
this.view = instantiationService.createInstance(ChartView);
}
public set queryRunner(runner: QueryRunner) {
this.view.queryRunner = runner;
}
public chart(dataId: { batchId: number, resultId: number }): void {
this.view.chart(dataId);
}
public dispose() {
this.view.dispose();
}
public clear() {
this.view.clear();
}
}

View File

@@ -0,0 +1,384 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/chartView';
import { IPanelView } from 'sql/base/browser/ui/panel/panel';
import { Insight } from './insight';
import QueryRunner from 'sql/platform/query/common/queryRunner';
import { ChartOptions, IChartOption, ControlType } from './chartOptions';
import { Extensions, IInsightRegistry } from 'sql/platform/dashboard/common/insightRegistry';
import { IInsightOptions } from './interfaces';
import { Registry } from 'vs/platform/registry/common/platform';
import * as DOM from 'vs/base/browser/dom';
import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle';
import { attachSelectBoxStyler, attachInputBoxStyler } from 'vs/platform/theme/common/styler';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { isUndefinedOrNull } from 'vs/base/common/types';
import { ChartType } from 'sql/workbench/parts/dashboard/widgets/insights/views/charts/interfaces';
import { IInsightData } from 'sql/workbench/parts/dashboard/widgets/insights/interfaces';
import { CreateInsightAction, CopyAction, SaveImageAction, IChartActionContext } from 'sql/workbench/parts/charts/browser/actions';
import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar';
import { Checkbox } from 'sql/base/browser/ui/checkbox/checkbox';
export class ChartState {
dataId: { batchId: number, resultId: number };
options: IInsightOptions = {
type: ChartType.Bar
};
dispose() {
}
}
declare class Proxy {
constructor(object, handler);
}
const insightRegistry = Registry.as<IInsightRegistry>(Extensions.InsightContribution);
export class ChartView extends Disposable implements IPanelView {
private insight: Insight;
private _queryRunner: QueryRunner;
private _data: IInsightData;
private _currentData: { batchId: number, resultId: number };
private taskbar: Taskbar;
private _createInsightAction: CreateInsightAction;
private _copyAction: CopyAction;
private _saveAction: SaveImageAction;
private _state: ChartState;
private options: IInsightOptions = {
type: ChartType.Bar
};
/** parent container */
private container: HTMLElement;
/** container for the options controls */
private optionsControl: HTMLElement;
/** container for type specific controls */
private typeControls: HTMLElement;
/** container for the insight */
private insightContainer: HTMLElement;
/** container for the action bar */
private taskbarContainer: HTMLElement;
/** container for the charting (includes insight and options) */
private chartingContainer: HTMLElement;
private optionDisposables: IDisposable[] = [];
private optionMap: { [x: string]: { element: HTMLElement; set: (val) => void } } = {};
constructor(
@IContextViewService private _contextViewService: IContextViewService,
@IThemeService private _themeService: IThemeService,
@IInstantiationService private _instantiationService: IInstantiationService,
) {
super();
this.taskbarContainer = DOM.$('div.taskbar-container');
this.taskbar = new Taskbar(this.taskbarContainer);
this.optionsControl = DOM.$('div.options-container');
const generalControls = DOM.$('div.general-controls');
this.optionsControl.appendChild(generalControls);
this.typeControls = DOM.$('div.type-controls');
this.optionsControl.appendChild(this.typeControls);
this._createInsightAction = this._instantiationService.createInstance(CreateInsightAction);
this._copyAction = this._instantiationService.createInstance(CopyAction);
this._saveAction = this._instantiationService.createInstance(SaveImageAction);
this.taskbar.setContent([{ action: this._createInsightAction }]);
const self = this;
this.options = new Proxy(this.options, {
get: function (target, key, receiver) {
return Reflect.get(target, key, receiver);
},
set: function (target, key, value, receiver) {
let change = false;
if (target[key] !== value) {
change = true;
}
let result = Reflect.set(target, key, value, receiver);
// mirror the change in our state
if (self.state) {
Reflect.set(self.state.options, key, value);
}
if (change) {
self.taskbar.context = <IChartActionContext>{ options: self.options, insight: self.insight ? self.insight.insight : undefined };
if (key === 'type') {
self.buildOptions();
} else {
self.verifyOptions();
}
}
return result;
}
}) as IInsightOptions;
ChartOptions.general[0].options = insightRegistry.getAllIds();
ChartOptions.general.map(o => {
this.createOption(o, generalControls);
});
this.buildOptions();
}
public clear() {
}
public dispose() {
dispose(this.optionDisposables);
super.dispose();
}
render(container: HTMLElement): void {
if (!this.container) {
this.container = DOM.$('div.chart-parent-container');
this.insightContainer = DOM.$('div.insight-container');
this.chartingContainer = DOM.$('div.charting-container');
this.container.appendChild(this.taskbarContainer);
this.container.appendChild(this.chartingContainer);
this.chartingContainer.appendChild(this.insightContainer);
this.chartingContainer.appendChild(this.optionsControl);
this.insight = new Insight(this.insightContainer, this.options, this._instantiationService);
}
container.appendChild(this.container);
if (this._data) {
this.insight.data = this._data;
} else {
this.queryRunner = this._queryRunner;
}
this.verifyOptions();
}
public chart(dataId: { batchId: number, resultId: number }) {
this.state.dataId = dataId;
this._currentData = dataId;
this.shouldGraph();
}
layout(dimension: DOM.Dimension): void {
if (this.insight) {
this.insight.layout(new DOM.Dimension(DOM.getContentWidth(this.insightContainer), DOM.getContentHeight(this.insightContainer)));
}
}
public set queryRunner(runner: QueryRunner) {
this._queryRunner = runner;
this.shouldGraph();
}
private shouldGraph() {
// Check if we have the necessary information
if (this._currentData && this._queryRunner) {
// check if we are being asked to graph something that is available
let batch = this._queryRunner.batchSets[this._currentData.batchId];
if (batch) {
let summary = batch.resultSetSummaries[this._currentData.resultId];
if (summary) {
this._queryRunner.getQueryRows(0, summary.rowCount, 0, 0).then(d => {
this._data = {
columns: summary.columnInfo.map(c => c.columnName),
rows: d.resultSubset.rows.map(r => r.map(c => c.displayValue))
};
if (this.insight) {
this.insight.data = this._data;
}
});
}
}
// if we have the necessary information but the information isn't available yet,
// we should be smart and retrying when the information might be available
}
}
private buildOptions() {
// The first element in the disposables list is for the chart type: the master dropdown that controls other option controls.
// whiling rebuilding the options we should not dispose it, otherwise it would react to the theme change event
if (this.optionDisposables.length > 1) {
dispose(this.optionDisposables.slice(1));
this.optionDisposables.splice(1);
}
this.optionMap = {
'type': this.optionMap['type']
};
DOM.clearNode(this.typeControls);
this.updateActionbar();
ChartOptions[this.options.type].map(o => {
this.createOption(o, this.typeControls);
});
if (this.insight) {
this.insight.options = this.options;
}
this.verifyOptions();
}
private verifyOptions() {
this.updateActionbar();
for (let key in this.optionMap) {
if (this.optionMap.hasOwnProperty(key)) {
let option = ChartOptions[this.options.type].find(e => e.configEntry === key);
if (option && option.if) {
if (option.if(this.options)) {
DOM.show(this.optionMap[key].element);
} else {
DOM.hide(this.optionMap[key].element);
}
}
}
}
}
private updateActionbar() {
if (this.insight && this.insight.isCopyable) {
this.taskbar.context = { insight: this.insight.insight, options: this.options };
this.taskbar.setContent([
{ action: this._createInsightAction },
{ action: this._copyAction },
{ action: this._saveAction }
]);
} else {
this.taskbar.setContent([{ action: this._createInsightAction }]);
}
}
private createOption(option: IChartOption, container: HTMLElement) {
let label = DOM.$('div');
label.innerText = option.label;
let optionContainer = DOM.$('div.option-container');
optionContainer.appendChild(label);
let setFunc: (val) => void;
let value = this.state ? this.state.options[option.configEntry] || option.default : option.default;
switch (option.type) {
case ControlType.checkbox:
let checkbox = new Checkbox(optionContainer, {
label: '',
ariaLabel: option.label,
checked: value,
onChange: () => {
if (this.options[option.configEntry] !== checkbox.checked) {
this.options[option.configEntry] = checkbox.checked;
if (this.insight) {
this.insight.options = this.options;
}
}
}
});
setFunc = (val: boolean) => {
checkbox.checked = val;
};
break;
case ControlType.combo:
let dropdown = new SelectBox(option.displayableOptions || option.options, undefined, this._contextViewService);
dropdown.select(option.options.indexOf(value));
dropdown.render(optionContainer);
dropdown.onDidSelect(e => {
if (this.options[option.configEntry] !== option.options[e.index]) {
this.options[option.configEntry] = option.options[e.index];
if (this.insight) {
this.insight.options = this.options;
}
}
});
setFunc = (val: string) => {
if (!isUndefinedOrNull(val)) {
dropdown.select(option.options.indexOf(val));
}
};
this.optionDisposables.push(attachSelectBoxStyler(dropdown, this._themeService));
break;
case ControlType.input:
let input = new InputBox(optionContainer, this._contextViewService);
input.value = value || '';
input.onDidChange(e => {
if (this.options[option.configEntry] !== e) {
this.options[option.configEntry] = e;
if (this.insight) {
this.insight.options = this.options;
}
}
});
setFunc = (val: string) => {
if (!isUndefinedOrNull(val)) {
input.value = val;
}
};
this.optionDisposables.push(attachInputBoxStyler(input, this._themeService));
break;
case ControlType.numberInput:
let numberInput = new InputBox(optionContainer, this._contextViewService, { type: 'number' });
numberInput.value = value || '';
numberInput.onDidChange(e => {
if (this.options[option.configEntry] !== Number(e)) {
this.options[option.configEntry] = Number(e);
if (this.insight) {
this.insight.options = this.options;
}
}
});
setFunc = (val: string) => {
if (!isUndefinedOrNull(val)) {
numberInput.value = val;
}
};
this.optionDisposables.push(attachInputBoxStyler(numberInput, this._themeService));
break;
case ControlType.dateInput:
let dateInput = new InputBox(optionContainer, this._contextViewService, { type: 'datetime-local' });
dateInput.value = value || '';
dateInput.onDidChange(e => {
if (this.options[option.configEntry] !== e) {
this.options[option.configEntry] = e;
if (this.insight) {
this.insight.options = this.options;
}
}
});
setFunc = (val: string) => {
if (!isUndefinedOrNull(val)) {
dateInput.value = val;
}
};
this.optionDisposables.push(attachInputBoxStyler(dateInput, this._themeService));
break;
}
this.optionMap[option.configEntry] = { element: optionContainer, set: setFunc };
container.appendChild(optionContainer);
this.options[option.configEntry] = value;
}
public set state(val: ChartState) {
this._state = val;
if (this.state.options) {
for (let key in this.state.options) {
if (this.state.options.hasOwnProperty(key) && this.optionMap[key]) {
this.options[key] = this.state.options[key];
this.optionMap[key].set(this.state.options[key]);
}
}
}
if (this.state.dataId) {
this.chart(this.state.dataId);
}
}
public get state(): ChartState {
return this._state;
}
}

View File

@@ -0,0 +1,45 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/countInsight';
import { IInsight, InsightType } from './interfaces';
import { IInsightData } from 'sql/workbench/parts/dashboard/widgets/insights/interfaces';
import { $, clearNode } from 'vs/base/browser/dom';
export class CountInsight implements IInsight {
public options;
public static readonly types = [InsightType.Count];
public readonly types = CountInsight.types;
private countImage: HTMLElement;
constructor(container: HTMLElement, options: any) {
this.countImage = $('div');
container.appendChild(this.countImage);
}
public layout() { }
set data(data: IInsightData) {
clearNode(this.countImage);
for (let i = 0; i < data.columns.length; i++) {
let container = $('div.count-label-container');
let label = $('span.label-container');
label.innerText = data.columns[i];
let value = $('span.value-container');
value.innerText = data.rows[0][i];
container.appendChild(label);
container.appendChild(value);
this.countImage.appendChild(container);
}
}
dispose() {
}
}

View File

@@ -0,0 +1,440 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Chart as ChartJs } from 'chart.js';
import { mixin } from 'sql/base/common/objects';
import { localize } from 'vs/nls';
import * as colors from 'vs/platform/theme/common/colorRegistry';
import { editorLineNumbers } from 'vs/editor/common/view/editorColorRegistry';
import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService';
import { IInsightData } from 'sql/workbench/parts/dashboard/widgets/insights/interfaces';
import { IInsightOptions, IInsight } from './interfaces';
import { ChartType, DataDirection, LegendPosition, DataType, IPointDataSet, customMixin } from 'sql/workbench/parts/dashboard/widgets/insights/views/charts/interfaces';
const noneLineGraphs = [ChartType.Doughnut, ChartType.Pie];
const timeSeriesScales: ChartJs.ChartOptions = {
scales: {
xAxes: [{
type: 'time',
display: true,
ticks: {
autoSkip: false,
maxRotation: 45,
minRotation: 45
}
}],
yAxes: [{
display: true,
}]
}
};
const defaultOptions: IInsightOptions = {
type: ChartType.Bar,
dataDirection: DataDirection.Horizontal
};
export class Graph implements IInsight {
private _options: IInsightOptions;
private canvas: HTMLCanvasElement;
private chartjs: ChartJs;
private _data: IInsightData;
private originalType: ChartType;
public static readonly types = [ChartType.Bar, ChartType.Doughnut, ChartType.HorizontalBar, ChartType.Line, ChartType.Pie, ChartType.Scatter, ChartType.TimeSeries];
public readonly types = Graph.types;
private _theme: ITheme;
constructor(
container: HTMLElement, options: IInsightOptions = defaultOptions,
@IThemeService themeService: IThemeService
) {
this._theme = themeService.getTheme();
themeService.onThemeChange(e => {
this._theme = e;
this.data = this._data;
});
this.options = mixin(options, defaultOptions, false);
let canvasContainer = document.createElement('div');
canvasContainer.style.width = '100%';
canvasContainer.style.height = '100%';
this.canvas = document.createElement('canvas');
canvasContainer.appendChild(this.canvas);
container.appendChild(canvasContainer);
}
public dispose() {
}
public layout() {
}
public getCanvasData(): string {
return this.chartjs.toBase64Image();
}
public set data(data: IInsightData) {
if (!data) {
return;
}
this._data = data;
let labels: Array<string>;
let chartData: Array<ChartJs.ChartDataSets>;
if (this.options.dataDirection === DataDirection.Horizontal) {
if (this.options.labelFirstColumn) {
labels = data.columns.slice(1);
} else {
labels = data.columns;
}
} else {
labels = data.rows.map(row => row[0]);
}
if (this.originalType === ChartType.TimeSeries) {
let dataSetMap: { [label: string]: IPointDataSet } = {};
this._data.rows.map(row => {
if (row && row.length >= 3) {
let legend = row[0];
if (!dataSetMap[legend]) {
dataSetMap[legend] = { label: legend, data: [], fill: false };
}
dataSetMap[legend].data.push({ x: row[1], y: Number(row[2]) });
}
});
chartData = Object.values(dataSetMap);
} else {
if (this.options.dataDirection === DataDirection.Horizontal) {
if (this.options.labelFirstColumn) {
chartData = data.rows.map((row) => {
return {
data: row.map(item => Number(item)).slice(1),
label: row[0]
};
});
} else {
chartData = data.rows.map((row, i) => {
return {
data: row.map(item => Number(item)),
label: localize('series', 'Series {0}', i)
};
});
}
} else {
if (this.options.columnsAsLabels) {
chartData = data.rows[0].slice(1).map((row, i) => {
return {
data: data.rows.map(row => Number(row[i + 1])),
label: data.columns[i + 1]
};
});
} else {
chartData = data.rows[0].slice(1).map((row, i) => {
return {
data: data.rows.map(row => Number(row[i + 1])),
label: localize('series', 'Series {0}', i + 1)
};
});
}
}
}
chartData = chartData.map((c, i) => {
return mixin(c, getColors(this.options.type, i, c.data.length), false);
});
if (this.chartjs) {
this.chartjs.data.datasets = chartData;
this.chartjs.config.type = this.options.type;
// we don't want to include lables for timeSeries
this.chartjs.data.labels = this.originalType === 'timeSeries' ? [] : labels;
this.chartjs.options = this.transformOptions(this.options);
this.chartjs.update(0);
} else {
this.chartjs = new ChartJs(this.canvas.getContext('2d'), {
data: {
// we don't want to include lables for timeSeries
labels: this.originalType === 'timeSeries' ? [] : labels,
datasets: chartData
},
type: this.options.type,
options: this.transformOptions(this.options)
});
}
}
private transformOptions(options: IInsightOptions): ChartJs.ChartOptions {
let retval: ChartJs.ChartOptions = {};
retval.maintainAspectRatio = false;
let foregroundColor = this._theme.getColor(colors.editorForeground);
let foreground = foregroundColor ? foregroundColor.toString() : null;
let gridLinesColor = this._theme.getColor(editorLineNumbers);
let gridLines = gridLinesColor ? gridLinesColor.toString() : null;
let backgroundColor = this._theme.getColor(colors.editorBackground);
let background = backgroundColor ? backgroundColor.toString() : null;
if (options) {
retval.scales = {};
// we only want to include axis if it is a axis based graph type
if (!noneLineGraphs.includes(options.type as ChartType)) {
retval.scales.xAxes = [{
scaleLabel: {
fontColor: foreground,
labelString: options.xAxisLabel,
display: options.xAxisLabel ? true : false
},
ticks: {
fontColor: foreground
},
gridLines: {
color: gridLines
}
}];
if (options.xAxisMax) {
retval.scales = mixin(retval.scales, { xAxes: [{ ticks: { max: options.xAxisMax } }] }, true, customMixin);
}
if (options.xAxisMin) {
retval.scales = mixin(retval.scales, { xAxes: [{ ticks: { min: options.xAxisMin } }] }, true, customMixin);
}
retval.scales.yAxes = [{
scaleLabel: {
fontColor: foreground,
labelString: options.yAxisLabel,
display: options.yAxisLabel ? true : false
},
ticks: {
fontColor: foreground
},
gridLines: {
color: gridLines
}
}];
if (options.yAxisMax) {
retval.scales = mixin(retval.scales, { yAxes: [{ ticks: { max: options.yAxisMax } }] }, true, customMixin);
}
if (options.yAxisMin) {
retval.scales = mixin(retval.scales, { yAxes: [{ ticks: { min: options.yAxisMin } }] }, true, customMixin);
}
if (this.originalType === ChartType.TimeSeries) {
retval = mixin(retval, timeSeriesScales, true, customMixin);
if (options.xAxisMax) {
retval = mixin(retval, {
scales: {
xAxes: [{
time: {
max: options.xAxisMax
}
}],
}
}, true, customMixin);
}
if (options.xAxisMin) {
retval = mixin(retval, {
scales: {
xAxes: [{
time: {
min: options.xAxisMin
}
}],
}
}, true, customMixin);
}
}
}
retval.legend = <ChartJs.ChartLegendOptions>{
position: options.legendPosition as ChartJs.PositionType,
display: options.legendPosition !== LegendPosition.None,
labels: {
fontColor: foreground
}
};
}
// these are custom options that will throw compile errors
(<any>retval).viewArea = {
backgroundColor: background
};
return retval;
}
public set options(options: IInsightOptions) {
this._options = options;
this.originalType = options.type as ChartType;
if (this.options.type === ChartType.TimeSeries) {
this.options.type = ChartType.Line;
this.options.dataType = DataType.Point;
this.options.dataDirection = DataDirection.Horizontal;
}
this.data = this._data;
}
public get options(): IInsightOptions {
return this._options;
}
}
/**
* The Following code is pulled from ng2-charting in order to keep the same
* color functionality
*/
const defaultColors: Array<Color> = [
[255, 99, 132],
[54, 162, 235],
[255, 206, 86],
[231, 233, 237],
[75, 192, 192],
[151, 187, 205],
[220, 220, 220],
[247, 70, 74],
[70, 191, 189],
[253, 180, 92],
[148, 159, 177],
[77, 83, 96]
];
type Color = [number, number, number];
interface ILineColor {
backgroundColor: string;
borderColor: string;
pointBackgroundColor: string;
pointBorderColor: string;
pointHoverBackgroundColor: string;
pointHoverBorderColor: string;
}
interface IBarColor {
backgroundColor: string;
borderColor: string;
hoverBackgroundColor: string;
hoverBorderColor: string;
}
interface IPieColors {
backgroundColor: Array<string>;
borderColor: Array<string>;
pointBackgroundColor: Array<string>;
pointBorderColor: Array<string>;
pointHoverBackgroundColor: Array<string>;
pointHoverBorderColor: Array<string>;
}
interface IPolarAreaColors {
backgroundColor: Array<string>;
borderColor: Array<string>;
hoverBackgroundColor: Array<string>;
hoverBorderColor: Array<string>;
}
function rgba(colour: Color, alpha: number): string {
return 'rgba(' + colour.concat(alpha).join(',') + ')';
}
function getRandomInt(min: number, max: number): number {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function getRandomColor(): Color {
return [getRandomInt(0, 255), getRandomInt(0, 255), getRandomInt(0, 255)];
}
/**
* Generate colors for line|bar charts
*/
function generateColor(index: number): Color {
return defaultColors[index] || getRandomColor();
}
/**
* Generate colors for pie|doughnut charts
*/
function generateColors(count: number): Array<Color> {
const colorsArr = new Array(count);
for (let i = 0; i < count; i++) {
colorsArr[i] = defaultColors[i] || getRandomColor();
}
return colorsArr;
}
function formatLineColor(colors: Color): ILineColor {
return {
backgroundColor: rgba(colors, 0.4),
borderColor: rgba(colors, 1),
pointBackgroundColor: rgba(colors, 1),
pointBorderColor: '#fff',
pointHoverBackgroundColor: '#fff',
pointHoverBorderColor: rgba(colors, 0.8)
};
}
function formatBarColor(colors: Color): IBarColor {
return {
backgroundColor: rgba(colors, 0.6),
borderColor: rgba(colors, 1),
hoverBackgroundColor: rgba(colors, 0.8),
hoverBorderColor: rgba(colors, 1)
};
}
function formatPieColors(colors: Array<Color>): IPieColors {
return {
backgroundColor: colors.map(color => rgba(color, 0.6)),
borderColor: colors.map(() => '#fff'),
pointBackgroundColor: colors.map(color => rgba(color, 1)),
pointBorderColor: colors.map(() => '#fff'),
pointHoverBackgroundColor: colors.map(color => rgba(color, 1)),
pointHoverBorderColor: colors.map(color => rgba(color, 1))
};
}
function formatPolarAreaColors(colors: Array<Color>): IPolarAreaColors {
return {
backgroundColor: colors.map(color => rgba(color, 0.6)),
borderColor: colors.map(color => rgba(color, 1)),
hoverBackgroundColor: colors.map(color => rgba(color, 0.8)),
hoverBorderColor: colors.map(color => rgba(color, 1))
};
}
/**
* Generate colors by chart type
*/
function getColors(chartType: string, index: number, count: number): Color | ILineColor | IBarColor | IPieColors | IPolarAreaColors {
if (chartType === 'pie' || chartType === 'doughnut') {
return formatPieColors(generateColors(count));
}
if (chartType === 'polarArea') {
return formatPolarAreaColors(generateColors(count));
}
if (chartType === 'line' || chartType === 'radar') {
return formatLineColor(generateColor(index));
}
if (chartType === 'bar' || chartType === 'horizontalBar') {
return formatBarColor(generateColor(index));
}
return generateColor(index);
}

View File

@@ -0,0 +1,72 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IInsight, IInsightOptions, InsightType } from './interfaces';
import { IInsightData } from 'sql/workbench/parts/dashboard/widgets/insights/interfaces';
import { $ } from 'vs/base/browser/dom';
import { mixin } from 'vs/base/common/objects';
export interface IConfig extends IInsightOptions {
encoding?: string;
imageFormat?: string;
}
const defaultConfig: IConfig = {
type: InsightType.Image,
encoding: 'hex',
imageFormat: 'jpeg'
};
export class ImageInsight implements IInsight {
public static readonly types = [InsightType.Image];
public readonly types = ImageInsight.types;
private _options: IConfig;
private imageEle: HTMLImageElement;
constructor(container: HTMLElement, options: IConfig) {
this._options = mixin(options, defaultConfig, false);
this.imageEle = $('img');
container.appendChild(this.imageEle);
}
public layout() {
}
public dispose() {
}
set options(config: IConfig) {
this._options = mixin(config, defaultConfig, false);
}
get options(): IConfig {
return this._options;
}
set data(data: IInsightData) {
if (data.rows && data.rows.length > 0 && data.rows[0].length > 0) {
let img = data.rows[0][0];
if (this._options.encoding === 'hex') {
img = ImageInsight._hexToBase64(img);
}
this.imageEle.src = `data:image/${this._options.imageFormat};base64,${img}`;
}
}
private static _hexToBase64(hexVal: string) {
if (hexVal.startsWith('0x')) {
hexVal = hexVal.slice(2);
}
// should be able to be replaced with new Buffer(hexVal, 'hex').toString('base64')
return btoa(String.fromCharCode.apply(null, hexVal.replace(/\r|\n/g, '').replace(/([\da-fA-F]{2}) ?/g, '0x$1 ').replace(/ +$/, '').split(' ').map(v => Number(v))));
}
}

View File

@@ -0,0 +1,105 @@
/*---------------------------------------------------------------------------------------------
* 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 { Graph } from './graphInsight';
import { IInsightData } from 'sql/workbench/parts/dashboard/widgets/insights/interfaces';
import { DataDirection, ChartType } from 'sql/workbench/parts/dashboard/widgets/insights/views/charts/interfaces';
import { ImageInsight } from './imageInsight';
import { TableInsight } from './tableInsight';
import { IInsightOptions, IInsight, InsightType, IInsightCtor } from './interfaces';
import { CountInsight } from './countInsight';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Dimension, clearNode } from 'vs/base/browser/dom';
import { deepClone } from 'vs/base/common/objects';
const defaultOptions: IInsightOptions = {
type: ChartType.Bar,
dataDirection: DataDirection.Horizontal
};
export class Insight {
private _insight: IInsight;
public get insight(): IInsight {
return this._insight;
}
private _options: IInsightOptions;
private _data: IInsightData;
private dim: Dimension;
constructor(
private container: HTMLElement, options: IInsightOptions = defaultOptions,
@IInstantiationService private _instantiationService: IInstantiationService
) {
this.options = options;
this.buildInsight();
}
public layout(dim: Dimension) {
this.dim = dim;
this.insight.layout(dim);
}
public set options(val: IInsightOptions) {
this._options = deepClone(val);
if (this.insight) {
// check to see if we need to change the insight type
if (!this.insight.types.includes(this.options.type)) {
this.buildInsight();
} else {
this.insight.options = this.options;
}
}
}
public get options(): IInsightOptions {
return this._options;
}
public set data(val: IInsightData) {
this._data = val;
if (this.insight) {
this.insight.data = val;
}
}
private buildInsight() {
if (this.insight) {
this.insight.dispose();
}
clearNode(this.container);
let ctor = this.findctor(this.options.type);
if (ctor) {
this._insight = this._instantiationService.createInstance(ctor, this.container, this.options);
this.insight.layout(this.dim);
if (this._data) {
this.insight.data = this._data;
}
}
}
public get isCopyable(): boolean {
return Graph.types.includes(this.options.type as ChartType);
}
private findctor(type: ChartType | InsightType): IInsightCtor {
if (Graph.types.includes(type as ChartType)) {
return Graph;
} else if (ImageInsight.types.includes(type as InsightType)) {
return ImageInsight;
} else if (TableInsight.types.includes(type as InsightType)) {
return TableInsight;
} else if (CountInsight.types.includes(type as InsightType)) {
return CountInsight;
}
return undefined;
}
}

View File

@@ -0,0 +1,45 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Dimension } from 'vs/base/browser/dom';
import { IInsightData } from 'sql/workbench/parts/dashboard/widgets/insights/interfaces';
import { DataDirection, ChartType, LegendPosition, DataType } from 'sql/workbench/parts/dashboard/widgets/insights/views/charts/interfaces';
export interface IInsightOptions {
type: InsightType | ChartType;
dataDirection?: DataDirection;
dataType?: DataType;
labelFirstColumn?: boolean;
columnsAsLabels?: boolean;
legendPosition?: LegendPosition;
yAxisLabel?: string;
yAxisMin?: number;
yAxisMax?: number;
xAxisLabel?: string;
xAxisMin?: number;
xAxisMax?: number;
encoding?: string;
imageFormat?: string;
}
export interface IInsight {
options: IInsightOptions;
data: IInsightData;
readonly types: Array<InsightType | ChartType>;
layout(dim: Dimension);
dispose();
}
export interface IInsightCtor {
new(container: HTMLElement, options: IInsightOptions, ...services: { _serviceBrand: any; }[]): IInsight;
readonly types: Array<InsightType | ChartType>;
}
export enum InsightType {
Image = 'image',
Table = 'table',
Count = 'count'
}

View File

@@ -0,0 +1,30 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.chart-parent-container {
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
}
.actionbar-container {
width: 100%;
flex: 0 0 auto;
}
.charting-container {
display: flex;
flex: 1 1 auto;
}
.insight-container {
flex: 1 1 0;
}
.options-container {
width: 250px;
padding-right: 10px;
}

View File

@@ -0,0 +1,12 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.count-label-container {
margin-left: 5px;
}
.label-container {
font-size: 20px
}

View File

@@ -0,0 +1,74 @@
/*---------------------------------------------------------------------------------------------
* 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 { IInsight, InsightType } from './interfaces';
import { IInsightData } from 'sql/workbench/parts/dashboard/widgets/insights/interfaces';
import { TableDataView } from 'sql/base/browser/ui/table/tableDataView';
import { Table } from 'sql/base/browser/ui/table/table';
import { attachTableStyler } from 'sql/platform/theme/common/styler';
import { CellSelectionModel } from 'sql/base/browser/ui/table/plugins/cellSelectionModel.plugin';
import { $, Dimension } from 'vs/base/browser/dom';
import { Disposable } from 'vs/base/common/lifecycle';
import { IThemeService } from 'vs/platform/theme/common/themeService';
export class TableInsight extends Disposable implements IInsight {
public static readonly types = [InsightType.Table];
public readonly types = TableInsight.types;
private table: Table<any>;
private dataView: TableDataView<any>;
private columns: Slick.Column<any>[];
constructor(container: HTMLElement, options: any,
@IThemeService themeService: IThemeService
) {
super();
let tableContainer = $('div');
tableContainer.style.width = '100%';
tableContainer.style.height = '100%';
container.appendChild(tableContainer);
this.dataView = new TableDataView();
this.table = new Table(tableContainer, { dataProvider: this.dataView }, { showRowNumber: true });
this.table.setSelectionModel(new CellSelectionModel());
this._register(attachTableStyler(this.table, themeService));
}
set data(data: IInsightData) {
this.dataView.clear();
this.dataView.push(transformData(data.rows, data.columns));
this.columns = transformColumns(data.columns);
this.table.columns = this.columns;
}
layout(dim: Dimension) {
this.table.layout(dim);
}
public options;
}
function transformData(rows: string[][], columns: string[]): { [key: string]: string }[] {
return rows.map(row => {
let object: { [key: string]: string } = {};
row.forEach((val, index) => {
object[columns[index]] = val;
});
return object;
});
}
function transformColumns(columns: string[]): Slick.Column<any>[] {
return columns.map(col => {
return <Slick.Column<any>>{
name: col,
id: col,
field: col
};
});
}

View File

@@ -12,7 +12,7 @@ import { INotificationService, INotificationActions } from 'vs/platform/notifica
import Severity from 'vs/base/common/severity';
import { IDialogService, IConfirmation, IConfirmationResult } from 'vs/platform/dialogs/common/dialogs';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { QueryInput } from 'sql/parts/query/common/queryInput';
import { QueryInput } from 'sql/workbench/parts/query/common/queryInput';
import { EditDataInput } from 'sql/workbench/parts/editData/common/editDataInput';
import { DashboardInput } from 'sql/workbench/parts/dashboard/dashboardInput';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';

View File

@@ -3,8 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!sql/parts/query/editor/media/queryEditor';
import * as strings from 'vs/base/common/strings';
import * as DOM from 'vs/base/browser/dom';
import * as nls from 'vs/nls';
@@ -19,7 +17,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { EditDataInput } from 'sql/workbench/parts/editData/common/editDataInput';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import * as queryContext from 'sql/parts/query/common/queryContext';
import * as queryContext from 'sql/workbench/parts/query/common/queryContext';
import { Taskbar, ITaskbarContent } from 'sql/base/browser/ui/taskbar/taskbar';
import { IActionItem } from 'vs/base/browser/ui/actionbar/actionbar';
import { Action } from 'vs/base/common/actions';
@@ -32,7 +30,7 @@ import { TextResourceEditor } from 'vs/workbench/browser/parts/editor/textResour
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
import { IFlexibleSash, HorizontalFlexibleSash } from 'sql/parts/query/views/flexibleSash';
import { IFlexibleSash, HorizontalFlexibleSash } from 'sql/workbench/parts/query/browser/flexibleSash';
import { EditDataResultsEditor } from 'sql/workbench/parts/editData/browser/editDataResultsEditor';
import { EditDataResultsInput } from 'sql/workbench/parts/editData/common/editDataResultsInput';
import { CancellationToken } from 'vs/base/common/cancellation';

View File

@@ -16,7 +16,7 @@ import * as types from 'vs/base/common/types';
import { IQueryModelService } from 'sql/platform/query/common/queryModel';
import { bootstrapAngular } from 'sql/platform/bootstrap/node/bootstrapService';
import { BareResultsGridInfo } from 'sql/parts/query/editor/queryResultsEditor';
import { BareResultsGridInfo } from 'sql/workbench/parts/query/browser/queryResultsEditor';
import { IEditDataComponentParams } from 'sql/platform/bootstrap/node/bootstrapParams';
import { EditDataModule } from 'sql/workbench/parts/grid/views/editData/editData.module';
import { EDITDATA_SELECTOR } from 'sql/workbench/parts/grid/views/editData/editData.component';

View File

@@ -5,7 +5,7 @@
import * as GridContentEvents from 'sql/workbench/parts/grid/common/gridContentEvents';
import { IQueryModelService } from 'sql/platform/query/common/queryModel';
import { QueryEditor } from 'sql/parts/query/editor/queryEditor';
import { QueryEditor } from 'sql/workbench/parts/query/browser/queryEditor';
import { EditDataEditor } from 'sql/workbench/parts/editData/browser/editDataEditor';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';

View File

@@ -10,15 +10,15 @@ import { Subscription, Subject } from 'rxjs/Rx';
import { ElementRef, QueryList, ChangeDetectorRef, ViewChildren } from '@angular/core';
import { SlickGrid } from 'angular2-slickgrid';
import { toDisposableSubscription } from 'sql/base/node/rxjsUtils';
import * as Constants from 'sql/parts/query/common/constants';
import * as LocalizedConstants from 'sql/parts/query/common/localizedConstants';
import * as Constants from 'sql/workbench/parts/query/common/constants';
import * as LocalizedConstants from 'sql/workbench/parts/query/common/localizedConstants';
import { IGridInfo, IGridDataSet, SaveFormat } from 'sql/workbench/parts/grid/common/interfaces';
import * as Utils from 'sql/platform/connection/common/utils';
import { DataService } from 'sql/workbench/parts/grid/services/dataService';
import * as actions from 'sql/workbench/parts/grid/views/gridActions';
import * as Services from 'sql/base/browser/ui/table/formatters';
import * as GridContentEvents from 'sql/workbench/parts/grid/common/gridContentEvents';
import { ResultsVisibleContext, ResultsGridFocussedContext, ResultsMessagesFocussedContext, QueryEditorVisibleContext } from 'sql/parts/query/common/queryContext';
import { ResultsVisibleContext, ResultsGridFocussedContext, ResultsMessagesFocussedContext, QueryEditorVisibleContext } from 'sql/workbench/parts/query/common/queryContext';
import { error } from 'sql/base/common/log';
import { IQueryEditorService } from 'sql/workbench/services/queryEditor/common/queryEditorService';
import { CellSelectionModel } from 'sql/base/browser/ui/table/plugins/cellSelectionModel.plugin';

View File

@@ -14,7 +14,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService';
import { MouseWheelSupport } from 'sql/base/browser/ui/table/plugins/mousewheelTableScroll.plugin';
import { AutoColumnSize } from 'sql/base/browser/ui/table/plugins/autoSizeColumns.plugin';
import { AdditionalKeyBindings } from 'sql/base/browser/ui/table/plugins/additionalKeyBindings.plugin';
import { RESULTS_GRID_DEFAULTS } from 'sql/parts/query/editor/queryResultsEditor';
import { RESULTS_GRID_DEFAULTS } from 'sql/workbench/parts/query/browser/queryResultsEditor';
/**
* Render DataResource as a grid into a host node.

View File

@@ -0,0 +1,216 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Action } from 'vs/base/common/actions';
import { localize } from 'vs/nls';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { ITree } from 'vs/base/parts/tree/browser/tree';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import QueryRunner from 'sql/platform/query/common/queryRunner';
import { SaveFormat } from 'sql/workbench/parts/grid/common/interfaces';
import { Table } from 'sql/base/browser/ui/table/table';
import { GridTableState } from 'sql/workbench/parts/query/electron-browser/gridPanel';
import { QueryEditor } from './queryEditor';
import { CellSelectionModel } from 'sql/base/browser/ui/table/plugins/cellSelectionModel.plugin';
import { isWindows } from 'vs/base/common/platform';
import { removeAnsiEscapeCodes } from 'vs/base/common/strings';
export interface IGridActionContext {
cell: { row: number; cell: number; };
selection: Slick.Range[];
runner: QueryRunner;
batchId: number;
resultId: number;
table: Table<any>;
selectionModel: CellSelectionModel<any>;
tableState: GridTableState;
}
export interface IMessagesActionContext {
selection: Selection;
tree: ITree;
}
function mapForNumberColumn(ranges: Slick.Range[]): Slick.Range[] {
if (ranges) {
return ranges.map(e => new Slick.Range(e.fromRow, e.fromCell - 1, e.toRow, e.toCell ? e.toCell - 1 : undefined));
} else {
return undefined;
}
}
export class SaveResultAction extends Action {
public static SAVECSV_ID = 'grid.saveAsCsv';
public static SAVECSV_LABEL = localize('saveAsCsv', 'Save As CSV');
public static SAVECSV_ICON = 'saveCsv';
public static SAVEJSON_ID = 'grid.saveAsJson';
public static SAVEJSON_LABEL = localize('saveAsJson', 'Save As JSON');
public static SAVEJSON_ICON = 'saveJson';
public static SAVEEXCEL_ID = 'grid.saveAsExcel';
public static SAVEEXCEL_LABEL = localize('saveAsExcel', 'Save As Excel');
public static SAVEEXCEL_ICON = 'saveExcel';
public static SAVEXML_ID = 'grid.saveAsXml';
public static SAVEXML_LABEL = localize('saveAsXml', 'Save As XML');
public static SAVEXML_ICON = 'saveXml';
constructor(
id: string,
label: string,
icon: string,
private format: SaveFormat,
private accountForNumberColumn = true
) {
super(id, label, icon);
}
public run(context: IGridActionContext): Promise<boolean> {
if (this.accountForNumberColumn) {
context.runner.serializeResults(context.batchId, context.resultId, this.format,
mapForNumberColumn(context.selection));
} else {
context.runner.serializeResults(context.batchId, context.resultId, this.format, context.selection);
}
return Promise.resolve(true);
}
}
export class CopyResultAction extends Action {
public static COPY_ID = 'grid.copySelection';
public static COPY_LABEL = localize('copySelection', 'Copy');
public static COPYWITHHEADERS_ID = 'grid.copyWithHeaders';
public static COPYWITHHEADERS_LABEL = localize('copyWithHeaders', 'Copy With Headers');
constructor(
id: string,
label: string,
private copyHeader: boolean,
private accountForNumberColumn = true
) {
super(id, label);
}
public run(context: IGridActionContext): Promise<boolean> {
if (this.accountForNumberColumn) {
context.runner.copyResults(
mapForNumberColumn(context.selection),
context.batchId, context.resultId, this.copyHeader);
} else {
context.runner.copyResults(context.selection, context.batchId, context.resultId, this.copyHeader);
}
return Promise.resolve(true);
}
}
export class SelectAllGridAction extends Action {
public static ID = 'grid.selectAll';
public static LABEL = localize('selectAll', 'Select All');
constructor() {
super(SelectAllGridAction.ID, SelectAllGridAction.LABEL);
}
public run(context: IGridActionContext): Promise<boolean> {
context.selectionModel.setSelectedRanges([new Slick.Range(0, 0, context.table.getData().getLength() - 1, context.table.columns.length - 1)]);
return Promise.resolve(true);
}
}
export class CopyMessagesAction extends Action {
public static ID = 'grid.messages.copy';
public static LABEL = localize('copyMessages', 'Copy');
constructor(
@IClipboardService private clipboardService: IClipboardService
) {
super(CopyMessagesAction.ID, CopyMessagesAction.LABEL);
}
public run(context: IMessagesActionContext): Promise<boolean> {
this.clipboardService.writeText(context.selection.toString());
return Promise.resolve(true);
}
}
const lineDelimiter = isWindows ? '\r\n' : '\n';
export class CopyAllMessagesAction extends Action {
public static ID = 'grid.messages.copyAll';
public static LABEL = localize('copyAll', "Copy All");
constructor(
private tree: ITree,
@IClipboardService private clipboardService: IClipboardService) {
super(CopyAllMessagesAction.ID, CopyAllMessagesAction.LABEL);
}
public run(): Promise<any> {
let text = '';
const navigator = this.tree.getNavigator();
// skip first navigator element - the root node
while (navigator.next()) {
if (text) {
text += lineDelimiter;
}
text += (navigator.current()).message;
}
this.clipboardService.writeText(removeAnsiEscapeCodes(text));
return Promise.resolve(null);
}
}
export class MaximizeTableAction extends Action {
public static ID = 'grid.maximize';
public static LABEL = localize('maximize', 'Maximize');
public static ICON = 'extendFullScreen';
constructor() {
super(MaximizeTableAction.ID, MaximizeTableAction.LABEL, MaximizeTableAction.ICON);
}
public run(context: IGridActionContext): Promise<boolean> {
context.tableState.maximized = true;
return Promise.resolve(true);
}
}
export class RestoreTableAction extends Action {
public static ID = 'grid.restore';
public static LABEL = localize('restore', 'Restore');
public static ICON = 'exitFullScreen';
constructor() {
super(RestoreTableAction.ID, RestoreTableAction.LABEL, RestoreTableAction.ICON);
}
public run(context: IGridActionContext): Promise<boolean> {
context.tableState.maximized = false;
return Promise.resolve(true);
}
}
export class ChartDataAction extends Action {
public static ID = 'grid.chart';
public static LABEL = localize('chart', 'Chart');
public static ICON = 'viewChart';
constructor(@IEditorService private editorService: IEditorService) {
super(ChartDataAction.ID, ChartDataAction.LABEL, ChartDataAction.ICON);
}
public run(context: IGridActionContext): Promise<boolean> {
let activeEditor = this.editorService.activeControl;
if (activeEditor instanceof QueryEditor) {
activeEditor.resultsEditor.chart({ batchId: context.batchId, resultId: context.resultId });
return Promise.resolve(true);
} else {
return Promise.resolve(false);
}
}
}

View File

@@ -0,0 +1,220 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/flavorStatus';
import { $, append, show, hide } from 'vs/base/browser/dom';
import { IDisposable, combinedDisposable } from 'vs/base/common/lifecycle';
import { IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar';
import { IEditorCloseEvent } from 'vs/workbench/common/editor';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Action } from 'vs/base/common/actions';
import errors = require('vs/base/common/errors');
import { getCodeEditor } from 'vs/editor/browser/editorBrowser';
import * as nls from 'vs/nls';
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
import * as WorkbenchUtils from 'sql/workbench/common/sqlWorkbenchUtils';
import { DidChangeLanguageFlavorParams } from 'azdata';
import Severity from 'vs/base/common/severity';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor';
import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
export interface ISqlProviderEntry extends IQuickPickItem {
providerId: string;
}
// Query execution status
class SqlProviderEntry implements ISqlProviderEntry {
constructor(public providerId: string, private _providerDisplayName?: string) {
}
public get label(): string {
// If display name is provided, use it. Else use default
if (this._providerDisplayName) {
return this._providerDisplayName;
}
if (!this.providerId) {
return SqlProviderEntry.getDefaultLabel();
}
// Note: consider adding API to connection management service to
// support getting display name for provider so this is consistent
switch (this.providerId) {
case 'MSSQL':
return 'MSSQL';
default:
return this.providerId;
}
}
public static getDefaultLabel(): string {
return nls.localize('chooseSqlLang', 'Choose SQL Language');
}
}
// Shows SQL flavor status in the editor
export class SqlFlavorStatusbarItem implements IStatusbarItem {
private _element: HTMLElement;
private _flavorElement: HTMLElement;
private _sqlStatusEditors: { [editorUri: string]: SqlProviderEntry };
private _toDispose: IDisposable[];
constructor(
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
@IEditorService private _editorService: EditorServiceImpl,
@IInstantiationService private _instantiationService: IInstantiationService,
) {
this._sqlStatusEditors = {};
}
public render(container: HTMLElement): IDisposable {
this._element = append(container, $('.query-statusbar-group'));
this._flavorElement = append(this._element, $('a.editor-status-selection'));
this._flavorElement.title = nls.localize('changeProvider', "Change SQL language provider");
this._flavorElement.onclick = () => this._onSelectionClick();
hide(this._flavorElement);
this._toDispose = [];
this._toDispose.push(
this._connectionManagementService.onLanguageFlavorChanged((changeParams: DidChangeLanguageFlavorParams) => this._onFlavorChanged(changeParams)),
this._editorService.onDidVisibleEditorsChange(() => this._onEditorsChanged()),
this._editorService.onDidCloseEditor(event => this._onEditorClosed(event))
);
return combinedDisposable(this._toDispose);
}
private _onSelectionClick() {
const action = this._instantiationService.createInstance(ChangeFlavorAction, ChangeFlavorAction.ID, ChangeFlavorAction.LABEL);
action.run().then(null, errors.onUnexpectedError);
action.dispose();
}
private _onEditorClosed(event: IEditorCloseEvent): void {
let uri = WorkbenchUtils.getEditorUri(event.editor);
if (uri && uri in this._sqlStatusEditors) {
// If active editor is being closed, hide the query status.
let activeEditor = this._editorService.activeControl;
if (activeEditor) {
let currentUri = WorkbenchUtils.getEditorUri(activeEditor.input);
if (uri === currentUri) {
hide(this._flavorElement);
}
}
// note: intentionally not removing language flavor. This is preserved across close/open events at present
// delete this._sqlStatusEditors[uri];
}
}
private _onEditorsChanged(): void {
let activeEditor = this._editorService.activeControl;
if (activeEditor) {
let uri = WorkbenchUtils.getEditorUri(activeEditor.input);
// Show active editor's language flavor status
if (uri) {
this._showStatus(uri);
} else {
hide(this._flavorElement);
}
} else {
hide(this._flavorElement);
}
}
private _onFlavorChanged(changeParams: DidChangeLanguageFlavorParams): void {
if (changeParams) {
this._updateStatus(changeParams.uri, new SqlProviderEntry(changeParams.flavor));
}
}
// Update query status for the editor
private _updateStatus(uri: string, newStatus: SqlProviderEntry): void {
if (uri) {
this._sqlStatusEditors[uri] = newStatus;
this._showStatus(uri);
}
}
// Show/hide query status for active editor
private _showStatus(uri: string): void {
let activeEditor = this._editorService.activeControl;
if (activeEditor) {
let currentUri = WorkbenchUtils.getEditorUri(activeEditor.input);
if (uri === currentUri) {
let flavor: SqlProviderEntry = this._sqlStatusEditors[uri];
if (flavor) {
this._flavorElement.textContent = flavor.label;
} else {
this._flavorElement.textContent = SqlProviderEntry.getDefaultLabel();
}
show(this._flavorElement);
}
}
}
}
export class ChangeFlavorAction extends Action {
public static ID = 'sql.action.editor.changeProvider';
public static LABEL = nls.localize('changeSqlProvider', "Change SQL Engine Provider");
constructor(
actionId: string,
actionLabel: string,
@IEditorService private _editorService: IEditorService,
@IQuickInputService private _quickInputService: IQuickInputService,
@INotificationService private _notificationService: INotificationService,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService
) {
super(actionId, actionLabel);
}
public run(): Promise<any> {
let activeEditor = this._editorService.activeControl;
let currentUri = WorkbenchUtils.getEditorUri(activeEditor.input);
if (this._connectionManagementService.isConnected(currentUri)) {
let currentProvider = this._connectionManagementService.getProviderIdFromUri(currentUri);
return this._showMessage(Severity.Info, nls.localize('alreadyConnected',
"A connection using engine {0} exists. To change please disconnect or change connection", currentProvider));
}
const editorWidget = getCodeEditor(activeEditor);
if (!editorWidget) {
return this._showMessage(Severity.Info, nls.localize('noEditor', "No text editor active at this time"));
}
// TODO #1334 use connectionManagementService.GetProviderNames here. The challenge is that the credentials provider is returned
// so we need a way to filter this using a capabilities check, with isn't yet implemented
const ProviderOptions: ISqlProviderEntry[] = [
new SqlProviderEntry('MSSQL')
];
// TODO: select the current language flavor
return this._quickInputService.pick(ProviderOptions, { placeHolder: nls.localize('pickSqlProvider', "Select SQL Language Provider") }).then(provider => {
if (provider) {
activeEditor = this._editorService.activeControl;
const editorWidget = getCodeEditor(activeEditor);
if (editorWidget) {
if (currentUri) {
this._connectionManagementService.doChangeLanguageFlavor(currentUri, 'sql', provider.providerId);
}
}
}
});
}
private _showMessage(sev: Severity, message: string): Promise<any> {
this._notificationService.notify({
severity: sev,
message: message
});
return Promise.resolve(undefined);
}
}

View File

@@ -0,0 +1,272 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Dimension } from 'vs/base/browser/dom';
import { Disposable } from 'vs/base/common/lifecycle';
import { Event, Emitter } from 'vs/base/common/event';
import {
IHorizontalSashLayoutProvider, IVerticalSashLayoutProvider,
ISashEvent, Orientation, Sash
} from 'vs/base/browser/ui/sash/sash';
// There is no need to import the sash CSS - 'vs/base/browser/ui/sash/sash' already includes it
/**
* Interface describing a sash that could be horizontal or vertical. This interface allows classes
* using the sash to have UI logic that is agnostic of the orientation of the sash.
*/
export interface IFlexibleSash {
// Get the value of the CSS property denoted by getMajorPosition()
getSplitPoint(): number;
// Sets the Dimension containing the height and width of the editor this sash will separate
setDimenesion(dimension: Dimension);
// Re-calculates the width and height of the sash
layout(): void;
// Hides the sash
hide(): void;
// Shows/unhides the sash
show(): void;
// Sets the top or left property of this sash
setEdge(edge: number);
// Fired when the position of this sash changes
onPositionChange: Event<number>;
}
/**
* A simple Vertical Sash that computes the position of the sash when it is moved between the given dimension.
* Triggers onPositionChange event when the position is changed. Implements IFlexibleSash to enable classes to be
* agnostic of the fact that this sash is vertical.
*/
export class VerticalFlexibleSash extends Disposable implements IVerticalSashLayoutProvider, IFlexibleSash {
private sash: Sash;
private ratio: number;
private startPosition: number;
private position: number;
private dimension: Dimension;
private top: number;
private _onPositionChange: Emitter<number> = new Emitter<number>();
public get onPositionChange(): Event<number> { return this._onPositionChange.event; }
constructor(container: HTMLElement, private minWidth: number) {
super();
this.ratio = 0.5;
this.top = 0;
this.sash = new Sash(container, this);
this._register(this.sash.onDidStart(() => this.onSashDragStart()));
this._register(this.sash.onDidChange((e: ISashEvent) => this.onSashDrag(e)));
this._register(this.sash.onDidEnd(() => this.onSashDragEnd()));
this._register(this.sash.onDidReset(() => this.onSashReset()));
}
public getSplitPoint(): number {
return this.getVerticalSashLeft();
}
public layout(): void {
this.sash.layout();
}
public show(): void {
this.sash.show();
}
public hide(): void {
this.sash.hide();
}
public getVerticalSashTop(): number {
return this.top;
}
public getVerticalSashLeft(): number {
return this.position;
}
public getVerticalSashHeight(): number {
return this.dimension.height;
}
public setDimenesion(dimension: Dimension) {
this.dimension = dimension;
this.compute(this.ratio);
}
public setEdge(edge: number) {
this.top = edge;
}
private onSashDragStart(): void {
this.startPosition = this.position;
}
private onSashDrag(e: ISashEvent): void {
this.compute((this.startPosition + (e.currentX - e.startX)) / this.dimension.width);
}
private compute(ratio: number) {
this.computeSashPosition(ratio);
this.ratio = this.position / this.dimension.width;
this._onPositionChange.fire(this.position);
}
private onSashDragEnd(): void {
this.sash.layout();
}
private onSashReset(): void {
this.ratio = 0.5;
this._onPositionChange.fire(this.position);
this.sash.layout();
}
private computeSashPosition(sashRatio: number = this.ratio) {
let contentWidth = this.dimension.width;
let sashPosition = Math.floor((sashRatio || 0.5) * contentWidth);
let midPoint = Math.floor(0.5 * contentWidth);
if (contentWidth > this.minWidth * 2) {
if (sashPosition < this.minWidth) {
sashPosition = this.minWidth;
}
if (sashPosition > contentWidth - this.minWidth) {
sashPosition = contentWidth - this.minWidth;
}
} else {
sashPosition = midPoint;
}
if (this.position !== sashPosition) {
this.position = sashPosition;
this.sash.layout();
}
}
}
/**
* A simple Horizontal Sash that computes the position of the sash when it is moved between the given dimension.
* Triggers onPositionChange event when the position is changed. Implements IFlexibleSash to enable classes to be
* agnostic of the fact that this sash is horizontal. Based off the VSash class.
*/
export class HorizontalFlexibleSash extends Disposable implements IHorizontalSashLayoutProvider, IFlexibleSash {
private static initialRatio: number = 0.4;
private sash: Sash;
private ratio: number;
private startPosition: number;
private position: number;
private dimension: Dimension;
private left: number;
private _onPositionChange: Emitter<number> = new Emitter<number>();
public get onPositionChange(): Event<number> { return this._onPositionChange.event; }
constructor(container: HTMLElement, private minHeight: number) {
super();
this.ratio = HorizontalFlexibleSash.initialRatio;
this.left = 0;
this.sash = new Sash(container, this, { orientation: Orientation.HORIZONTAL });
this._register(this.sash.onDidStart(() => this.onSashDragStart()));
this._register(this.sash.onDidChange((e: ISashEvent) => this.onSashDrag(e)));
this._register(this.sash.onDidEnd(() => this.onSashDragEnd()));
this._register(this.sash.onDidReset(() => this.onSashReset()));
}
public getSplitPoint(): number {
return this.getHorizontalSashTop();
}
public getHorizontalSashLeft(): number {
return this.left;
}
public getHorizontalSashTop(): number {
return this.position;
}
public layout(): void {
this.sash.layout();
}
public show(): void {
this.sash.show();
}
public hide(): void {
this.sash.hide();
}
public getHorizontalSashWidth?(): number {
return this.dimension.width;
}
public setDimenesion(dimension: Dimension) {
this.dimension = dimension;
this.compute(this.ratio);
}
public setEdge(edge: number) {
this.left = edge;
}
private onSashDragStart(): void {
this.startPosition = this.position;
}
private onSashDrag(e: ISashEvent): void {
this.compute((this.startPosition + (e.currentY - e.startY)) / this.dimension.height);
}
private compute(ratio: number) {
this.computeSashPosition(ratio);
this.ratio = this.position / this.dimension.height;
this._onPositionChange.fire(this.position);
}
private onSashDragEnd(): void {
this.sash.layout();
}
private onSashReset(): void {
this.ratio = HorizontalFlexibleSash.initialRatio;
this._onPositionChange.fire(this.position);
this.sash.layout();
}
/**
* Computes where the sash should be located and re-renders the sash.
*/
private computeSashPosition(sashRatio: number = this.ratio) {
let contentHeight = this.dimension.height;
let sashPosition = Math.floor((sashRatio || 0.5) * contentHeight);
let midPoint = Math.floor(0.5 * contentHeight);
if (contentHeight > this.minHeight * 4) {
if (sashPosition < this.minHeight) {
sashPosition = this.minHeight;
}
if (sashPosition > contentHeight - this.minHeight) {
sashPosition = contentHeight - this.minHeight;
}
} else {
sashPosition = midPoint;
}
if (this.position !== sashPosition) {
this.position = sashPosition;
this.sash.layout();
}
}
}

View File

@@ -0,0 +1,455 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import { Action } from 'vs/base/common/actions';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import * as azdata from 'azdata';
import { IQueryManagementService } from 'sql/platform/query/common/queryManagement';
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
import { QueryEditor } from 'sql/workbench/parts/query/browser/queryEditor';
import { IQueryModelService } from 'sql/platform/query/common/queryModel';
import * as WorkbenchUtils from 'sql/workbench/common/sqlWorkbenchUtils';
import * as Constants from 'sql/workbench/parts/query/common/constants';
import * as ConnectionConstants from 'sql/platform/connection/common/constants';
import { EditDataEditor } from 'sql/workbench/parts/editData/browser/editDataEditor';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
const singleQuote = '\'';
function isConnected(editor: QueryEditor, connectionManagementService: IConnectionManagementService): boolean {
if (!editor || !editor.currentQueryInput) {
return false;
}
return connectionManagementService.isConnected(editor.currentQueryInput.uri);
}
function runActionOnActiveQueryEditor(editorService: IEditorService, action: (QueryEditor) => void): void {
const candidates = [editorService.activeControl, ...editorService.visibleControls].filter(e => e instanceof QueryEditor);
if (candidates.length > 0) {
action(candidates[0]);
}
}
function escapeSqlString(input: string, escapeChar: string) {
if (!input) {
return input;
}
let output = '';
for (let i = 0; i < input.length; i++) {
let char = input.charAt(i);
output += char;
if (escapeChar === char) {
output += char;
}
}
return output;
}
/**
* Locates the active editor and call focus() on the editor if it is a QueryEditor.
*/
export class FocusOnCurrentQueryKeyboardAction extends Action {
public static ID = 'focusOnCurrentQueryKeyboardAction';
public static LABEL = nls.localize('focusOnCurrentQueryKeyboardAction', 'Focus on Current Query');
constructor(
id: string,
label: string,
@IEditorService private _editorService: IEditorService
) {
super(id, label);
this.enabled = true;
}
public run(): Promise<void> {
let editor = this._editorService.activeControl;
if (editor && editor instanceof QueryEditor) {
let queryEditor: QueryEditor = editor;
queryEditor.focus();
}
return Promise.resolve(null);
}
}
/**
* Locates the active editor and calls runQuery() on the editor if it is a QueryEditor.
*/
export class RunQueryKeyboardAction extends Action {
public static ID = 'runQueryKeyboardAction';
public static LABEL = nls.localize('runQueryKeyboardAction', 'Run Query');
constructor(
id: string,
label: string,
@IEditorService private _editorService: IEditorService
) {
super(id, label);
this.enabled = true;
}
public run(): Promise<void> {
let editor = this._editorService.activeControl;
if (editor && (editor instanceof QueryEditor || editor instanceof EditDataEditor)) {
let queryEditor: QueryEditor | EditDataEditor = editor;
queryEditor.runQuery();
}
return Promise.resolve(null);
}
}
/**
* Locates the active editor and calls runCurrentQuery() on the editor if it is a QueryEditor.
*/
export class RunCurrentQueryKeyboardAction extends Action {
public static ID = 'runCurrentQueryKeyboardAction';
public static LABEL = nls.localize('runCurrentQueryKeyboardAction', 'Run Current Query');
constructor(
id: string,
label: string,
@IEditorService private _editorService: IEditorService
) {
super(id, label);
this.enabled = true;
}
public run(): Promise<void> {
let editor = this._editorService.activeControl;
if (editor && editor instanceof QueryEditor) {
let queryEditor: QueryEditor = editor;
queryEditor.runCurrentQuery();
}
return Promise.resolve(null);
}
}
export class RunCurrentQueryWithActualPlanKeyboardAction extends Action {
public static ID = 'runCurrentQueryWithActualPlanKeyboardAction';
public static LABEL = nls.localize('runCurrentQueryWithActualPlanKeyboardAction', 'Run Current Query with Actual Plan');
constructor(
id: string,
label: string,
@IEditorService private _editorService: IEditorService
) {
super(id, label);
this.enabled = true;
}
public run(): Promise<void> {
let editor = this._editorService.activeControl;
if (editor && editor instanceof QueryEditor) {
let queryEditor: QueryEditor = editor;
queryEditor.runCurrentQueryWithActualPlan();
}
return Promise.resolve(null);
}
}
/**
* Locates the active editor and calls cancelQuery() on the editor if it is a QueryEditor.
*/
export class CancelQueryKeyboardAction extends Action {
public static ID = 'cancelQueryKeyboardAction';
public static LABEL = nls.localize('cancelQueryKeyboardAction', 'Cancel Query');
constructor(
id: string,
label: string,
@IEditorService private _editorService: IEditorService
) {
super(id, label);
this.enabled = true;
}
public run(): Promise<void> {
let editor = this._editorService.activeControl;
if (editor && (editor instanceof QueryEditor || editor instanceof EditDataEditor)) {
let queryEditor: QueryEditor | EditDataEditor = editor;
queryEditor.cancelQuery();
}
return Promise.resolve(null);
}
}
/**
* Refresh the IntelliSense cache
*/
export class RefreshIntellisenseKeyboardAction extends Action {
public static ID = 'refreshIntellisenseKeyboardAction';
public static LABEL = nls.localize('refreshIntellisenseKeyboardAction', 'Refresh IntelliSense Cache');
constructor(
id: string,
label: string,
@IEditorService private _editorService: IEditorService
) {
super(id, label);
this.enabled = true;
}
public run(): Promise<void> {
let editor = this._editorService.activeControl;
if (editor && editor instanceof QueryEditor) {
let queryEditor: QueryEditor = editor;
queryEditor.rebuildIntelliSenseCache();
}
return Promise.resolve(null);
}
}
/**
* Hide the query results
*/
export class ToggleQueryResultsKeyboardAction extends Action {
public static ID = 'toggleQueryResultsKeyboardAction';
public static LABEL = nls.localize('toggleQueryResultsKeyboardAction', 'Toggle Query Results');
constructor(
id: string,
label: string,
@IEditorService private _editorService: IEditorService
) {
super(id, label);
this.enabled = true;
}
public run(): Promise<void> {
let editor = this._editorService.activeControl;
if (editor && editor instanceof QueryEditor) {
let queryEditor: QueryEditor = editor;
queryEditor.toggleResultsEditorVisibility();
}
return Promise.resolve(null);
}
}
/**
* Action class that runs a query in the active SQL text document.
*/
export class RunQueryShortcutAction extends Action {
public static ID = 'runQueryShortcutAction';
constructor(
@IEditorService private _editorService: IEditorService,
@IQueryModelService protected _queryModelService: IQueryModelService,
@IQueryManagementService private _queryManagementService: IQueryManagementService,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
@IConfigurationService private _workspaceConfigurationService: IConfigurationService
) {
super(RunQueryShortcutAction.ID);
}
public run(index: number): Promise<void> {
let promise: Thenable<void> = Promise.resolve(null);
runActionOnActiveQueryEditor(this._editorService, (editor) => {
promise = this.runQueryShortcut(editor, index);
});
return new Promise((resolve, reject) => {
promise.then(success => resolve(null), err => resolve(null));
});
}
/**
* Runs one of the optionally registered query shortcuts. This will lookup the shortcut's stored procedure
* reference from the settings, and if found will execute it plus any
*
* @param shortcutIndex which shortcut should be run?
*/
public runQueryShortcut(editor: QueryEditor, shortcutIndex: number): Thenable<void> {
if (!editor) {
throw new Error(nls.localize('queryShortcutNoEditor', 'Editor parameter is required for a shortcut to be executed'));
}
if (isConnected(editor, this._connectionManagementService)) {
let shortcutText = this.getShortcutText(shortcutIndex);
if (!shortcutText.trim()) {
// no point going further
return Promise.resolve(null);
}
// if the selection isn't empty then execute the selection
// otherwise, either run the statement or the script depending on parameter
let parameterText: string = editor.getSelectionText();
return this.escapeStringParamIfNeeded(editor, shortcutText, parameterText).then((escapedParam) => {
let queryString = `${shortcutText} ${escapedParam}`;
editor.currentQueryInput.runQueryString(queryString);
}).then(success => null, err => {
// swallow errors for now
return null;
});
} else {
return Promise.resolve(null);
}
}
private getShortcutText(shortcutIndex: number) {
let shortcutSetting = Constants.shortcutStart + shortcutIndex;
let querySettings = WorkbenchUtils.getSqlConfigSection(this._workspaceConfigurationService, Constants.querySection);
let shortcutText = querySettings[shortcutSetting];
return shortcutText;
}
private escapeStringParamIfNeeded(editor: QueryEditor, shortcutText: string, parameterText: string): Thenable<string> {
if (parameterText && parameterText.length > 0) {
if (this.canQueryProcMetadata(editor)) {
let dbName = this.getDatabaseName(editor);
let query = `exec dbo.sp_sproc_columns @procedure_name = N'${escapeSqlString(shortcutText, singleQuote)}', @procedure_owner = null, @procedure_qualifier = N'${escapeSqlString(dbName, singleQuote)}'`;
return this._queryManagementService.runQueryAndReturn(editor.uri, query)
.then(result => {
switch (this.isProcWithSingleArgument(result)) {
case 1:
// sproc was found and it meets criteria of having 1 string param
// if selection is quoted, leave as-is. Else quote
let trimmedText = parameterText.trim();
if (trimmedText.length > 0) {
if (trimmedText.charAt(0) !== singleQuote || trimmedText.charAt(trimmedText.length - 1) !== singleQuote) {
// Note: SSMS uses the original text, but this causes issues if you have spaces. We intentionally use
// trimmed text since it's likely to be more accurate in this case. For non-quoted cases it shouldn't matter
return `'${trimmedText}'`;
}
}
break;
case -1:
// sproc was found but didn't meet criteria, so append as-is
case 0:
// sproc wasn't found, just append as-is and hope it works
break;
}
return parameterText;
}, err => {
return parameterText;
});
}
return Promise.resolve(parameterText);
}
return Promise.resolve('');
}
private isProcWithSingleArgument(result: azdata.SimpleExecuteResult): number {
let columnTypeOrdinal = this.getColumnIndex(result.columnInfo, 'COLUMN_TYPE');
let dataTypeOrdinal = this.getColumnIndex(result.columnInfo, 'DATA_TYPE');
if (columnTypeOrdinal && dataTypeOrdinal) {
let count = 0;
for (let row of result.rows) {
let columnType = parseInt(row[columnTypeOrdinal].displayValue);
if (columnType !== 5) {
if (count > 0) // more than one argument.
{
return -1;
}
let dataType = parseInt(row[dataTypeOrdinal].displayValue);
if (dataType === -9 || // nvarchar
dataType === 12 || // varchar
dataType === -8 || // nchar
dataType === 1 || // char
dataType === -1 || // text
dataType === -10 // ntext
) {
count++;
} else {
// not a string
return -1;
}
}
}
return count;
}
return -1; // Couldn't process so return default value
}
private getColumnIndex(columnInfo: azdata.IDbColumn[], columnName: string): number {
return columnInfo ? columnInfo.findIndex(c => c.columnName === columnName) : undefined;
}
private canQueryProcMetadata(editor: QueryEditor): boolean {
let info = this._connectionManagementService.getConnectionInfo(editor.uri);
return (info && info.providerId === ConnectionConstants.mssqlProviderName);
}
private getDatabaseName(editor: QueryEditor): string {
let info = this._connectionManagementService.getConnectionInfo(editor.uri);
return info.connectionProfile.databaseName;
}
}
/**
* Action class that parses the query string in the current SQL text document.
*/
export class ParseSyntaxAction extends Action {
public static ID = 'parseQueryAction';
public static LABEL = nls.localize('parseSyntaxLabel', 'Parse Query');
constructor(
id: string,
label: string,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
@IQueryManagementService private _queryManagementService: IQueryManagementService,
@IEditorService private _editorService: IEditorService,
@INotificationService private _notificationService: INotificationService
) {
super(id, label);
this.enabled = true;
}
public run(): Promise<void> {
let editor = this._editorService.activeControl;
if (editor && editor instanceof QueryEditor) {
let queryEditor: QueryEditor = editor;
if (!queryEditor.isSelectionEmpty()) {
if (this.isConnected(queryEditor)) {
let text = queryEditor.getSelectionText();
if (text === '') {
text = queryEditor.getAllText();
}
this._queryManagementService.parseSyntax(queryEditor.connectedUri, text).then(result => {
if (result && result.parseable) {
this._notificationService.notify({
severity: Severity.Info,
message: nls.localize('queryActions.parseSyntaxSuccess', 'Commands completed successfully')
});
} else if (result && result.errors.length > 0) {
let errorMessage = nls.localize('queryActions.parseSyntaxFailure', 'Command failed: ');
this._notificationService.error(`${errorMessage}${result.errors[0]}`);
}
});
} else {
this._notificationService.notify({
severity: Severity.Error,
message: nls.localize('queryActions.notConnected', 'Please connect to a server')
});
}
}
}
return Promise.resolve(null);
}
/**
* Returns the URI of the given editor if it is not undefined and is connected.
* Public for testing only.
*/
private isConnected(editor: QueryEditor): boolean {
if (!editor || !editor.currentQueryInput) {
return false;
}
return this._connectionManagementService.isConnected(editor.currentQueryInput.uri);
}
}

View File

@@ -0,0 +1,20 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-workbench .binarydiff-left {
float: left;
}
.monaco-workbench .binarydiff-right {
border-left: 3px solid #DDD;
}
.vs-dark .monaco-workbench .binarydiff-right {
border-left: 3px solid rgb(20, 20, 20);
}
.hc-black .monaco-workbench .binarydiff-right {
border-left: 3px solid #6FC3DF;
}

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" enable-background="new 0 0 16 16" height="16" width="16"><circle fill="#C5C5C5" cx="8" cy="8" r="4"/></svg>

After

Width:  |  Height:  |  Size: 167 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" enable-background="new 0 0 16 16" height="16" width="16"><circle fill="#424242" cx="8" cy="8" r="4"/></svg>

After

Width:  |  Height:  |  Size: 167 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="3 3 16 16" enable-background="new 3 3 16 16"><polygon fill="#e8e8e8" points="12.597,11.042 15.4,13.845 13.844,15.4 11.042,12.598 8.239,15.4 6.683,13.845 9.485,11.042 6.683,8.239 8.238,6.683 11.042,9.486 13.845,6.683 15.4,8.239"/></svg>

After

Width:  |  Height:  |  Size: 307 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="3 3 16 16" enable-background="new 3 3 16 16"><polygon fill="#424242" points="12.597,11.042 15.4,13.845 13.844,15.4 11.042,12.598 8.239,15.4 6.683,13.845 9.485,11.042 6.683,8.239 8.238,6.683 11.042,9.486 13.845,6.683 15.4,8.239"/></svg>

After

Width:  |  Height:  |  Size: 307 B

View File

@@ -0,0 +1,152 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.vs .monaco-workbench > .editor > .content.drag {
background-color: #ECECEC;
}
.vs-dark .monaco-workbench > .editor > .content.drag {
background-color: #2D2D2D;
}
.monaco-workbench > .editor > .content.dragging > .monaco-sash {
display: none; /* hide sashes while dragging editors around */
}
#monaco-workbench-editor-move-overlay,
#monaco-workbench-editor-drop-overlay {
position: absolute;
left: 0;
width: 100%;
z-index: 10000;
}
#monaco-workbench-editor-drop-overlay {
opacity: 0; /* initially not visible until moving around */
}
.vs #monaco-workbench-editor-drop-overlay,
.vs .monaco-workbench > .editor.empty > .content.dropfeedback {
background-color: rgba(51,153,255, 0.18);
}
.vs-dark #monaco-workbench-editor-drop-overlay,
.vs-dark .monaco-workbench > .editor.empty > .content.dropfeedback {
background-color: rgba(83, 89, 93, 0.5);
}
.hc-black #monaco-workbench-editor-drop-overlay,
.hc-black .monaco-workbench > .editor.empty > .content.dropfeedback {
background: none !important;
outline: 2px dashed #f38518;
outline-offset: -2px;
}
.monaco-workbench > .editor > .content > .one-editor-silo {
position: absolute;
box-sizing: border-box; /* use border box to be able to draw a border as separator between editors */
}
.monaco-workbench > .editor > .content > .one-editor-silo.editor-one {
left: 0;
top: 0;
}
.monaco-workbench > .editor > .content.vertical-layout > .one-editor-silo.editor-three {
right: 0;
}
.monaco-workbench > .editor > .content.horizontal-layout > .one-editor-silo.editor-three {
bottom: 0;
}
.monaco-workbench > .editor > .content > .one-editor-silo.dragging {
z-index: 70;
box-sizing: content-box;
}
.vs .monaco-workbench > .editor > .content.vertical-layout > .one-editor-silo.dragging {
border-left: 1px solid #E7E7E7;
border-right: 1px solid #E7E7E7;
}
.vs .monaco-workbench > .editor > .content.horizontal-layout > .one-editor-silo.dragging {
border-top: 1px solid #E7E7E7;
border-bottom: 1px solid #E7E7E7;
}
.vs .monaco-workbench > .editor > .content.vertical-layout > .one-editor-silo.editor-two,
.vs .monaco-workbench > .editor > .content.vertical-layout > .one-editor-silo.editor-three {
border-left: 1px solid #E7E7E7;
}
.vs .monaco-workbench > .editor > .content.horizontal-layout > .one-editor-silo.editor-two,
.vs .monaco-workbench > .editor > .content.horizontal-layout > .one-editor-silo.editor-three {
border-top: 1px solid #E7E7E7;
}
.vs-dark .monaco-workbench > .editor > .content.vertical-layout > .one-editor-silo.dragging {
border-left: 1px solid #444;
border-right: 1px solid #444;
}
.vs-dark .monaco-workbench > .editor > .content.horizontal-layout > .one-editor-silo.dragging {
border-top: 1px solid #444;
border-bottom: 1px solid #444;
}
.vs-dark .monaco-workbench > .editor > .content.vertical-layout > .one-editor-silo.editor-two,
.vs-dark .monaco-workbench > .editor > .content.vertical-layout > .one-editor-silo.editor-three {
border-left: 1px solid #444;
}
.vs-dark .monaco-workbench > .editor > .content.horizontal-layout > .one-editor-silo.editor-two,
.vs-dark .monaco-workbench > .editor > .content.horizontal-layout > .one-editor-silo.editor-three {
border-top: 1px solid #444;
}
.hc-black .monaco-workbench > .editor > .content.vertical-layout > .one-editor-silo.dragging {
border-left: 1px solid #6FC3DF;
border-right: 1px solid #6FC3DF;
}
.hc-black .monaco-workbench > .editor > .content.horizontal-layout > .one-editor-silo.dragging {
border-top: 1px solid #6FC3DF;
border-bottom: 1px solid #6FC3DF;
}
.hc-black .monaco-workbench > .editor > .content.vertical-layout > .one-editor-silo.editor-two,
.hc-black .monaco-workbench > .editor > .content.vertical-layout > .one-editor-silo.editor-three {
border-left: 1px solid #6FC3DF;
}
.hc-black .monaco-workbench > .editor > .content.horizontal-layout > .one-editor-silo.editor-two,
.hc-black .monaco-workbench > .editor > .content.horizontal-layout > .one-editor-silo.editor-three {
border-top: 1px solid #6FC3DF;
}
.monaco-workbench > .editor > .content.vertical-layout > .one-editor-silo.draggedunder {
transition: left 200ms ease-out;
}
.monaco-workbench > .editor > .content.vertical-layout > .editor-three.draggedunder {
transition-property: right;
}
.monaco-workbench > .editor > .content.horizontal-layout > .one-editor-silo.draggedunder {
transition: top 200ms ease-out;
}
.monaco-workbench > .editor > .content.horizontal-layout > .editor-three.draggedunder {
transition-property: bottom;
}
.monaco-workbench > .editor > .content > .one-editor-silo > .container {
height: 100%;
}
.monaco-workbench > .editor > .content > .one-editor-silo > .container > .editor-container {
height: calc(100% - 35px); /* Editor is below editor title */
}

View File

@@ -0,0 +1,41 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.vs .monaco-workbench .monaco-editor-background {
background-color: white;
}
.vs-dark .monaco-workbench .monaco-editor-background {
background-color: #1E1E1E;
}
.hc-black .monaco-workbench .monaco-editor-background {
background-color: #000;
}
.monaco-workbench .part.editor {
background-repeat: no-repeat;
background-position: 50% 50%;
}
.monaco-workbench .part.editor.empty {
background-image: url('letterpress.svg');
}
.vs-dark .monaco-workbench .part.editor.empty {
background-image: url('letterpress-dark.svg');
}
.hc-black .monaco-workbench .part.editor.empty {
background-image: url('letterpress-hc.svg');
}
@media
(-webkit-min-device-pixel-ratio: 2),
(min-resolution: 192dppx) {
.monaco-workbench .part.editor {
background-size: 260px 260px;
}
}

View File

@@ -0,0 +1,8 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-workbench .quick-open-widget .quick-open-tree .quick-open-entry.editor-preview {
font-style: italic;
}

View File

@@ -0,0 +1,26 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-workbench .editor-statusbar-item > a:not(:first-child) {
margin-left: 5px;
}
.monaco-workbench .editor-statusbar-item > .editor-status-mode,
.monaco-workbench .editor-statusbar-item > .editor-status-encoding,
.monaco-workbench .editor-statusbar-item > .editor-status-eol,
.monaco-workbench .editor-statusbar-item > .editor-status-selection,
.monaco-workbench .editor-statusbar-item > .editor-status-indentation,
.monaco-workbench .editor-statusbar-item > .editor-status-metadata {
padding: 0 5px 0 5px;
}
.monaco-workbench .editor-statusbar-item > .editor-status-metadata {
cursor: default;
}
.monaco-workbench .editor-statusbar-item > .editor-status-tabfocusmode {
padding: 0 5px 0 5px;
background-color: brown !important;
}

View File

@@ -0,0 +1,8 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-workbench .query-statusbar-group > .editor-status-selection {
padding: 0 5px 0 5px;
}

View File

@@ -0,0 +1 @@
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" width="260" height="260" viewBox="0 0 260 260"><style>.st0{opacity:.25}.st1{opacity:3e-2}.st2{fill:#fff}</style><path d="M194 2L92.4 103.6 27.6 53.2 2 66v128l25.6 12.8 64.8-50.4L194 258l64-25.6V27.6L194 2zM27 169V91l39 39-39 39zm99.4-39L194 77v106l-67.6-53z" class="st0"/><path class="st1 st2" d="M194 2l64 25.6v204.8L194 258 92.4 156.4l-64.8 50.4L2 194V66l25.6-12.8 64.8 50.4L194 2m0 181V77l-67.6 53 67.6 53M27 169l39-39-39-39v78M193.8.8l-.5.5-101 101-64.1-49.9-.5-.4-.6.3L1.6 65.1l-.6.3v129.2l.6.3 25.6 12.8.6.3.5-.4 64.1-49.9 101 101 .5.5.6-.2 64-25.6.6-.3V26.9l-.6-.3-64-25.6-.7-.2zM128 130l65-50.9V181l-65-51zM28 166.6V93.4L64.6 130 28 166.6z"/></svg>

After

Width:  |  Height:  |  Size: 709 B

View File

@@ -0,0 +1 @@
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" width="260" height="260" viewBox="0 0 260 260"><style>.st0{fill:#fff;fill-opacity:.13;enable-background:new}</style><path class="st0" d="M194 2L92.4 103.6 27.6 53.2 2 66v128l25.6 12.8 64.8-50.4L194 258l64-25.6V27.6L194 2zM27 169V91l39 39-39 39zm99.4-39L194 77v106l-67.6-53z"/></svg>

After

Width:  |  Height:  |  Size: 335 B

View File

@@ -0,0 +1 @@
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" width="260" height="260" viewBox="0 0 260 260"><style>.st0{opacity:.1}.st1{opacity:5e-2}.st2{fill:#231f20}</style><path d="M194 2L92.4 103.6 27.6 53.2 2 66v128l25.6 12.8 64.8-50.4L194 258l64-25.6V27.6L194 2zM27 169V91l39 39-39 39zm99.4-39L194 77v106l-67.6-53z" class="st0"/><path class="st1 st2" d="M194 2l64 25.6v204.8L194 258 92.4 156.4l-64.8 50.4L2 194V66l25.6-12.8 64.8 50.4L194 2m0 181V77l-67.6 53 67.6 53M27 169l39-39-39-39v78M193.8.8l-.5.5-101 101-64.1-49.9-.5-.4-.6.3L1.6 65.1l-.6.3v129.2l.6.3 25.6 12.8.6.3.5-.4 64.1-49.9 101 101 .5.5.6-.2 64-25.6.6-.3V26.9l-.6-.3-64-25.6-.7-.2zM128 130l65-50.9V181l-65-51zM28 166.6V93.4L64.6 130 28 166.6z"/></svg>

After

Width:  |  Height:  |  Size: 711 B

View File

@@ -0,0 +1,46 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/* Disable repl hover highlight in tree. */
.monaco-workbench .message-tree .monaco-tree .monaco-tree-rows > .monaco-tree-row:hover:not(.highlighted):not(.selected):not(.focused) {
background-color: inherit;
}
/* Disable repl hover highlight in tree. */
.monaco-workbench .message-tree .monaco-tree .monaco-tree-row > .content {
line-height: 18px;
user-select: text;
word-wrap: break-word;
/* white-space: pre-wrap; */
word-break: break-all;
}
.monaco-workbench .message-tree .monaco-tree .monaco-tree-rows>.monaco-tree-row {
cursor: default;
}
.message-tree .time-stamp {
width: 100px;
display: inline-block;
}
.message-tree .message,
.message-tree .batch-start,
.message-tree .error-message {
display: inline-block;
}
.message-tree .batch-start {
text-decoration: underline;
cursor: pointer;
}
.message-tree .error-message {
color: red;
}
.message-tree .batch-start:hover {
color: red;
}

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="-1 -3 16 16" enable-background="new -1 -3 16 16"><path fill="#C5C5C5" d="M1 4h7l-3-3h3l4 4-4 4h-3l3-3h-7v-2z"/></svg>

After

Width:  |  Height:  |  Size: 189 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="-1 -3 16 16" enable-background="new -1 -3 16 16"><path fill="#656565" d="M1 4h7l-3-3h3l4 4-4 4h-3l3-3h-7v-2z"/></svg>

After

Width:  |  Height:  |  Size: 189 B

View File

@@ -0,0 +1,47 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/* Title Label */
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .title-label {
line-height: 35px;
overflow: hidden;
text-overflow: ellipsis;
position: relative;
padding-left: 20px;
}
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .monaco-icon-label::before {
height: 35px; /* tweak the icon size of the editor labels when icons are enabled */
}
/* Title Actions */
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .title-actions {
display: flex;
flex: initial;
opacity: 0.5;
}
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.active .title-actions {
opacity: 1;
}
.vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.dirty .title-actions .close-editor-action {
background: url('close-dirty.svg') center center no-repeat;
}
.vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.dirty .title-actions .close-editor-action,
.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.dirty .title-actions .close-editor-action {
background: url('close-dirty-inverse.svg') center center no-repeat;
}
.vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.dirty .title-actions .close-editor-action:hover {
background: url('close.svg') center center no-repeat;
}
.vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.dirty .title-actions .close-editor-action:hover,
.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.dirty .title-actions .close-editor-action:hover {
background: url('close-inverse.svg') center center no-repeat;
}

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><title>ParseQuery_16x</title><rect width="16" height="16" fill="#f6f6f6" opacity="0"/><polygon points="4.382 15 0.382 7 5.618 7 6.5 8.764 10.382 1 15.618 1 8.618 15 4.382 15" fill="#f6f6f6"/><polygon points="11 2 6.5 11 5 8 2 8 5 14 8 14 14 2 11 2" fill="#424242"/></svg>

After

Width:  |  Height:  |  Size: 331 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="-1 -3 16 16" enable-background="new -1 -3 16 16"><polygon fill="#C5C5C5" points="13,4 6,4 9,1 6,1 2,5 6,9 9,9 6,6 13,6"/></svg>

After

Width:  |  Height:  |  Size: 199 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="-1 -3 16 16" enable-background="new -1 -3 16 16"><polygon fill="#656565" points="13,4 6,4 9,1 6,1 2,5 6,9 9,9 6,6 13,6"/></svg>

After

Width:  |  Height:  |  Size: 199 B

View File

@@ -0,0 +1,8 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.databaseListDropdown {
min-width: 150px;
}

View File

@@ -0,0 +1,29 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.vs-dark .side-by-side-editor > .master-editor-container {
box-shadow: -6px 0 5px -5px black;
}
.side-by-side-editor > .master-editor-container {
box-shadow: -6px 0 5px -5px #DDD;
}
.vs-dark .side-by-side-editor > .master-editor-container-horizontal {
box-shadow: 0 -6px 5px -5px black;
}
.side-by-side-editor > .master-editor-container-horizontal {
box-shadow: 0 -6px 5px -5px #DDD;
}
.editDataEditor {
height: inherit
}
#chartViewerDiv .chartViewer {
display: flex;
flex-direction: column;
}

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1,.cls-2{fill:#f6f6f6;}.cls-1{opacity:0;}.cls-3{fill:#388a34;}.cls-4{fill:#f0eff1;}</style></defs><title>StartWithoutDebug@2x</title><g id="Layer_2" data-name="Layer 2"><g id="outline"><rect class="cls-1" width="16" height="16"/><path class="cls-2" d="M3,0,13.67,8,3,16Z"/></g><g id="color_action"><path class="cls-3" d="M6,6,8.67,8,6,10V6M4,2V14l8-6L4,2Z"/></g><g id="icon_fg"><path class="cls-4" d="M8.67,8,6,10V6Z"/></g></g></svg>

After

Width:  |  Height:  |  Size: 511 B

View File

@@ -0,0 +1,12 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.vs-dark .side-by-side-editor > .master-editor-container {
box-shadow: -6px 0 5px -5px black;
}
.side-by-side-editor > .master-editor-container {
box-shadow: -6px 0 5px -5px #DDD;
}

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#2d2d30}.icon-vs-out{fill:#2d2d30}.icon-vs-bg{fill:#c5c5c5}.icon-vs-fg{fill:#2b282e}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M16 15H0V1h16v14z" id="outline" style="display: none;"/><path class="icon-vs-bg" d="M1 2v12h14V2H1zm13 11H2v-3h12v3zm0-5H2V5h12v3z" id="iconBg"/><g id="iconFg" style="display: none;"><path class="icon-vs-fg" d="M14 8H2V5h12v3zm0 2H2v3h12v-3z"/></g></svg>

After

Width:  |  Height:  |  Size: 578 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#f6f6f6}.icon-vs-out{fill:#f6f6f6}.icon-vs-bg{fill:#424242}.icon-vs-fg{fill:#f0eff1}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M16 15H0V1h16v14z" id="outline" style="display: none;"/><path class="icon-vs-bg" d="M1 2v12h14V2H1zm13 11H2v-3h12v3zm0-5H2V5h12v3z" id="iconBg"/><g id="iconFg" style="display: none;"><path class="icon-vs-fg" d="M14 8H2V5h12v3zm0 2H2v3h12v-3z"/></g></svg>

After

Width:  |  Height:  |  Size: 578 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 -1 16 16" enable-background="new 0 -1 16 16"><path fill="#C5C5C5" d="M1 1v12h14v-12h-14zm1 3h4.999v8h-4.999v-8zm12 8h-5.001v-8h5.001v8z"/></svg>

After

Width:  |  Height:  |  Size: 218 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 -1 16 16" enable-background="new 0 -1 16 16"><path fill="#656565" d="M1 1v12h14v-12h-14zm1 3h4.999v8h-4.999v-8zm12 8h-5.001v-8h5.001v8z"/></svg>

After

Width:  |  Height:  |  Size: 218 B

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
<style type="text/css">
.st0{fill:#C5C5C5;}
</style>
<g id="outline">
</g>
<g id="icon_x5F_bg">
<path class="st0" d="M7.6,5H3c0,0-1,0-1,1c0,0.8,0,5.4,0,8c0,1,1,1,1,1s1.5,0,3,0s3,0,3,0s1,0,1-1c0-2.6,0-6.9,0-6.9L7.6,5z M9,14
H3V6h4v2h2V14z"/>
<path class="st0" d="M9.6,3H5c0,0-1,0-1,1h5v0.9L10.2,6H11v7c1,0,1-1,1-1V5.1L9.6,3z"/>
<path class="st0" d="M11.6,1H7c0,0-1,0-1,1h5v0.9L12.2,4H13v7c1,0,1-1,1-1V3.1L11.6,1z"/>
</g>
<g id="color_x5F_action">
</g>
<g id="icon_x5F_fg">
</g>
</svg>

After

Width:  |  Height:  |  Size: 822 B

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
<style type="text/css">
.st0{fill:#656565;}
</style>
<g id="outline">
</g>
<g id="icon_x5F_bg">
<path class="st0" d="M7.6,5H3c0,0-1,0-1,1c0,0.8,0,5.4,0,8c0,1,1,1,1,1s1.5,0,3,0s3,0,3,0s1,0,1-1c0-2.6,0-6.9,0-6.9L7.6,5z M9,14
H3V6h4v2h2V14z"/>
<path class="st0" d="M9.6,3H5c0,0-1,0-1,1h5v0.9L10.2,6H11v7c1,0,1-1,1-1V5.1L9.6,3z"/>
<path class="st0" d="M11.6,1H7c0,0-1,0-1,1h5v0.9L12.2,4H13v7c1,0,1-1,1-1V3.1L11.6,1z"/>
</g>
<g id="color_x5F_action">
</g>
<g id="icon_x5F_fg">
</g>
</svg>

After

Width:  |  Height:  |  Size: 822 B

View File

@@ -0,0 +1,220 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/* Title Container */
.vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.tabs {
background: #F3F3F3;
}
.vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.tabs {
background: #252526;
}
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.tabs > .monaco-scrollable-element {
flex: 1;
}
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.tabs > .monaco-scrollable-element .scrollbar {
z-index: 3; /* on top of tabs */
cursor: default;
}
/* Tabs Container */
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container {
display: flex;
height: 35px;
}
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container.scroll {
overflow: scroll !important;
}
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container::-webkit-scrollbar {
display: none;
}
/* Tab */
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab {
display: flex;
width: 120px;
min-width: fit-content;
white-space: nowrap;
cursor: pointer;
height: 35px;
box-sizing: border-box;
border: 1px solid transparent;
padding-left: 10px;
}
.vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab:not(.active) {
background-color: #ECECEC;
}
.vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab:not(.active) {
background-color: #2D2D2D;
}
.vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab {
border-left-color: #F3F3F3;
}
.vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.active:last-child {
border-right-color: #F3F3F3;
}
.vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab {
border-left-color: #252526;
}
.vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.active:last-child {
border-right-color: #252526;
}
.vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab:first-child,
.vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab:first-child {
border-left-color: transparent;
}
.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab {
border-left-color: #6FC3DF;
}
.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.active {
outline: 2px solid #f38518;
outline-offset: -1px;
}
.vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container.dropfeedback,
.vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dropfeedback {
background-color: #DDECFF;
}
.vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container.dropfeedback,
.vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dropfeedback {
background-color: #383B3D;
}
.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container.dropfeedback,
.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dropfeedback {
background: none !important;
outline: 2px dashed #f38518;
outline-offset: -2px;
}
/* Tab Label */
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab .tab-label {
margin-top: auto;
margin-bottom: auto;
}
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab .monaco-icon-label::before {
height: 16px; /* tweak the icon size of the editor labels when icons are enabled */
}
.vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab .tab-label {
opacity: 0.7 !important;
}
.vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab .tab-label {
opacity: 0.5 !important;
}
.vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.active .tab-label,
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dropfeedback .tab-label {
opacity: 1 !important;
}
.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab .tab-label {
opacity: 1 !important;
}
/* Tab Close */
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab > .tab-close {
margin-top: auto;
margin-bottom: auto;
width: 28px;
}
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.no-close-button > .tab-close {
display: none; /* hide the close action bar when we are configured to hide it */
}
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.active .tabs-container > .tab.active > .tab-close .action-label, /* always show it for active tab */
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.active .tabs-container > .tab > .tab-close .action-label:focus, /* always show it on focus */
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.active .tabs-container > .tab:hover > .tab-close .action-label, /* always show it on hover */
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.active .tabs-container > .tab.active:hover > .tab-close .action-label, /* always show it on hover */
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.active .tabs-container > .tab.dirty > .tab-close .action-label { /* always show it for dirty tabs */
opacity: 1;
}
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.active > .tab-close .action-label, /* show dimmed for inactive group */
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.active:hover > .tab-close .action-label, /* show dimmed for inactive group */
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dirty > .tab-close .action-label, /* show dimmed for inactive group */
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab:hover > .tab-close .action-label { /* show dimmed for inactive group */
opacity: 0.5;
}
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab > .tab-close .action-label {
opacity: 0;
display: block;
height: 16px;
width: 16px;
background-size: 16px;
background-position: center center;
background-repeat: no-repeat;
margin-right: 0.5em;
}
.vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dirty .close-editor-action {
background: url('close-dirty.svg') center center no-repeat;
}
.vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dirty .close-editor-action,
.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dirty .close-editor-action {
background: url('close-dirty-inverse.svg') center center no-repeat;
}
.vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dirty .close-editor-action:hover {
background: url('close.svg') center center no-repeat;
}
.vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dirty .close-editor-action:hover,
.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.dirty .close-editor-action:hover {
background: url('close-inverse.svg') center center no-repeat;
}
/* No Tab Close Button */
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.no-close-button {
padding-right: 28px; /* make room for dirty indication when we are running without close button */
}
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.no-close-button.dirty {
background-repeat: no-repeat;
background-position-y: center;
background-position-x: calc(100% - 6px); /* to the right of the tab label */
}
.vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.no-close-button.dirty {
background-image: url('close-dirty.svg');
}
.vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.no-close-button.dirty,
.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.no-close-button.dirty {
background-image: url('close-dirty-inverse.svg');
}
/* Editor Actions */
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .editor-actions {
cursor: default;
flex: initial;
padding-left: 4px;
}

View File

@@ -0,0 +1,22 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.vs .monaco-workbench .textdiff-editor-action.next {
background: url('next-diff.svg') center center no-repeat;
}
.vs .monaco-workbench .textdiff-editor-action.previous {
background: url('previous-diff.svg') center center no-repeat;
}
.vs-dark .monaco-workbench .textdiff-editor-action.next,
.hc-black .monaco-workbench .textdiff-editor-action.next {
background: url('next-diff-inverse.svg') center center no-repeat;
}
.vs-dark .monaco-workbench .textdiff-editor-action.previous,
.hc-black .monaco-workbench .textdiff-editor-action.previous {
background: url('previous-diff-inverse.svg') center center no-repeat;
}

View File

@@ -0,0 +1,127 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/* Editor Label */
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .title-label,
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab .tab-label {
white-space: nowrap;
flex: 1;
}
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .title-label a,
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab .tab-label a {
text-decoration: none;
font-size: 13px;
}
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .monaco-icon-label::before,
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab .monaco-icon-label::before,
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .title-label a,
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab .tab-label a,
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .title-label span,
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab .tab-label span {
cursor: pointer;
}
.vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .title-label a,
.vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab .tab-label a {
color: rgba(51, 51, 51, 0.5);
}
.vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.active .title-label a,
.vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.active .tabs-container > .tab .tab-label a {
color: #333333;
}
.vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .title-label a,
.vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab .tab-label a {
color: rgba(255, 255, 255, 0.5);
}
.vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.active .title-label a,
.vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.active .tabs-container > .tab .tab-label a {
color: white;
}
/* Title Actions */
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .title-actions .action-label,
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .editor-actions .action-label {
display: block;
height: 35px;
line-height: 35px;
min-width: 28px;
background-size: 16px;
background-position: center center;
background-repeat: no-repeat;
}
.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .title-actions .action-label,
.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .editor-actions .action-label {
line-height: initial;
}
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .editor-actions .action-label .label,
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .title-actions .action-label .label {
display: none;
}
/* Drag Cursor */
.monaco-workbench > .part.editor > .content.multiple-editors > .one-editor-silo > .container > .title,
.monaco-workbench > .part.editor > .content.multiple-editors > .one-editor-silo > .container > .title.tabs .scrollbar .slider,
.monaco-workbench > .part.editor > .content.multiple-editors > .one-editor-silo > .container > .title .monaco-icon-label::before,
.monaco-workbench > .part.editor > .content.multiple-editors > .one-editor-silo > .container > .title .title-label a,
.monaco-workbench > .part.editor > .content.multiple-editors > .one-editor-silo > .container > .title .title-label span {
cursor: -webkit-grab;
}
#monaco-workbench-editor-move-overlay,
.monaco-workbench > .part.editor > .content.multiple-editors > .one-editor-silo.drag,
.monaco-workbench > .part.editor > .content.multiple-editors > .one-editor-silo.drag > .container > .title,
.monaco-workbench > .part.editor > .content.multiple-editors > .one-editor-silo.drag > .container > .title.tabs .scrollbar .slider,
.monaco-workbench > .part.editor > .content.multiple-editors > .one-editor-silo.drag > .container > .title .monaco-icon-label::before,
.monaco-workbench > .part.editor > .content.multiple-editors > .one-editor-silo.drag > .container > .title .title-label a,
.monaco-workbench > .part.editor > .content.multiple-editors > .one-editor-silo.drag > .container > .title .title-label span {
cursor: -webkit-grabbing;
}
/* Actions */
.monaco-workbench .close-editor-action {
background: url('close.svg') center center no-repeat;
}
.vs-dark .monaco-workbench .close-editor-action,
.hc-black .monaco-workbench .close-editor-action {
background: url('close-inverse.svg') center center no-repeat;
}
.monaco-workbench > .part.editor > .content.vertical-layout > .one-editor-silo > .container > .title .split-editor-action {
background: url('split-editor-vertical.svg') center center no-repeat;
}
.vs-dark .monaco-workbench > .part.editor > .content.vertical-layout > .one-editor-silo > .container > .title .split-editor-action,
.hc-black .monaco-workbench > .part.editor > .content.vertical-layout > .one-editor-silo > .container > .title .split-editor-action {
background: url('split-editor-vertical-inverse.svg') center center no-repeat;
}
.monaco-workbench > .part.editor > .content.horizontal-layout > .one-editor-silo > .container > .title .split-editor-action {
background: url('split-editor-horizontal.svg') center center no-repeat;
}
.vs-dark .monaco-workbench > .part.editor > .content.horizontal-layout > .one-editor-silo > .container > .title .split-editor-action,
.hc-black .monaco-workbench > .part.editor > .content.horizontal-layout > .one-editor-silo > .container > .title .split-editor-action {
background: url('split-editor-horizontal-inverse.svg') center center no-repeat;
}
.monaco-workbench .show-group-editors-action {
background: url('stackview.svg') center center no-repeat;
}
.vs-dark .monaco-workbench .show-group-editors-action,
.hc-black .monaco-workbench .show-group-editors-action {
background: url('stackview-inverse.svg') center center no-repeat;
}

View File

@@ -0,0 +1,428 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/messagePanel';
import { IMessagesActionContext, CopyMessagesAction, CopyAllMessagesAction } from './actions';
import QueryRunner from 'sql/platform/query/common/queryRunner';
import { QueryInput } from 'sql/workbench/parts/query/common/queryInput';
import { IExpandableTree } from 'sql/workbench/parts/objectExplorer/browser/treeUpdateUtils';
import { IResultMessage, ISelectionData } from 'azdata';
import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet';
import { IDataSource, ITree, IRenderer, ContextMenuEvent } from 'vs/base/parts/tree/browser/tree';
import { generateUuid } from 'vs/base/common/uuid';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
import { attachListStyler } from 'vs/platform/theme/common/styler';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { OpenMode, ClickBehavior, ICancelableEvent, IControllerOptions } from 'vs/base/parts/tree/browser/treeDefaults';
import { WorkbenchTreeController } from 'vs/platform/list/browser/listService';
import { IMouseEvent } from 'vs/base/browser/mouseEvent';
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 { ScrollbarVisibility } from 'vs/base/common/scrollable';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { $ } from 'vs/base/browser/dom';
export interface IResultMessageIntern extends IResultMessage {
id?: string;
}
export interface IMessagePanelMessage {
message: string;
isError: boolean;
}
export interface IMessagePanelBatchMessage extends IMessagePanelMessage {
selection: ISelectionData;
time: string;
}
interface IMessageTemplate {
message: HTMLElement;
}
interface IBatchTemplate extends IMessageTemplate {
timeStamp: HTMLElement;
}
const TemplateIds = {
MESSAGE: 'message',
BATCH: 'batch',
MODEL: 'model',
ERROR: 'error'
};
export class MessagePanelState {
public scrollPosition: number;
public collapsed = false;
constructor(@IConfigurationService configurationService: IConfigurationService) {
let messagesOpenedSettings = configurationService.getValue<boolean>('sql.messagesDefaultOpen');
if (!isUndefinedOrNull(messagesOpenedSettings)) {
this.collapsed = !messagesOpenedSettings;
}
}
dispose() {
}
}
export class MessagePanel extends ViewletPanel {
private messageLineCountMap = new Map<IResultMessage, number>();
private ds = new MessageDataSource();
private renderer = new MessageRenderer(this.messageLineCountMap);
private model = new Model();
private controller: MessageController;
private container = $('.message-tree');
private queryRunnerDisposables: IDisposable[] = [];
private _state: MessagePanelState;
private tree: ITree;
constructor(
options: IViewletPanelOptions,
@IKeybindingService keybindingService: IKeybindingService,
@IContextMenuService contextMenuService: IContextMenuService,
@IConfigurationService configurationService: IConfigurationService,
@IThemeService private themeService: IThemeService,
@IInstantiationService instantiationService: IInstantiationService,
@IClipboardService private clipboardService: IClipboardService
) {
super(options, keybindingService, contextMenuService, configurationService);
this.controller = instantiationService.createInstance(MessageController, { openMode: OpenMode.SINGLE_CLICK, clickBehavior: ClickBehavior.ON_MOUSE_UP /* do not change, to preserve focus behaviour in input field */ });
this.controller.toFocusOnClick = this.model;
this.tree = new Tree(this.container, {
dataSource: this.ds,
renderer: this.renderer,
controller: this.controller
}, { keyboardSupport: false, horizontalScrollMode: ScrollbarVisibility.Auto });
this.disposables.push(this.tree);
this.tree.onDidScroll(e => {
// convert to old VS Code tree interface with expandable methods
let expandableTree: IExpandableTree = <IExpandableTree>this.tree;
if (this.state) {
this.state.scrollPosition = expandableTree.getScrollPosition();
}
});
this.onDidChange(e => {
if (this.state) {
this.state.collapsed = !this.isExpanded();
}
});
this.controller.onKeyDown = (tree, event) => {
if (event.ctrlKey) {
let context: IMessagesActionContext = {
selection: document.getSelection(),
tree: this.tree,
};
// Ctrl + C for copy
if (event.code === 'KeyC') {
let copyMessageAction = instantiationService.createInstance(CopyMessagesAction, this.clipboardService);
copyMessageAction.run(context);
}
}
event.preventDefault();
event.stopPropagation();
return true;
};
this.controller.onContextMenu = (tree, element, event) => {
if (event.target && event.target.tagName && event.target.tagName.toLowerCase() === 'input') {
return false; // allow context menu on input fields
}
// Prevent native context menu from showing up
if (event) {
event.preventDefault();
event.stopPropagation();
}
const selection = document.getSelection();
this.contextMenuService.showContextMenu({
getAnchor: () => {
return { x: event.posx, y: event.posy };
},
getActions: () => {
return [
instantiationService.createInstance(CopyMessagesAction, this.clipboardService),
instantiationService.createInstance(CopyAllMessagesAction, this.tree, this.clipboardService)
];
},
getActionsContext: () => {
return <IMessagesActionContext>{
selection,
tree
};
}
});
return true;
};
}
protected renderBody(container: HTMLElement): void {
this.container.style.width = '100%';
this.container.style.height = '100%';
this.disposables.push(attachListStyler(this.tree, this.themeService));
container.appendChild(this.container);
this.tree.setInput(this.model);
}
protected layoutBody(size: number): void {
// convert to old VS Code tree interface with expandable methods
let expandableTree: IExpandableTree = <IExpandableTree>this.tree;
const previousScrollPosition = expandableTree.getScrollPosition();
this.tree.layout(size);
if (this.state && this.state.scrollPosition) {
expandableTree.setScrollPosition(this.state.scrollPosition);
} else {
if (previousScrollPosition === 1) {
expandableTree.setScrollPosition(1);
}
}
}
public set queryRunner(runner: QueryRunner) {
dispose(this.queryRunnerDisposables);
this.queryRunnerDisposables = [];
this.reset();
this.queryRunnerDisposables.push(runner.onQueryStart(() => this.reset()));
this.queryRunnerDisposables.push(runner.onMessage(e => this.onMessage(e)));
this.onMessage(runner.messages);
}
private onMessage(message: IResultMessage | IResultMessage[]) {
let hasError = false;
let lines: number;
if (isArray(message)) {
hasError = message.find(e => e.isError) ? true : false;
lines = message.reduce((currentTotal, resultMessage) => currentTotal + this.countMessageLines(resultMessage), 0);
this.model.messages.push(...message);
} else {
hasError = message.isError;
lines = this.countMessageLines(message);
this.model.messages.push(message);
}
this.maximumBodySize += lines * 22;
if (hasError) {
this.setExpanded(true);
}
// convert to old VS Code tree interface with expandable methods
let expandableTree: IExpandableTree = <IExpandableTree>this.tree;
if (this.state.scrollPosition) {
this.tree.refresh(this.model).then(() => {
// Restore the previous scroll position when switching between tabs
expandableTree.setScrollPosition(this.state.scrollPosition);
});
} else {
const previousScrollPosition = expandableTree.getScrollPosition();
this.tree.refresh(this.model).then(() => {
// Scroll to the end if the user was already at the end otherwise leave the current scroll position
if (previousScrollPosition === 1) {
expandableTree.setScrollPosition(1);
}
});
}
}
private countMessageLines(resultMessage: IResultMessage): number {
let lines = resultMessage.message.split('\n').length;
this.messageLineCountMap.set(resultMessage, lines);
return lines;
}
private reset() {
this.model.messages = [];
this.model.totalExecuteMessage = undefined;
this.tree.refresh(this.model);
}
public set state(val: MessagePanelState) {
this._state = val;
// convert to old VS Code tree interface with expandable methods
let expandableTree: IExpandableTree = <IExpandableTree>this.tree;
if (this.state.scrollPosition) {
expandableTree.setScrollPosition(this.state.scrollPosition);
}
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 {
getId(tree: ITree, element: Model | IResultMessageIntern): string {
if (element instanceof Model) {
return element.uuid;
} else {
if (!element.id) {
element.id = generateUuid();
}
return element.id;
}
}
hasChildren(tree: ITree, element: any): boolean {
return element instanceof Model;
}
getChildren(tree: ITree, element: any): Promise<(IMessagePanelMessage | IMessagePanelBatchMessage)[]> {
if (element instanceof Model) {
let messages = element.messages;
if (element.totalExecuteMessage) {
messages = messages.concat(element.totalExecuteMessage);
}
return Promise.resolve(messages);
} else {
return Promise.resolve(undefined);
}
}
getParent(tree: ITree, element: any): Promise<void> {
return Promise.resolve(null);
}
}
class MessageRenderer implements IRenderer {
constructor(private messageLineCountMap: Map<IResultMessage, number>) {
}
getHeight(tree: ITree, element: any): number {
const lineHeight = 22;
if (this.messageLineCountMap.has(element)) {
return lineHeight * this.messageLineCountMap.get(element);
}
return lineHeight;
}
getTemplateId(tree: ITree, element: any): string {
if (element instanceof Model) {
return TemplateIds.MODEL;
} else if (element.selection) {
return TemplateIds.BATCH;
} else if (element.isError) {
return TemplateIds.ERROR;
} else {
return TemplateIds.MESSAGE;
}
}
renderTemplate(tree: ITree, templateId: string, container: HTMLElement): IMessageTemplate | IBatchTemplate {
if (templateId === TemplateIds.MESSAGE) {
container.append($('.time-stamp'));
const message = $('.message');
message.style.whiteSpace = 'pre';
container.append(message);
return { message };
} else if (templateId === TemplateIds.BATCH) {
const timeStamp = $('.time-stamp');
container.append(timeStamp);
const message = $('.batch-start');
message.style.whiteSpace = 'pre';
container.append(message);
return { message, timeStamp };
} else if (templateId === TemplateIds.ERROR) {
container.append($('.time-stamp'));
const message = $('.error-message');
container.append(message);
return { message };
} else {
return undefined;
}
}
renderElement(tree: ITree, element: IResultMessage, templateId: string, templateData: IMessageTemplate | IBatchTemplate): void {
if (templateId === TemplateIds.MESSAGE || templateId === TemplateIds.ERROR) {
let data: IMessageTemplate = templateData;
data.message.innerText = element.message;
} else if (templateId === TemplateIds.BATCH) {
let data = templateData as IBatchTemplate;
data.timeStamp.innerText = element.time;
data.message.innerText = element.message;
}
}
disposeTemplate(tree: ITree, templateId: string, templateData: any): void {
}
}
export class MessageController extends WorkbenchTreeController {
private lastSelectedString: string = null;
public toFocusOnClick: { focus(): void };
constructor(
options: IControllerOptions,
@IConfigurationService configurationService: IConfigurationService,
@IEditorService private workbenchEditorService: IEditorService,
@IContextMenuService private contextMenuService: IContextMenuService,
@IInstantiationService private instantiationService: IInstantiationService
) {
super(options, configurationService);
}
protected onLeftClick(tree: ITree, element: any, eventish: ICancelableEvent, origin: string = 'mouse'): boolean {
const mouseEvent = <IMouseEvent>eventish;
// input and output are one element in the tree => we only expand if the user clicked on the output.
// if ((element.reference > 0 || (element instanceof RawObjectReplElement && element.hasChildren)) && mouseEvent.target.className.indexOf('input expression') === -1) {
super.onLeftClick(tree, element, eventish, origin);
tree.clearFocus();
tree.deselect(element);
// }
const selection = window.getSelection();
if (selection.type !== 'Range' || this.lastSelectedString === selection.toString()) {
// only focus the input if the user is not currently selecting.
this.toFocusOnClick.focus();
}
this.lastSelectedString = selection.toString();
if (element.selection) {
let selection: ISelectionData = element.selection;
// this is a batch statement
let input = this.workbenchEditorService.activeEditor as QueryInput;
input.updateSelection(selection);
}
return true;
}
public onContextMenu(tree: ITree, element: any, event: ContextMenuEvent): boolean {
return true;
}
}
export class Model {
public messages: Array<IMessagePanelMessage | IMessagePanelBatchMessage> = [];
public totalExecuteMessage: IMessagePanelMessage;
public uuid = generateUuid();
public focus() {
}
}

View File

@@ -0,0 +1,449 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!sql/media/overwriteVsIcons';
import { Registry } from 'vs/platform/registry/common/platform';
import { EditorDescriptor, IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/browser/editor';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions';
import { IConfigurationRegistry, Extensions as ConfigExtensions } from 'vs/platform/configuration/common/configurationRegistry';
import { SyncActionDescriptor, MenuId, MenuRegistry } from 'vs/platform/actions/common/actions';
import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes';
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { ContextKeyExpr, ContextKeyEqualsExpr } from 'vs/platform/contextkey/common/contextkey';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { isMacintosh } from 'vs/base/common/platform';
import { QueryEditor } from 'sql/workbench/parts/query/browser/queryEditor';
import { QueryResultsEditor } from 'sql/workbench/parts/query/browser/queryResultsEditor';
import { QueryResultsInput } from 'sql/workbench/parts/query/common/queryResultsInput';
import * as queryContext from 'sql/workbench/parts/query/common/queryContext';
import { QueryInput } from 'sql/workbench/parts/query/common/queryInput';
import { EditDataEditor } from 'sql/workbench/parts/editData/browser/editDataEditor';
import { EditDataInput } from 'sql/workbench/parts/editData/common/editDataInput';
import {
RunQueryKeyboardAction, RunCurrentQueryKeyboardAction, CancelQueryKeyboardAction, RefreshIntellisenseKeyboardAction, ToggleQueryResultsKeyboardAction,
RunQueryShortcutAction, RunCurrentQueryWithActualPlanKeyboardAction, FocusOnCurrentQueryKeyboardAction, ParseSyntaxAction
} from 'sql/workbench/parts/query/browser/keyboardQueryActions';
import * as gridActions from 'sql/workbench/parts/grid/views/gridActions';
import * as gridCommands from 'sql/workbench/parts/grid/views/gridCommands';
import { QueryPlanEditor } from 'sql/workbench/parts/queryPlan/electron-browser/queryPlanEditor';
import { QueryPlanInput } from 'sql/workbench/parts/queryPlan/common/queryPlanInput';
import * as Constants from 'sql/workbench/parts/query/common/constants';
import { localize } from 'vs/nls';
import { EditDataResultsEditor } from 'sql/workbench/parts/editData/browser/editDataResultsEditor';
import { EditDataResultsInput } from 'sql/workbench/parts/editData/common/editDataResultsInput';
const gridCommandsWeightBonus = 100; // give our commands a little bit more weight over other default list/tree commands
export const QueryEditorVisibleCondition = ContextKeyExpr.has(queryContext.queryEditorVisibleId);
export const ResultsGridFocusCondition = ContextKeyExpr.and(ContextKeyExpr.has(queryContext.resultsVisibleId), ContextKeyExpr.has(queryContext.resultsGridFocussedId));
export const ResultsMessagesFocusCondition = ContextKeyExpr.and(ContextKeyExpr.has(queryContext.resultsVisibleId), ContextKeyExpr.has(queryContext.resultsMessagesFocussedId));
// Editor
const queryResultsEditorDescriptor = new EditorDescriptor(
QueryResultsEditor,
QueryResultsEditor.ID,
'QueryResults'
);
Registry.as<IEditorRegistry>(EditorExtensions.Editors)
.registerEditor(queryResultsEditorDescriptor, [new SyncDescriptor(QueryResultsInput)]);
// Editor
const queryEditorDescriptor = new EditorDescriptor(
QueryEditor,
QueryEditor.ID,
'Query'
);
Registry.as<IEditorRegistry>(EditorExtensions.Editors)
.registerEditor(queryEditorDescriptor, [new SyncDescriptor(QueryInput)]);
// Query Plan editor registration
const queryPlanEditorDescriptor = new EditorDescriptor(
QueryPlanEditor,
QueryPlanEditor.ID,
'QueryPlan'
);
Registry.as<IEditorRegistry>(EditorExtensions.Editors)
.registerEditor(queryPlanEditorDescriptor, [new SyncDescriptor(QueryPlanInput)]);
// Editor
const editDataEditorDescriptor = new EditorDescriptor(
EditDataEditor,
EditDataEditor.ID,
'EditData'
);
Registry.as<IEditorRegistry>(EditorExtensions.Editors)
.registerEditor(editDataEditorDescriptor, [new SyncDescriptor(EditDataInput)]);
// Editor
const editDataResultsEditorDescriptor = new EditorDescriptor(
EditDataResultsEditor,
EditDataResultsEditor.ID,
'EditDataResults'
);
Registry.as<IEditorRegistry>(EditorExtensions.Editors)
.registerEditor(editDataResultsEditorDescriptor, [new SyncDescriptor(EditDataResultsInput)]);
let actionRegistry = <IWorkbenchActionRegistry>Registry.as(Extensions.WorkbenchActions);
// Query Actions
actionRegistry.registerWorkbenchAction(
new SyncActionDescriptor(
RunQueryKeyboardAction,
RunQueryKeyboardAction.ID,
RunQueryKeyboardAction.LABEL,
{ primary: KeyCode.F5 }
),
RunQueryKeyboardAction.LABEL
);
// Touch Bar
if (isMacintosh) {
// Only show Run Query if the active editor is a query editor.
MenuRegistry.appendMenuItem(MenuId.TouchBarContext, {
command: { id: RunQueryKeyboardAction.ID, title: RunQueryKeyboardAction.LABEL },
group: 'query',
when: new ContextKeyEqualsExpr('activeEditor', 'workbench.editor.queryEditor')
});
}
actionRegistry.registerWorkbenchAction(
new SyncActionDescriptor(
RunCurrentQueryKeyboardAction,
RunCurrentQueryKeyboardAction.ID,
RunCurrentQueryKeyboardAction.LABEL,
{ primary: KeyMod.CtrlCmd | KeyCode.F5 }
),
RunCurrentQueryKeyboardAction.LABEL
);
actionRegistry.registerWorkbenchAction(
new SyncActionDescriptor(
RunCurrentQueryWithActualPlanKeyboardAction,
RunCurrentQueryWithActualPlanKeyboardAction.ID,
RunCurrentQueryWithActualPlanKeyboardAction.LABEL,
{ primary: KeyMod.CtrlCmd | KeyCode.KEY_M }
),
RunCurrentQueryWithActualPlanKeyboardAction.LABEL
);
actionRegistry.registerWorkbenchAction(
new SyncActionDescriptor(
CancelQueryKeyboardAction,
CancelQueryKeyboardAction.ID,
CancelQueryKeyboardAction.LABEL,
{ primary: KeyMod.Alt | KeyCode.PauseBreak }
),
CancelQueryKeyboardAction.LABEL
);
actionRegistry.registerWorkbenchAction(
new SyncActionDescriptor(
RefreshIntellisenseKeyboardAction,
RefreshIntellisenseKeyboardAction.ID,
RefreshIntellisenseKeyboardAction.LABEL
),
RefreshIntellisenseKeyboardAction.LABEL
);
actionRegistry.registerWorkbenchAction(
new SyncActionDescriptor(
FocusOnCurrentQueryKeyboardAction,
FocusOnCurrentQueryKeyboardAction.ID,
FocusOnCurrentQueryKeyboardAction.LABEL,
{ primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_O }
),
FocusOnCurrentQueryKeyboardAction.LABEL
);
actionRegistry.registerWorkbenchAction(
new SyncActionDescriptor(
ParseSyntaxAction,
ParseSyntaxAction.ID,
ParseSyntaxAction.LABEL
),
ParseSyntaxAction.LABEL
);
// Grid actions
actionRegistry.registerWorkbenchAction(
new SyncActionDescriptor(
ToggleQueryResultsKeyboardAction,
ToggleQueryResultsKeyboardAction.ID,
ToggleQueryResultsKeyboardAction.LABEL,
{ primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.KEY_R },
QueryEditorVisibleCondition
),
ToggleQueryResultsKeyboardAction.LABEL
);
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: gridActions.GRID_COPY_ID,
weight: KeybindingWeight.EditorContrib,
when: ResultsGridFocusCondition,
primary: KeyMod.CtrlCmd | KeyCode.KEY_C,
handler: gridCommands.copySelection
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: gridActions.MESSAGES_SELECTALL_ID,
weight: KeybindingWeight.EditorContrib,
when: ResultsMessagesFocusCondition,
primary: KeyMod.CtrlCmd | KeyCode.KEY_A,
handler: gridCommands.selectAllMessages
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: gridActions.GRID_SELECTALL_ID,
weight: KeybindingWeight.EditorContrib,
when: ResultsGridFocusCondition,
primary: KeyMod.CtrlCmd | KeyCode.KEY_A,
handler: gridCommands.selectAll
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: gridActions.MESSAGES_COPY_ID,
weight: KeybindingWeight.EditorContrib,
when: ResultsMessagesFocusCondition,
primary: KeyMod.CtrlCmd | KeyCode.KEY_C,
handler: gridCommands.copyMessagesSelection
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: gridActions.GRID_SAVECSV_ID,
weight: KeybindingWeight.EditorContrib,
when: ResultsGridFocusCondition,
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_R, KeyMod.CtrlCmd | KeyCode.KEY_C),
handler: gridCommands.saveAsCsv
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: gridActions.GRID_SAVEJSON_ID,
weight: KeybindingWeight.EditorContrib,
when: ResultsGridFocusCondition,
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_R, KeyMod.CtrlCmd | KeyCode.KEY_J),
handler: gridCommands.saveAsJson
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: gridActions.GRID_SAVEEXCEL_ID,
weight: KeybindingWeight.EditorContrib,
when: ResultsGridFocusCondition,
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_R, KeyMod.CtrlCmd | KeyCode.KEY_E),
handler: gridCommands.saveAsExcel
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: gridActions.GRID_SAVEXML_ID,
weight: KeybindingWeight.EditorContrib,
when: ResultsGridFocusCondition,
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_R, KeyMod.CtrlCmd | KeyCode.KEY_X),
handler: gridCommands.saveAsXml
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: gridActions.GRID_VIEWASCHART_ID,
weight: KeybindingWeight.EditorContrib,
when: ResultsGridFocusCondition,
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_R, KeyMod.CtrlCmd | KeyCode.KEY_V),
handler: gridCommands.viewAsChart
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: gridActions.GRID_GOTONEXTGRID_ID,
weight: KeybindingWeight.EditorContrib,
when: ResultsGridFocusCondition,
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_R, KeyMod.CtrlCmd | KeyCode.KEY_N),
handler: gridCommands.goToNextGrid
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: gridActions.TOGGLERESULTS_ID,
weight: KeybindingWeight.EditorContrib,
when: QueryEditorVisibleCondition,
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_R,
handler: gridCommands.toggleResultsPane
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: gridActions.TOGGLEMESSAGES_ID,
weight: KeybindingWeight.EditorContrib,
when: QueryEditorVisibleCondition,
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Y,
handler: gridCommands.toggleMessagePane
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: gridActions.GOTONEXTQUERYOUTPUTTAB_ID,
weight: KeybindingWeight.EditorContrib,
when: QueryEditorVisibleCondition,
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_P,
handler: gridCommands.goToNextQueryOutputTab
});
// Intellisense and other configuration options
let registryProperties = {
'sql.messagesDefaultOpen': {
'type': 'boolean',
'description': localize('sql.messagesDefaultOpen', 'True for the messages pane to be open by default; false for closed'),
'default': true
},
'sql.saveAsCsv.includeHeaders': {
'type': 'boolean',
'description': localize('sql.saveAsCsv.includeHeaders', '[Optional] When true, column headers are included when saving results as CSV'),
'default': true
},
'sql.saveAsCsv.delimiter': {
'type': 'string',
'description': localize('sql.saveAsCsv.delimiter', '[Optional] The custom delimiter to use between values when saving as CSV'),
'default': ','
},
'sql.saveAsCsv.lineSeperator': {
'type': '',
'description': localize('sql.saveAsCsv.lineSeperator', '[Optional] Character(s) used for seperating rows when saving results as CSV'),
'default': null
},
'sql.saveAsCsv.textIdentifier': {
'type': 'string',
'description': localize('sql.saveAsCsv.textIdentifier', '[Optional] Character used for enclosing text fields when saving results as CSV'),
'default': '\"'
},
'sql.saveAsCsv.encoding': {
'type': 'string',
'description': localize('sql.saveAsCsv.encoding', '[Optional] File encoding used when saving results as CSV'),
'default': 'utf-8'
},
'sql.results.streaming': {
'type': 'boolean',
'description': localize('sql.results.streaming', 'Enable results streaming; contains few minor visual issues'),
'default': true
},
'sql.saveAsXml.formatted': {
'type': 'string',
'description': localize('sql.saveAsXml.formatted', '[Optional] When true, XML output will be formatted when saving results as XML'),
'default': true
},
'sql.saveAsXml.encoding': {
'type': 'string',
'description': localize('sql.saveAsXml.encoding', '[Optional] File encoding used when saving results as XML'),
'default': 'utf-8'
},
'sql.copyIncludeHeaders': {
'type': 'boolean',
'description': localize('sql.copyIncludeHeaders', '[Optional] Configuration options for copying results from the Results View'),
'default': false
},
'sql.copyRemoveNewLine': {
'type': 'boolean',
'description': localize('sql.copyRemoveNewLine', '[Optional] Configuration options for copying multi-line results from the Results View'),
'default': true
},
'sql.showBatchTime': {
'type': 'boolean',
'description': localize('sql.showBatchTime', '[Optional] Should execution time be shown for individual batches'),
'default': false
},
'sql.chart.defaultChartType': {
'enum': Constants.allChartTypes,
'default': Constants.chartTypeHorizontalBar,
'description': localize('defaultChartType', "[Optional] the default chart type to use when opening Chart Viewer from a Query Results")
},
'sql.tabColorMode': {
'type': 'string',
'enum': [Constants.tabColorModeOff, Constants.tabColorModeBorder, Constants.tabColorModeFill],
'enumDescriptions': [
localize('tabColorMode.off', "Tab coloring will be disabled"),
localize('tabColorMode.border', "The top border of each editor tab will be colored to match the relevant server group"),
localize('tabColorMode.fill', "Each editor tab's background color will match the relevant server group"),
],
'default': Constants.tabColorModeOff,
'description': localize('tabColorMode', "Controls how to color tabs based on the server group of their active connection")
},
'sql.showConnectionInfoInTitle': {
'type': 'boolean',
'description': localize('showConnectionInfoInTitle', "Controls whether to show the connection info for a tab in the title."),
'default': true
},
'sql.promptToSaveGeneratedFiles': {
'type': 'boolean',
'default': false,
'description': localize('sql.promptToSaveGeneratedFiles', 'Prompt to save generated SQL files')
},
'mssql.intelliSense.enableIntelliSense': {
'type': 'boolean',
'default': true,
'description': localize('mssql.intelliSense.enableIntelliSense', 'Should IntelliSense be enabled')
},
'mssql.intelliSense.enableErrorChecking': {
'type': 'boolean',
'default': true,
'description': localize('mssql.intelliSense.enableErrorChecking', 'Should IntelliSense error checking be enabled')
},
'mssql.intelliSense.enableSuggestions': {
'type': 'boolean',
'default': true,
'description': localize('mssql.intelliSense.enableSuggestions', 'Should IntelliSense suggestions be enabled')
},
'mssql.intelliSense.enableQuickInfo': {
'type': 'boolean',
'default': true,
'description': localize('mssql.intelliSense.enableQuickInfo', 'Should IntelliSense quick info be enabled')
},
'mssql.intelliSense.lowerCaseSuggestions': {
'type': 'boolean',
'default': false,
'description': localize('mssql.intelliSense.lowerCaseSuggestions', 'Should IntelliSense suggestions be lowercase')
}
};
// Setup keybindings
let initialShortcuts = [
{ name: 'sp_help', primary: KeyMod.Alt + KeyCode.F2 },
// Note: using Ctrl+Shift+N since Ctrl+N is used for "open editor at index" by default. This means it's different from SSMS
{ name: 'sp_who', primary: KeyMod.WinCtrl + KeyMod.Shift + KeyCode.KEY_1 },
{ name: 'sp_lock', primary: KeyMod.WinCtrl + KeyMod.Shift + KeyCode.KEY_2 }
];
for (let i = 0; i < 9; i++) {
const queryIndex = i + 1;
let settingKey = `sql.query.shortcut${queryIndex}`;
let defaultVal = i < initialShortcuts.length ? initialShortcuts[i].name : '';
let defaultPrimary = i < initialShortcuts.length ? initialShortcuts[i].primary : null;
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: `workbench.action.query.shortcut${queryIndex}`,
weight: KeybindingWeight.WorkbenchContrib,
when: QueryEditorVisibleCondition,
primary: defaultPrimary,
handler: accessor => {
accessor.get(IInstantiationService).createInstance(RunQueryShortcutAction).run(queryIndex);
}
});
registryProperties[settingKey] = {
'type': 'string',
'default': defaultVal,
'description': localize('queryShortcutDescription',
'Set keybinding workbench.action.query.shortcut{0} to run the shortcut text as a procedure call. Any selected text in the query editor will be passed as a parameter',
queryIndex)
};
}
// Register the query-related configuration options
let configurationRegistry = <IConfigurationRegistry>Registry.as(ConfigExtensions.Configuration);
configurationRegistry.registerConfiguration({
'id': 'sqlEditor',
'title': 'SQL Editor',
'type': 'object',
'properties': registryProperties
});

View File

@@ -0,0 +1,660 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/queryActions';
import * as nls from 'vs/nls';
import { Action, IActionItem, IActionRunner } from 'vs/base/common/actions';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { INotificationService } from 'vs/platform/notification/common/notification';
import Severity from 'vs/base/common/severity';
import { append, $ } from 'vs/base/browser/dom';
import { ISelectionData } from 'azdata';
import {
IConnectionManagementService,
IConnectionParams,
INewConnectionParams,
ConnectionType,
RunQueryOnConnectionMode
} from 'sql/platform/connection/common/connectionManagement';
import { QueryEditor } from 'sql/workbench/parts/query/browser/queryEditor';
import { IQueryModelService } from 'sql/platform/query/common/queryModel';
import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox';
import { attachEditableDropdownStyler, attachSelectBoxStyler } from 'sql/platform/theme/common/styler';
import { EventEmitter } from 'sql/base/common/eventEmitter';
import { Dropdown } from 'sql/base/browser/ui/editableDropdown/dropdown';
import { ILayoutService } from 'vs/platform/layout/browser/layoutService';
/**
* Action class that query-based Actions will extend. This base class automatically handles activating and
* deactivating the button when a SQL file is opened.
*/
export abstract class QueryTaskbarAction extends Action {
private _classes: string[];
constructor(
protected _connectionManagementService: IConnectionManagementService,
protected editor: QueryEditor,
id: string,
enabledClass: string
) {
super(id);
this.enabled = true;
this._setCssClass(enabledClass);
}
/**
* This method is executed when the button is clicked.
*/
public abstract run(): Promise<void>;
protected updateCssClass(enabledClass: string): void {
// set the class, useful on change of label or icon
this._setCssClass(enabledClass);
}
/**
* Sets the CSS classes combining the parent and child classes.
* Public for testing only.
*/
private _setCssClass(enabledClass: string): void {
this._classes = [];
if (enabledClass) {
this._classes.push(enabledClass);
}
this.class = this._classes.join(' ');
}
/**
* Returns the URI of the given editor if it is not undefined and is connected.
* Public for testing only.
*/
public isConnected(editor: QueryEditor): boolean {
if (!editor || !editor.currentQueryInput) {
return false;
}
return this._connectionManagementService.isConnected(editor.currentQueryInput.uri);
}
/**
* Connects the given editor to it's current URI.
* Public for testing only.
*/
protected connectEditor(editor: QueryEditor, runQueryOnCompletion?: RunQueryOnConnectionMode, selection?: ISelectionData): void {
let params: INewConnectionParams = {
input: editor.currentQueryInput,
connectionType: ConnectionType.editor,
runQueryOnCompletion: runQueryOnCompletion ? runQueryOnCompletion : RunQueryOnConnectionMode.none,
querySelection: selection
};
this._connectionManagementService.showConnectionDialog(params);
}
}
/**
* Action class that runs a query in the active SQL text document.
*/
export class RunQueryAction extends QueryTaskbarAction {
public static EnabledClass = 'start';
public static ID = 'runQueryAction';
constructor(
editor: QueryEditor,
@IQueryModelService protected _queryModelService: IQueryModelService,
@IConnectionManagementService connectionManagementService: IConnectionManagementService
) {
super(connectionManagementService, editor, RunQueryAction.ID, RunQueryAction.EnabledClass);
this.label = nls.localize('runQueryLabel', 'Run');
}
public run(): Promise<void> {
if (!this.editor.isSelectionEmpty()) {
if (this.isConnected(this.editor)) {
// If we are already connected, run the query
this.runQuery(this.editor);
} else {
// If we are not already connected, prompt for connection and run the query if the
// connection succeeds. "runQueryOnCompletion=true" will cause the query to run after connection
this.connectEditor(this.editor, RunQueryOnConnectionMode.executeQuery, this.editor.getSelection());
}
}
return Promise.resolve(null);
}
public runCurrent(): Promise<void> {
if (!this.editor.isSelectionEmpty()) {
if (this.isConnected(this.editor)) {
// If we are already connected, run the query
this.runQuery(this.editor, true);
} else {
// If we are not already connected, prompt for connection and run the query if the
// connection succeeds. "runQueryOnCompletion=true" will cause the query to run after connection
this.connectEditor(this.editor, RunQueryOnConnectionMode.executeCurrentQuery, this.editor.getSelection(false));
}
}
return Promise.resolve(null);
}
public runQuery(editor: QueryEditor, runCurrentStatement: boolean = false) {
if (!editor) {
editor = this.editor;
}
if (this.isConnected(editor)) {
// if the selection isn't empty then execute the selection
// otherwise, either run the statement or the script depending on parameter
let selection: ISelectionData = editor.getSelection(false);
if (runCurrentStatement && selection && this.isCursorPosition(selection)) {
editor.currentQueryInput.runQueryStatement(selection);
} else {
// get the selection again this time with trimming
selection = editor.getSelection();
editor.currentQueryInput.runQuery(selection);
}
}
}
protected isCursorPosition(selection: ISelectionData) {
return selection.startLine === selection.endLine
&& selection.startColumn === selection.endColumn;
}
}
/**
* Action class that cancels the running query in the current SQL text document.
*/
export class CancelQueryAction extends QueryTaskbarAction {
public static EnabledClass = 'stop';
public static ID = 'cancelQueryAction';
constructor(
editor: QueryEditor,
@IQueryModelService private _queryModelService: IQueryModelService,
@IConnectionManagementService connectionManagementService: IConnectionManagementService
) {
super(connectionManagementService, editor, CancelQueryAction.ID, CancelQueryAction.EnabledClass);
this.enabled = false;
this.label = nls.localize('cancelQueryLabel', 'Cancel');
}
public run(): Promise<void> {
if (this.isConnected(this.editor)) {
this._queryModelService.cancelQuery(this.editor.currentQueryInput.uri);
}
return Promise.resolve(null);
}
}
/**
* Action class that runs a query in the active SQL text document.
*/
export class EstimatedQueryPlanAction extends QueryTaskbarAction {
public static EnabledClass = 'estimatedQueryPlan';
public static ID = 'estimatedQueryPlanAction';
constructor(
editor: QueryEditor,
@IQueryModelService private _queryModelService: IQueryModelService,
@IConnectionManagementService connectionManagementService: IConnectionManagementService
) {
super(connectionManagementService, editor, EstimatedQueryPlanAction.ID, EstimatedQueryPlanAction.EnabledClass);
this.label = nls.localize('estimatedQueryPlan', 'Explain');
}
public run(): Promise<void> {
if (!this.editor.isSelectionEmpty()) {
if (this.isConnected(this.editor)) {
// If we are already connected, run the query
this.runQuery(this.editor);
} else {
// If we are not already connected, prompt for connection and run the query if the
// connection succeeds. "runQueryOnCompletion=true" will cause the query to run after connection
this.connectEditor(this.editor, RunQueryOnConnectionMode.estimatedQueryPlan, this.editor.getSelection());
}
}
return Promise.resolve(null);
}
public runQuery(editor: QueryEditor) {
if (!editor) {
editor = this.editor;
}
if (this.isConnected(editor)) {
editor.currentQueryInput.runQuery(editor.getSelection(), {
displayEstimatedQueryPlan: true
});
}
}
}
export class ActualQueryPlanAction extends QueryTaskbarAction {
public static EnabledClass = 'actualQueryPlan';
public static ID = 'actualQueryPlanAction';
constructor(
editor: QueryEditor,
@IQueryModelService private _queryModelService: IQueryModelService,
@IConnectionManagementService connectionManagementService: IConnectionManagementService
) {
super(connectionManagementService, editor, ActualQueryPlanAction.ID, ActualQueryPlanAction.EnabledClass);
this.label = nls.localize('actualQueryPlan', "Actual");
}
public run(): Promise<void> {
if (!this.editor.isSelectionEmpty()) {
if (this.isConnected(this.editor)) {
// If we are already connected, run the query
this.runQuery(this.editor);
} else {
// If we are not already connected, prompt for connection and run the query if the
// connection succeeds. "runQueryOnCompletion=true" will cause the query to run after connection
this.connectEditor(this.editor, RunQueryOnConnectionMode.actualQueryPlan, this.editor.getSelection());
}
}
return Promise.resolve(null);
}
public runQuery(editor: QueryEditor) {
if (!editor) {
editor = this.editor;
}
if (this.isConnected(editor)) {
let selection = editor.getSelection();
if (!selection) {
selection = editor.getAllSelection();
}
editor.currentQueryInput.runQuery(selection, {
displayActualQueryPlan: true
});
}
}
}
/**
* Action class that disconnects the connection associated with the current query file.
*/
export class DisconnectDatabaseAction extends QueryTaskbarAction {
public static EnabledClass = 'disconnect';
public static ID = 'disconnectDatabaseAction';
constructor(
editor: QueryEditor,
@IConnectionManagementService connectionManagementService: IConnectionManagementService
) {
super(connectionManagementService, editor, DisconnectDatabaseAction.ID, DisconnectDatabaseAction.EnabledClass);
this.label = nls.localize('disconnectDatabaseLabel', 'Disconnect');
}
public run(): Promise<void> {
// Call disconnectEditor regardless of the connection state and let the ConnectionManagementService
// determine if we need to disconnect, cancel an in-progress conneciton, or do nothing
this._connectionManagementService.disconnectEditor(this.editor.currentQueryInput);
return Promise.resolve(null);
}
}
/**
* Action class that launches a connection dialogue for the current query file
*/
export class ConnectDatabaseAction extends QueryTaskbarAction {
public static EnabledDefaultClass = 'connect';
public static EnabledChangeClass = 'changeConnection';
public static ID = 'connectDatabaseAction';
constructor(
editor: QueryEditor,
isChangeConnectionAction: boolean,
@IConnectionManagementService connectionManagementService: IConnectionManagementService
) {
let label: string;
let enabledClass: string;
if (isChangeConnectionAction) {
enabledClass = ConnectDatabaseAction.EnabledChangeClass;
label = nls.localize('changeConnectionDatabaseLabel', 'Change Connection');
} else {
enabledClass = ConnectDatabaseAction.EnabledDefaultClass;
label = nls.localize('connectDatabaseLabel', 'Connect');
}
super(connectionManagementService, editor, ConnectDatabaseAction.ID, enabledClass);
this.label = label;
}
public run(): Promise<void> {
this.connectEditor(this.editor);
return Promise.resolve(null);
}
}
/**
* Action class that either launches a connection dialogue for the current query file,
* or disconnects the active connection
*/
export class ToggleConnectDatabaseAction extends QueryTaskbarAction {
public static ConnectClass = 'connect';
public static DisconnectClass = 'disconnect';
public static ID = 'toggleConnectDatabaseAction';
private _connected: boolean;
private _connectLabel: string;
private _disconnectLabel: string;
constructor(
editor: QueryEditor,
isConnected: boolean,
@IConnectionManagementService connectionManagementService: IConnectionManagementService
) {
let enabledClass: string;
super(connectionManagementService, editor, ToggleConnectDatabaseAction.ID, enabledClass);
this._connectLabel = nls.localize('connectDatabaseLabel', 'Connect');
this._disconnectLabel = nls.localize('disconnectDatabaseLabel', 'Disconnect');
this.connected = isConnected;
}
public get connected(): boolean {
return this._connected;
}
public set connected(value: boolean) {
// intentionally always updating, since parent class handles skipping if values
this._connected = value;
this.updateLabelAndIcon();
}
private updateLabelAndIcon(): void {
if (this._connected) {
// We are connected, so show option to disconnect
this.label = this._disconnectLabel;
this.updateCssClass(ToggleConnectDatabaseAction.DisconnectClass);
} else {
this.label = this._connectLabel;
this.updateCssClass(ToggleConnectDatabaseAction.ConnectClass);
}
}
public run(): Promise<void> {
if (this.connected) {
// Call disconnectEditor regardless of the connection state and let the ConnectionManagementService
// determine if we need to disconnect, cancel an in-progress connection, or do nothing
this._connectionManagementService.disconnectEditor(this.editor.currentQueryInput);
} else {
this.connectEditor(this.editor);
}
return Promise.resolve(null);
}
}
/**
* Action class that is tied with ListDatabasesActionItem.
*/
export class ListDatabasesAction extends QueryTaskbarAction {
public static EnabledClass = '';
public static ID = 'listDatabaseQueryAction';
constructor(
editor: QueryEditor,
@IConnectionManagementService connectionManagementService: IConnectionManagementService
) {
super(connectionManagementService, editor, ListDatabasesAction.ID, undefined);
this.enabled = false;
this.class = ListDatabasesAction.EnabledClass;
}
public run(): Promise<void> {
return Promise.resolve(null);
}
}
/*
* Action item that handles the dropdown (combobox) that lists the available databases.
* Based off StartDebugActionItem.
*/
export class ListDatabasesActionItem extends EventEmitter implements IActionItem {
public static ID = 'listDatabaseQueryActionItem';
public actionRunner: IActionRunner;
private _toDispose: IDisposable[];
private _context: any;
private _currentDatabaseName: string;
private _isConnected: boolean;
private _databaseListDropdown: HTMLElement;
private _dropdown: Dropdown;
private _databaseSelectBox: SelectBox;
private _isInAccessibilityMode: boolean;
private readonly _selectDatabaseString: string = nls.localize("selectDatabase", "Select Database");
// CONSTRUCTOR /////////////////////////////////////////////////////////
constructor(
private _editor: QueryEditor,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
@INotificationService private _notificationService: INotificationService,
@IContextViewService contextViewProvider: IContextViewService,
@IConfigurationService private readonly _configurationService: IConfigurationService,
@ILayoutService layoutService: ILayoutService
) {
super();
this._toDispose = [];
this._databaseListDropdown = $('.databaseListDropdown');
this._isInAccessibilityMode = this._configurationService.getValue('editor.accessibilitySupport') === 'on';
if (this._isInAccessibilityMode) {
this._databaseSelectBox = new SelectBox([this._selectDatabaseString], this._selectDatabaseString, contextViewProvider, undefined, { ariaLabel: this._selectDatabaseString });
this._databaseSelectBox.render(this._databaseListDropdown);
this._databaseSelectBox.onDidSelect(e => { this.databaseSelected(e.selected); });
this._databaseSelectBox.disable();
} else {
this._dropdown = new Dropdown(this._databaseListDropdown, contextViewProvider, layoutService, {
strictSelection: true,
placeholder: this._selectDatabaseString,
ariaLabel: this._selectDatabaseString,
actionLabel: nls.localize('listDatabases.toggleDatabaseNameDropdown', 'Select Database Toggle Dropdown')
});
this._dropdown.onValueChange(s => this.databaseSelected(s));
this._toDispose.push(this._dropdown.onFocus(() => { self.onDropdownFocus(); }));
}
// Register event handlers
let self = this;
this._toDispose.push(this._connectionManagementService.onConnectionChanged(params => { self.onConnectionChanged(params); }));
}
// PUBLIC METHODS //////////////////////////////////////////////////////
public render(container: HTMLElement): void {
append(container, this._databaseListDropdown);
}
public style(styles) {
if (this._isInAccessibilityMode) {
this._databaseSelectBox.style(styles);
}
else {
this._dropdown.style(styles);
}
}
public setActionContext(context: any): void {
this._context = context;
}
public isEnabled(): boolean {
return !!this._isConnected;
}
public focus(): void {
if (this._isInAccessibilityMode) {
this._databaseSelectBox.focus();
} else {
this._dropdown.focus();
}
}
public blur(): void {
if (this._isInAccessibilityMode) {
this._databaseSelectBox.blur();
} else {
this._dropdown.blur();
}
}
public attachStyler(themeService: IThemeService): IDisposable {
if (this._isInAccessibilityMode) {
return attachSelectBoxStyler(this, themeService);
} else {
return attachEditableDropdownStyler(this, themeService);
}
}
public dispose(): void {
this._toDispose = dispose(this._toDispose);
}
// EVENT HANDLERS FROM EDITOR //////////////////////////////////////////
public onConnected(): void {
let dbName = this.getCurrentDatabaseName();
this.updateConnection(dbName);
}
public onDisconnect(): void {
this._isConnected = false;
this._currentDatabaseName = undefined;
if (this._isInAccessibilityMode) {
this._databaseSelectBox.disable();
this._databaseSelectBox.setOptions([this._selectDatabaseString]);
} else {
this._dropdown.enabled = false;
this._dropdown.value = '';
}
}
// PRIVATE HELPERS /////////////////////////////////////////////////////
private databaseSelected(dbName: string): void {
let uri = this._editor.connectedUri;
if (!uri) {
return;
}
let profile = this._connectionManagementService.getConnectionProfile(uri);
if (!profile) {
return;
}
this._connectionManagementService.changeDatabase(this._editor.uri, dbName)
.then(
result => {
if (!result) {
this.resetDatabaseName();
this._notificationService.notify({
severity: Severity.Error,
message: nls.localize('changeDatabase.failed', "Failed to change database")
});
}
},
error => {
this.resetDatabaseName();
this._notificationService.notify({
severity: Severity.Error,
message: nls.localize('changeDatabase.failedWithError', "Failed to change database {0}", error)
});
});
}
private getCurrentDatabaseName() {
let uri = this._editor.connectedUri;
if (uri) {
let profile = this._connectionManagementService.getConnectionProfile(uri);
if (profile) {
return profile.databaseName;
}
}
return undefined;
}
private resetDatabaseName() {
if (this._isInAccessibilityMode) {
this._databaseSelectBox.selectWithOptionName(this.getCurrentDatabaseName());
} else {
this._dropdown.value = this.getCurrentDatabaseName();
}
}
private onConnectionChanged(connParams: IConnectionParams): void {
if (!connParams) {
return;
}
let uri = this._editor.connectedUri;
if (uri !== connParams.connectionUri) {
return;
}
this.updateConnection(connParams.connectionProfile.databaseName);
}
private onDropdownFocus(): void {
let self = this;
let uri = self._editor.connectedUri;
if (!uri) {
return;
}
self._connectionManagementService.listDatabases(uri)
.then(result => {
if (result && result.databaseNames) {
this._dropdown.values = result.databaseNames;
}
});
}
private updateConnection(databaseName: string) {
this._isConnected = true;
this._currentDatabaseName = databaseName;
if (this._isInAccessibilityMode) {
this._databaseSelectBox.enable();
let self = this;
let uri = self._editor.connectedUri;
if (!uri) {
return;
}
self._connectionManagementService.listDatabases(uri)
.then(result => {
if (result && result.databaseNames) {
this._databaseSelectBox.setOptions(result.databaseNames);
}
this._databaseSelectBox.selectWithOptionName(databaseName);
});
} else {
this._dropdown.enabled = true;
this._dropdown.value = databaseName;
}
}
// TESTING PROPERTIES //////////////////////////////////////////////////
public get currentDatabaseName(): string {
return this._currentDatabaseName;
}
}

View File

@@ -0,0 +1,973 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/queryEditor';
import * as strings from 'vs/base/common/strings';
import * as DOM from 'vs/base/browser/dom';
import * as types from 'vs/base/common/types';
import { EditorInput, EditorOptions, IEditorControl, IEditor, TextEditorOptions } from 'vs/workbench/common/editor';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
import { VerticalFlexibleSash, HorizontalFlexibleSash, IFlexibleSash } from 'sql/workbench/parts/query/browser/flexibleSash';
import { Orientation } from 'vs/base/browser/ui/sash/sash';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
import { TextResourceEditor } from 'vs/workbench/browser/parts/editor/textResourceEditor';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IActionItem } from 'vs/base/browser/ui/actionbar/actionbar';
import { Action } from 'vs/base/common/actions';
import { ISelectionData } from 'azdata';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IRange } from 'vs/editor/common/core/range';
import { QueryResultsInput } from 'sql/workbench/parts/query/common/queryResultsInput';
import { QueryInput } from 'sql/workbench/parts/query/common/queryInput';
import { QueryResultsEditor } from 'sql/workbench/parts/query/browser/queryResultsEditor';
import * as queryContext from 'sql/workbench/parts/query/common/queryContext';
import { Taskbar, ITaskbarContent } from 'sql/base/browser/ui/taskbar/taskbar';
import {
RunQueryAction, CancelQueryAction, ListDatabasesAction, ListDatabasesActionItem,
ConnectDatabaseAction, ToggleConnectDatabaseAction, EstimatedQueryPlanAction,
ActualQueryPlanAction
} from 'sql/workbench/parts/query/browser/queryActions';
import { IQueryModelService } from 'sql/platform/query/common/queryModel';
import { IEditorDescriptorService } from 'sql/workbench/services/queryEditor/common/editorDescriptorService';
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { CancellationToken } from 'vs/base/common/cancellation';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService';
/**
* Editor that hosts 2 sub-editors: A TextResourceEditor for SQL file editing, and a QueryResultsEditor
* for viewing and editing query results. This editor is based off SideBySideEditor.
*/
export class QueryEditor extends BaseEditor {
public static ID: string = 'workbench.editor.queryEditor';
// The height of the tabs above the editor
private readonly _tabHeight: number = 35;
// The minimum width/height of the editors hosted in the QueryEditor
private readonly _minEditorSize: number = 100;
private _sash: IFlexibleSash;
private _editorTopOffset: number;
private _orientation: Orientation;
private _dimension: DOM.Dimension;
private _resultsEditor: QueryResultsEditor;
private _resultsEditorContainer: HTMLElement;
private _sqlEditor: TextResourceEditor;
private _sqlEditorContainer: HTMLElement;
private _taskbar: Taskbar;
private _taskbarContainer: HTMLElement;
private _listDatabasesActionItem: ListDatabasesActionItem;
private queryEditorVisible: IContextKey<boolean>;
private _runQueryAction: RunQueryAction;
private _cancelQueryAction: CancelQueryAction;
private _toggleConnectDatabaseAction: ToggleConnectDatabaseAction;
private _changeConnectionAction: ConnectDatabaseAction;
private _listDatabasesAction: ListDatabasesAction;
private _estimatedQueryPlanAction: EstimatedQueryPlanAction;
private _actualQueryPlanAction: ActualQueryPlanAction;
constructor(
@ITelemetryService _telemetryService: ITelemetryService,
@IThemeService themeService: IThemeService,
@IInstantiationService private _instantiationService: IInstantiationService,
@IEditorService private _editorService: IEditorService,
@IQueryModelService private _queryModelService: IQueryModelService,
@IEditorDescriptorService private _editorDescriptorService: IEditorDescriptorService,
@IContextKeyService contextKeyService: IContextKeyService,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
@IConfigurationService private _configurationService: IConfigurationService,
@IStorageService storageService: IStorageService
) {
super(QueryEditor.ID, _telemetryService, themeService, storageService);
this._orientation = Orientation.HORIZONTAL;
if (contextKeyService) {
this.queryEditorVisible = queryContext.QueryEditorVisibleContext.bindTo(contextKeyService);
}
if (_editorService) {
_editorService.overrideOpenEditor((editor, options, group) => {
if (this.isVisible() && (editor !== this.input || group !== this.group)) {
this.saveEditorViewState();
}
return {};
});
}
}
// PROPERTIES //////////////////////////////////////////////////////////
/**
* Returns the URI of this editor if it is connected.
* @returns URI of the editor if connected, undefined otherwise
*/
public get connectedUri(): string {
return this._connectionManagementService.isConnected(this.uri)
? this.uri
: undefined;
}
/**
* Returns the URI of this editor if an input is associated with it
* @return URI of this if input is associated, undefined otherwise
*/
get uri(): string {
let input: QueryInput = <QueryInput>this.input;
return input
? input.getQueryResultsInputResource()
: undefined;
}
// PUBLIC METHODS ////////////////////////////////////////////////////////////
public get currentQueryInput(): QueryInput {
return <QueryInput>this.input;
}
/**
* Called to create the editor in the parent element.
*/
public createEditor(parent: HTMLElement): void {
const parentElement = parent;
DOM.addClass(parentElement, 'side-by-side-editor');
this._createTaskbar(parentElement);
}
/**
* Sets the input data for this editor.
*/
public setInput(newInput: QueryInput, options?: EditorOptions): Promise<void> {
const oldInput = <QueryInput>this.input;
if (newInput.matches(oldInput)) {
return Promise.resolve(undefined);
}
// Make sure all event callbacks will be sent to this QueryEditor in the case that this QueryInput was moved from
// another QueryEditor
let taskbarCallback: IDisposable = newInput.updateTaskbarEvent(() => this._updateTaskbar());
let showResultsCallback: IDisposable = newInput.showQueryResultsEditorEvent(() => this._showQueryResultsEditor());
let selectionCallback: IDisposable = newInput.updateSelectionEvent((selection) => this._setSelection(selection));
newInput.setEventCallbacks([taskbarCallback, showResultsCallback, selectionCallback]);
return super.setInput(newInput, options, CancellationToken.None)
.then(() => this._updateInput(oldInput, newInput, options));
}
/**
* Sets this editor and the 2 sub-editors to visible.
*/
public setEditorVisible(visible: boolean, group: IEditorGroup): void {
if (this._resultsEditor) {
this._resultsEditor.setVisible(visible, group);
}
if (this._sqlEditor) {
this._sqlEditor.setVisible(visible, group);
}
super.setEditorVisible(visible, group);
// Note: must update after calling super.setEditorVisible so that the accurate count is handled
this.updateQueryEditorVisible(visible);
}
private updateQueryEditorVisible(currentEditorIsVisible: boolean): void {
if (this.queryEditorVisible) {
let visible = currentEditorIsVisible;
if (!currentEditorIsVisible) {
// Current editor is closing but still tracked as visible. Check if any other editor is visible
const candidates = [...this._editorService.visibleControls].filter(e => {
if (e && e.getId) {
return e.getId() === QueryEditor.ID;
}
return false;
});
// Note: require 2 or more candidates since current is closing but still
// counted as visible
visible = candidates.length > 1;
}
this.queryEditorVisible.set(visible);
}
}
/**
* Called to indicate to the editor that the input should be cleared and resources associated with the
* input should be freed.
*/
public clearInput(): void {
if (this._resultsEditor) {
this._resultsEditor.clearInput();
}
if (this._sqlEditor) {
this._sqlEditor.clearInput();
}
this._disposeEditors();
super.clearInput();
}
/**
* Sets focus on this editor. Specifically, it sets the focus on the hosted text editor.
*/
public focus(): void {
if (this._sqlEditor) {
this._sqlEditor.focus();
}
}
/**
* Updates the internal variable keeping track of the editor's size, and re-calculates the sash position.
* To be called when the container of this editor changes size.
*/
public layout(dimension: DOM.Dimension): void {
this._dimension = dimension;
if (this._sash) {
this._setSashDimension();
this.sash.layout();
}
this._doLayout();
this._resizeGridContents();
}
/**
* Returns the editor control for the text editor.
*/
public getControl(): IEditorControl {
if (this._sqlEditor) {
return this._sqlEditor.getControl();
}
return null;
}
public getQueryResultsEditor(): QueryResultsEditor {
return this._resultsEditor;
}
public getSqlEditor(): TextResourceEditor {
return this._sqlEditor;
}
public dispose(): void {
this._disposeEditors();
super.dispose();
}
public close(): void {
let queryInput: QueryInput = <QueryInput>this.input;
queryInput.sql.close();
queryInput.results.close();
}
/**
* Makes visible the QueryResultsEditor for the current QueryInput (if it is not
* already visible).
*/
public _showQueryResultsEditor(): void {
if (this._isResultsEditorVisible()) {
return;
}
const activeControl = this._editorService.activeControl;
activeControl.group.pinEditor(activeControl.input);
let input = <QueryInput>this.input;
this._createResultsEditorContainer();
this._createEditor(<QueryResultsInput>input.results, this._resultsEditorContainer, this.group)
.then(result => {
this._onResultsEditorCreated(<any>result, input.results, this.options);
this.resultsEditorVisibility = true;
this.hideQueryResultsView = false;
this._doLayout(true);
});
}
private hideQueryResultsView = false;
/**
* Toggle the visibility of the view state of results
*/
public toggleResultsEditorVisibility(): void {
let input = <QueryInput>this.input;
let hideResults = this.hideQueryResultsView;
this.hideQueryResultsView = !this.hideQueryResultsView;
if (!input.results) {
return;
}
this.resultsEditorVisibility = hideResults;
this._doLayout();
}
/**
* Returns the underlying SQL editor's text selection in a 0-indexed format. Returns undefined if there
* is no selected text.
*/
public getSelection(checkIfRange: boolean = true): ISelectionData {
if (this._sqlEditor && this._sqlEditor.getControl()) {
let vscodeSelection = this._sqlEditor.getControl().getSelection();
// If the selection is a range of characters rather than just a cursor position, return the range
let isRange: boolean =
!(vscodeSelection.getStartPosition().lineNumber === vscodeSelection.getEndPosition().lineNumber &&
vscodeSelection.getStartPosition().column === vscodeSelection.getEndPosition().column);
if (!checkIfRange || isRange) {
let sqlToolsServiceSelection: ISelectionData = {
startLine: vscodeSelection.getStartPosition().lineNumber - 1,
startColumn: vscodeSelection.getStartPosition().column - 1,
endLine: vscodeSelection.getEndPosition().lineNumber - 1,
endColumn: vscodeSelection.getEndPosition().column - 1,
};
return sqlToolsServiceSelection;
}
}
// Otherwise return undefined because there is no selected text
return undefined;
}
public isSelectionEmpty(): boolean {
if (this._sqlEditor && this._sqlEditor.getControl()) {
let control = this._sqlEditor.getControl();
let codeEditor: ICodeEditor = <ICodeEditor>control;
if (codeEditor) {
let value = codeEditor.getValue();
if (value !== undefined && value.length > 0) {
return false;
}
}
}
return true;
}
public getAllText(): string {
if (this._sqlEditor && this._sqlEditor.getControl()) {
let control = this._sqlEditor.getControl();
let codeEditor: ICodeEditor = <ICodeEditor>control;
if (codeEditor) {
let value = codeEditor.getValue();
if (value !== undefined && value.length > 0) {
return value;
} else {
return '';
}
}
}
return undefined;
}
public getAllSelection(): ISelectionData {
if (this._sqlEditor && this._sqlEditor.getControl()) {
let control = this._sqlEditor.getControl();
let codeEditor: ICodeEditor = <ICodeEditor>control;
if (codeEditor) {
let model = codeEditor.getModel();
let totalLines = model.getLineCount();
let endColumn = model.getLineMaxColumn(totalLines);
let selection: ISelectionData = {
startLine: 0,
startColumn: 0,
endLine: totalLines - 1,
endColumn: endColumn - 1,
};
return selection;
}
}
return undefined;
}
public getSelectionText(): string {
if (this._sqlEditor && this._sqlEditor.getControl()) {
let control = this._sqlEditor.getControl();
let codeEditor: ICodeEditor = <ICodeEditor>control;
let vscodeSelection = control.getSelection();
if (codeEditor && vscodeSelection) {
let model = codeEditor.getModel();
let value = model.getValueInRange(vscodeSelection);
if (value !== undefined && value.length > 0) {
return value;
}
}
}
return '';
}
/**
* Calls the run method of this editor's RunQueryAction
*/
public runQuery(): void {
this._runQueryAction.run();
}
/**
* Calls the runCurrent method of this editor's RunQueryAction
*/
public runCurrentQuery(): void {
this._runQueryAction.runCurrent();
}
/**
* Calls the runCurrentQueryWithActualPlan method of this editor's ActualQueryPlanAction
*/
public runCurrentQueryWithActualPlan(): void {
this._actualQueryPlanAction.run();
}
/**
* Calls the run method of this editor's CancelQueryAction
*/
public cancelQuery(): void {
this._cancelQueryAction.run();
}
public rebuildIntelliSenseCache(): void {
this._connectionManagementService.rebuildIntelliSenseCache(this.connectedUri);
}
public setOptions(options: EditorOptions): void {
const textOptions = <TextEditorOptions>options;
if (textOptions && types.isFunction(textOptions.apply)) {
textOptions.apply(this.getControl() as editorCommon.IEditor, editorCommon.ScrollType.Smooth);
}
}
// PRIVATE METHODS ////////////////////////////////////////////////////////////
/**
* Creates the query execution taskbar that appears at the top of the QueryEditor
*/
private _createTaskbar(parentElement: HTMLElement): void {
// Create QueryTaskbar
this._taskbarContainer = DOM.append(parentElement, DOM.$('div'));
this._taskbar = new Taskbar(this._taskbarContainer, {
actionItemProvider: (action: Action) => this._getActionItemForAction(action),
});
// Create Actions for the toolbar
this._runQueryAction = this._instantiationService.createInstance(RunQueryAction, this);
this._cancelQueryAction = this._instantiationService.createInstance(CancelQueryAction, this);
this._toggleConnectDatabaseAction = this._instantiationService.createInstance(ToggleConnectDatabaseAction, this, false);
this._changeConnectionAction = this._instantiationService.createInstance(ConnectDatabaseAction, this, true);
this._listDatabasesAction = this._instantiationService.createInstance(ListDatabasesAction, this);
this._estimatedQueryPlanAction = this._instantiationService.createInstance(EstimatedQueryPlanAction, this);
this._actualQueryPlanAction = this._instantiationService.createInstance(ActualQueryPlanAction, this);
this.setTaskbarContent();
this._toDispose.push(this._configurationService.onDidChangeConfiguration(e => {
if (e.affectedKeys.includes('workbench.enablePreviewFeatures')) {
this.setTaskbarContent();
}
}));
}
private setTaskbarContent(): void {
// Create HTML Elements for the taskbar
let separator = Taskbar.createTaskbarSeparator();
// Set the content in the order we desire
let content: ITaskbarContent[] = [
{ action: this._runQueryAction },
{ action: this._cancelQueryAction },
{ element: separator },
{ action: this._toggleConnectDatabaseAction },
{ action: this._changeConnectionAction },
{ action: this._listDatabasesAction },
{ element: separator },
{ action: this._estimatedQueryPlanAction }
];
// Remove the estimated query plan action if preview features are not enabled
let previewFeaturesEnabled = this._configurationService.getValue('workbench')['enablePreviewFeatures'];
if (!previewFeaturesEnabled) {
content = content.slice(0, -2);
}
this._taskbar.setContent(content);
}
/**
* Gets the IActionItem for the List Databases dropdown if provided the associated Action.
* Otherwise returns null.
*/
private _getActionItemForAction(action: Action): IActionItem {
if (action.id === ListDatabasesAction.ID) {
return this.listDatabasesActionItem;
}
return null;
}
/**
* Public for testing purposes only
*/
public get listDatabasesActionItem(): ListDatabasesActionItem {
if (!this._listDatabasesActionItem) {
this._listDatabasesActionItem = this._instantiationService.createInstance(ListDatabasesActionItem, this);
this._register(this._listDatabasesActionItem.attachStyler(this.themeService));
}
return this._listDatabasesActionItem;
}
/**
* Handles setting input for this editor.
*/
private _updateInput(oldInput: QueryInput, newInput: QueryInput, options?: EditorOptions): Promise<void> {
if (this._sqlEditor) {
this._sqlEditor.clearInput();
}
if (oldInput) {
this._disposeEditors();
}
this._createSqlEditorContainer();
if (this._isResultsEditorVisible()) {
this._createResultsEditorContainer();
let uri: string = newInput.getQueryResultsInputResource();
if (uri) {
this._queryModelService.refreshResultsets(uri);
}
}
if (this._sash) {
if (this._isResultsEditorVisible()) {
this._sash.show();
} else {
this._sash.hide();
}
}
this._updateTaskbar();
return this._setNewInput(newInput, options);
}
/**
* Handles setting input and creating editors when this QueryEditor is either:
* - Opened for the first time
* - Opened with a new QueryInput
* This will create only the SQL editor if the results editor does not yet exist for the
* given QueryInput.
*/
private _setNewInput(newInput: QueryInput, options?: EditorOptions): Promise<any> {
// Promises that will ensure proper ordering of editor creation logic
let createEditors: () => Promise<any>;
let onEditorsCreated: (result) => Promise<any>;
// If both editors exist, create joined promises - one for each editor
if (this._isResultsEditorVisible()) {
createEditors = () => {
return Promise.all([
this._createEditor(<QueryResultsInput>newInput.results, this._resultsEditorContainer, this.group),
this._createEditor(<UntitledEditorInput>newInput.sql, this._sqlEditorContainer, this.group)
]);
};
onEditorsCreated = (result: IEditor[]) => {
return Promise.all([
this._onResultsEditorCreated(<QueryResultsEditor>result[0], newInput.results, options),
this._onSqlEditorCreated(<TextResourceEditor>result[1], newInput.sql, options)
]);
};
// If only the sql editor exists, create a promise and wait for the sql editor to be created
} else {
createEditors = () => {
return this._createEditor(<UntitledEditorInput>newInput.sql, this._sqlEditorContainer, this.group);
};
onEditorsCreated = (result: TextResourceEditor) => {
return Promise.all([
this._onSqlEditorCreated(result, newInput.sql, options)
]);
};
}
// Create a promise to re render the layout after the editor creation logic
let doLayout: () => Promise<any> = () => {
this._doLayout();
return Promise.resolve(undefined);
};
// Run all three steps synchronously
return createEditors()
.then(onEditorsCreated)
.then(doLayout)
.then(() => {
if (newInput.results) {
newInput.results.onRestoreViewStateEmitter.fire();
}
if (newInput.savedViewState) {
this._sqlEditor.getControl().restoreViewState(newInput.savedViewState);
}
});
}
/**
* Create a single editor based on the type of the given EditorInput.
*/
private _createEditor(editorInput: EditorInput, container: HTMLElement, group: IEditorGroup): Promise<BaseEditor> {
const descriptor = this._editorDescriptorService.getEditor(editorInput);
if (!descriptor) {
return Promise.reject(new Error(strings.format('Can not find a registered editor for the input {0}', editorInput)));
}
let editor = descriptor.instantiate(this._instantiationService);
editor.create(container);
editor.setVisible(this.isVisible(), group);
return Promise.resolve(editor);
}
/**
* Sets input for the SQL editor after it has been created.
*/
private _onSqlEditorCreated(sqlEditor: TextResourceEditor, sqlInput: UntitledEditorInput, options: EditorOptions): Thenable<void> {
this._sqlEditor = sqlEditor;
return this._sqlEditor.setInput(sqlInput, options, CancellationToken.None);
}
/**
* Sets input for the results editor after it has been created.
*/
private _onResultsEditorCreated(resultsEditor: QueryResultsEditor, resultsInput: QueryResultsInput, options: EditorOptions): Promise<void> {
this._resultsEditor = resultsEditor;
return this._resultsEditor.setInput(resultsInput, options);
}
/**
* Appends the HTML for the SQL editor. Creates new HTML every time.
*/
private _createSqlEditorContainer() {
const parentElement = this.getContainer();
this._sqlEditorContainer = DOM.append(parentElement, DOM.$('.details-editor-container'));
this._sqlEditorContainer.style.position = 'absolute';
}
/**
* Appends the HTML for the QueryResultsEditor to the QueryEditor. If the HTML has not yet been
* created, it creates it and appends it. If it has already been created, it locates it and
* appends it.
*/
private _createResultsEditorContainer() {
this._createSash();
const parentElement = this.getContainer();
let input = <QueryInput>this.input;
if (!input.results.container) {
let cssClass: string = '.master-editor-container';
if (this._orientation === Orientation.HORIZONTAL) {
cssClass = '.master-editor-container-horizontal';
}
this._resultsEditorContainer = DOM.append(parentElement, DOM.$(cssClass));
this._resultsEditorContainer.style.position = 'absolute';
input.results.container = this._resultsEditorContainer;
} else {
this._resultsEditorContainer = DOM.append(parentElement, input.results.container);
}
}
/**
* Creates the sash with the requested orientation and registers sash callbacks
*/
private _createSash(): void {
if (!this._sash) {
let parentElement: HTMLElement = this.getContainer();
if (this._orientation === Orientation.HORIZONTAL) {
this._sash = this._register(new HorizontalFlexibleSash(parentElement, this._minEditorSize));
} else {
this._sash = this._register(new VerticalFlexibleSash(parentElement, this._minEditorSize));
this._sash.setEdge(this.getTaskBarHeight() + this._tabHeight);
}
this._setSashDimension();
this._register(this._sash.onPositionChange(position => this._doLayout()));
}
this.sash.show();
}
private _setSashDimension(): void {
if (!this._dimension) {
return;
}
if (this._orientation === Orientation.HORIZONTAL) {
this._sash.setDimenesion(this._dimension);
} else {
this._sash.setDimenesion(new DOM.Dimension(this._dimension.width, this._dimension.height - this.getTaskBarHeight()));
}
}
/**
* Updates the size of the 2 sub-editors. Uses agnostic dimensions due to the fact that
* the IFlexibleSash could be horizontal or vertical. The same logic is used for horizontal
* and vertical sashes.
*/
private _doLayout(skipResizeGridContent: boolean = false): void {
if (!this._isResultsEditorVisible() && this._sqlEditor) {
this._doLayoutSql();
return;
}
if (!this._sqlEditor || !this._resultsEditor || !this._dimension || !this._sash) {
return;
}
if (this._orientation === Orientation.HORIZONTAL) {
this._doLayoutHorizontal();
} else {
this._doLayoutVertical();
}
if (!skipResizeGridContent) {
this._resizeGridContents();
}
}
private getTaskBarHeight(): number {
let taskBarElement = this.taskbar.getContainer();
return DOM.getContentHeight(taskBarElement);
}
private _doLayoutHorizontal(): void {
let splitPointTop: number = this._sash.getSplitPoint();
let parent: ClientRect = this.getContainer().getBoundingClientRect();
let sqlEditorHeight = splitPointTop - (parent.top + this.getTaskBarHeight());
let titleBar = document.getElementById('workbench.parts.titlebar');
if (titleBar) {
sqlEditorHeight += DOM.getContentHeight(titleBar);
}
let queryResultsEditorHeight = parent.bottom - splitPointTop;
this._resultsEditorContainer.hidden = false;
this._sqlEditorContainer.style.height = `${sqlEditorHeight}px`;
this._sqlEditorContainer.style.width = `${this._dimension.width}px`;
this._sqlEditorContainer.style.top = `${this._editorTopOffset}px`;
this._resultsEditorContainer.style.height = `${queryResultsEditorHeight}px`;
this._resultsEditorContainer.style.width = `${this._dimension.width}px`;
this._resultsEditorContainer.style.top = `${splitPointTop}px`;
this._sqlEditor.layout(new DOM.Dimension(this._dimension.width, sqlEditorHeight));
this._resultsEditor.layout(new DOM.Dimension(this._dimension.width, queryResultsEditorHeight));
}
private _doLayoutVertical(): void {
let splitPointLeft: number = this._sash.getSplitPoint();
let parent: ClientRect = this.getContainer().getBoundingClientRect();
let sqlEditorWidth = splitPointLeft;
let queryResultsEditorWidth = parent.width - splitPointLeft;
let taskbarHeight = this.getTaskBarHeight();
this._sqlEditorContainer.style.width = `${sqlEditorWidth}px`;
this._sqlEditorContainer.style.height = `${this._dimension.height - taskbarHeight}px`;
this._sqlEditorContainer.style.left = `0px`;
this._resultsEditorContainer.hidden = false;
this._resultsEditorContainer.style.width = `${queryResultsEditorWidth}px`;
this._resultsEditorContainer.style.height = `${this._dimension.height - taskbarHeight}px`;
this._resultsEditorContainer.style.left = `${splitPointLeft}px`;
this._sqlEditor.layout(new DOM.Dimension(sqlEditorWidth, this._dimension.height - taskbarHeight));
this._resultsEditor.layout(new DOM.Dimension(queryResultsEditorWidth, this._dimension.height - taskbarHeight));
}
private _doLayoutSql() {
if (this._resultsEditorContainer) {
this._resultsEditorContainer.style.width = '0px';
this._resultsEditorContainer.style.height = '0px';
this._resultsEditorContainer.style.left = '0px';
this._resultsEditorContainer.hidden = true;
}
if (this._dimension) {
this._sqlEditor.layout(new DOM.Dimension(this._dimension.width, this._dimension.height - this.getTaskBarHeight()));
}
}
private _resizeGridContents(): void {
if (this._isResultsEditorVisible()) {
let queryInput: QueryInput = <QueryInput>this.input;
let uri: string = queryInput.getQueryResultsInputResource();
if (uri) {
this._queryModelService.resizeResultsets(uri);
}
}
}
private _disposeEditors(): void {
if (this._sqlEditor) {
this._sqlEditor.dispose();
this._sqlEditor = null;
}
if (this._resultsEditor) {
this._resultsEditor.dispose();
this._resultsEditor = null;
}
let thisEditorParent: HTMLElement = this.getContainer();
if (this._sqlEditorContainer) {
let sqlEditorParent: HTMLElement = this._sqlEditorContainer.parentElement;
if (sqlEditorParent && sqlEditorParent === thisEditorParent) {
this._sqlEditorContainer.parentElement.removeChild(this._sqlEditorContainer);
}
this._sqlEditorContainer = null;
}
if (this._resultsEditorContainer) {
let resultsEditorParent: HTMLElement = this._resultsEditorContainer.parentElement;
if (resultsEditorParent && resultsEditorParent === thisEditorParent) {
this._resultsEditorContainer.parentElement.removeChild(this._resultsEditorContainer);
}
this._resultsEditorContainer = null;
this.hideQueryResultsView = false;
}
}
/**
* Returns true if the QueryResultsInput has denoted that the results editor
* should be visible.
* Public for testing only.
*/
public _isResultsEditorVisible(): boolean {
let input: QueryInput = <QueryInput>this.input;
if (!input) {
return false;
}
return input.results.visible;
}
set resultsEditorVisibility(isVisible: boolean) {
let input: QueryInput = <QueryInput>this.input;
input.results.visible = isVisible;
}
/**
* Update the buttons on the taskbar to reflect the state of the current input.
*/
private _updateTaskbar(): void {
let queryInput: QueryInput = <QueryInput>this.input;
if (queryInput) {
this._cancelQueryAction.enabled = queryInput.cancelQueryEnabled;
this._changeConnectionAction.enabled = queryInput.changeConnectionEnabled;
// For the toggle database action, it should always be enabled since it's a toggle.
// We use inverse of connect enabled state for now, should refactor queryInput in the future to
// define connected as a boolean instead of using the enabled flag
this._toggleConnectDatabaseAction.enabled = true;
this._toggleConnectDatabaseAction.connected = !queryInput.connectEnabled;
this._runQueryAction.enabled = queryInput.runQueryEnabled;
if (queryInput.listDatabasesConnected) {
this.listDatabasesActionItem.onConnected();
} else {
this.listDatabasesActionItem.onDisconnect();
}
}
}
/**
* Sets the text selection for the SQL editor based on the given ISelectionData.
*/
private _setSelection(selection: ISelectionData): void {
let rangeConversion: IRange = {
startLineNumber: selection.startLine + 1,
startColumn: selection.startColumn + 1,
endLineNumber: selection.endLine + 1,
endColumn: selection.endColumn + 1
};
let editor = this._sqlEditor.getControl();
editor.revealRange(rangeConversion);
editor.setSelection(rangeConversion);
editor.focus();
}
private saveEditorViewState(): void {
let queryInput = this.input as QueryInput;
if (queryInput) {
if (this._sqlEditor) {
queryInput.savedViewState = this._sqlEditor.getControl().saveViewState();
}
if (queryInput.results) {
queryInput.results.onSaveViewStateEmitter.fire();
}
}
}
// TESTING PROPERTIES ////////////////////////////////////////////////////////////
public get resultsEditor(): QueryResultsEditor {
return this._resultsEditor;
}
public get sqlEditor(): TextResourceEditor {
return this._sqlEditor;
}
public get taskbar(): Taskbar {
return this._taskbar;
}
public get sash(): IFlexibleSash {
return this._sash;
}
public get resultsEditorContainer(): HTMLElement {
return this._resultsEditorContainer;
}
public get sqlEditorContainer(): HTMLElement {
return this._sqlEditorContainer;
}
public get taskbarContainer(): HTMLElement {
return this._taskbarContainer;
}
public get runQueryAction(): RunQueryAction {
return this._runQueryAction;
}
public get cancelQueryAction(): CancelQueryAction {
return this._cancelQueryAction;
}
public get changeConnectionAction(): ConnectDatabaseAction {
return this._changeConnectionAction;
}
public registerQueryModelViewTab(title: string, componentId: string): void {
this._resultsEditor.registerQueryModelViewTab(title, componentId);
}
}

View File

@@ -0,0 +1,170 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { EditorOptions } from 'vs/workbench/common/editor';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { BareFontInfo } from 'vs/editor/common/config/fontInfo';
import { getZoomLevel } from 'vs/base/browser/browser';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import * as DOM from 'vs/base/browser/dom';
import * as types from 'vs/base/common/types';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { QueryResultsInput } from 'sql/workbench/parts/query/common/queryResultsInput';
import { IQueryModelService } from 'sql/platform/query/common/queryModel';
import { QueryResultsView } from 'sql/workbench/parts/query/browser/queryResultsView';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IStorageService } from 'vs/platform/storage/common/storage';
export const RESULTS_GRID_DEFAULTS = {
cellPadding: [5, 8, 4],
rowHeight: 24
};
export const TextCompareEditorVisible = new RawContextKey<boolean>('textCompareEditorVisible', false);
export class BareResultsGridInfo extends BareFontInfo {
public static createFromRawSettings(opts: {
fontFamily?: string;
fontWeight?: string;
fontSize?: number | string;
lineHeight?: number | string;
letterSpacing?: number | string;
cellPadding?: number | number[];
}, zoomLevel: number): BareResultsGridInfo {
let cellPadding = !types.isUndefinedOrNull(opts.cellPadding) ? opts.cellPadding : RESULTS_GRID_DEFAULTS.cellPadding;
return new BareResultsGridInfo(BareFontInfo.createFromRawSettings(opts, zoomLevel), { cellPadding });
}
readonly cellPadding: number | number[];
protected constructor(fontInfo: BareFontInfo, opts: {
cellPadding: number | number[];
}) {
super({
zoomLevel: fontInfo.zoomLevel,
fontFamily: fontInfo.fontFamily,
fontWeight: fontInfo.fontWeight,
fontSize: fontInfo.fontSize,
lineHeight: fontInfo.lineHeight,
letterSpacing: fontInfo.letterSpacing
});
this.cellPadding = opts.cellPadding;
}
}
function getBareResultsGridInfoStyles(info: BareResultsGridInfo): string {
let content = '';
if (info.fontFamily) {
content += `font-family: ${info.fontFamily};`;
}
if (info.fontWeight) {
content += `font-weight: ${info.fontWeight};`;
}
if (info.fontSize) {
content += `font-size: ${info.fontSize}px;`;
}
if (info.lineHeight) {
content += `line-height: ${info.lineHeight}px;`;
}
if (info.letterSpacing) {
content += `letter-spacing: ${info.letterSpacing}px;`;
}
return content;
}
/**
* Editor associated with viewing and editing the data of a query results grid.
*/
export class QueryResultsEditor extends BaseEditor {
public static ID: string = 'workbench.editor.queryResultsEditor';
public static AngularSelectorString: string = 'slickgrid-container.slickgridContainer';
protected _rawOptions: BareResultsGridInfo;
private resultsView: QueryResultsView;
private styleSheet = DOM.createStyleSheet();
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@IThemeService themeService: IThemeService,
@IQueryModelService private _queryModelService: IQueryModelService,
@IConfigurationService private _configurationService: IConfigurationService,
@IInstantiationService private _instantiationService: IInstantiationService,
@IStorageService storageService: IStorageService
) {
super(QueryResultsEditor.ID, telemetryService, themeService, storageService);
this._rawOptions = BareResultsGridInfo.createFromRawSettings(this._configurationService.getValue('resultsGrid'), getZoomLevel());
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 as QueryResultsInput;
}
private applySettings() {
let cssRuleText = '';
if (types.isNumber(this._rawOptions.cellPadding)) {
cssRuleText = this._rawOptions.cellPadding + 'px';
} else {
cssRuleText = this._rawOptions.cellPadding.join('px ') + 'px;';
}
let content = `.grid-panel .monaco-table .slick-cell { padding: ${cssRuleText} }`;
content += `.grid-panel .monaco-table, .message-tree { ${getBareResultsGridInfoStyles(this._rawOptions)} }`;
this.styleSheet.innerHTML = content;
}
createEditor(parent: HTMLElement): void {
this.styleSheet.remove();
parent.appendChild(this.styleSheet);
if (!this.resultsView) {
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);
}
setInput(input: QueryResultsInput, options: EditorOptions): Promise<void> {
super.setInput(input, options, CancellationToken.None);
this.resultsView.input = input;
return Promise.resolve<void>(null);
}
clearInput() {
this.resultsView.clearInput();
super.clearInput();
}
public chart(dataId: { batchId: number, resultId: number }) {
this.resultsView.chartData(dataId);
}
public showQueryPlan(xml: string) {
this.resultsView.showPlan(xml);
}
public registerQueryModelViewTab(title: string, componentId: string): void {
this.resultsView.registerQueryModelViewTab(title, componentId);
}
}

View File

@@ -0,0 +1,364 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { QueryResultsInput, ResultsViewState } from 'sql/workbench/parts/query/common/queryResultsInput';
import { TabbedPanel, IPanelTab, IPanelView } from 'sql/base/browser/ui/panel/panel';
import { IQueryModelService } from 'sql/platform/query/common/queryModel';
import QueryRunner from 'sql/platform/query/common/queryRunner';
import { MessagePanel } from './messagePanel';
import { GridPanel } from '../electron-browser/gridPanel';
import { ChartTab } from '../../charts/browser/chartTab';
import { QueryPlanTab } from 'sql/workbench/parts/queryPlan/electron-browser/queryPlan';
import { TopOperationsTab } from 'sql/workbench/parts/queryPlan/browser/topOperations';
import { QueryModelViewTab } from 'sql/workbench/parts/query/modelViewTab/queryModelViewTab';
import * as nls from 'vs/nls';
import { PanelViewlet } from 'vs/workbench/browser/parts/views/panelViewlet';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import * as DOM from 'vs/base/browser/dom';
import { Event } from 'vs/base/common/event';
import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle';
class ResultsView extends Disposable implements IPanelView {
private panelViewlet: PanelViewlet;
private gridPanel: GridPanel;
private messagePanel: MessagePanel;
private container = document.createElement('div');
private currentDimension: DOM.Dimension;
private needsGridResize = false;
private _state: ResultsViewState;
constructor(private instantiationService: IInstantiationService) {
super();
this.panelViewlet = this._register(this.instantiationService.createInstance(PanelViewlet, 'resultsView', { showHeaderInTitleWhenSingleView: false }));
this.gridPanel = this._register(this.instantiationService.createInstance(GridPanel, { title: nls.localize('gridPanel', 'Results'), id: 'gridPanel' }));
this.messagePanel = this._register(this.instantiationService.createInstance(MessagePanel, { title: nls.localize('messagePanel', 'Messages'), minimumBodySize: 0, id: 'messagePanel' }));
this.gridPanel.render();
this.messagePanel.render();
this.panelViewlet.create(this.container);
this.gridPanel.setVisible(false);
this.panelViewlet.addPanels([
{ panel: this.messagePanel, size: this.messagePanel.minimumSize, index: 1 }
]);
Event.any(this.gridPanel.onDidChange, this.messagePanel.onDidChange)(e => {
let size = this.gridPanel.maximumBodySize;
if (size < 1 && this.gridPanel.isVisible()) {
this.gridPanel.setVisible(false);
this.panelViewlet.removePanels([this.gridPanel]);
this.gridPanel.layout(0);
} else if (size > 0 && !this.gridPanel.isVisible()) {
this.gridPanel.setVisible(true);
let panelSize: number;
if (this.state && this.state.gridPanelSize) {
panelSize = this.state.gridPanelSize;
} else if (this.currentDimension) {
panelSize = Math.round(this.currentDimension.height * 0.7);
} else {
panelSize = 200;
this.needsGridResize = true;
}
this.panelViewlet.addPanels([{ panel: this.gridPanel, index: 0, size: panelSize }]);
}
});
let resizeList = Event.any(this.gridPanel.onDidChange, this.messagePanel.onDidChange)(() => {
let panelSize: number;
if (this.state && this.state.gridPanelSize) {
panelSize = this.state.gridPanelSize;
} else if (this.currentDimension) {
panelSize = Math.round(this.currentDimension.height * 0.7);
} else {
panelSize = 200;
this.needsGridResize = true;
}
if (this.state.messagePanelSize) {
this.panelViewlet.resizePanel(this.gridPanel, this.state.messagePanelSize);
}
this.panelViewlet.resizePanel(this.gridPanel, panelSize);
});
// once the user changes the sash we should stop trying to resize the grid
Event.once(this.panelViewlet.onDidSashChange)(e => {
this.needsGridResize = false;
resizeList.dispose();
});
this.panelViewlet.onDidSashChange(e => {
if (this.state) {
if (this.gridPanel.isExpanded()) {
this.state.gridPanelSize = this.panelViewlet.getPanelSize(this.gridPanel);
}
if (this.messagePanel.isExpanded()) {
this.state.messagePanelSize = this.panelViewlet.getPanelSize(this.messagePanel);
}
}
});
}
render(container: HTMLElement): void {
container.appendChild(this.container);
}
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;
if (this.needsGridResize) {
this.panelViewlet.resizePanel(this.gridPanel, this.state.gridPanelSize || Math.round(this.currentDimension.height * 0.7));
// we have the right scroll position saved as part of gridPanel state, use this to re-position scrollbar
this.gridPanel.resetScrollPosition();
}
}
dispose() {
super.dispose();
}
public clear() {
this.gridPanel.clear();
this.messagePanel.clear();
}
remove(): void {
this.container.remove();
}
public set queryRunner(runner: QueryRunner) {
this.gridPanel.queryRunner = runner;
this.messagePanel.queryRunner = runner;
}
public hideResultHeader() {
this.gridPanel.headerVisible = false;
}
public set state(val: ResultsViewState) {
this._state = val;
this.gridPanel.state = val.gridPanelState;
this.messagePanel.state = val.messagePanelState;
}
public get state(): ResultsViewState {
return this._state;
}
}
class ResultsTab implements IPanelTab {
public readonly title = nls.localize('resultsTabTitle', 'Results');
public readonly identifier = 'resultsTab';
public readonly view: ResultsView;
constructor(instantiationService: IInstantiationService) {
this.view = new ResultsView(instantiationService);
}
public set queryRunner(runner: QueryRunner) {
this.view.queryRunner = runner;
}
public dispose() {
dispose(this.view);
}
public clear() {
this.view.clear();
}
}
export class QueryResultsView extends Disposable {
private _panelView: TabbedPanel;
private _input: QueryResultsInput;
private resultsTab: ResultsTab;
private chartTab: ChartTab;
private qpTab: QueryPlanTab;
private topOperationsTab: TopOperationsTab;
private dynamicModelViewTabs: QueryModelViewTab[] = [];
private runnerDisposables: IDisposable[];
constructor(
container: HTMLElement,
@IInstantiationService private instantiationService: IInstantiationService,
@IQueryModelService private queryModelService: IQueryModelService
) {
super();
this.resultsTab = this._register(new ResultsTab(instantiationService));
this.chartTab = this._register(new ChartTab(instantiationService));
this._panelView = this._register(new TabbedPanel(container, { showHeaderWhenSingleView: false }));
this.qpTab = this._register(new QueryPlanTab());
this.topOperationsTab = this._register(new TopOperationsTab(instantiationService));
this._panelView.pushTab(this.resultsTab);
this._register(this._panelView.onTabChange(e => {
if (this.input) {
this.input.state.activeTab = e;
}
}));
}
public style() {
}
private setQueryRunner(runner: QueryRunner) {
this.resultsTab.queryRunner = runner;
this.chartTab.queryRunner = runner;
this.runnerDisposables.push(runner.onQueryStart(e => {
this.hideChart();
this.hidePlan();
this.hideDynamicViewModelTabs();
this.input.state.visibleTabs = new Set();
this.input.state.activeTab = this.resultsTab.identifier;
}));
if (this.input.state.visibleTabs.has(this.chartTab.identifier)) {
if (!this._panelView.contains(this.chartTab)) {
this._panelView.pushTab(this.chartTab);
}
}
if (this.input.state.visibleTabs.has(this.qpTab.identifier)) {
if (!this._panelView.contains(this.qpTab)) {
this._panelView.pushTab(this.qpTab);
}
}
if (this.input.state.visibleTabs.has(this.topOperationsTab.identifier)) {
if (!this._panelView.contains(this.topOperationsTab)) {
this._panelView.pushTab(this.topOperationsTab);
}
}
// restore query model view tabs
this.input.state.visibleTabs.forEach(tabId => {
if (tabId.startsWith('querymodelview;')) {
// tab id format is 'tab type;title;model view id'
let parts = tabId.split(';');
if (parts.length === 3) {
let tab = this._register(new QueryModelViewTab(parts[1], this.instantiationService));
tab.view._componentId = parts[2];
this.dynamicModelViewTabs.push(tab);
if (!this._panelView.contains(tab)) {
this._panelView.pushTab(tab);
}
}
}
});
this.runnerDisposables.push(runner.onQueryEnd(() => {
if (runner.isQueryPlan) {
runner.planXml.then(e => {
this.showPlan(e);
});
}
}));
if (this.input.state.activeTab) {
this._panelView.showTab(this.input.state.activeTab);
}
}
public set input(input: QueryResultsInput) {
this._input = input;
dispose(this.runnerDisposables);
this.runnerDisposables = [];
this.resultsTab.view.state = this.input.state;
this.qpTab.view.state = this.input.state.queryPlanState;
this.topOperationsTab.view.state = this.input.state.topOperationsState;
this.chartTab.view.state = this.input.state.chartState;
let info = this.queryModelService._getQueryInfo(input.uri);
if (info) {
this.setQueryRunner(info.queryRunner);
} else {
let disposeable = this.queryModelService.onRunQueryStart(c => {
if (c === input.uri) {
let info = this.queryModelService._getQueryInfo(input.uri);
this.setQueryRunner(info.queryRunner);
disposeable.dispose();
}
});
}
}
clearInput() {
this._input = undefined;
this.resultsTab.clear();
this.qpTab.clear();
this.topOperationsTab.clear();
this.chartTab.clear();
}
public get input(): QueryResultsInput {
return this._input;
}
public layout(dimension: DOM.Dimension) {
this._panelView.layout(dimension);
}
public chartData(dataId: { resultId: number, batchId: number }): void {
this.input.state.visibleTabs.add(this.chartTab.identifier);
if (!this._panelView.contains(this.chartTab)) {
this._panelView.pushTab(this.chartTab);
}
this._panelView.showTab(this.chartTab.identifier);
this.chartTab.chart(dataId);
}
public hideChart() {
if (this._panelView.contains(this.chartTab)) {
this._panelView.removeTab(this.chartTab.identifier);
}
}
public showPlan(xml: string) {
this.input.state.visibleTabs.add(this.qpTab.identifier);
if (!this._panelView.contains(this.qpTab)) {
this._panelView.pushTab(this.qpTab);
}
this.input.state.visibleTabs.add(this.topOperationsTab.identifier);
if (!this._panelView.contains(this.topOperationsTab)) {
this._panelView.pushTab(this.topOperationsTab);
}
this._panelView.showTab(this.qpTab.identifier);
this.qpTab.view.showPlan(xml);
this.topOperationsTab.view.showPlan(xml);
}
public hidePlan() {
if (this._panelView.contains(this.qpTab)) {
this._panelView.removeTab(this.qpTab.identifier);
}
if (this._panelView.contains(this.topOperationsTab)) {
this._panelView.removeTab(this.topOperationsTab.identifier);
}
}
public hideDynamicViewModelTabs() {
this.dynamicModelViewTabs.forEach(tab => {
if (this._panelView.contains(tab)) {
this._panelView.removeTab(tab.identifier);
}
});
this.dynamicModelViewTabs = [];
}
public dispose() {
dispose(this.runnerDisposables);
super.dispose();
}
public registerQueryModelViewTab(title: string, componentId: string): void {
let tab = this._register(new QueryModelViewTab(title, this.instantiationService));
tab.view._componentId = componentId;
this.dynamicModelViewTabs.push(tab);
this.input.state.visibleTabs.add('querymodelview;' + title + ';' + componentId);
if (!this._panelView.contains(tab)) {
this._panelView.pushTab(tab);
}
}
}

View File

@@ -0,0 +1,117 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { $, append, show, hide } from 'vs/base/browser/dom';
import { IDisposable, combinedDisposable } from 'vs/base/common/lifecycle';
import { IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar';
import { IEditorCloseEvent } from 'vs/workbench/common/editor';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IQueryModelService } from 'sql/platform/query/common/queryModel';
import * as LocalizedConstants from 'sql/workbench/parts/query/common/localizedConstants';
import * as WorkbenchUtils from 'sql/workbench/common/sqlWorkbenchUtils';
import { EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor';
// Query execution status
enum QueryExecutionStatus {
Executing,
Completed
}
// Shows query status in the editor
export class QueryStatusbarItem implements IStatusbarItem {
private _element: HTMLElement;
private _queryElement: HTMLElement;
private _queryStatusEditors: { [editorUri: string]: QueryExecutionStatus };
private _toDispose: IDisposable[];
constructor(
@IQueryModelService private _queryModelService: IQueryModelService,
@IEditorService private _editorService: EditorServiceImpl
) {
this._queryStatusEditors = {};
}
public render(container: HTMLElement): IDisposable {
this._element = append(container, $('.query-statusbar-group'));
this._queryElement = append(this._element, $('div.query-statusbar-item'));
hide(this._queryElement);
this._toDispose = [];
this._toDispose.push(
this._queryModelService.onRunQueryStart((uri: string) => this._onRunQueryStart(uri)),
this._queryModelService.onRunQueryComplete((uri: string) => this._onRunQueryComplete(uri)),
this._editorService.onDidVisibleEditorsChange(() => this._onEditorsChanged()),
this._editorService.onDidCloseEditor(event => this._onEditorClosed(event))
);
return combinedDisposable(this._toDispose);
}
private _onEditorClosed(event: IEditorCloseEvent): void {
let uri = WorkbenchUtils.getEditorUri(event.editor);
if (uri && uri in this._queryStatusEditors) {
// If active editor is being closed, hide the query status.
let activeEditor = this._editorService.activeControl;
if (activeEditor) {
let currentUri = WorkbenchUtils.getEditorUri(activeEditor.input);
if (uri === currentUri) {
hide(this._queryElement);
}
}
delete this._queryStatusEditors[uri];
}
}
private _onEditorsChanged(): void {
let activeEditor = this._editorService.activeControl;
if (activeEditor) {
let uri = WorkbenchUtils.getEditorUri(activeEditor.input);
// Show active editor's query status
if (uri && uri in this._queryStatusEditors) {
this._showStatus(uri);
} else {
hide(this._queryElement);
}
} else {
hide(this._queryElement);
}
}
private _onRunQueryStart(uri: string): void {
this._updateStatus(uri, QueryExecutionStatus.Executing);
}
private _onRunQueryComplete(uri: string): void {
this._updateStatus(uri, QueryExecutionStatus.Completed);
}
// Update query status for the editor
private _updateStatus(uri: string, newStatus: QueryExecutionStatus) {
if (uri) {
this._queryStatusEditors[uri] = newStatus;
this._showStatus(uri);
}
}
// Show/hide query status for active editor
private _showStatus(uri: string): void {
let activeEditor = this._editorService.activeControl;
if (activeEditor) {
let currentUri = WorkbenchUtils.getEditorUri(activeEditor.input);
if (uri === currentUri) {
switch (this._queryStatusEditors[uri]) {
case QueryExecutionStatus.Executing:
this._queryElement.textContent = LocalizedConstants.msgStatusRunQueryInProgress;
show(this._queryElement);
break;
default:
hide(this._queryElement);
}
}
}
}
}

View File

@@ -0,0 +1,90 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as WorkbenchUtils from 'sql/workbench/common/sqlWorkbenchUtils';
import { IQueryModelService } from 'sql/platform/query/common/queryModel';
import QueryRunner from 'sql/platform/query/common/queryRunner';
import { IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar';
import { IDisposable, combinedDisposable, dispose } from 'vs/base/common/lifecycle';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IEditorCloseEvent } from 'vs/workbench/common/editor';
import { append, $, hide, show } from 'vs/base/browser/dom';
import * as nls from 'vs/nls';
import { EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor';
export class RowCountStatusBarItem implements IStatusbarItem {
private _element: HTMLElement;
private _flavorElement: HTMLElement;
private dispose: IDisposable;
constructor(
@IEditorService private _editorService: EditorServiceImpl,
@IQueryModelService private _queryModelService: IQueryModelService
) { }
render(container: HTMLElement): IDisposable {
let disposables = [
this._editorService.onDidVisibleEditorsChange(() => this._onEditorsChanged()),
this._editorService.onDidCloseEditor(event => this._onEditorClosed(event))
];
this._element = append(container, $('.query-statusbar-group'));
this._flavorElement = append(this._element, $('.editor-status-selection'));
this._flavorElement.title = nls.localize('rowStatus', "Row Count");
hide(this._flavorElement);
this._showStatus();
return combinedDisposable(disposables);
}
private _onEditorsChanged() {
this._showStatus();
}
private _onEditorClosed(event: IEditorCloseEvent) {
hide(this._flavorElement);
}
// Show/hide query status for active editor
private _showStatus(): void {
hide(this._flavorElement);
dispose(this.dispose);
let activeEditor = this._editorService.activeControl;
if (activeEditor) {
let currentUri = WorkbenchUtils.getEditorUri(activeEditor.input);
if (currentUri) {
let queryRunner = this._queryModelService.getQueryRunner(currentUri);
if (queryRunner) {
if (queryRunner.hasCompleted) {
this._displayValue(queryRunner);
}
this.dispose = queryRunner.onQueryEnd(e => {
this._displayValue(queryRunner);
});
} else {
this.dispose = this._queryModelService.onRunQueryComplete(e => {
if (e === currentUri) {
this._displayValue(this._queryModelService.getQueryRunner(currentUri));
}
});
}
}
}
}
private _displayValue(runner: QueryRunner) {
let rowCount = runner.batchSets.reduce((p, c) => {
return p + c.resultSetSummaries.reduce((rp, rc) => {
return rp + rc.rowCount;
}, 0);
}, 0);
this._flavorElement.innerText = nls.localize('rowCount', "{0} rows", rowCount);
show(this._flavorElement);
}
}

View File

@@ -0,0 +1,112 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as WorkbenchUtils from 'sql/workbench/common/sqlWorkbenchUtils';
import { IQueryModelService } from 'sql/platform/query/common/queryModel';
import QueryRunner from 'sql/platform/query/common/queryRunner';
import { parseNumAsTimeString } from 'sql/platform/connection/common/utils';
import { IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar';
import { IDisposable, combinedDisposable, dispose } from 'vs/base/common/lifecycle';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IEditorCloseEvent } from 'vs/workbench/common/editor';
import { append, $, hide, show } from 'vs/base/browser/dom';
import * as nls from 'vs/nls';
import { EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor';
import { IntervalTimer } from 'vs/base/common/async';
export class TimeElapsedStatusBarItem implements IStatusbarItem {
private _element: HTMLElement;
private _flavorElement: HTMLElement;
private dispose: IDisposable[] = [];
private intervalTimer = new IntervalTimer();
constructor(
@IEditorService private _editorService: EditorServiceImpl,
@IQueryModelService private _queryModelService: IQueryModelService
) { }
render(container: HTMLElement): IDisposable {
let disposables = [
this._editorService.onDidVisibleEditorsChange(() => this._onEditorsChanged()),
this._editorService.onDidCloseEditor(event => this._onEditorClosed(event))
];
this._element = append(container, $('.query-statusbar-group'));
this._flavorElement = append(this._element, $('.editor-status-selection'));
this._flavorElement.title = nls.localize('timeElapsed', "Time Elapsed");
hide(this._flavorElement);
this._showStatus();
return combinedDisposable(disposables);
}
private _onEditorsChanged() {
this._showStatus();
}
private _onEditorClosed(event: IEditorCloseEvent) {
hide(this._flavorElement);
}
// Show/hide query status for active editor
private _showStatus(): void {
this.intervalTimer.cancel();
hide(this._flavorElement);
dispose(this.dispose);
this._flavorElement.innerText = '';
this.dispose = [];
let activeEditor = this._editorService.activeControl;
if (activeEditor) {
let currentUri = WorkbenchUtils.getEditorUri(activeEditor.input);
if (currentUri) {
let queryRunner = this._queryModelService.getQueryRunner(currentUri);
if (queryRunner) {
if (queryRunner.hasCompleted || queryRunner.isExecuting) {
this._displayValue(queryRunner);
}
this.dispose.push(queryRunner.onQueryStart(e => {
this._displayValue(queryRunner);
}));
this.dispose.push(queryRunner.onQueryEnd(e => {
this._displayValue(queryRunner);
}));
} else {
this.dispose.push(this._queryModelService.onRunQueryStart(e => {
if (e === currentUri) {
this._displayValue(this._queryModelService.getQueryRunner(currentUri));
}
}));
this.dispose.push(this._queryModelService.onRunQueryComplete(e => {
if (e === currentUri) {
this._displayValue(this._queryModelService.getQueryRunner(currentUri));
}
}));
}
}
}
}
private _displayValue(runner: QueryRunner) {
this.intervalTimer.cancel();
if (runner.isExecuting) {
this.intervalTimer.cancelAndSet(() => {
let value = runner.queryStartTime ? Date.now() - runner.queryStartTime.getTime() : 0;
this._flavorElement.innerText = parseNumAsTimeString(value, false);
}, 1000);
let value = runner.queryStartTime ? Date.now() - runner.queryStartTime.getTime() : 0;
this._flavorElement.innerText = parseNumAsTimeString(value, false);
} else {
let value = runner.queryStartTime && runner.queryEndTime
? runner.queryEndTime.getTime() - runner.queryStartTime.getTime() : 0;
this._flavorElement.innerText = parseNumAsTimeString(value, false);
}
show(this._flavorElement);
}
}

View File

@@ -0,0 +1,27 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export const copyIncludeHeaders = 'copyIncludeHeaders';
export const configSaveAsCsv = 'saveAsCsv';
export const configSaveAsXml = 'saveAsXml';
export const configCopyRemoveNewLine = 'copyRemoveNewLine';
export const configShowBatchTime = 'showBatchTime';
export const querySection = 'query';
export const shortcutStart = 'shortcut';
export const tabColorModeOff = 'off';
export const tabColorModeBorder = 'border';
export const tabColorModeFill = 'fill';
export const defaultChartType = 'defaultChartType';
export const chartTypeBar = 'bar';
export const chartTypeDoughnut = 'doughnut';
export const chartTypeHorizontalBar = 'horizontalBar';
export const chartTypeLine = 'line';
export const chartTypePie = 'pie';
export const chartTypeScatter = 'scatter';
export const chartTypeTimeSeries = 'timeSeries';
export const allChartTypes = [chartTypeBar, chartTypeDoughnut, chartTypeHorizontalBar, chartTypeLine,
chartTypePie, chartTypeScatter, chartTypeTimeSeries];

View File

@@ -0,0 +1,38 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
// localizable strings
export const runQueryBatchStartMessage = localize('runQueryBatchStartMessage', 'Started executing query at ');
export const runQueryBatchStartLine = localize('runQueryBatchStartLine', 'Line {0}');
export const msgCancelQueryFailed = localize('msgCancelQueryFailed', 'Canceling the query failed: {0}');
export const msgSaveStarted = localize('msgSaveStarted', 'Started saving results to ');
export const msgSaveFailed = localize('msgSaveFailed', 'Failed to save results. ');
export const msgSaveSucceeded = localize('msgSaveSucceeded', 'Successfully saved results to ');
export const msgStatusRunQueryInProgress = localize('msgStatusRunQueryInProgress', 'Executing query...');
// /** Results Pane Labels */
export const maximizeLabel = localize('maximizeLabel', 'Maximize');
export const restoreLabel = localize('resultsPane.restoreLabel', 'Restore');
export const saveCSVLabel = localize('saveCSVLabel', 'Save as CSV');
export const saveJSONLabel = localize('saveJSONLabel', 'Save as JSON');
export const saveExcelLabel = localize('saveExcelLabel', 'Save as Excel');
export const saveXMLLabel = localize('saveXMLLabel', 'Save as XML');
export const viewChartLabel = localize('viewChartLabel', 'View as Chart');
export const resultPaneLabel = localize('resultPaneLabel', 'Results');
export const executeQueryLabel = localize('executeQueryLabel', 'Executing query ');
/** Messages Pane Labels */
export const messagePaneLabel = localize('messagePaneLabel', 'Messages');
export const elapsedTimeLabel = localize('elapsedTimeLabel', 'Total execution time: {0}');
/** Warning message for save icons */
export const msgCannotSaveMultipleSelections = localize('msgCannotSaveMultipleSelections', 'Save results command cannot be used with multiple selections.');

View File

@@ -0,0 +1,19 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
/**
* Context Keys to use with keybindings for the results grid and messages used in query and edit data views
*/
export const queryEditorVisibleId = 'queryEditorVisible';
export const resultsVisibleId = 'resultsVisible';
export const resultsGridFocussedId = 'resultsGridFocussed';
export const resultsMessagesFocussedId = 'resultsMessagesFocussed';
export const QueryEditorVisibleContext = new RawContextKey<boolean>(queryEditorVisibleId, false);
export const ResultsVisibleContext = new RawContextKey<boolean>(resultsVisibleId, false);
export const ResultsGridFocussedContext = new RawContextKey<boolean>(resultsGridFocussedId, false);
export const ResultsMessagesFocussedContext = new RawContextKey<boolean>(resultsMessagesFocussedId, false);

View File

@@ -0,0 +1,319 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { Event, Emitter } from 'vs/base/common/event';
import { URI } from 'vs/base/common/uri';
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
import { EditorInput, EditorModel, ConfirmResult, EncodingMode, IEncodingSupport } from 'vs/workbench/common/editor';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IEditorViewState } from 'vs/editor/common/editorCommon';
import { IConnectionManagementService, IConnectableInput, INewConnectionParams, RunQueryOnConnectionMode } from 'sql/platform/connection/common/connectionManagement';
import { QueryResultsInput } from 'sql/workbench/parts/query/common/queryResultsInput';
import { IQueryModelService } from 'sql/platform/query/common/queryModel';
import { IQueryEditorService } from 'sql/workbench/services/queryEditor/common/queryEditorService';
import { ISelectionData, ExecutionPlanOptions } from 'azdata';
const MAX_SIZE = 13;
function trimTitle(title: string): string {
const length = title.length;
const diff = length - MAX_SIZE;
if (Math.sign(diff) <= 0) {
return title;
} else {
const start = (length / 2) - (diff / 2);
return title.slice(0, start) + '...' + title.slice(start + diff, length);
}
}
/**
* Input for the QueryEditor. This input is simply a wrapper around a QueryResultsInput for the QueryResultsEditor
* and a UntitledEditorInput for the SQL File Editor.
*/
export class QueryInput extends EditorInput implements IEncodingSupport, IConnectableInput, IDisposable {
public static ID: string = 'workbench.editorinputs.queryInput';
public static SCHEMA: string = 'sql';
private _runQueryEnabled: boolean;
private _cancelQueryEnabled: boolean;
private _connectEnabled: boolean;
private _disconnectEnabled: boolean;
private _changeConnectionEnabled: boolean;
private _listDatabasesConnected: boolean;
private _updateTaskbar: Emitter<void>;
private _showQueryResultsEditor: Emitter<void>;
private _updateSelection: Emitter<ISelectionData>;
private _currentEventCallbacks: IDisposable[];
public savedViewState: IEditorViewState;
constructor(
private _description: string,
private _sql: UntitledEditorInput,
private _results: QueryResultsInput,
private _connectionProviderName: string,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
@IQueryModelService private _queryModelService: IQueryModelService,
@IQueryEditorService private _queryEditorService: IQueryEditorService,
@IConfigurationService private _configurationService: IConfigurationService
) {
super();
this._updateTaskbar = new Emitter<void>();
this._showQueryResultsEditor = new Emitter<void>();
this._updateSelection = new Emitter<ISelectionData>();
this._toDispose = [];
this._currentEventCallbacks = [];
// re-emit sql editor events through this editor if it exists
if (this._sql) {
this._toDispose.push(this._sql.onDidChangeDirty(() => this._onDidChangeDirty.fire()));
}
// Attach to event callbacks
if (this._queryModelService) {
// Register callbacks for the Actions
this._toDispose.push(
this._queryModelService.onRunQueryStart(uri => {
if (this.uri === uri) {
this.onRunQuery();
}
})
);
this._toDispose.push(
this._queryModelService.onRunQueryComplete(uri => {
if (this.uri === uri) {
this.onQueryComplete();
}
})
);
}
if (this._connectionManagementService) {
this._toDispose.push(this._connectionManagementService.onDisconnect(result => {
if (result.connectionUri === this.uri) {
this.onDisconnect();
}
}));
if (this.uri) {
if (this._connectionProviderName) {
this._connectionManagementService.doChangeLanguageFlavor(this.uri, 'sql', this._connectionProviderName);
} else {
this._connectionManagementService.ensureDefaultLanguageFlavor(this.uri);
}
}
}
if (this._configurationService) {
this._toDispose.push(this._configurationService.onDidChangeConfiguration(e => {
if (e.affectedKeys.includes('sql.showConnectionInfoInTitle')) {
this._onDidChangeLabel.fire();
}
}));
}
this.onDisconnect();
this.onQueryComplete();
}
// Getters for private properties
public get uri(): string { return this.getResource().toString(); }
public get sql(): UntitledEditorInput { return this._sql; }
public get results(): QueryResultsInput { return this._results; }
public get updateTaskbarEvent(): Event<void> { return this._updateTaskbar.event; }
public get showQueryResultsEditorEvent(): Event<void> { return this._showQueryResultsEditor.event; }
public get updateSelectionEvent(): Event<ISelectionData> { return this._updateSelection.event; }
public get runQueryEnabled(): boolean { return this._runQueryEnabled; }
public get cancelQueryEnabled(): boolean { return this._cancelQueryEnabled; }
public get connectEnabled(): boolean { return this._connectEnabled; }
public get disconnectEnabled(): boolean { return this._disconnectEnabled; }
public get changeConnectionEnabled(): boolean { return this._changeConnectionEnabled; }
public get listDatabasesConnected(): boolean { return this._listDatabasesConnected; }
public getQueryResultsInputResource(): string { return this._results.uri; }
public showQueryResultsEditor(): void { this._showQueryResultsEditor.fire(); }
public updateSelection(selection: ISelectionData): void { this._updateSelection.fire(selection); }
public getTypeId(): string { return QueryInput.ID; }
// Description is shown beside the tab name in the combobox of open editors
public getDescription(): string { return this._description; }
public supportsSplitEditor(): boolean { return false; }
public getModeId(): string { return QueryInput.SCHEMA; }
public revert(): Promise<boolean> { return this._sql.revert(); }
public matches(otherInput: any): boolean {
if (otherInput instanceof QueryInput) {
return this._sql.matches(otherInput.sql);
}
return this._sql.matches(otherInput);
}
// Forwarding resource functions to the inline sql file editor
public get onDidModelChangeContent(): Event<void> { return this._sql.onDidModelChangeContent; }
public get onDidModelChangeEncoding(): Event<void> { return this._sql.onDidModelChangeEncoding; }
public resolve(refresh?: boolean): Promise<EditorModel> { return this._sql.resolve(); }
public save(): Promise<boolean> { return this._sql.save(); }
public isDirty(): boolean { return this._sql.isDirty(); }
public confirmSave(): Promise<ConfirmResult> { return this._sql.confirmSave(); }
public getResource(): URI { return this._sql.getResource(); }
public getEncoding(): string { return this._sql.getEncoding(); }
public suggestFileName(): string { return this._sql.suggestFileName(); }
public getName(longForm?: boolean): string {
if (this._configurationService.getValue('sql.showConnectionInfoInTitle')) {
let profile = this._connectionManagementService.getConnectionProfile(this.uri);
let title = '';
if (this._description && this._description !== '') {
title = this._description + ' ';
}
if (profile) {
if (profile.userName) {
title += `${profile.serverName}.${profile.databaseName} (${profile.userName})`;
} else {
title += `${profile.serverName}.${profile.databaseName} (${profile.authenticationType})`;
}
} else {
title += localize('disconnected', 'disconnected');
}
return this._sql.getName() + (longForm ? (' - ' + title) : ` - ${trimTitle(title)}`);
} else {
return this._sql.getName();
}
}
// Called to get the tooltip of the tab
public getTitle() {
return this.getName(true);
}
public get hasAssociatedFilePath(): boolean { return this._sql.hasAssociatedFilePath; }
public setEncoding(encoding: string, mode: EncodingMode /* ignored, we only have Encode */): void {
this._sql.setEncoding(encoding, mode);
}
// State update funtions
public runQuery(selection: ISelectionData, executePlanOptions?: ExecutionPlanOptions): void {
this._queryModelService.runQuery(this.uri, selection, this, executePlanOptions);
this.showQueryResultsEditor();
}
public runQueryStatement(selection: ISelectionData): void {
this._queryModelService.runQueryStatement(this.uri, selection, this);
this.showQueryResultsEditor();
}
public runQueryString(text: string): void {
this._queryModelService.runQueryString(this.uri, text, this);
this.showQueryResultsEditor();
}
public onConnectStart(): void {
this._runQueryEnabled = false;
this._cancelQueryEnabled = false;
this._connectEnabled = false;
this._disconnectEnabled = true;
this._changeConnectionEnabled = false;
this._listDatabasesConnected = false;
this._updateTaskbar.fire();
}
public onConnectReject(): void {
this.onDisconnect();
this._updateTaskbar.fire();
}
public onConnectCanceled(): void {
}
public onConnectSuccess(params?: INewConnectionParams): void {
this._runQueryEnabled = true;
this._connectEnabled = false;
this._disconnectEnabled = true;
this._changeConnectionEnabled = true;
this._listDatabasesConnected = true;
let isRunningQuery = this._queryModelService.isRunningQuery(this.uri);
if (!isRunningQuery && params && params.runQueryOnCompletion) {
let selection: ISelectionData = params ? params.querySelection : undefined;
if (params.runQueryOnCompletion === RunQueryOnConnectionMode.executeCurrentQuery) {
this.runQueryStatement(selection);
} else if (params.runQueryOnCompletion === RunQueryOnConnectionMode.executeQuery) {
this.runQuery(selection);
} else if (params.runQueryOnCompletion === RunQueryOnConnectionMode.estimatedQueryPlan) {
this.runQuery(selection, { displayEstimatedQueryPlan: true });
} else if (params.runQueryOnCompletion === RunQueryOnConnectionMode.actualQueryPlan) {
this.runQuery(selection, { displayActualQueryPlan: true });
}
}
this._updateTaskbar.fire();
this._onDidChangeLabel.fire();
}
public onDisconnect(): void {
this._runQueryEnabled = true;
this._cancelQueryEnabled = false;
this._connectEnabled = true;
this._disconnectEnabled = false;
this._changeConnectionEnabled = false;
this._listDatabasesConnected = false;
this._updateTaskbar.fire();
}
public onRunQuery(): void {
this._runQueryEnabled = false;
this._cancelQueryEnabled = true;
this._updateTaskbar.fire();
}
public onQueryComplete(): void {
this._runQueryEnabled = true;
this._cancelQueryEnabled = false;
this._updateTaskbar.fire();
}
// Clean up functions
public dispose(): void {
this._sql.dispose();
this._results.dispose();
this._toDispose = dispose(this._toDispose);
this._currentEventCallbacks = dispose(this._currentEventCallbacks);
super.dispose();
}
public close(): void {
this._queryModelService.disposeQuery(this.uri);
this._connectionManagementService.disconnectEditor(this, true);
this._sql.close();
this._results.close();
}
/**
* Unsubscribe all events in _currentEventCallbacks and set the new callbacks
* to be unsubscribed the next time this method is called.
*
* This method is used to ensure that all callbacks point to the current QueryEditor
* in the case that this QueryInput is moved between different QueryEditors.
*/
public setEventCallbacks(callbacks: IDisposable[]): void {
this._currentEventCallbacks = dispose(this._currentEventCallbacks);
this._currentEventCallbacks = callbacks;
}
/**
* Get the color that should be displayed
*/
public get tabColor(): string {
return this._connectionManagementService.getTabColorForUri(this.uri);
}
}

View File

@@ -0,0 +1,155 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import { EditorInput } from 'vs/workbench/common/editor';
import { Emitter } from 'vs/base/common/event';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { GridPanelState } from 'sql/workbench/parts/query/electron-browser/gridPanel';
import { MessagePanelState } from 'sql/workbench/parts/query/browser/messagePanel';
import { QueryPlanState } from 'sql/workbench/parts/queryPlan/electron-browser/queryPlan';
import { ChartState } from 'sql/workbench/parts/charts/browser/chartView';
import { TopOperationsState } from 'sql/workbench/parts/queryPlan/browser/topOperations';
export class ResultsViewState {
public gridPanelState: GridPanelState = new GridPanelState();
public messagePanelState: MessagePanelState = new MessagePanelState(this.configurationService);
public chartState: ChartState = new ChartState();
public queryPlanState: QueryPlanState = new QueryPlanState();
public topOperationsState = new TopOperationsState();
public gridPanelSize: number;
public messagePanelSize: number;
public activeTab: string;
public visibleTabs: Set<string> = new Set<string>();
constructor(@IConfigurationService private configurationService: IConfigurationService) {
}
dispose() {
this.gridPanelState.dispose();
this.messagePanelState.dispose();
this.chartState.dispose();
this.queryPlanState.dispose();
}
}
/**
* Input for the QueryResultsEditor. This input helps with logic for the viewing and editing of
* data in the results grid.
*/
export class QueryResultsInput extends EditorInput {
// Tracks if the editor that holds this input should be visible (i.e. true if a query has been run)
private _visible: boolean;
// Tracks if the editor has holds this input has has bootstrapped angular yet
private _hasBootstrapped: boolean;
// Holds the HTML content for the editor when the editor discards this input and loads another
private _editorContainer: HTMLElement;
public css: HTMLStyleElement;
public readonly onRestoreViewStateEmitter = new Emitter<void>();
public readonly onSaveViewStateEmitter = new Emitter<void>();
private _state = new ResultsViewState(this.configurationService);
public get state(): ResultsViewState {
return this._state;
}
constructor(private _uri: string,
@IConfigurationService private configurationService: IConfigurationService
) {
super();
this._visible = false;
this._hasBootstrapped = false;
}
close() {
this.state.dispose();
this._state = undefined;
super.close();
}
getTypeId(): string {
return QueryResultsInput.ID;
}
getName(): string {
return localize('extensionsInputName', 'Extension');
}
matches(other: any): boolean {
if (other instanceof QueryResultsInput) {
return (other._uri === this._uri);
}
return false;
}
resolve(refresh?: boolean): Promise<any> {
return Promise.resolve(null);
}
supportsSplitEditor(): boolean {
return false;
}
public setBootstrappedTrue(): void {
this._hasBootstrapped = true;
}
public dispose(): void {
this._disposeContainer();
super.dispose();
}
private _disposeContainer() {
if (!this._editorContainer) {
return;
}
let parentContainer = this._editorContainer.parentNode;
if (parentContainer) {
parentContainer.removeChild(this._editorContainer);
this._editorContainer = null;
}
}
//// Properties
static get ID() {
return 'workbench.query.queryResultsInput';
}
set container(container: HTMLElement) {
this._disposeContainer();
this._editorContainer = container;
}
get container(): HTMLElement {
return this._editorContainer;
}
get hasBootstrapped(): boolean {
return this._hasBootstrapped;
}
get visible(): boolean {
return this._visible;
}
set visible(visible: boolean) {
this._visible = visible;
}
get uri(): string {
return this._uri;
}
}

View File

@@ -0,0 +1,76 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Extensions, IConfigurationRegistry, IConfigurationNode } from 'vs/platform/configuration/common/configurationRegistry';
import { Registry } from 'vs/platform/registry/common/platform';
import * as nls from 'vs/nls';
import * as editorOptions from 'vs/editor/common/config/editorOptions';
import EDITOR_FONT_DEFAULTS = editorOptions.EDITOR_FONT_DEFAULTS;
import { RESULTS_GRID_DEFAULTS } from 'sql/workbench/parts/query/browser/queryResultsEditor';
const configurationRegistry = <IConfigurationRegistry>Registry.as(Extensions.Configuration);
const resultsGridConfiguration: IConfigurationNode = {
id: 'resultsGrid',
type: 'object',
title: nls.localize('resultsGridConfigurationTitle', "Results Grid"),
overridable: true,
properties: {
'resultsGrid.fontFamily': {
type: 'string',
default: EDITOR_FONT_DEFAULTS.fontFamily,
description: nls.localize('fontFamily', "Controls the font family.")
},
'resultsGrid.fontWeight': {
type: 'string',
enum: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'],
default: EDITOR_FONT_DEFAULTS.fontWeight,
description: nls.localize('fontWeight', "Controls the font weight.")
},
'resultsGrid.fontSize': {
type: 'number',
default: EDITOR_FONT_DEFAULTS.fontSize,
description: nls.localize('fontSize', "Controls the font size in pixels.")
},
'resultsGrid.letterSpacing': {
type: 'number',
default: EDITOR_FONT_DEFAULTS.letterSpacing,
description: nls.localize('letterSpacing', "Controls the letter spacing in pixels.")
},
'resultsGrid.rowHeight': {
type: 'number',
default: RESULTS_GRID_DEFAULTS.rowHeight,
description: nls.localize('rowHeight', "Controls the row height in pixels")
},
'resultsGrid.cellPadding': {
oneOf: [
{
type: 'number'
},
{
type: 'array',
items: {
type: 'number'
}
}
],
default: RESULTS_GRID_DEFAULTS.cellPadding,
description: nls.localize('cellPadding', "Controls the cell padding in pixels")
},
'resultsGrid.autoSizeColumns': {
type: 'boolean',
default: true,
description: nls.localize('autoSizeColumns', "Auto size the columns width on inital results. Could have performance problems with large number of columns or large cells")
},
'resultsGrid.maxColumnWidth': {
type: 'number',
default: 212,
description: nls.localize('maxColumnWidth', "The maximum width in pixels for auto-sized columns")
}
}
};
configurationRegistry.registerConfiguration(resultsGridConfiguration);

View File

@@ -0,0 +1,814 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as pretty from 'pretty-data';
import { attachTableStyler } from 'sql/platform/theme/common/styler';
import QueryRunner from 'sql/platform/query/common/queryRunner';
import { VirtualizedCollection, AsyncDataProvider } from 'sql/base/browser/ui/table/asyncDataView';
import { Table } from 'sql/base/browser/ui/table/table';
import { ScrollableSplitView, IView } 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';
import { SaveFormat } from 'sql/workbench/parts/grid/common/interfaces';
import { IGridActionContext, SaveResultAction, CopyResultAction, SelectAllGridAction, MaximizeTableAction, RestoreTableAction, ChartDataAction } from 'sql/workbench/parts/query/browser/actions';
import { CellSelectionModel } from 'sql/base/browser/ui/table/plugins/cellSelectionModel.plugin';
import { RowNumberColumn } from 'sql/base/browser/ui/table/plugins/rowNumberColumn.plugin';
import { escape } from 'sql/base/common/strings';
import { hyperLinkFormatter, textFormatter } from 'sql/base/browser/ui/table/formatters';
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 { warn } from 'sql/base/common/log';
import * as azdata from 'azdata';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { Emitter, Event } from 'vs/base/common/event';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet';
import { isUndefinedOrNull } from 'vs/base/common/types';
import { range } from 'vs/base/common/arrays';
import { Orientation } from 'vs/base/browser/ui/splitview/splitview';
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
import { generateUuid } from 'vs/base/common/uuid';
import { Separator, ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
import { isInDOM } from 'vs/base/browser/dom';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IAction } from 'vs/base/common/actions';
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
const ROW_HEIGHT = 29;
const HEADER_HEIGHT = 26;
const MIN_GRID_HEIGHT_ROWS = 8;
const ESTIMATED_SCROLL_BAR_HEIGHT = 10;
const BOTTOM_PADDING = 15;
const ACTIONBAR_WIDTH = 36;
// minimum height needed to show the full actionbar
const ACTIONBAR_HEIGHT = 120;
// this handles min size if rows is greater than the min grid visible rows
const MIN_GRID_HEIGHT = (MIN_GRID_HEIGHT_ROWS * ROW_HEIGHT) + HEADER_HEIGHT + ESTIMATED_SCROLL_BAR_HEIGHT;
export class GridPanelState {
public tableStates: GridTableState[] = [];
public scrollPosition: number;
public collapsed = false;
dispose() {
dispose(this.tableStates);
}
}
export class GridTableState extends Disposable {
private _maximized: boolean;
private _onMaximizedChange = this._register(new Emitter<boolean>());
public onMaximizedChange: Event<boolean> = this._onMaximizedChange.event;
private _onCanBeMaximizedChange = this._register(new Emitter<boolean>());
public onCanBeMaximizedChange: Event<boolean> = this._onCanBeMaximizedChange.event;
private _canBeMaximized: boolean;
/* The top row of the current scroll */
public scrollPositionY = 0;
public scrollPositionX = 0;
public columnSizes: number[] = undefined;
public selection: Slick.Range[];
public activeCell: Slick.Cell;
constructor(public readonly resultId: number, public readonly batchId: number) {
super();
}
public get canBeMaximized(): boolean {
return this._canBeMaximized;
}
public set canBeMaximized(val: boolean) {
if (val === this._canBeMaximized) {
return;
}
this._canBeMaximized = val;
this._onCanBeMaximizedChange.fire(val);
}
public get maximized(): boolean {
return this._maximized;
}
public set maximized(val: boolean) {
if (val === this._maximized) {
return;
}
this._maximized = val;
this._onMaximizedChange.fire(val);
}
}
export class GridPanel extends ViewletPanel {
private container = document.createElement('div');
private splitView: ScrollableSplitView;
private tables: GridTable<any>[] = [];
private tableDisposable: IDisposable[] = [];
private queryRunnerDisposables: IDisposable[] = [];
private currentHeight: number;
private runner: QueryRunner;
private maximizedGrid: GridTable<any>;
private _state: GridPanelState;
constructor(
options: IViewletPanelOptions,
@IKeybindingService keybindingService: IKeybindingService,
@IContextMenuService contextMenuService: IContextMenuService,
@IConfigurationService configurationService: IConfigurationService,
@IThemeService private themeService: IThemeService,
@IInstantiationService private instantiationService: IInstantiationService
) {
super(options, keybindingService, contextMenuService, configurationService);
this.splitView = new ScrollableSplitView(this.container, { enableResizing: false, verticalScrollbarVisibility: ScrollbarVisibility.Visible });
this.splitView.onScroll(e => {
if (this.state && this.splitView.length !== 0) {
this.state.scrollPosition = e;
}
});
this.onDidChange(e => {
if (this.state) {
this.state.collapsed = !this.isExpanded();
}
});
}
protected renderBody(container: HTMLElement): void {
this.container.style.width = '100%';
this.container.style.height = '100%';
container.appendChild(this.container);
}
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) {
dispose(this.queryRunnerDisposables);
this.reset();
this.queryRunnerDisposables = [];
this.runner = runner;
this.queryRunnerDisposables.push(this.runner.onResultSet(this.onResultSet, this));
this.queryRunnerDisposables.push(this.runner.onResultSetUpdate(this.updateResultSet, this));
this.queryRunnerDisposables.push(this.runner.onQueryStart(() => {
if (this.state) {
this.state.tableStates = [];
}
this.reset();
}));
this.addResultSet(this.runner.batchSets.reduce<azdata.ResultSetSummary[]>((p, e) => {
if (this.configurationService.getValue<boolean>('sql.results.streaming')) {
p = p.concat(e.resultSetSummaries);
} else {
p = p.concat(e.resultSetSummaries.filter(c => c.complete));
}
return p;
}, []));
this.maximumBodySize = this.tables.reduce((p, c) => {
return p + c.maximumSize;
}, 0);
if (this.state && this.state.scrollPosition) {
this.splitView.setScrollPosition(this.state.scrollPosition);
}
}
public resetScrollPosition(): void {
this.splitView.setScrollPosition(this.state.scrollPosition);
}
private onResultSet(resultSet: azdata.ResultSetSummary | azdata.ResultSetSummary[]) {
let resultsToAdd: azdata.ResultSetSummary[];
if (!Array.isArray(resultSet)) {
resultsToAdd = [resultSet];
} else {
resultsToAdd = resultSet.splice(0);
}
const sizeChanges = () => {
this.tables.map(t => {
t.state.canBeMaximized = this.tables.length > 1;
});
this.maximumBodySize = this.tables.reduce((p, c) => {
return p + c.maximumSize;
}, 0);
if (this.state && this.state.scrollPosition) {
this.splitView.setScrollPosition(this.state.scrollPosition);
}
};
if (this.configurationService.getValue<boolean>('sql.results.streaming')) {
this.addResultSet(resultsToAdd);
sizeChanges();
} else {
resultsToAdd = resultsToAdd.filter(e => e.complete);
if (resultsToAdd.length > 0) {
this.addResultSet(resultsToAdd);
}
sizeChanges();
}
}
private updateResultSet(resultSet: azdata.ResultSetSummary | azdata.ResultSetSummary[]) {
let resultsToUpdate: azdata.ResultSetSummary[];
if (!Array.isArray(resultSet)) {
resultsToUpdate = [resultSet];
} else {
resultsToUpdate = resultSet.splice(0);
}
const sizeChanges = () => {
this.maximumBodySize = this.tables.reduce((p, c) => {
return p + c.maximumSize;
}, 0);
if (this.state && this.state.scrollPosition) {
this.splitView.setScrollPosition(this.state.scrollPosition);
}
};
if (this.configurationService.getValue<boolean>('sql.results.streaming')) {
for (let set of resultsToUpdate) {
let table = this.tables.find(t => t.resultSet.batchId === set.batchId && t.resultSet.id === set.id);
if (table) {
table.updateResult(set);
} else {
warn('Got result set update request for non-existant table');
}
}
sizeChanges();
} else {
resultsToUpdate = resultsToUpdate.filter(e => e.complete);
if (resultsToUpdate.length > 0) {
this.addResultSet(resultsToUpdate);
}
sizeChanges();
}
}
private addResultSet(resultSet: azdata.ResultSetSummary[]) {
let tables: GridTable<any>[] = [];
for (let set of resultSet) {
let tableState: GridTableState;
if (this._state) {
tableState = this.state.tableStates.find(e => e.batchId === set.batchId && e.resultId === set.id);
}
if (!tableState) {
tableState = new GridTableState(set.id, set.batchId);
if (this._state) {
this._state.tableStates.push(tableState);
}
}
let table = this.instantiationService.createInstance(GridTable, this.runner, set, tableState);
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);
}
// possible to need a sort?
if (isUndefinedOrNull(this.maximizedGrid)) {
this.splitView.addViews(tables, tables.map(i => i.minimumSize), this.splitView.length);
}
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;
this.maximumBodySize = this.tables.reduce((p, c) => {
return p + c.maximumSize;
}, 0);
}
private maximizeTable(tableid: string): void {
if (!this.tables.find(t => t.id === tableid)) {
return;
}
for (let i = this.tables.length - 1; i >= 0; i--) {
if (this.tables[i].id === tableid) {
this.tables[i].state.maximized = true;
this.maximizedGrid = this.tables[i];
continue;
}
this.splitView.removeView(i);
}
}
private minimizeTables(): void {
if (this.maximizedGrid) {
this.maximizedGrid.state.maximized = false;
this.maximizedGrid = undefined;
this.splitView.removeView(0);
this.splitView.addViews(this.tables, this.tables.map(i => i.minimumSize));
}
}
public set state(val: GridPanelState) {
this._state = val;
this.tables.map(t => {
let state = this.state.tableStates.find(s => s.batchId === t.resultSet.batchId && s.resultId === t.resultSet.id);
if (!state) {
this.state.tableStates.push(t.state);
}
if (state) {
t.state = state;
}
});
this.setExpanded(!this.state.collapsed);
}
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<T> extends Disposable implements IView {
private table: Table<T>;
private actionBar: ActionBar;
private container = document.createElement('div');
private selectionModel = new CellSelectionModel();
private styles: ITableStyles;
private currentHeight: number;
private dataProvider: AsyncDataProvider<T>;
private columns: Slick.Column<T>[];
private rowNumberColumn: RowNumberColumn<T>;
private _onDidChange = new Emitter<number>();
public readonly onDidChange: Event<number> = this._onDidChange.event;
public id = generateUuid();
readonly element: HTMLElement = this.container;
private _state: GridTableState;
private scrolled = false;
private visible = false;
private rowHeight: number;
public get resultSet(): azdata.ResultSetSummary {
return this._resultSet;
}
// this handles if the row count is small, like 4-5 rows
private get maxSize(): number {
return ((this.resultSet.rowCount) * this.rowHeight) + HEADER_HEIGHT + ESTIMATED_SCROLL_BAR_HEIGHT;
}
constructor(
private runner: QueryRunner,
private _resultSet: azdata.ResultSetSummary,
state: GridTableState,
@IContextMenuService private contextMenuService: IContextMenuService,
@IInstantiationService private instantiationService: IInstantiationService,
@IEditorService private editorService: IEditorService,
@IUntitledEditorService private untitledEditorService: IUntitledEditorService,
@IConfigurationService private configurationService: IConfigurationService
) {
super();
let config = this.configurationService.getValue<{ rowHeight: number }>('resultsGrid');
this.rowHeight = config && config.rowHeight ? config.rowHeight : ROW_HEIGHT;
this.state = state;
this.container.style.width = '100%';
this.container.style.height = '100%';
this.container.className = 'grid-panel';
this.columns = this.resultSet.columnInfo.map((c, i) => {
let isLinked = c.isXml || c.isJson;
return <Slick.Column<T>>{
id: i.toString(),
name: c.columnName === 'Microsoft SQL Server 2005 XML Showplan'
? 'XML Showplan'
: escape(c.columnName),
field: i.toString(),
formatter: isLinked ? hyperLinkFormatter : textFormatter,
width: this.state.columnSizes && this.state.columnSizes[i] ? this.state.columnSizes[i] : undefined
};
});
}
public onAdd() {
this.visible = true;
let collection = new VirtualizedCollection(
50,
index => this.placeholdGenerator(index),
this.resultSet.rowCount,
(offset, count) => this.loadData(offset, count)
);
collection.setCollectionChangedCallback((startIndex, count) => {
this.renderGridDataRowsRange(startIndex, count);
});
this.dataProvider.dataRows = collection;
this.table.updateRowCount();
this.setupState();
}
public onRemove() {
this.visible = false;
let collection = new VirtualizedCollection(
50,
index => this.placeholdGenerator(index),
0,
() => Promise.resolve([])
);
this.dataProvider.dataRows = collection;
this.table.updateRowCount();
// when we are removed slickgrid acts badly so we need to account for that
this.scrolled = false;
}
private build(): void {
let tableContainer = document.createElement('div');
tableContainer.style.display = 'inline-block';
tableContainer.style.width = `calc(100% - ${ACTIONBAR_WIDTH}px)`;
this.container.appendChild(tableContainer);
let collection = new VirtualizedCollection(
50,
index => this.placeholdGenerator(index),
0,
() => Promise.resolve([])
);
collection.setCollectionChangedCallback((startIndex, count) => {
this.renderGridDataRowsRange(startIndex, count);
});
this.rowNumberColumn = new RowNumberColumn({ numberOfRows: this.resultSet.rowCount });
let copyHandler = new CopyKeybind();
copyHandler.onCopy(e => {
new CopyResultAction(CopyResultAction.COPY_ID, CopyResultAction.COPY_LABEL, false).run(this.generateContext());
});
this.columns.unshift(this.rowNumberColumn.getColumnDefinition());
let tableOptions: Slick.GridOptions<T> = {
rowHeight: this.rowHeight,
showRowNumber: true,
forceFitColumns: false,
defaultColumnWidth: 120
};
this.dataProvider = new AsyncDataProvider(collection);
this.table = this._register(new Table(tableContainer, { dataProvider: this.dataProvider, columns: this.columns }, tableOptions));
this.table.setSelectionModel(this.selectionModel);
this.table.registerPlugin(new MouseWheelSupport());
this.table.registerPlugin(new AutoColumnSize({ autoSizeOnRender: !this.state.columnSizes && this.configurationService.getValue('resultsGrid.autoSizeColumns'), maxWidth: this.configurationService.getValue<number>('resultsGrid.maxColumnWidth') }));
this.table.registerPlugin(copyHandler);
this.table.registerPlugin(this.rowNumberColumn);
this.table.registerPlugin(new AdditionalKeyBindings());
this._register(this.table.onContextMenu(this.contextMenu, this));
this._register(this.table.onClick(this.onTableClick, this));
if (this.styles) {
this.table.style(this.styles);
}
let actions = this.getCurrentActions();
let actionBarContainer = document.createElement('div');
actionBarContainer.style.width = ACTIONBAR_WIDTH + 'px';
actionBarContainer.style.display = 'inline-block';
actionBarContainer.style.height = '100%';
actionBarContainer.style.verticalAlign = 'top';
this.container.appendChild(actionBarContainer);
this.actionBar = new ActionBar(actionBarContainer, {
orientation: ActionsOrientation.VERTICAL, context: {
runner: this.runner,
batchId: this.resultSet.batchId,
resultId: this.resultSet.id,
table: this.table,
tableState: this.state
}
});
// update context before we run an action
this.selectionModel.onSelectedRangesChanged.subscribe(e => {
this.actionBar.context = this.generateContext();
});
this.actionBar.push(actions, { icon: true, label: false });
this.selectionModel.onSelectedRangesChanged.subscribe(e => {
if (this.state) {
this.state.selection = this.selectionModel.getSelectedRanges();
}
});
this.table.grid.onScroll.subscribe((e, data) => {
if (!this.visible) {
// If the grid is not set up yet it can get scroll events resetting the top to 0px,
// so ignore those events
return;
}
if (!this.scrolled && (this.state.scrollPositionY || this.state.scrollPositionX) && isInDOM(this.container)) {
this.scrolled = true;
this.restoreScrollState();
}
if (this.state && isInDOM(this.container)) {
this.state.scrollPositionY = data.scrollTop;
this.state.scrollPositionX = data.scrollLeft;
}
});
// we need to remove the first column since this is the row number
this.table.onColumnResize(() => {
let columnSizes = this.table.grid.getColumns().slice(1).map(v => v.width);
this.state.columnSizes = columnSizes;
});
this.table.grid.onActiveCellChanged.subscribe(e => {
if (this.state) {
this.state.activeCell = this.table.grid.getActiveCell();
}
});
}
private restoreScrollState() {
if (this.state.scrollPositionX || this.state.scrollPositionY) {
this.table.grid.scrollTo(this.state.scrollPositionY);
this.table.grid.getContainerNode().children[3].scrollLeft = this.state.scrollPositionX;
}
}
private setupState() {
// change actionbar on maximize change
this._register(this.state.onMaximizedChange(this.rebuildActionBar, this));
this._register(this.state.onCanBeMaximizedChange(this.rebuildActionBar, this));
this.restoreScrollState();
this.rebuildActionBar();
// Setting the active cell resets the selection so save it here
let savedSelection = this.state.selection;
if (this.state.activeCell) {
this.table.setActiveCell(this.state.activeCell.row, this.state.activeCell.cell);
}
if (savedSelection) {
this.selectionModel.setSelectedRanges(savedSelection);
}
}
public get state(): GridTableState {
return this._state;
}
public set state(val: GridTableState) {
this._state = val;
}
private onTableClick(event: ITableMouseEvent) {
// account for not having the number column
let column = this.resultSet.columnInfo[event.cell.cell - 1];
// handle if a showplan link was clicked
if (column && (column.isXml || column.isJson)) {
this.runner.getQueryRows(event.cell.row, 1, this.resultSet.batchId, this.resultSet.id).then(d => {
let value = d.resultSubset.rows[0][event.cell.cell - 1];
let content = value.displayValue;
if (column.isXml) {
try {
content = pretty.pd.xml(content);
} catch (e) {
// If Xml fails to parse, fall back on original Xml content
}
} else {
let jsonContent: string = undefined;
try {
jsonContent = JSON.parse(content);
} catch (e) {
// If Json fails to parse, fall back on original Json content
}
if (jsonContent) {
// If Json content was valid and parsed, pretty print content to a string
content = JSON.stringify(jsonContent, undefined, 4);
}
}
let input = this.untitledEditorService.createOrGet(undefined, column.isXml ? 'xml' : 'json', content);
this.editorService.openEditor(input);
});
}
}
public updateResult(resultSet: azdata.ResultSetSummary) {
this._resultSet = resultSet;
if (this.table && this.visible) {
this.dataProvider.length = resultSet.rowCount;
this.table.updateRowCount();
}
this._onDidChange.fire(undefined);
}
private generateContext(cell?: Slick.Cell): IGridActionContext {
const selection = this.selectionModel.getSelectedRanges();
return <IGridActionContext>{
cell,
selection,
runner: this.runner,
batchId: this.resultSet.batchId,
resultId: this.resultSet.id,
table: this.table,
tableState: this.state,
selectionModel: this.selectionModel
};
}
private rebuildActionBar() {
let actions = this.getCurrentActions();
this.actionBar.clear();
this.actionBar.push(actions, { icon: true, label: false });
}
private getCurrentActions(): IAction[] {
let actions = [];
if (this.state.canBeMaximized) {
if (this.state.maximized) {
actions.splice(1, 0, new RestoreTableAction());
} else {
actions.splice(1, 0, new MaximizeTableAction());
}
}
actions.push(
new SaveResultAction(SaveResultAction.SAVECSV_ID, SaveResultAction.SAVECSV_LABEL, SaveResultAction.SAVECSV_ICON, SaveFormat.CSV),
new SaveResultAction(SaveResultAction.SAVEEXCEL_ID, SaveResultAction.SAVEEXCEL_LABEL, SaveResultAction.SAVEEXCEL_ICON, SaveFormat.EXCEL),
new SaveResultAction(SaveResultAction.SAVEJSON_ID, SaveResultAction.SAVEJSON_LABEL, SaveResultAction.SAVEJSON_ICON, SaveFormat.JSON),
new SaveResultAction(SaveResultAction.SAVEXML_ID, SaveResultAction.SAVEXML_LABEL, SaveResultAction.SAVEXML_ICON, SaveFormat.XML),
this.instantiationService.createInstance(ChartDataAction)
);
return actions;
}
public layout(size?: number): void {
if (!this.table) {
this.build();
}
if (!size) {
size = this.currentHeight;
} else {
this.currentHeight = size;
}
this.table.layout(size, Orientation.VERTICAL);
}
public get minimumSize(): number {
// clamp between ensuring we can show the actionbar, while also making sure we don't take too much space
return Math.max(Math.min(this.maxSize, MIN_GRID_HEIGHT), ACTIONBAR_HEIGHT + BOTTOM_PADDING);
}
public get maximumSize(): number {
return Math.max(this.maxSize, ACTIONBAR_HEIGHT + BOTTOM_PADDING);
}
private loadData(offset: number, count: number): Thenable<T[]> {
return this.runner.getQueryRows(offset, count, this.resultSet.batchId, this.resultSet.id).then(response => {
if (!response.resultSubset) {
return [];
}
return response.resultSubset.rows.map(r => {
let dataWithSchema = {};
// skip the first column since its a number column
for (let i = 1; i < this.columns.length; i++) {
dataWithSchema[this.columns[i].field] = {
displayValue: r[i - 1].displayValue,
ariaLabel: escape(r[i - 1].displayValue),
isNull: r[i - 1].isNull
};
}
return dataWithSchema as T;
});
});
}
private contextMenu(e: ITableMouseEvent): void {
const { cell } = e;
this.contextMenuService.showContextMenu({
getAnchor: () => e.anchor,
getActions: () => {
let actions = [
new SelectAllGridAction(),
new Separator(),
new SaveResultAction(SaveResultAction.SAVECSV_ID, SaveResultAction.SAVECSV_LABEL, SaveResultAction.SAVECSV_ICON, SaveFormat.CSV),
new SaveResultAction(SaveResultAction.SAVEEXCEL_ID, SaveResultAction.SAVEEXCEL_LABEL, SaveResultAction.SAVEEXCEL_ICON, SaveFormat.EXCEL),
new SaveResultAction(SaveResultAction.SAVEJSON_ID, SaveResultAction.SAVEJSON_LABEL, SaveResultAction.SAVEJSON_ICON, SaveFormat.JSON),
new SaveResultAction(SaveResultAction.SAVEXML_ID, SaveResultAction.SAVEXML_LABEL, SaveResultAction.SAVEXML_ICON, SaveFormat.XML),
new Separator(),
new CopyResultAction(CopyResultAction.COPY_ID, CopyResultAction.COPY_LABEL, false),
new CopyResultAction(CopyResultAction.COPYWITHHEADERS_ID, CopyResultAction.COPYWITHHEADERS_LABEL, true)
];
if (this.state.canBeMaximized) {
if (this.state.maximized) {
actions.splice(1, 0, new RestoreTableAction());
} else {
actions.splice(1, 0, new MaximizeTableAction());
}
}
return actions;
},
getActionsContext: () => {
return this.generateContext(cell);
}
});
}
private placeholdGenerator(index: number): any {
return {};
}
private renderGridDataRowsRange(startIndex: number, count: number): void {
// let editor = this.table.getCellEditor();
// let oldValue = editor ? editor.getValue() : undefined;
// let wasValueChanged = editor ? editor.isValueChanged() : false;
this.invalidateRange(startIndex, startIndex + count);
// let activeCell = this._grid.getActiveCell();
// if (editor && activeCell.row >= startIndex && activeCell.row < startIndex + count) {
// if (oldValue && wasValueChanged) {
// editor.setValue(oldValue);
// }
// }
}
private invalidateRange(start: number, end: number): void {
let refreshedRows = range(start, end);
if (this.table) {
this.table.invalidateRows(refreshedRows, true);
}
}
public style(styles: ITableStyles) {
if (this.table) {
this.table.style(styles);
} else {
this.styles = styles;
}
}
public dispose() {
this.container.remove();
this.table.dispose();
this.actionBar.dispose();
super.dispose();
}
}

View File

@@ -0,0 +1,79 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.dialogModal-body {
display: flex;
flex-direction: row;
height: 100%;
width: 100%;
min-width: 500px;
min-height: 600px;
}
.modal.wide .dialogModal-body {
min-width: 800px;
}
.dialog-message-and-page-container {
display: flex;
flex-direction: column;
flex: 1 1;
overflow: hidden;
}
.dialogModal-page-container {
flex: 1 1;
overflow: hidden;
}
.dialogModal-pane {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
overflow-x: hidden;
overflow-y: scroll;
}
.dialogModal-hidden {
display: none;
}
.footer-button.dialogModal-hidden {
margin: 0;
}
.footer-button .validating {
background-size: 15px;
background-repeat: no-repeat;
background-position: center;
}
.vs .footer-button .validating {
background-image: url("loading.svg");
}
.vs-dark .footer-button .validating,
.hc-black .footer-button .validating {
background-image: url("loading_inverse.svg");
}
.dialogModal-wizardHeader {
padding: 10px 30px;
}
.dialogModal-wizardHeader h1 {
margin-top: 10px;
margin-bottom: 3px;
font-size: 1.5em;
font-weight: lighter;
}
.dialogContainer {
display: flex;
flex-direction: column;
height: 100%;
width: 100%;
}

View File

@@ -0,0 +1,31 @@
<?xml version='1.0' standalone='no' ?>
<svg xmlns='http://www.w3.org/2000/svg' version='1.1' width='10px' height='10px'>
<style>
circle {
animation: ball 0.6s linear infinite;
}
circle:nth-child(2) { animation-delay: 0.075s; }
circle:nth-child(3) { animation-delay: 0.15s; }
circle:nth-child(4) { animation-delay: 0.225s; }
circle:nth-child(5) { animation-delay: 0.3s; }
circle:nth-child(6) { animation-delay: 0.375s; }
circle:nth-child(7) { animation-delay: 0.45s; }
circle:nth-child(8) { animation-delay: 0.525s; }
@keyframes ball {
from { opacity: 1; }
to { opacity: 0.3; }
}
</style>
<g>
<circle cx='5' cy='1' r='1' style='opacity:0.3;' />
<circle cx='7.8284' cy='2.1716' r='1' style='opacity:0.3;' />
<circle cx='9' cy='5' r='1' style='opacity:0.3;' />
<circle cx='7.8284' cy='7.8284' r='1' style='opacity:0.3;' />
<circle cx='5' cy='9' r='1' style='opacity:0.3;' />
<circle cx='2.1716' cy='7.8284' r='1' style='opacity:0.3;' />
<circle cx='1' cy='5' r='1' style='opacity:0.3;' />
<circle cx='2.1716' cy='2.1716' r='1' style='opacity:0.3;' />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,31 @@
<?xml version='1.0' standalone='no' ?>
<svg xmlns='http://www.w3.org/2000/svg' version='1.1' width='10px' height='10px'>
<style>
circle {
animation: ball 0.6s linear infinite;
}
circle:nth-child(2) { animation-delay: 0.075s; }
circle:nth-child(3) { animation-delay: 0.15s; }
circle:nth-child(4) { animation-delay: 0.225s; }
circle:nth-child(5) { animation-delay: 0.3s; }
circle:nth-child(6) { animation-delay: 0.375s; }
circle:nth-child(7) { animation-delay: 0.45s; }
circle:nth-child(8) { animation-delay: 0.525s; }
@keyframes ball {
from { opacity: 1; }
to { opacity: 0.3; }
}
</style>
<g style="fill:white;">
<circle cx='5' cy='1' r='1' style='opacity:0.3;' />
<circle cx='7.8284' cy='2.1716' r='1' style='opacity:0.3;' />
<circle cx='9' cy='5' r='1' style='opacity:0.3;' />
<circle cx='7.8284' cy='7.8284' r='1' style='opacity:0.3;' />
<circle cx='5' cy='9' r='1' style='opacity:0.3;' />
<circle cx='2.1716' cy='7.8284' r='1' style='opacity:0.3;' />
<circle cx='1' cy='5' r='1' style='opacity:0.3;' />
<circle cx='2.1716' cy='2.1716' r='1' style='opacity:0.3;' />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,93 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.wizardNavigation-container {
display: flex;
flex-direction: column;
width: 80px;
height: 100%;
}
.hc-black .wizardNavigation-container {
border-right-color: #2b56f2;
border-right-style: solid;
border-right-width: 1px;
background-color: unset;
}
.wizardNavigation-pageNumber {
flex-grow: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
max-height: 100px;
}
.wizardNavigation-pageNumber a {
text-decoration: none;
}
.wizardNavigation-dot {
height: 30px;
width: 30px;
background-color: rgb(200, 200, 200);
color: white;
border-radius: 50%;
border-style: none;
display: flex;
justify-content: center;
align-items: center;
font-size: 14px;
}
.hc-black .wizardNavigation-dot {
flex-grow: 1;
background-color: unset;
border-style: solid;
border-width: 1px;
border-color: white;
}
.wizardNavigation-connector {
width: 3px;
display: inline-block;
flex-grow: 1;
background-color: rgb(200, 200, 200);
}
.hc-black .wizardNavigation-connector {
display: none;
}
.wizardNavigation-connector.active,
.wizardNavigation-dot.active {
background-color: rgb(9, 109, 201);
}
.hc-black .wizardNavigation-dot.active {
border-color: #2b56f2;
background-color: unset;
border-style: solid;
}
.hc-black .wizardNavigation-dot.active:hover,
.hc-black .wizardNavigation-dot.active.currentPage:hover {
border-color: #F38518;
border-style: dashed;
}
.wizardNavigation-dot.active.currentPage {
border-style: double;
}
.hc-black .wizardNavigation-dot.active.currentPage {
border-style: solid;
border-color: #F38518;
}
.wizardNavigation-connector.invisible {
visibility: hidden;
}

View File

@@ -0,0 +1,77 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/dialogModal';
import { forwardRef, NgModule, ComponentFactoryResolver, Inject, ApplicationRef } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { CommonModule, APP_BASE_HREF } from '@angular/common';
import { BrowserModule } from '@angular/platform-browser';
import { WizardNavigation } from 'sql/platform/dialog/wizardNavigation.component';
import { Extensions, IComponentRegistry } from 'sql/platform/dashboard/common/modelComponentRegistry';
import { ModelViewContent } from 'sql/workbench/electron-browser/modelComponents/modelViewContent.component';
import { ModelComponentWrapper } from 'sql/workbench/electron-browser/modelComponents/modelComponentWrapper.component';
import { ComponentHostDirective } from 'sql/workbench/parts/dashboard/common/componentHost.directive';
import { IBootstrapParams, ISelector, providerIterator } from 'sql/platform/bootstrap/node/bootstrapService';
import { CommonServiceInterface } from 'sql/platform/bootstrap/node/commonServiceInterface.service';
import { Checkbox } from 'sql/base/browser/ui/checkbox/checkbox.component';
import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox.component';
import { EditableDropDown } from 'sql/base/browser/ui/editableDropdown/editableDropdown.component';
import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox.component';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Registry } from 'vs/platform/registry/common/platform';
import { QueryModelViewTabContainer } from 'sql/workbench/parts/query/modelViewTab/queryModelViewTabContainer.component';
export const QueryModelViewTabModule = (params, selector: string, instantiationService: IInstantiationService): any => {
/* Model-backed components */
let extensionComponents = Registry.as<IComponentRegistry>(Extensions.ComponentContribution).getAllCtors();
@NgModule({
declarations: [
Checkbox,
SelectBox,
EditableDropDown,
InputBox,
QueryModelViewTabContainer,
WizardNavigation,
ModelViewContent,
ModelComponentWrapper,
ComponentHostDirective,
...extensionComponents
],
entryComponents: [QueryModelViewTabContainer, WizardNavigation, ...extensionComponents],
imports: [
FormsModule,
CommonModule,
BrowserModule
],
providers: [
{ provide: APP_BASE_HREF, useValue: '/' },
CommonServiceInterface,
{ provide: IBootstrapParams, useValue: params },
{ provide: ISelector, useValue: selector },
...providerIterator(instantiationService)
]
})
class ModuleClass {
constructor(
@Inject(forwardRef(() => ComponentFactoryResolver)) private _resolver: ComponentFactoryResolver,
@Inject(ISelector) private selector: string
) {
}
ngDoBootstrap(appRef: ApplicationRef) {
let componentClass = QueryModelViewTabContainer;
const factoryWrapper: any = this._resolver.resolveComponentFactory<QueryModelViewTabContainer>(componentClass);
factoryWrapper.factory.selector = this.selector;
appRef.bootstrap(factoryWrapper);
}
}
return ModuleClass;
};

View File

@@ -0,0 +1,63 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Dimension } from 'vs/base/browser/dom';
import { dispose } from 'vs/base/common/lifecycle';
import { IPanelView, IPanelTab } from 'sql/base/browser/ui/panel/panel';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { bootstrapAngular } from 'sql/platform/bootstrap/node/bootstrapService';
import { QueryModelViewTabModule } from 'sql/workbench/parts/query/modelViewTab/queryModelViewTab.module';
export class QueryModelViewTab implements IPanelTab {
public identifier = 'QueryModelViewTab_';
public readonly view: QueryModelViewTabView;
constructor(public title: string, @IInstantiationService instantiationService: IInstantiationService) {
this.identifier += title;
this.view = instantiationService.createInstance(QueryModelViewTabView);
}
public dispose() {
dispose(this.view);
}
public clear() {
this.view.clear();
}
}
export class QueryModelViewTabView implements IPanelView {
public _componentId: string;
constructor(
@IInstantiationService private _instantiationService: IInstantiationService) {
}
public render(container: HTMLElement): void {
this.bootstrapAngular(container);
}
dispose() {
}
public clear() {
}
public layout(dimension: Dimension): void {
}
/**
* Load the angular components and record for this input that we have done so
*/
private bootstrapAngular(container: HTMLElement): string {
let uniqueSelector = bootstrapAngular(this._instantiationService,
QueryModelViewTabModule,
container,
'querytab-modelview-container',
{ modelViewId: this._componentId });
return uniqueSelector;
}
}

View File

@@ -0,0 +1,59 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/dialogModal';
import { Component, ViewChild, Inject, forwardRef, ElementRef, AfterViewInit } from '@angular/core';
import { ModelViewContent } from 'sql/workbench/electron-browser/modelComponents/modelViewContent.component';
import { IBootstrapParams } from 'sql/platform/bootstrap/node/bootstrapService';
import { DialogPane } from 'sql/platform/dialog/dialogPane';
import { ComponentEventType } from 'sql/workbench/electron-browser/modelComponents/interfaces';
import { Event, Emitter } from 'vs/base/common/event';
export interface LayoutRequestParams {
modelViewId?: string;
alwaysRefresh?: boolean;
}
export interface DialogComponentParams extends IBootstrapParams {
modelViewId: string;
validityChangedCallback: (valid: boolean) => void;
onLayoutRequested: Event<LayoutRequestParams>;
dialogPane: DialogPane;
}
@Component({
selector: 'querytab-modelview-container',
providers: [],
template: `
<modelview-content [modelViewId]="modelViewId">
</modelview-content>
`
})
export class QueryModelViewTabContainer implements AfterViewInit {
private _onResize = new Emitter<void>();
public readonly onResize: Event<void> = this._onResize.event;
public modelViewId: string;
@ViewChild(ModelViewContent) private _modelViewContent: ModelViewContent;
constructor(
@Inject(forwardRef(() => ElementRef)) private _el: ElementRef,
@Inject(IBootstrapParams) private _params: DialogComponentParams) {
this.modelViewId = this._params.modelViewId;
}
ngAfterViewInit(): void {
this._modelViewContent.onEvent(event => {
if (event.isRootComponent && event.eventType === ComponentEventType.validityChanged) {
this._params.validityChangedCallback(event.args);
}
});
let element = <HTMLElement>this._el.nativeElement;
element.style.height = '100%';
element.style.width = '100%';
}
public layout(): void {
this._modelViewContent.layout();
}
}

View File

@@ -3,7 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!sql/parts/query/editor/media/queryEditor';
import * as DOM from 'vs/base/browser/dom';
import { EditorOptions } from 'vs/workbench/common/editor';
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
@@ -12,10 +11,6 @@ import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { QueryPlanInput } from 'sql/workbench/parts/queryPlan/common/queryPlanInput';
import { QueryPlanModule } from 'sql/workbench/parts/queryPlan/electron-browser/queryPlan.module';
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
import { IMetadataService } from 'sql/platform/metadata/common/metadataService';
import { IScriptingService } from 'sql/platform/scripting/common/scriptingService';
import { IQueryEditorService } from 'sql/workbench/services/queryEditor/common/queryEditorService';
import { bootstrapAngular } from 'sql/platform/bootstrap/node/bootstrapService';
import { IQueryPlanParams } from 'sql/platform/bootstrap/node/bootstrapParams';
import { QUERYPLAN_SELECTOR } from 'sql/workbench/parts/queryPlan/electron-browser/queryPlan.component';
@@ -30,11 +25,7 @@ export class QueryPlanEditor extends BaseEditor {
@ITelemetryService telemetryService: ITelemetryService,
@IThemeService themeService: IThemeService,
@IInstantiationService private instantiationService: IInstantiationService,
@IConnectionManagementService private _connectionService: IConnectionManagementService,
@IMetadataService private _metadataService: IMetadataService,
@IScriptingService private _scriptingService: IScriptingService,
@IQueryEditorService private _queryEditorService: IQueryEditorService,
@IStorageService private storageService: IStorageService
@IStorageService storageService: IStorageService
) {
super(QueryPlanEditor.ID, telemetryService, themeService, storageService);
}

View File

@@ -18,7 +18,7 @@ import { IErrorMessageService } from 'sql/platform/errorMessage/common/errorMess
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { escape } from 'sql/base/common/strings';
import { elapsedTimeLabel } from 'sql/parts/query/common/localizedConstants';
import { elapsedTimeLabel } from 'sql/workbench/parts/query/common/localizedConstants';
import * as notebookUtils from 'sql/workbench/parts/notebook/notebookUtils';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';

View File

@@ -3,8 +3,8 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { QueryResultsInput } from 'sql/parts/query/common/queryResultsInput';
import { QueryInput } from 'sql/parts/query/common/queryInput';
import { QueryResultsInput } from 'sql/workbench/parts/query/common/queryResultsInput';
import { QueryInput } from 'sql/workbench/parts/query/common/queryInput';
import { EditDataInput } from 'sql/workbench/parts/editData/common/editDataInput';
import { IConnectableInput, IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
import { IQueryEditorService, IQueryEditorOptions } from 'sql/workbench/services/queryEditor/common/queryEditorService';