move code from parts to contrib (#8319)

This commit is contained in:
Anthony Dresser
2019-11-14 12:23:11 -08:00
committed by GitHub
parent 6438967202
commit 7a2c30e159
619 changed files with 848 additions and 848 deletions

View File

@@ -0,0 +1,251 @@
/*---------------------------------------------------------------------------------------------
* 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 { IExtensionTipsService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { SaveFormat } from 'sql/workbench/contrib/grid/common/interfaces';
import { Table } from 'sql/base/browser/ui/table/table';
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';
import { IGridDataProvider } from 'sql/platform/query/common/gridDataProvider';
import { INotificationService } from 'vs/platform/notification/common/notification';
import QueryRunner from 'sql/platform/query/common/queryRunner';
import { GridTableState } from 'sql/workbench/contrib/query/common/gridPanelState';
import * as Constants from 'sql/workbench/contrib/extensions/common/constants';
import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys';
import { getErrorMessage } from 'vs/base/common/errors';
export interface IGridActionContext {
gridDataProvider: IGridDataProvider;
table: Table<any>;
tableState: GridTableState;
cell?: { row: number; cell: number; };
selection?: Slick.Range[];
selectionModel?: CellSelectionModel<any>;
batchId: number;
resultId: number;
}
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,
@INotificationService private notificationService: INotificationService
) {
super(id, label, icon);
}
public async run(context: IGridActionContext): Promise<boolean> {
if (!context.gridDataProvider.canSerialize) {
this.notificationService.warn(localize('saveToFileNotSupported', "Save to file is not supported by the backing data source"));
}
try {
await context.gridDataProvider.serializeResults(this.format, mapForNumberColumn(context.selection));
} catch (error) {
this.notificationService.error(getErrorMessage(error));
return false;
}
return 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.gridDataProvider.copyResults(
mapForNumberColumn(context.selection),
this.copyHeader);
} else {
context.gridDataProvider.copyResults(context.selection, 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,
@IExtensionTipsService private readonly extensionTipsService: IExtensionTipsService
) {
super(ChartDataAction.ID, ChartDataAction.LABEL, ChartDataAction.ICON);
}
public run(context: IGridActionContext): Promise<boolean> {
// show the visualizer extension recommendation notification
this.extensionTipsService.promptRecommendedExtensionsByScenario(Constants.visualizerExtensions);
const activeEditor = this.editorService.activeControl as QueryEditor;
activeEditor.chart({ batchId: context.batchId, resultId: context.resultId });
return Promise.resolve(true);
}
}
export class VisualizerDataAction extends Action {
public static ID = 'grid.visualizer';
public static LABEL = localize("visualizer", "Visualizer");
public static ICON = 'viewVisualizer';
constructor(
private runner: QueryRunner,
@IAdsTelemetryService private adsTelemetryService: IAdsTelemetryService
) {
super(VisualizerDataAction.ID, VisualizerDataAction.LABEL, VisualizerDataAction.ICON);
}
public run(context: IGridActionContext): Promise<boolean> {
this.adsTelemetryService.sendActionEvent(
TelemetryKeys.TelemetryView.ResultsPanel,
TelemetryKeys.TelemetryAction.Click,
'VisualizerButton',
'VisualizerDataAction'
);
this.runner.notifyVisualizeRequested(context.batchId, context.resultId);
return Promise.resolve(true);
}
}

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 { Disposable } from 'vs/base/common/lifecycle';
import { IEditorCloseEvent } from 'vs/workbench/common/editor';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { Action } from 'vs/base/common/actions';
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';
import { mssqlProviderName } from 'sql/platform/connection/common/constants';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { IStatusbarService, StatusbarAlignment, IStatusbarEntryAccessor } from 'vs/workbench/services/statusbar/common/statusbar';
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 mssqlProviderName:
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 extends Disposable implements IWorkbenchContribution {
private static readonly ID = 'status.query.flavor';
private statusItem: IStatusbarEntryAccessor;
private _sqlStatusEditors: { [editorUri: string]: SqlProviderEntry };
constructor(
@IStatusbarService private readonly statusbarService: IStatusbarService,
@IEditorService private readonly editorService: EditorServiceImpl,
@IConnectionManagementService private readonly connectionManagementService: IConnectionManagementService
) {
super();
this._sqlStatusEditors = {};
this.statusItem = this._register(
this.statusbarService.addEntry({
text: nls.localize('changeProvider', "Change SQL language provider"),
},
SqlFlavorStatusbarItem.ID,
nls.localize('status.query.flavor', "SQL Language Flavor"),
StatusbarAlignment.RIGHT, 100)
);
this._register(this.connectionManagementService.onLanguageFlavorChanged((changeParams: DidChangeLanguageFlavorParams) => this._onFlavorChanged(changeParams)));
this._register(this.editorService.onDidVisibleEditorsChange(() => this._onEditorsChanged()));
this._register(this.editorService.onDidCloseEditor(event => this._onEditorClosed(event)));
}
private hide() {
this.statusbarService.updateEntryVisibility(SqlFlavorStatusbarItem.ID, false);
}
private show() {
this.statusbarService.updateEntryVisibility(SqlFlavorStatusbarItem.ID, true);
}
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) {
this.hide();
}
}
// 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 {
this.hide();
}
} else {
this.hide();
}
}
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.statusItem.update({ text: flavor.label });
} else {
this.statusItem.update({ text: SqlProviderEntry.getDefaultLabel() });
}
this.show();
}
}
}
}
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(mssqlProviderName)
];
// 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,157 @@
/*---------------------------------------------------------------------------------------------
* 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,
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 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,800 @@
/*---------------------------------------------------------------------------------------------
* 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/gridPanel';
import { attachTableStyler } from 'sql/platform/theme/common/styler';
import QueryRunner, { QueryGridDataProvider } 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/contrib/grid/common/interfaces';
import { IGridActionContext, SaveResultAction, CopyResultAction, SelectAllGridAction, MaximizeTableAction, RestoreTableAction, ChartDataAction, VisualizerDataAction } from 'sql/workbench/contrib/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 * as azdata from 'azdata';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { Emitter, Event } from 'vs/base/common/event';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { isUndefinedOrNull } from 'vs/base/common/types';
import { range, find } from 'vs/base/common/arrays';
import { Orientation } from 'vs/base/browser/ui/splitview/splitview';
import { Disposable, dispose, DisposableStore } 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, Dimension } 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';
import { ILogService } from 'vs/platform/log/common/log';
import { localize } from 'vs/nls';
import { IGridDataProvider } from 'sql/platform/query/common/gridDataProvider';
import { formatDocumentWithSelectedProvider, FormattingMode } from 'vs/editor/contrib/format/format';
import { CancellationToken } from 'vs/base/common/cancellation';
import { GridPanelState, GridTableState } from 'sql/workbench/contrib/query/common/gridPanelState';
const ROW_HEIGHT = 29;
const HEADER_HEIGHT = 26;
const MIN_GRID_HEIGHT_ROWS = 8;
const ESTIMATED_SCROLL_BAR_HEIGHT = 15;
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 GridPanel extends Disposable {
private container = document.createElement('div');
private splitView: ScrollableSplitView;
private tables: GridTable<any>[] = [];
private tableDisposable = this._register(new DisposableStore());
private queryRunnerDisposables = this._register(new DisposableStore());
private currentHeight: number;
private runner: QueryRunner;
private maximizedGrid: GridTable<any>;
private _state: GridPanelState | undefined;
constructor(
@IConfigurationService private readonly configurationService: IConfigurationService,
@IThemeService private readonly themeService: IThemeService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@ILogService private readonly logService: ILogService
) {
super();
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;
}
});
}
public render(container: HTMLElement): void {
this.container.style.width = '100%';
this.container.style.height = '100%';
container.appendChild(this.container);
}
public layout(size: Dimension): void {
this.splitView.layout(size.height);
// if the size hasn't change it won't layout our table so we have to do it manually
if (size.height === this.currentHeight) {
this.tables.map(e => e.layout());
}
this.currentHeight = size.height;
}
public focus(): void {
// will need to add logic to save the focused grid and focus that
}
public set queryRunner(runner: QueryRunner) {
this.queryRunnerDisposables.clear();
this.reset();
this.runner = runner;
this.queryRunnerDisposables.add(this.runner.onResultSet(this.onResultSet, this));
this.queryRunnerDisposables.add(this.runner.onResultSetUpdate(this.updateResultSet, this));
this.queryRunnerDisposables.add(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;
}, []));
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;
});
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 = () => {
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 = find(this.tables, t => t.resultSet.batchId === set.batchId && t.resultSet.id === set.id);
if (table) {
table.updateResult(set);
} else {
this.logService.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) {
// ensure we aren't adding a resultSet that is already visible
if (find(this.tables, t => t.resultSet.batchId === set.batchId && t.resultSet.id === set.id)) {
continue;
}
let tableState: GridTableState;
if (this.state) {
tableState = find(this.state.tableStates, 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.add(tableState.onMaximizedChange(e => {
if (e) {
this.maximizeTable(table.id);
} else {
this.minimizeTables();
}
}));
this.tableDisposable.add(attachTableStyler(table, this.themeService));
tables.push(table);
}
this.tables = this.tables.concat(tables);
// turn-off special-case process when only a single table is being displayed
if (this.tables.length > 1) {
for (let i = 0; i < this.tables.length; ++i) {
this.tables[i].isOnlyTable = false;
}
}
if (isUndefinedOrNull(this.maximizedGrid)) {
this.splitView.addViews(tables, tables.map(i => i.minimumSize), this.splitView.length);
}
}
public clear() {
this.reset();
this.state = undefined;
}
private reset() {
for (let i = this.splitView.length - 1; i >= 0; i--) {
this.splitView.removeView(i);
}
dispose(this.tables);
this.tableDisposable.clear();
this.tables = [];
this.maximizedGrid = undefined;
}
private maximizeTable(tableid: string): void {
if (!find(this.tables, 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;
if (this.state) {
this.tables.map(t => {
let state = find(this.state.tableStates, s => s.batchId === t.resultSet.batchId && s.resultId === t.resultSet.id);
if (!state) {
this.state.tableStates.push(t.state);
}
if (state) {
t.state = state;
}
});
}
}
public get state() {
return this._state;
}
public dispose() {
dispose(this.tables);
this.tables = undefined;
super.dispose();
}
}
export interface IDataSet {
rowCount: number;
columnInfo: azdata.IDbColumn[];
}
export abstract class GridTableBase<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 isOnlyTable: boolean = true;
// this handles if the row count is small, like 4-5 rows
protected get maxSize(): number {
return ((this.resultSet.rowCount) * this.rowHeight) + HEADER_HEIGHT + ESTIMATED_SCROLL_BAR_HEIGHT;
}
constructor(
state: GridTableState,
protected _resultSet: azdata.ResultSetSummary,
protected contextMenuService: IContextMenuService,
protected instantiationService: IInstantiationService,
protected editorService: IEditorService,
protected untitledEditorService: IUntitledEditorService,
protected 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'
? localize('xmlShowplan', "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
};
});
}
abstract get gridDataProvider(): IGridDataProvider;
public get resultSet(): azdata.ResultSetSummary {
return this._resultSet;
}
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;
}
// actionsOrientation controls the orientation (horizontal or vertical) of the actionBar
private build(actionsOrientation?: ActionsOrientation): void {
// Default is VERTICAL
if (isUndefinedOrNull(actionsOrientation)) {
actionsOrientation = ActionsOrientation.VERTICAL;
}
let actionBarContainer = document.createElement('div');
// Create a horizontal actionbar if orientation passed in is HORIZONTAL
if (actionsOrientation === ActionsOrientation.HORIZONTAL) {
actionBarContainer.className = 'grid-panel action-bar horizontal';
this.container.appendChild(actionBarContainer);
}
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.setTableTitle(localize('resultsGrid', "Results grid"));
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);
}
// If the actionsOrientation passed in is "VERTICAL" (or no actionsOrientation is passed in at all), create a vertical actionBar
if (actionsOrientation === ActionsOrientation.VERTICAL) {
actionBarContainer.className = 'grid-panel action-bar vertical';
actionBarContainer.style.width = ACTIONBAR_WIDTH + 'px';
this.container.appendChild(actionBarContainer);
}
let context: IGridActionContext = {
gridDataProvider: this.gridDataProvider,
table: this.table,
tableState: this.state,
batchId: this.resultSet.batchId,
resultId: this.resultSet.id
};
this.actionBar = new ActionBar(actionBarContainer, {
orientation: actionsOrientation, context: context
});
// update context before we run an action
this.selectionModel.onSelectedRangesChanged.subscribe(e => {
this.actionBar.context = this.generateContext();
});
this.rebuildActionBar();
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.gridDataProvider.getRowData(event.cell.row, 1).then(async d => {
let value = d.resultSubset.rows[0][event.cell.cell - 1];
let content = value.displayValue;
const input = this.untitledEditorService.createOrGet(undefined, column.isXml ? 'xml' : 'json', content);
const model = await input.resolve();
await this.instantiationService.invokeFunction(formatDocumentWithSelectedProvider, model.textEditorModel, FormattingMode.Explicit, CancellationToken.None);
return 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,
gridDataProvider: this.gridDataProvider,
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 });
}
protected abstract getCurrentActions(): IAction[];
protected abstract getContextActions(): IAction[];
// The actionsOrientation passed in controls the actionBar orientation
public layout(size?: number, orientation?: Orientation, actionsOrientation?: ActionsOrientation): void {
if (!this.table) {
this.build(actionsOrientation);
}
if (!size) {
size = this.currentHeight;
} else {
this.currentHeight = size;
}
// Table is always called with Orientation as VERTICAL
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
// if there is only one table then allow a minimum size of ROW_HEIGHT
return this.isOnlyTable ? ROW_HEIGHT : 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.gridDataProvider.getRowData(offset, count).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: IAction[] = [
new SelectAllGridAction(),
new Separator()
];
let contributedActions: IAction[] = this.getContextActions();
if (contributedActions && contributedActions.length > 0) {
actions.push(...contributedActions);
actions.push(new Separator());
}
actions.push(
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();
if (this.table) {
this.table.dispose();
}
if (this.actionBar) {
this.actionBar.dispose();
}
super.dispose();
}
}
class GridTable<T> extends GridTableBase<T> {
private _gridDataProvider: IGridDataProvider;
constructor(
private _runner: QueryRunner,
resultSet: azdata.ResultSetSummary,
state: GridTableState,
@IContextMenuService contextMenuService: IContextMenuService,
@IInstantiationService instantiationService: IInstantiationService,
@IContextKeyService private contextKeyService: IContextKeyService,
@IEditorService editorService: IEditorService,
@IUntitledEditorService untitledEditorService: IUntitledEditorService,
@IConfigurationService configurationService: IConfigurationService
) {
super(state, resultSet, contextMenuService, instantiationService, editorService, untitledEditorService, configurationService);
this._gridDataProvider = this.instantiationService.createInstance(QueryGridDataProvider, this._runner, resultSet.batchId, resultSet.id);
}
get gridDataProvider(): IGridDataProvider {
return this._gridDataProvider;
}
protected 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(
this.instantiationService.createInstance(SaveResultAction, SaveResultAction.SAVECSV_ID, SaveResultAction.SAVECSV_LABEL, SaveResultAction.SAVECSV_ICON, SaveFormat.CSV),
this.instantiationService.createInstance(SaveResultAction, SaveResultAction.SAVEEXCEL_ID, SaveResultAction.SAVEEXCEL_LABEL, SaveResultAction.SAVEEXCEL_ICON, SaveFormat.EXCEL),
this.instantiationService.createInstance(SaveResultAction, SaveResultAction.SAVEJSON_ID, SaveResultAction.SAVEJSON_LABEL, SaveResultAction.SAVEJSON_ICON, SaveFormat.JSON),
this.instantiationService.createInstance(SaveResultAction, SaveResultAction.SAVEXML_ID, SaveResultAction.SAVEXML_LABEL, SaveResultAction.SAVEXML_ICON, SaveFormat.XML),
this.instantiationService.createInstance(ChartDataAction)
);
if (this.contextKeyService.getContextKeyValue('showVisualizer')) {
actions.push(this.instantiationService.createInstance(VisualizerDataAction, this._runner));
}
return actions;
}
protected getContextActions(): IAction[] {
return [
this.instantiationService.createInstance(SaveResultAction, SaveResultAction.SAVECSV_ID, SaveResultAction.SAVECSV_LABEL, SaveResultAction.SAVECSV_ICON, SaveFormat.CSV),
this.instantiationService.createInstance(SaveResultAction, SaveResultAction.SAVEEXCEL_ID, SaveResultAction.SAVEEXCEL_LABEL, SaveResultAction.SAVEEXCEL_ICON, SaveFormat.EXCEL),
this.instantiationService.createInstance(SaveResultAction, SaveResultAction.SAVEJSON_ID, SaveResultAction.SAVEJSON_LABEL, SaveResultAction.SAVEJSON_ICON, SaveFormat.JSON),
this.instantiationService.createInstance(SaveResultAction, SaveResultAction.SAVEXML_ID, SaveResultAction.SAVEXML_LABEL, SaveResultAction.SAVEXML_ICON, SaveFormat.XML),
];
}
}

View File

@@ -0,0 +1,450 @@
/*---------------------------------------------------------------------------------------------
* 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/contrib/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/contrib/query/common/constants';
import * as ConnectionConstants from 'sql/platform/connection/common/constants';
import { EditDataEditor } from 'sql/workbench/contrib/editData/browser/editDataEditor';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { QueryInput } from 'sql/workbench/contrib/query/common/queryInput';
import { firstIndex } from 'vs/base/common/arrays';
const singleQuote = '\'';
function isConnected(editor: QueryEditor, connectionManagementService: IConnectionManagementService): boolean {
if (!editor || !editor.input) {
return false;
}
return connectionManagementService.isConnected(editor.input.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> {
const editor = this._editorService.activeControl;
if (editor instanceof QueryEditor) {
editor.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> {
const editor = this._editorService.activeControl;
if (editor instanceof QueryEditor || editor instanceof EditDataEditor) {
editor.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> {
const editor = this._editorService.activeControl;
if (editor instanceof QueryEditor) {
editor.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> {
const editor = this._editorService.activeControl;
if (editor instanceof QueryEditor) {
editor.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> {
const editor = this._editorService.activeControl;
if (editor instanceof QueryEditor || editor instanceof EditDataEditor) {
editor.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,
@IConnectionManagementService private connectionManagementService: IConnectionManagementService,
@IEditorService private editorService: IEditorService
) {
super(id, label);
this.enabled = true;
}
public run(): Promise<void> {
const editor = this.editorService.activeEditor;
if (editor instanceof QueryInput) {
this.connectionManagementService.rebuildIntelliSenseCache(editor.uri);
}
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> {
const editor = this._editorService.activeControl;
if (editor instanceof QueryEditor) {
editor.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 readonly editorService: IEditorService,
@IQueryModelService protected readonly queryModelService: IQueryModelService,
@IQueryManagementService private readonly queryManagementService: IQueryManagementService,
@IConnectionManagementService private readonly connectionManagementService: IConnectionManagementService,
@IConfigurationService private readonly configurationService: 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.input.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.configurationService, 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.input.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 ? firstIndex(columnInfo, c => c.columnName === columnName) : undefined;
}
private canQueryProcMetadata(editor: QueryEditor): boolean {
let info = this.connectionManagementService.getConnectionInfo(editor.input.uri);
return (info && info.providerId === ConnectionConstants.mssqlProviderName);
}
private getDatabaseName(editor: QueryEditor): string {
let info = this.connectionManagementService.getConnectionInfo(editor.input.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 readonly connectionManagementService: IConnectionManagementService,
@IQueryManagementService private readonly queryManagementService: IQueryManagementService,
@IEditorService private readonly editorService: IEditorService,
@INotificationService private readonly notificationService: INotificationService
) {
super(id, label);
this.enabled = true;
}
public run(): Promise<void> {
const editor = this.editorService.activeControl;
if (editor instanceof QueryEditor) {
if (!editor.isSelectionEmpty()) {
if (this.isConnected(editor)) {
let text = editor.getSelectionText();
if (text === '') {
text = editor.getAllText();
}
this.queryManagementService.parseSyntax(editor.input.uri, 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.input) {
return false;
}
return this.connectionManagementService.isConnected(editor.input.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,15 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.grid-panel.action-bar.vertical {
display : inline-block;
height : 100%;
vertical-align : top;
}
.grid-panel.action-bar.horizontal {
width : 100%;
display: flex;
}

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,42 @@
/*---------------------------------------------------------------------------------------------
* 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 .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,38 @@
/*---------------------------------------------------------------------------------------------
* 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;
}

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,8 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.query-editor-view {
width: 100%;
}

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,408 @@
/*---------------------------------------------------------------------------------------------
* 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, { IQueryMessage } from 'sql/platform/query/common/queryRunner';
import { IExpandableTree } from 'sql/workbench/contrib/objectExplorer/browser/treeUpdateUtils';
import { ISelectionData } from 'azdata';
import { IDataSource, ITree, IRenderer, ContextMenuEvent } from 'vs/base/parts/tree/browser/tree';
import { generateUuid } from 'vs/base/common/uuid';
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, ITheme } 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 { isArray } from 'vs/base/common/types';
import { Disposable, DisposableStore } 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 { $, Dimension, createStyleSheet } from 'vs/base/browser/dom';
import { QueryEditor } from 'sql/workbench/contrib/query/browser/queryEditor';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { resultsErrorColor } from 'sql/platform/theme/common/colors';
import { MessagePanelState } from 'sql/workbench/contrib/query/common/messagePanelState';
export interface IResultMessageIntern extends IQueryMessage {
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 MessagePanel extends Disposable {
private ds = new MessageDataSource();
private renderer = new MessageRenderer();
private model = new Model();
private controller: MessageController;
private container = $('.message-tree');
private styleElement = createStyleSheet(this.container);
private queryRunnerDisposables = this._register(new DisposableStore());
private _state: MessagePanelState | undefined;
private tree: ITree;
constructor(
@IInstantiationService instantiationService: IInstantiationService,
@IThemeService private readonly themeService: IThemeService,
@IClipboardService private readonly clipboardService: IClipboardService,
@IContextMenuService private readonly contextMenuService: IContextMenuService
) {
super();
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 = this._register(new Tree(this.container, {
dataSource: this.ds,
renderer: this.renderer,
controller: this.controller
}, { keyboardSupport: false, horizontalScrollMode: ScrollbarVisibility.Auto }));
this.tree.setInput(this.model);
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.container.style.width = '100%';
this.container.style.height = '100%';
this._register(attachListStyler(this.tree, this.themeService));
this._register(this.themeService.onThemeChange(this.applyStyles, this));
this.applyStyles(this.themeService.getTheme());
this.controller.onKeyDown = (tree, event) => {
if (event.ctrlKey && event.code === 'KeyC') {
let context: IMessagesActionContext = {
selection: document.getSelection(),
tree: this.tree,
};
let copyMessageAction = instantiationService.createInstance(CopyMessagesAction, this.clipboardService);
copyMessageAction.run(context);
event.preventDefault();
event.stopPropagation();
return true;
}
return false;
};
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;
};
}
public render(container: HTMLElement): void {
container.appendChild(this.container);
}
public layout(size: Dimension): 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.height);
if (this.state && this.state.scrollPosition) {
expandableTree.setScrollPosition(this.state.scrollPosition);
} else {
if (previousScrollPosition === 1) {
expandableTree.setScrollPosition(1);
}
}
}
public focus(): void {
this.tree.refresh();
this.tree.domFocus();
}
public set queryRunner(runner: QueryRunner) {
this.queryRunnerDisposables.clear();
this.reset();
this.queryRunnerDisposables.add(runner.onQueryStart(() => this.reset()));
this.queryRunnerDisposables.add(runner.onMessage(e => this.onMessage(e)));
this.onMessage(runner.messages);
}
private onMessage(message: IQueryMessage | IQueryMessage[]) {
if (isArray(message)) {
this.model.messages.push(...message);
} else {
this.model.messages.push(message);
}
// convert to old VS Code tree interface with expandable methods
let expandableTree: IExpandableTree = <IExpandableTree>this.tree;
if (this.state && this.state.scrollPosition) {
const previousScroll = this.state.scrollPosition;
this.tree.refresh(this.model).then(() => {
// Restore the previous scroll position when switching between tabs
expandableTree.setScrollPosition(previousScroll);
});
} 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 applyStyles(theme: ITheme): void {
const errorColor = theme.getColor(resultsErrorColor);
const content: string[] = [];
if (errorColor) {
content.push(`.message-tree .monaco-tree-rows .error-message { color: ${errorColor}; }`);
}
const newStyles = content.join('\n');
if (newStyles !== this.styleElement.innerHTML) {
this.styleElement.innerHTML = newStyles;
}
}
private reset() {
this.model.messages = [];
this._state = undefined;
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);
}
}
public get state(): MessagePanelState {
return this._state;
}
public clear() {
this.reset();
}
public dispose() {
if (this.container) {
this.container.remove();
this.container = undefined;
}
if (this.styleElement) {
this.styleElement.remove();
this.styleElement = undefined;
}
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 {
getHeight(tree: ITree, element: IQueryMessage): number {
const lineHeight = 22;
let lines = element.message.split('\n').length;
return lineHeight * lines;
}
getTemplateId(tree: ITree, element: IQueryMessage): 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: IQueryMessage, 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
) {
super(options, configurationService);
}
protected onLeftClick(tree: ITree, element: any, eventish: ICancelableEvent, origin: string = 'mouse'): boolean {
// 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 editor = this.workbenchEditorService.activeControl as QueryEditor;
const codeEditor = <ICodeEditor>editor.getControl();
codeEditor.focus();
codeEditor.setSelection({ endColumn: selection.endColumn + 1, endLineNumber: selection.endLine + 1, startColumn: selection.startColumn + 1, startLineNumber: selection.startLine + 1 });
codeEditor.revealRangeInCenterIfOutsideViewport({ endColumn: selection.endColumn + 1, endLineNumber: selection.endLine + 1, startColumn: selection.startColumn + 1, startLineNumber: selection.startLine + 1 });
}
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,73 @@
/*---------------------------------------------------------------------------------------------
* 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%;
}
.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: auto;
overflow-y: auto;
}
.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,78 @@
/*---------------------------------------------------------------------------------------------
* 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/workbench/services/dialog/browser/wizardNavigation.component';
import { Extensions, IComponentRegistry } from 'sql/platform/dashboard/browser/modelComponentRegistry';
import { ModelViewContent } from 'sql/workbench/browser/modelComponents/modelViewContent.component';
import { ModelComponentWrapper } from 'sql/workbench/browser/modelComponents/modelComponentWrapper.component';
import { ComponentHostDirective } from 'sql/workbench/contrib/dashboard/browser/core/componentHost.directive';
import { providerIterator } from 'sql/workbench/services/bootstrap/browser/bootstrapService';
import { CommonServiceInterface } from 'sql/workbench/services/bootstrap/browser/commonServiceInterface.service';
import { EditableDropDown } from 'sql/platform/browser/editableDropdown/editableDropdown.component';
import { QueryModelViewTabContainer } from 'sql/workbench/contrib/query/browser/modelViewTab/queryModelViewTabContainer.component';
import { Checkbox } from 'sql/base/browser/ui/checkbox/checkbox.component';
import { SelectBox } from 'sql/platform/browser/selectBox/selectBox.component';
import { InputBox } from 'sql/platform/browser/inputbox/inputBox.component';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Registry } from 'vs/platform/registry/common/platform';
import { IBootstrapParams, ISelector } from 'sql/workbench/services/bootstrap/common/bootstrapParams';
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,88 @@
/*---------------------------------------------------------------------------------------------
* 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/workbench/services/bootstrap/browser/bootstrapService';
import { QueryModelViewTabModule } from 'sql/workbench/contrib/query/browser/modelViewTab/queryModelViewTab.module';
import { QueryModelViewState } from 'sql/workbench/contrib/query/common/modelViewTab/modelViewState';
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 putState(dynamicModelViewTabsState: Map<string, QueryModelViewState>): void {
dynamicModelViewTabsState.set(this.view.componentId, this.view.state);
}
public captureState(dynamicModelViewTabsState: Map<string, QueryModelViewState>): void {
for (let i = 0; i < dynamicModelViewTabsState.keys.length; ++i) {
let currentIdentifier = dynamicModelViewTabsState[dynamicModelViewTabsState.keys[i]];
if (currentIdentifier === this.view.componentId) {
this.view.state = dynamicModelViewTabsState[dynamicModelViewTabsState.keys[i]];
break;
}
}
}
public dispose() {
dispose(this.view);
}
public clear() {
this.view.clear();
}
}
export class QueryModelViewTabView implements IPanelView {
public state: QueryModelViewState = new QueryModelViewState();
constructor(
@IInstantiationService private _instantiationService: IInstantiationService) {
}
public render(container: HTMLElement): void {
this.bootstrapAngular(container);
}
public dispose() {
}
public clear() {
}
public layout(dimension: Dimension): void {
}
public focus(): void {
}
public get componentId(): string {
return this.state.componentId;
}
public set componentId(value: string) {
this.state.componentId = value;
}
/**
* 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.state.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/browser/modelComponents/modelViewContent.component';
import { DialogPane } from 'sql/workbench/services/dialog/browser/dialogPane';
import { ComponentEventType } from 'sql/workbench/browser/modelComponents/interfaces';
import { Event, Emitter } from 'vs/base/common/event';
import { IBootstrapParams } from 'sql/workbench/services/bootstrap/common/bootstrapParams';
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

@@ -0,0 +1,566 @@
/*---------------------------------------------------------------------------------------------
* 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 { QueryEditor } from 'sql/workbench/contrib/query/browser/queryEditor';
import { QueryResultsEditor } from 'sql/workbench/contrib/query/browser/queryResultsEditor';
import { QueryResultsInput } from 'sql/workbench/contrib/query/common/queryResultsInput';
import * as queryContext from 'sql/workbench/contrib/query/common/queryContext';
import { QueryInput } from 'sql/workbench/contrib/query/common/queryInput';
import {
RunQueryKeyboardAction, RunCurrentQueryKeyboardAction, CancelQueryKeyboardAction, RefreshIntellisenseKeyboardAction, ToggleQueryResultsKeyboardAction,
RunQueryShortcutAction, RunCurrentQueryWithActualPlanKeyboardAction, FocusOnCurrentQueryKeyboardAction, ParseSyntaxAction
} from 'sql/workbench/contrib/query/browser/keyboardQueryActions';
import * as gridActions from 'sql/workbench/contrib/editData/common/gridActions';
import * as gridCommands from 'sql/workbench/contrib/editData/browser/gridCommands';
import * as Constants from 'sql/workbench/contrib/query/common/constants';
import { localize } from 'vs/nls';
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import { TimeElapsedStatusBarContributions, RowCountStatusBarContributions, QueryStatusStatusBarContributions } from 'sql/workbench/contrib/query/browser/statusBarItems';
import { SqlFlavorStatusbarItem } from 'sql/workbench/contrib/query/browser/flavorStatus';
import { NewQueryTask, OE_NEW_QUERY_ACTION_ID, DE_NEW_QUERY_COMMAND_ID } from 'sql/workbench/contrib/query/browser/queryActions';
import { TreeNodeContextKey } from 'sql/workbench/contrib/objectExplorer/common/treeNodeContextKey';
import { MssqlNodeContext } from 'sql/workbench/contrib/dataExplorer/browser/mssqlNodeContext';
import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands';
import { ManageActionContext } from 'sql/workbench/browser/actions';
import { ItemContextKey } from 'sql/workbench/contrib/dashboard/browser/widgets/explorer/explorerTreeContext';
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)]);
const actionRegistry = <IWorkbenchActionRegistry>Registry.as(Extensions.WorkbenchActions);
new NewQueryTask().registerTask();
MenuRegistry.appendMenuItem(MenuId.ObjectExplorerItemContext, {
group: '0_query',
order: 1,
command: {
id: OE_NEW_QUERY_ACTION_ID,
title: localize('newQuery', "New Query")
},
when: ContextKeyExpr.or(ContextKeyExpr.and(TreeNodeContextKey.Status.notEqualsTo('Unavailable'), TreeNodeContextKey.NodeType.isEqualTo('Server')), ContextKeyExpr.and(TreeNodeContextKey.Status.notEqualsTo('Unavailable'), TreeNodeContextKey.NodeType.isEqualTo('Database')))
});
// New Query
MenuRegistry.appendMenuItem(MenuId.DataExplorerContext, {
group: '0_query',
order: 1,
command: {
id: DE_NEW_QUERY_COMMAND_ID,
title: localize('newQuery', "New Query")
},
when: MssqlNodeContext.IsDatabaseOrServer
});
const ExplorerNewQueryActionID = 'explorer.query';
CommandsRegistry.registerCommand(ExplorerNewQueryActionID, (accessor, context: ManageActionContext) => {
const commandService = accessor.get(ICommandService);
return commandService.executeCommand(NewQueryTask.ID, context.profile);
});
MenuRegistry.appendMenuItem(MenuId.ExplorerWidgetContext, {
command: {
id: ExplorerNewQueryActionID,
title: NewQueryTask.LABEL
},
when: ItemContextKey.ItemType.isEqualTo('database'),
order: 1
});
// Query Actions
actionRegistry.registerWorkbenchAction(
new SyncActionDescriptor(
RunQueryKeyboardAction,
RunQueryKeyboardAction.ID,
RunQueryKeyboardAction.LABEL,
{ primary: KeyCode.F5 }
),
RunQueryKeyboardAction.LABEL
);
// 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: ContextKeyEqualsExpr.create('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
const registryProperties = {
'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")
},
'mssql.query.rowCount': {
'type': 'number',
'default': 0,
'description': localize('mssql.query.setRowCount', "Maximum number of rows to return before the server stops processing your query.")
},
'mssql.query.textSize': {
'type': 'number',
'default': 2147483647,
'description': localize('mssql.query.textSize', "Maximum size of text and ntext data returned from a SELECT statement")
},
'mssql.query.executionTimeout': {
'type': 'number',
'default': 0,
'description': localize('mssql.query.executionTimeout', "An execution time-out of 0 indicates an unlimited wait (no time-out)")
},
'mssql.query.noCount': {
'type': 'boolean',
'default': false,
'description': localize('mssql.query.noCount', "Enable SET NOCOUNT option")
},
'mssql.query.noExec': {
'type': 'boolean',
'default': false,
'description': localize('mssql.query.noExec', "Enable SET NOEXEC option")
},
'mssql.query.parseOnly': {
'type': 'boolean',
'default': false,
'description': localize('mssql.query.parseOnly', "Enable SET PARSEONLY option")
},
'mssql.query.arithAbort': {
'type': 'boolean',
'default': true,
'description': localize('mssql.query.arithAbort', "Enable SET ARITHABORT option")
},
'mssql.query.statisticsTime': {
'type': 'boolean',
'default': false,
'description': localize('mssql.query.statisticsTime', "Enable SET STATISTICS TIME option")
},
'mssql.query.statisticsIO': {
'type': 'boolean',
'default': false,
'description': localize('mssql.query.statisticsIO', "Enable SET STATISTICS IO option")
},
'mssql.query.xactAbortOn': {
'type': 'boolean',
'default': false,
'description': localize('mssql.query.xactAbortOn', "Enable SET XACT_ABORT ON option")
},
'mssql.query.transactionIsolationLevel': {
'enum': ['READ COMMITTED', 'READ UNCOMMITTED', 'REPEATABLE READ', 'SERIALIZABLE'],
'default': 'READ COMMITTED',
'description': localize('mssql.query.transactionIsolationLevel', "Enable SET TRANSACTION ISOLATION LEVEL option")
},
'mssql.query.deadlockPriority': {
'enum': ['Normal', 'Low'],
'default': 'Normal',
'description': localize('mssql.query.deadlockPriority', "Enable SET DEADLOCK_PRIORITY option")
},
'mssql.query.lockTimeout': {
'type': 'number',
'default': -1,
'description': localize('mssql.query.lockTimeout', "Enable SET LOCK TIMEOUT option (in milliseconds)")
},
'mssql.query.queryGovernorCostLimit': {
'type': 'number',
'default': -1,
'description': localize('mssql.query.queryGovernorCostLimit', "Enable SET QUERY_GOVERNOR_COST_LIMIT")
},
'mssql.query.ansiDefaults': {
'type': 'boolean',
'default': false,
'description': localize('mssql.query.ansiDefaults', "Enable SET ANSI_DEFAULTS")
},
'mssql.query.quotedIdentifier': {
'type': 'boolean',
'default': true,
'description': localize('mssql.query.quotedIdentifier', "Enable SET QUOTED_IDENTIFIER")
},
'mssql.query.ansiNullDefaultOn': {
'type': 'boolean',
'default': true,
'description': localize('mssql.query.ansiNullDefaultOn', "Enable SET ANSI_NULL_DFLT_ON")
},
'mssql.query.implicitTransactions': {
'type': 'boolean',
'default': false,
'description': localize('mssql.query.implicitTransactions', "Enable SET IMPLICIT_TRANSACTIONS")
},
'mssql.query.cursorCloseOnCommit': {
'type': 'boolean',
'default': false,
'description': localize('mssql.query.cursorCloseOnCommit', "Enable SET CURSOR_CLOSE_ON_COMMIT")
},
'mssql.query.ansiPadding': {
'type': 'boolean',
'default': true,
'description': localize('mssql.query.ansiPadding', "Enable SET ANSI_PADDING")
},
'mssql.query.ansiWarnings': {
'type': 'boolean',
'default': true,
'description': localize('mssql.query.ansiWarnings', "Enable SET ANSI_WARNINGS")
},
'mssql.query.ansiNulls': {
'type': 'boolean',
'default': true,
'description': localize('mssql.query.ansiNulls', "Enable SET ANSI_NULLS")
}
};
// Setup keybindings
const 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
const configurationRegistry = <IConfigurationRegistry>Registry.as(ConfigExtensions.Configuration);
configurationRegistry.registerConfiguration({
'id': 'sqlEditor',
'title': 'SQL Editor',
'type': 'object',
'properties': registryProperties
});
const workbenchRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
workbenchRegistry.registerWorkbenchContribution(TimeElapsedStatusBarContributions, LifecyclePhase.Restored);
workbenchRegistry.registerWorkbenchContribution(RowCountStatusBarContributions, LifecyclePhase.Restored);
workbenchRegistry.registerWorkbenchContribution(QueryStatusStatusBarContributions, LifecyclePhase.Restored);
workbenchRegistry.registerWorkbenchContribution(SqlFlavorStatusbarItem, LifecyclePhase.Restored);

View File

@@ -0,0 +1,810 @@
/*---------------------------------------------------------------------------------------------
* 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, IActionViewItem, IActionRunner } from 'vs/base/common/actions';
import { IDisposable, Disposable } 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, QueryExecutionOptions } from 'azdata';
import {
IConnectionManagementService,
IConnectionParams,
INewConnectionParams,
ConnectionType,
RunQueryOnConnectionMode,
IConnectionCompletionOptions,
IConnectableInput
} from 'sql/platform/connection/common/connectionManagement';
import { QueryEditor } from 'sql/workbench/contrib/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 { Dropdown } from 'sql/base/parts/editableDropdown/browser/dropdown';
import { Task } from 'sql/platform/tasks/browser/tasksRegistry';
import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/browser/objectExplorerService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IQueryEditorService } from 'sql/workbench/services/queryEditor/common/queryEditorService';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { getCurrentGlobalConnection } from 'sql/workbench/browser/taskUtilities';
import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { OEAction } from 'sql/workbench/contrib/objectExplorer/browser/objectExplorerActions';
import { TreeViewItemHandleArg } from 'sql/workbench/common/views';
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
import { IQueryManagementService } from 'sql/platform/query/common/queryManagement';
/**
* 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 readonly connectionManagementService: IConnectionManagementService,
protected readonly 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.input) {
return false;
}
return this.connectionManagementService.isConnected(editor.input.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.input,
connectionType: ConnectionType.editor,
runQueryOnCompletion: runQueryOnCompletion ? runQueryOnCompletion : RunQueryOnConnectionMode.none,
querySelection: selection
};
this.connectionManagementService.showConnectionDialog(params);
}
}
export function openNewQuery(accessor: ServicesAccessor, profile?: IConnectionProfile, initalContent?: string, onConnection?: RunQueryOnConnectionMode): Promise<void> {
const editorService = accessor.get(IEditorService);
const queryEditorService = accessor.get(IQueryEditorService);
const objectExplorerService = accessor.get(IObjectExplorerService);
const connectionManagementService = accessor.get(IConnectionManagementService);
if (!profile) {
profile = getCurrentGlobalConnection(objectExplorerService, connectionManagementService, editorService);
}
return queryEditorService.newSqlEditor(initalContent).then((owner: IConnectableInput) => {
// Connect our editor to the input connection
let options: IConnectionCompletionOptions = {
params: { connectionType: ConnectionType.editor, runQueryOnCompletion: onConnection, input: owner },
saveTheConnection: false,
showDashboard: false,
showConnectionDialogOnError: true,
showFirewallRuleOnError: true
};
if (profile) {
return connectionManagementService.connect(profile, owner.uri, options).then();
}
return undefined;
});
}
// --- actions
export class NewQueryTask extends Task {
public static ID = 'newQuery';
public static LABEL = nls.localize('newQueryTask.newQuery', "New Query");
public static ICON = 'new-query';
constructor() {
super({
id: NewQueryTask.ID,
title: NewQueryTask.LABEL,
iconPath: undefined,
iconClass: NewQueryTask.ICON
});
}
public runTask(accessor: ServicesAccessor, profile: IConnectionProfile): Promise<void> {
return openNewQuery(accessor, profile);
}
}
export const OE_NEW_QUERY_ACTION_ID = 'objectExplorer.newQuery';
CommandsRegistry.registerCommand(OE_NEW_QUERY_ACTION_ID, (accessor: ServicesAccessor, actionContext: any) => {
const instantiationService = accessor.get(IInstantiationService);
return instantiationService.createInstance(OEAction, NewQueryTask.ID, NewQueryTask.LABEL).run(actionContext);
});
export const DE_NEW_QUERY_COMMAND_ID = 'dataExplorer.newQuery';
// New Query
CommandsRegistry.registerCommand({
id: DE_NEW_QUERY_COMMAND_ID,
handler: async (accessor, args: TreeViewItemHandleArg) => {
if (args.$treeItem) {
const queryEditorService = accessor.get(IQueryEditorService);
const connectionService = accessor.get(IConnectionManagementService);
const capabilitiesService = accessor.get(ICapabilitiesService);
const owner = await queryEditorService.newSqlEditor();
// Connect our editor to the input connection
let options: IConnectionCompletionOptions = {
params: { connectionType: ConnectionType.editor, input: owner },
saveTheConnection: false,
showDashboard: false,
showConnectionDialogOnError: true,
showFirewallRuleOnError: true
};
return connectionService.connect(new ConnectionProfile(capabilitiesService, args.$treeItem.payload), owner.uri, options);
}
return true;
}
});
/**
* 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 readonly queryModelService: IQueryModelService,
@IConnectionManagementService connectionManagementService: IConnectionManagementService
) {
super(connectionManagementService, editor, RunQueryAction.ID, RunQueryAction.EnabledClass);
this.label = nls.localize('runQueryLabel', "Run");
}
public async 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;
}
public async 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;
}
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.input.runQueryStatement(selection);
} else {
// get the selection again this time with trimming
selection = editor.getSelection();
editor.input.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 readonly queryModelService: IQueryModelService,
@IConnectionManagementService connectionManagementService: IConnectionManagementService
) {
super(connectionManagementService, editor, CancelQueryAction.ID, CancelQueryAction.EnabledClass);
this.enabled = false;
this.label = nls.localize('cancelQueryLabel', "Cancel");
}
public async run(): Promise<void> {
if (this.isConnected(this.editor)) {
if (!this.editor.input) {
console.error('editor input was null');
return;
}
this.queryModelService.cancelQuery(this.editor.input.uri);
}
}
}
/**
* 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,
@IConnectionManagementService connectionManagementService: IConnectionManagementService
) {
super(connectionManagementService, editor, EstimatedQueryPlanAction.ID, EstimatedQueryPlanAction.EnabledClass);
this.label = nls.localize('estimatedQueryPlan', "Explain");
}
public async 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;
}
public runQuery(editor: QueryEditor) {
if (!editor) {
editor = this.editor;
}
if (this.isConnected(editor)) {
editor.input.runQuery(editor.getSelection(), {
displayEstimatedQueryPlan: true
});
}
}
}
export class ActualQueryPlanAction extends QueryTaskbarAction {
public static EnabledClass = 'actualQueryPlan';
public static ID = 'actualQueryPlanAction';
constructor(
editor: QueryEditor,
@IConnectionManagementService connectionManagementService: IConnectionManagementService
) {
super(connectionManagementService, editor, ActualQueryPlanAction.ID, ActualQueryPlanAction.EnabledClass);
this.label = nls.localize('actualQueryPlan', "Actual");
}
public async 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;
}
public runQuery(editor: QueryEditor) {
if (!editor) {
editor = this.editor;
}
if (this.isConnected(editor)) {
let selection = editor.getSelection();
if (!selection) {
selection = editor.getAllSelection();
}
editor.input.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 async 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.input);
return;
}
}
/**
* 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 async run(): Promise<void> {
this.connectEditor(this.editor);
return;
}
}
/**
* 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 _connectLabel = nls.localize('connectDatabaseLabel', "Connect");
private _disconnectLabel = nls.localize('disconnectDatabaseLabel', "Disconnect");
constructor(
editor: QueryEditor,
private _connected: boolean,
@IConnectionManagementService connectionManagementService: IConnectionManagementService
) {
super(connectionManagementService, editor, ToggleConnectDatabaseAction.ID, undefined);
}
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 async run(): Promise<void> {
if (!this.editor.input.isSharedSession) {
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.input);
} else {
this.connectEditor(this.editor);
}
}
return;
}
}
/**
* 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 async run(): Promise<void> {
return;
}
}
/**
* Action class that toggles SQLCMD mode for the editor
*/
export class ToggleSqlCmdModeAction extends QueryTaskbarAction {
public static EnableSqlcmdClass = 'enablesqlcmd';
public static DisableSqlcmdClass = 'disablesqlcmd';
public static ID = 'ToggleSqlCmdModeAction';
private _enablesqlcmdLabel = nls.localize('enablesqlcmdLabel', "Enable SQLCMD");
private _disablesqlcmdLabel = nls.localize('disablesqlcmdLabel', "Disable SQLCMD");
constructor(
editor: QueryEditor,
private _isSqlCmdMode: boolean,
@IQueryManagementService protected readonly queryManagementService: IQueryManagementService,
@IConfigurationService protected readonly configurationService: IConfigurationService,
@IConnectionManagementService connectionManagementService: IConnectionManagementService
) {
super(connectionManagementService, editor, ToggleSqlCmdModeAction.ID, undefined);
}
public get isSqlCmdMode(): boolean {
return this._isSqlCmdMode;
}
public set isSqlCmdMode(value: boolean) {
this._isSqlCmdMode = value;
this.updateLabelAndIcon();
}
private updateLabelAndIcon(): void {
// show option to disable sql cmd mode if already enabled
this.label = this.isSqlCmdMode ? this._disablesqlcmdLabel : this._enablesqlcmdLabel;
this.isSqlCmdMode ? this.updateCssClass(ToggleSqlCmdModeAction.DisableSqlcmdClass) : this.updateCssClass(ToggleSqlCmdModeAction.EnableSqlcmdClass);
}
public async run(): Promise<void> {
const toSqlCmdState = !this.isSqlCmdMode; // input.state change triggers event that changes this.isSqlCmdMode, so store it before using
this.editor.input.state.isSqlCmdMode = toSqlCmdState;
// set query options
let queryoptions: QueryExecutionOptions = { options: {} };
queryoptions.options['isSqlCmdMode'] = toSqlCmdState;
if (!this.editor.input) {
console.error('editor input was null');
return;
}
this.queryManagementService.setQueryExecutionOptions(this.editor.input.uri, queryoptions);
// set intellisense options
toSqlCmdState ? this.connectionManagementService.doChangeLanguageFlavor(this.editor.input.uri, 'sqlcmd', 'MSSQL') : this.connectionManagementService.doChangeLanguageFlavor(this.editor.input.uri, 'sql', 'MSSQL');
}
}
/*
* Action item that handles the dropdown (combobox) that lists the available databases.
* Based off StartDebugActionItem.
*/
export class ListDatabasesActionItem extends Disposable implements IActionViewItem {
public static ID = 'listDatabaseQueryActionItem';
public actionRunner: IActionRunner;
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,
@IContextViewService contextViewProvider: IContextViewService,
@IConnectionManagementService private readonly connectionManagementService: IConnectionManagementService,
@INotificationService private readonly notificationService: INotificationService,
@IConfigurationService private readonly configurationService: IConfigurationService
) {
super();
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, {
strictSelection: true,
placeholder: this._selectDatabaseString,
ariaLabel: this._selectDatabaseString,
actionLabel: nls.localize('listDatabases.toggleDatabaseNameDropdown', "Select Database Toggle Dropdown")
});
this._register(this._dropdown.onValueChange(s => this.databaseSelected(s)));
this._register(this._dropdown.onFocus(() => this.onDropdownFocus()));
}
// Register event handlers
this._register(this.connectionManagementService.onConnectionChanged(params => this.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 {
}
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);
}
}
// 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 {
if (!this._editor.input) {
console.error('editor input was null');
return;
}
let uri = this._editor.input.uri;
if (!uri) {
return;
}
let profile = this.connectionManagementService.getConnectionProfile(uri);
if (!profile) {
return;
}
this.connectionManagementService.changeDatabase(this._editor.input.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(): string | undefined {
if (!this._editor.input) {
console.error('editor input was null');
return undefined;
}
let uri = this._editor.input.uri;
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;
}
if (!this._editor.input) {
console.error('editor input was null');
return;
}
let uri = this._editor.input.uri;
if (uri !== connParams.connectionUri) {
return;
}
this.updateConnection(connParams.connectionProfile.databaseName);
}
private onDropdownFocus(): void {
if (!this._editor.input) {
console.error('editor input was null');
return;
}
let uri = this._editor.input.uri;
if (!uri) {
return;
}
this.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();
if (!this._editor.input) {
console.error('editor input was null');
return;
}
let uri = this._editor.input.uri;
if (!uri) {
return;
}
this.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,613 @@
/*---------------------------------------------------------------------------------------------
* 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 DOM from 'vs/base/browser/dom';
import { EditorOptions, IEditorControl, IEditorMemento } from 'vs/workbench/common/editor';
import { BaseEditor, EditorMemento } from 'vs/workbench/browser/parts/editor/baseEditor';
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 { TextResourceEditor } from 'vs/workbench/browser/parts/editor/textResourceEditor';
import { TextFileEditor } from 'vs/workbench/contrib/files/browser/editors/textFileEditor';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { CancellationToken } from 'vs/base/common/cancellation';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { SplitView, Sizing } from 'vs/base/browser/ui/splitview/splitview';
import { Event } from 'vs/base/common/event';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { ISelectionData } from 'azdata';
import { Action, IActionViewItem } from 'vs/base/common/actions';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { BaseTextEditor } from 'vs/workbench/browser/parts/editor/textEditor';
import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput';
import { URI } from 'vs/base/common/uri';
import { IFileService, FileChangesEvent } from 'vs/platform/files/common/files';
import { QueryInput, IQueryEditorStateChange } from 'sql/workbench/contrib/query/common/queryInput';
import { QueryResultsEditor } from 'sql/workbench/contrib/query/browser/queryResultsEditor';
import * as queryContext from 'sql/workbench/contrib/query/common/queryContext';
import { Taskbar, ITaskbarContent } from 'sql/base/browser/ui/taskbar/taskbar';
import * as actions from 'sql/workbench/contrib/query/browser/queryActions';
const QUERY_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'queryEditorViewState';
interface IQueryEditorViewState {
resultsHeight: number | undefined;
}
/**
* 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';
private dimension: DOM.Dimension = new DOM.Dimension(0, 0);
private resultsEditor: QueryResultsEditor;
private resultsEditorContainer: HTMLElement;
private textResourceEditor: TextResourceEditor;
private textFileEditor: TextFileEditor;
private currentTextEditor: BaseTextEditor;
private textResourceEditorContainer: HTMLElement;
private textFileEditorContainer: HTMLElement;
private taskbar: Taskbar;
private splitviewContainer: HTMLElement;
private splitview: SplitView;
private inputDisposables = this._register(new DisposableStore());
private resultsVisible = false;
private queryEditorVisible: IContextKey<boolean>;
private editorMemento: IEditorMemento<IQueryEditorViewState>;
//actions
private _runQueryAction: actions.RunQueryAction;
private _cancelQueryAction: actions.CancelQueryAction;
private _toggleConnectDatabaseAction: actions.ToggleConnectDatabaseAction;
private _changeConnectionAction: actions.ConnectDatabaseAction;
private _listDatabasesAction: actions.ListDatabasesAction;
private _estimatedQueryPlanAction: actions.EstimatedQueryPlanAction;
private _actualQueryPlanAction: actions.ActualQueryPlanAction;
private _listDatabasesActionItem: actions.ListDatabasesActionItem;
private _toggleSqlcmdMode: actions.ToggleSqlCmdModeAction;
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@IThemeService themeService: IThemeService,
@IStorageService storageService: IStorageService,
@IContextKeyService contextKeyService: IContextKeyService,
@IEditorGroupsService editorGroupService: IEditorGroupsService,
@IFileService fileService: IFileService,
@IEditorService private readonly editorService: IEditorService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IConfigurationService private readonly configurationService: IConfigurationService
) {
super(QueryEditor.ID, telemetryService, themeService, storageService);
this.editorMemento = this.getEditorMemento<IQueryEditorViewState>(editorGroupService, QUERY_EDITOR_VIEW_STATE_PREFERENCE_KEY, 100);
this.queryEditorVisible = queryContext.QueryEditorVisibleContext.bindTo(contextKeyService);
// Clear view state for deleted files
this._register(fileService.onFileChanges(e => this.onFilesChanged(e)));
}
private onFilesChanged(e: FileChangesEvent): void {
const deleted = e.getDeleted();
if (deleted && deleted.length) {
this.clearTextEditorViewState(deleted.map(d => d.resource));
}
}
protected getEditorMemento<T>(editorGroupService: IEditorGroupsService, key: string, limit: number = 10): IEditorMemento<T> {
return new EditorMemento(this.getId(), key, Object.create(null), limit, editorGroupService); // do not persist in storage as results are never persisted
}
// PUBLIC METHODS ////////////////////////////////////////////////////////////
public get input(): QueryInput | null {
return this._input as QueryInput;
}
/**
* Called to create the editor in the parent element.
*/
public createEditor(parent: HTMLElement): void {
DOM.addClass(parent, 'query-editor');
this.splitviewContainer = DOM.$('.query-editor-view');
this.createTaskbar(parent);
parent.appendChild(this.splitviewContainer);
this.splitview = this._register(new SplitView(this.splitviewContainer, { orientation: Orientation.VERTICAL }));
this._register(this.splitview.onDidSashReset(() => this.splitview.distributeViewSizes()));
// We create two separate editors - one for Untitled Documents (ad-hoc queries) and another for queries from
// files. This is necessary because TextResourceEditor by default makes all non-Untitled inputs to be
// read-only so we need to use a TextFileEditor for files in order to edit them.
this.textResourceEditor = this._register(this.instantiationService.createInstance(TextResourceEditor));
this.textFileEditor = this._register(this.instantiationService.createInstance(TextFileEditor));
this.textResourceEditorContainer = DOM.$('.text-resource-editor-container');
this.textResourceEditor.create(this.textResourceEditorContainer);
this.textFileEditorContainer = DOM.$('.text-file-editor-container');
this.textFileEditor.create(this.textFileEditorContainer);
this.currentTextEditor = this.textResourceEditor;
this.splitview.addView({
element: this.textResourceEditorContainer,
layout: size => this.currentTextEditor.layout(new DOM.Dimension(this.dimension.width, size)),
minimumSize: 0,
maximumSize: Number.POSITIVE_INFINITY,
onDidChange: Event.None
}, Sizing.Distribute);
this.resultsEditorContainer = DOM.$('.results-editor-container');
this.resultsEditor = this._register(this.instantiationService.createInstance(QueryResultsEditor));
this.resultsEditor.create(this.resultsEditorContainer);
}
/**
* Creates the query execution taskbar that appears at the top of the QueryEditor
*/
private createTaskbar(parentElement: HTMLElement): void {
// Create QueryTaskbar
let taskbarContainer = DOM.append(parentElement, DOM.$('div'));
this.taskbar = this._register(new Taskbar(taskbarContainer, {
actionViewItemProvider: (action: Action) => this._getActionItemForAction(action),
}));
// Create Actions for the toolbar
this._runQueryAction = this.instantiationService.createInstance(actions.RunQueryAction, this);
this._cancelQueryAction = this.instantiationService.createInstance(actions.CancelQueryAction, this);
this._toggleConnectDatabaseAction = this.instantiationService.createInstance(actions.ToggleConnectDatabaseAction, this, false);
this._changeConnectionAction = this.instantiationService.createInstance(actions.ConnectDatabaseAction, this, true);
this._listDatabasesAction = this.instantiationService.createInstance(actions.ListDatabasesAction, this);
this._estimatedQueryPlanAction = this.instantiationService.createInstance(actions.EstimatedQueryPlanAction, this);
this._actualQueryPlanAction = this.instantiationService.createInstance(actions.ActualQueryPlanAction, this);
this._toggleSqlcmdMode = this.instantiationService.createInstance(actions.ToggleSqlCmdModeAction, this, false);
this.setTaskbarContent();
this._register(this.configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration('workbench.enablePreviewFeatures')) {
this.setTaskbarContent();
}
}));
}
/**
* Update the buttons on the taskbar to reflect the state of the current input.
*/
private updateState(stateChangeEvent: IQueryEditorStateChange): void {
if (stateChangeEvent.connectedChange) {
this._toggleConnectDatabaseAction.connected = this.input.state.connected;
this._changeConnectionAction.enabled = this.input.state.connected;
if (this.input.state.connected) {
this.listDatabasesActionItem.onConnected();
} else {
this.listDatabasesActionItem.onDisconnect();
}
}
if (stateChangeEvent.sqlCmdModeChanged) {
this._toggleSqlcmdMode.isSqlCmdMode = this.input.state.isSqlCmdMode;
}
if (stateChangeEvent.connectingChange) {
this._runQueryAction.enabled = !this.input.state.connecting;
this._estimatedQueryPlanAction.enabled = !this.input.state.connecting;
}
if (stateChangeEvent.executingChange) {
this._runQueryAction.enabled = !this.input.state.executing;
this._estimatedQueryPlanAction.enabled = !this.input.state.executing;
this._cancelQueryAction.enabled = this.input.state.executing;
}
if (stateChangeEvent.resultsVisibleChange) {
if (this.input.state.resultsVisible) {
this.addResultsEditor();
} else {
this.removeResultsEditor();
}
}
}
/**
* Gets the IActionItem for the List Databases dropdown if provided the associated Action.
* Otherwise returns null.
*/
private _getActionItemForAction(action: Action): IActionViewItem {
if (action.id === actions.ListDatabasesAction.ID) {
return this.listDatabasesActionItem;
}
return null;
}
private get listDatabasesActionItem(): actions.ListDatabasesActionItem {
if (!this._listDatabasesActionItem) {
this._listDatabasesActionItem = this.instantiationService.createInstance(actions.ListDatabasesActionItem, this);
this._register(this._listDatabasesActionItem.attachStyler(this.themeService));
}
return this._listDatabasesActionItem;
}
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 },
{ action: this._toggleSqlcmdMode }
];
// 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);
}
public async setInput(newInput: QueryInput, options: EditorOptions, token: CancellationToken): Promise<void> {
const oldInput = this.input;
if (newInput.matches(oldInput)) {
return Promise.resolve();
}
if (oldInput) {
// Remember view settings if input changes
this.saveQueryEditorViewState(this.input);
this.currentTextEditor.clearInput();
this.resultsEditor.clearInput();
}
// If we're switching editor types switch out the views
const newTextEditor = newInput.sql instanceof FileEditorInput ? this.textFileEditor : this.textResourceEditor;
if (newTextEditor !== this.currentTextEditor) {
this.currentTextEditor = newTextEditor;
this.splitview.removeView(0, Sizing.Distribute);
this.splitview.addView({
element: this.currentTextEditor.getContainer(),
layout: size => this.currentTextEditor.layout(new DOM.Dimension(this.dimension.width, size)),
minimumSize: 0,
maximumSize: Number.POSITIVE_INFINITY,
onDidChange: Event.None
}, Sizing.Distribute, 0);
}
await Promise.all([
super.setInput(newInput, options, token),
this.currentTextEditor.setInput(newInput.sql, options, token),
this.resultsEditor.setInput(newInput.results, options)
]);
this.inputDisposables.clear();
this.inputDisposables.add(this.input.state.onChange(c => this.updateState(c)));
this.updateState({ connectingChange: true, connectedChange: true, executingChange: true, resultsVisibleChange: true, sqlCmdModeChanged: true });
const editorViewState = this.loadTextEditorViewState(this.input.getResource());
if (editorViewState && editorViewState.resultsHeight && this.splitview.length > 1) {
this.splitview.resizeView(1, editorViewState.resultsHeight);
}
}
private saveQueryEditorViewState(input: QueryInput): void {
if (!input) {
return; // ensure we have an input to handle view state for
}
// Otherwise we save the view state to restore it later
else if (!input.isDisposed()) {
this.saveTextEditorViewState(input.getResource());
}
}
private clearTextEditorViewState(resources: URI[], group?: IEditorGroup): void {
resources.forEach(resource => {
this.editorMemento.clearEditorState(resource, group);
});
}
private saveTextEditorViewState(resource: URI): void {
const editorViewState = {
resultsHeight: this.resultsVisible ? this.splitview.getViewSize(1) : undefined
} as IQueryEditorViewState;
if (!this.group) {
return;
}
this.editorMemento.saveEditorState(this.group, resource, editorViewState);
}
/**
* Loads the text editor view state for the given resource and returns it.
*/
protected loadTextEditorViewState(resource: URI): IQueryEditorViewState | undefined {
return this.group ? this.editorMemento.loadEditorState(this.group, resource) : undefined;
}
protected saveState(): void {
// Update/clear editor view State
this.saveQueryEditorViewState(this.input);
super.saveState();
}
public toggleResultsEditorVisibility(): void {
if (this.resultsVisible) {
this.removeResultsEditor();
} else {
this.addResultsEditor();
}
}
/**
* Sets this editor and the 2 sub-editors to visible.
*/
public setEditorVisible(visible: boolean, group: IEditorGroup): void {
this.textFileEditor.setVisible(visible, group);
this.textResourceEditor.setVisible(visible, group);
this.resultsEditor.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 {
this.saveQueryEditorViewState(this.input);
this.currentTextEditor.clearInput();
this.resultsEditor.clearInput();
super.clearInput();
}
/**
* Sets focus on this editor. Specifically, it sets the focus on the hosted text editor.
*/
public focus(): void {
this.currentTextEditor.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;
const queryEditorHeight = dimension.height - DOM.getTotalHeight(this.taskbar.getContainer());
this.splitviewContainer.style.height = queryEditorHeight + 'px';
this.splitview.layout(queryEditorHeight);
}
/**
* Returns the editor control for the text editor.
*/
public getControl(): IEditorControl {
return this.currentTextEditor.getControl();
}
public setOptions(options: EditorOptions): void {
this.currentTextEditor.setOptions(options);
}
private removeResultsEditor(): void {
if (this.resultsVisible) {
this.splitview.removeView(1, Sizing.Distribute);
this.resultsVisible = false;
if (this.input && this.input.state) {
this.input.state.resultsVisible = false;
}
}
}
private addResultsEditor(): void {
if (!this.resultsVisible) {
// size the results section to 65% of available height or at least 100px
let initialViewSize = Math.round(Math.max(this.dimension.height * 0.65, 100));
this.splitview.addView({
element: this.resultsEditorContainer,
layout: size => this.resultsEditor && this.resultsEditor.layout(new DOM.Dimension(this.dimension.width, size)),
minimumSize: 0,
maximumSize: Number.POSITIVE_INFINITY,
onDidChange: Event.None
}, initialViewSize);
this.resultsVisible = true;
if (this.input && this.input.state) {
this.input.state.resultsVisible = true;
}
}
}
// helper functions
public isSelectionEmpty(): boolean {
if (this.currentTextEditor && this.currentTextEditor.getControl()) {
let control = this.currentTextEditor.getControl();
let codeEditor: ICodeEditor = <ICodeEditor>control;
if (codeEditor) {
let value = codeEditor.getValue();
if (value !== undefined && value.length > 0) {
return false;
}
}
}
return true;
}
/**
* 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.currentTextEditor && this.currentTextEditor.getControl()) {
let vscodeSelection = this.currentTextEditor.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 getAllSelection(): ISelectionData {
if (this.currentTextEditor && this.currentTextEditor.getControl()) {
let control = this.currentTextEditor.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 getAllText(): string {
if (this.currentTextEditor && this.currentTextEditor.getControl()) {
let control = this.currentTextEditor.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 getSelectionText(): string {
if (this.currentTextEditor && this.currentTextEditor.getControl()) {
let control = this.currentTextEditor.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 runCurrent method of this editor's RunQueryAction
*/
public async runCurrentQuery(): Promise<void> {
return this._runQueryAction.runCurrent();
}
/**
* Calls the runCurrentQueryWithActualPlan method of this editor's ActualQueryPlanAction
*/
public async runCurrentQueryWithActualPlan(): Promise<void> {
return this._actualQueryPlanAction.run();
}
/**
* Calls the run method of this editor's RunQueryAction
*/
public async runQuery(): Promise<void> {
return this._runQueryAction.run();
}
/**
* Calls the run method of this editor's CancelQueryAction
*/
public async cancelQuery(): Promise<void> {
return this._cancelQueryAction.run();
}
public registerQueryModelViewTab(title: string, componentId: string): void {
this.resultsEditor.registerQueryModelViewTab(title, componentId);
}
public chart(dataId: { batchId: number, resultId: number }): void {
this.resultsEditor.chart(dataId);
}
}

View File

@@ -0,0 +1,157 @@
/*---------------------------------------------------------------------------------------------
* 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/contrib/query/common/queryResultsInput';
import { QueryResultsView } from 'sql/workbench/contrib/query/browser/queryResultsView';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { RESULTS_GRID_DEFAULTS } from 'sql/workbench/contrib/query/common/resultsGridContribution';
export const TextCompareEditorVisible = new RawContextKey<boolean>('textCompareEditorVisible', false);
export class BareResultsGridInfo extends BareFontInfo {
public static createFromRawSettings(opts: {
fontFamily?: string;
fontWeight?: string;
fontSize?: number;
lineHeight?: number;
letterSpacing?: number;
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(fontInfo);
this.cellPadding = opts.cellPadding;
}
}
export 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,
@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(this._instantiationService.createInstance(QueryResultsView, parent));
}
}
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,432 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { QueryResultsInput } from 'sql/workbench/contrib/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 'sql/workbench/contrib/query/browser/messagePanel';
import { GridPanel } from 'sql/workbench/contrib/query/browser/gridPanel';
import { ChartTab } from 'sql/workbench/contrib/charts/browser/chartTab';
import { QueryPlanTab } from 'sql/workbench/contrib/queryPlan/browser/queryPlan';
import { TopOperationsTab } from 'sql/workbench/contrib/queryPlan/browser/topOperations';
import { QueryModelViewTab } from 'sql/workbench/contrib/query/browser/modelViewTab/queryModelViewTab';
import { MessagePanelState } from 'sql/workbench/contrib/query/common/messagePanelState';
import { GridPanelState } from 'sql/workbench/contrib/query/common/gridPanelState';
import * as nls from 'vs/nls';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import * as DOM from 'vs/base/browser/dom';
import { dispose, Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { attachTabbedPanelStyler } from 'sql/platform/theme/common/styler';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { Event } from 'vs/base/common/event';
import { startsWith } from 'vs/base/common/strings';
class MessagesView extends Disposable implements IPanelView {
private messagePanel: MessagePanel;
private container = document.createElement('div');
constructor(private instantiationService: IInstantiationService) {
super();
this.messagePanel = this._register(this.instantiationService.createInstance(MessagePanel));
this.messagePanel.render(this.container);
}
render(container: HTMLElement): void {
container.appendChild(this.container);
}
layout(dimension: DOM.Dimension): void {
this.container.style.width = `${dimension.width}px`;
this.container.style.height = `${dimension.height}px`;
this.messagePanel.layout(dimension);
}
focus(): void {
this.messagePanel.focus();
}
public clear() {
this.messagePanel.clear();
}
remove(): void {
this.container.remove();
}
public set queryRunner(runner: QueryRunner) {
this.messagePanel.queryRunner = runner;
}
public set state(val: MessagePanelState) {
this.messagePanel.state = val;
}
}
class ResultsView extends Disposable implements IPanelView {
private gridPanel: GridPanel;
private container = document.createElement('div');
private _state: GridPanelState;
private _runner: QueryRunner;
constructor(private instantiationService: IInstantiationService) {
super();
this.gridPanel = this._register(this.instantiationService.createInstance(GridPanel));
this.gridPanel.render(this.container);
}
render(container: HTMLElement): void {
container.appendChild(this.container);
}
layout(dimension: DOM.Dimension): void {
this.container.style.width = `${dimension.width}px`;
this.container.style.height = `${dimension.height}px`;
this.gridPanel.layout(dimension);
}
focus(): void {
this.gridPanel.focus();
}
public clear() {
this.gridPanel.clear();
}
remove(): void {
this.container.remove();
}
onHide(): void {
this._state = this.gridPanel.state;
this.gridPanel.clear();
}
onShow(): void {
if (this._state) {
this.state = this._state;
this.queryRunner = this._runner;
}
}
public set queryRunner(runner: QueryRunner) {
this._runner = runner;
this.gridPanel.queryRunner = runner;
}
public set state(val: GridPanelState) {
this.gridPanel.state = val;
}
}
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();
}
}
class MessagesTab implements IPanelTab {
public readonly title = nls.localize('messagesTabTitle', "Messages");
public readonly identifier = 'messagesTab';
public readonly view: MessagesView;
constructor(instantiationService: IInstantiationService) {
this.view = new MessagesView(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 messagesTab: MessagesTab;
private chartTab: ChartTab;
private qpTab: QueryPlanTab;
private topOperationsTab: TopOperationsTab;
private dynamicModelViewTabs: QueryModelViewTab[] = [];
private runnerDisposables = new DisposableStore();
constructor(
container: HTMLElement,
@IThemeService themeService: IThemeService,
@IInstantiationService private instantiationService: IInstantiationService,
@IQueryModelService private queryModelService: IQueryModelService
) {
super();
this.resultsTab = this._register(new ResultsTab(instantiationService));
this.messagesTab = this._register(new MessagesTab(instantiationService));
this.chartTab = this._register(new ChartTab(instantiationService));
this._panelView = this._register(new TabbedPanel(container, { showHeaderWhenSingleView: true }));
this._register(attachTabbedPanelStyler(this._panelView, themeService));
this.qpTab = this._register(new QueryPlanTab());
this.topOperationsTab = this._register(new TopOperationsTab(instantiationService));
this._panelView.pushTab(this.resultsTab);
this._panelView.pushTab(this.messagesTab);
this._register(this._panelView.onTabChange(e => {
if (this.input) {
this.input.state.activeTab = e;
}
}));
}
private hasResults(runner: QueryRunner): boolean {
let hasResults = false;
for (const batch of runner.batchSets) {
if (batch.resultSetSummaries.length > 0) {
hasResults = true;
break;
}
}
return hasResults;
}
private setQueryRunner(runner: QueryRunner) {
const activeTab = this._input.state.activeTab;
if (this.hasResults(runner)) {
this.showResults();
} else {
if (runner.isExecuting) { // in case we don't have results yet, but we also have already started executing
this.runnerDisposables.add(Event.once(runner.onResultSet)(() => this.showResults()));
}
this.hideResults();
}
this.resultsTab.queryRunner = runner;
this.messagesTab.queryRunner = runner;
this.chartTab.queryRunner = runner;
this.runnerDisposables.add(runner.onQueryStart(e => {
this.runnerDisposables.add(Event.once(runner.onResultSet)(() => this.showResults()));
this.hideResults();
this.hideChart();
this.hidePlan();
this.hideDynamicViewModelTabs();
this.input.state.visibleTabs.clear();
this.input.state.activeTab = this.resultsTab.identifier;
}));
this.runnerDisposables.add(runner.onQueryEnd(() => {
if (runner.messages.some(v => v.isError)) {
this._panelView.showTab(this.messagesTab.identifier);
}
}));
if (this.input.state.visibleTabs.has(this.chartTab.identifier) && !this._panelView.contains(this.chartTab)) {
this._panelView.pushTab(this.chartTab);
} else if (!this.input.state.visibleTabs.has(this.chartTab.identifier) && this._panelView.contains(this.chartTab)) {
this._panelView.removeTab(this.chartTab.identifier);
}
if (this.input.state.visibleTabs.has(this.qpTab.identifier) && !this._panelView.contains(this.qpTab)) {
this._panelView.pushTab(this.qpTab);
} else if (!this.input.state.visibleTabs.has(this.qpTab.identifier) && this._panelView.contains(this.qpTab)) {
this._panelView.removeTab(this.qpTab.identifier);
}
if (this.input.state.visibleTabs.has(this.topOperationsTab.identifier) && !this._panelView.contains(this.topOperationsTab)) {
this._panelView.pushTab(this.topOperationsTab);
} else if (!this.input.state.visibleTabs.has(this.topOperationsTab.identifier) && this._panelView.contains(this.topOperationsTab)) {
this._panelView.removeTab(this.topOperationsTab.identifier);
}
// restore query model view tabs
this.dynamicModelViewTabs.forEach(tab => {
if (this._panelView.contains(tab)) {
this._panelView.removeTab(tab.identifier);
}
});
this.dynamicModelViewTabs = [];
this.input.state.visibleTabs.forEach(tabId => {
if (startsWith(tabId, '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, undefined, true);
}
}
}
});
this.runnerDisposables.add(runner.onQueryEnd(() => {
if (runner.isQueryPlan) {
runner.planXml.then(e => {
this.showPlan(e);
});
}
}));
if (activeTab) {
this._panelView.showTab(activeTab);
} else {
this._panelView.showTab(this.resultsTab.identifier); // our default tab is the results view
}
}
public set input(input: QueryResultsInput) {
this._input = input;
this.runnerDisposables.dispose();
this.runnerDisposables = new DisposableStore();
[this.resultsTab, this.messagesTab, this.qpTab, this.topOperationsTab, this.chartTab].forEach(t => t.clear());
this.dynamicModelViewTabs.forEach(t => t.clear());
this.resultsTab.view.state = this.input.state.gridPanelState;
this.messagesTab.view.state = this.input.state.messagePanelState;
this.qpTab.view.state = this.input.state.queryPlanState;
this.topOperationsTab.view.state = this.input.state.topOperationsState;
this.chartTab.view.state = this.input.state.chartState;
this.dynamicModelViewTabs.forEach((dynamicTab: QueryModelViewTab) => {
dynamicTab.captureState(this.input.state.dynamicModelViewTabsState);
});
let info = this.queryModelService._getQueryInfo(input.uri);
if (info) {
this.setQueryRunner(info.queryRunner);
} else {
let disposable = this.queryModelService.onRunQueryStart(c => {
if (c === input.uri) {
let info = this.queryModelService._getQueryInfo(input.uri);
this.setQueryRunner(info.queryRunner);
disposable.dispose();
}
});
this.runnerDisposables.add(disposable);
}
}
clearInput() {
this._input = undefined;
this.runnerDisposables.dispose();
this.runnerDisposables = new DisposableStore();
this.resultsTab.clear();
this.messagesTab.clear();
this.qpTab.clear();
this.topOperationsTab.clear();
this.chartTab.clear();
this.dynamicModelViewTabs.forEach(t => t.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 hideResults() {
if (this._panelView.contains(this.resultsTab)) {
this._panelView.removeTab(this.resultsTab.identifier);
}
}
public showResults() {
if (!this._panelView.contains(this.resultsTab)) {
this._panelView.pushTab(this.resultsTab, 0);
}
this._panelView.showTab(this.resultsTab.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() {
this.runnerDisposables.dispose();
this.runnerDisposables = new DisposableStore();
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, undefined, true);
}
tab.putState(this.input.state.dynamicModelViewTabsState);
}
}

View File

@@ -0,0 +1,246 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IQueryModelService } from 'sql/platform/query/common/queryModel';
import { IntervalTimer } from 'vs/base/common/async';
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { localize } from 'vs/nls';
import { QueryInput } from 'sql/workbench/contrib/query/common/queryInput';
import QueryRunner from 'sql/platform/query/common/queryRunner';
import { parseNumAsTimeString } from 'sql/platform/connection/common/utils';
import { Event } from 'vs/base/common/event';
import { IStatusbarService, IStatusbarEntryAccessor, StatusbarAlignment } from 'vs/workbench/services/statusbar/common/statusbar';
export class TimeElapsedStatusBarContributions extends Disposable implements IWorkbenchContribution {
private static readonly ID = 'status.query.timeElapsed';
private statusItem: IStatusbarEntryAccessor;
private intervalTimer = new IntervalTimer();
private disposable = this._register(new DisposableStore());
constructor(
@IStatusbarService private readonly statusbarService: IStatusbarService,
@IEditorService private readonly editorService: IEditorService,
@IQueryModelService private readonly queryModelService: IQueryModelService
) {
super();
this.statusItem = this._register(
this.statusbarService.addEntry({
text: '',
},
TimeElapsedStatusBarContributions.ID,
localize('status.query.timeElapsed', "Time Elapsed"),
StatusbarAlignment.RIGHT, 100)
);
this._register(editorService.onDidActiveEditorChange(this.update, this));
this.update();
}
private hide() {
this.statusbarService.updateEntryVisibility(TimeElapsedStatusBarContributions.ID, false);
}
private show() {
this.statusbarService.updateEntryVisibility(TimeElapsedStatusBarContributions.ID, true);
}
private update() {
this.intervalTimer.cancel();
this.disposable.clear();
this.hide();
const activeInput = this.editorService.activeEditor;
if (activeInput && activeInput instanceof QueryInput && activeInput.uri) {
const uri = activeInput.uri;
const runner = this.queryModelService.getQueryRunner(uri);
if (runner) {
if (runner.hasCompleted || runner.isExecuting) {
this._displayValue(runner);
}
this.disposable.add(runner.onQueryStart(e => {
this._displayValue(runner);
}));
this.disposable.add(runner.onQueryEnd(e => {
this._displayValue(runner);
}));
} else {
this.disposable.add(this.queryModelService.onRunQueryStart(e => {
if (e === uri) {
this._displayValue(this.queryModelService.getQueryRunner(uri));
}
}));
this.disposable.add(this.queryModelService.onRunQueryComplete(e => {
if (e === uri) {
this._displayValue(this.queryModelService.getQueryRunner(uri));
}
}));
}
}
}
private _displayValue(runner: QueryRunner) {
this.intervalTimer.cancel();
if (runner.isExecuting) {
this.intervalTimer.cancelAndSet(() => {
const value = runner.queryStartTime ? Date.now() - runner.queryStartTime.getTime() : 0;
this.statusItem.update({
text: parseNumAsTimeString(value, false)
});
}, 1000);
const value = runner.queryStartTime ? Date.now() - runner.queryStartTime.getTime() : 0;
this.statusItem.update({
text: parseNumAsTimeString(value, false)
});
} else {
const value = runner.queryStartTime && runner.queryEndTime
? runner.queryEndTime.getTime() - runner.queryStartTime.getTime() : 0;
this.statusItem.update({
text: parseNumAsTimeString(value, false)
});
}
this.show();
}
}
export class RowCountStatusBarContributions extends Disposable implements IWorkbenchContribution {
private static readonly ID = 'status.query.rowCount';
private statusItem: IStatusbarEntryAccessor;
private disposable = this._register(new DisposableStore());
constructor(
@IStatusbarService private readonly statusbarService: IStatusbarService,
@IEditorService private readonly editorService: IEditorService,
@IQueryModelService private readonly queryModelService: IQueryModelService
) {
super();
this.statusItem = this._register(
this.statusbarService.addEntry({
text: '',
},
RowCountStatusBarContributions.ID,
localize('status.query.rowCount', "Row Count"),
StatusbarAlignment.RIGHT, 100)
);
this._register(editorService.onDidActiveEditorChange(this.update, this));
this.update();
}
private hide() {
this.statusbarService.updateEntryVisibility(RowCountStatusBarContributions.ID, false);
}
private show() {
this.statusbarService.updateEntryVisibility(RowCountStatusBarContributions.ID, true);
}
private update() {
this.disposable.clear();
this.hide();
const activeInput = this.editorService.activeEditor;
if (activeInput && activeInput instanceof QueryInput && activeInput.uri) {
const uri = activeInput.uri;
const runner = this.queryModelService.getQueryRunner(uri);
if (runner) {
if (runner.hasCompleted || runner.isExecuting) {
this._displayValue(runner);
}
this.disposable.add(runner.onQueryStart(e => {
this._displayValue(runner);
}));
this.disposable.add(runner.onResultSetUpdate(e => {
this._displayValue(runner);
}));
this.disposable.add(runner.onQueryEnd(e => {
this._displayValue(runner);
}));
} else {
this.disposable.add(this.queryModelService.onRunQueryStart(e => {
if (e === uri) {
this._displayValue(this.queryModelService.getQueryRunner(uri));
}
}));
this.disposable.add(this.queryModelService.onRunQueryUpdate(e => {
if (e === uri) {
this._displayValue(this.queryModelService.getQueryRunner(uri));
}
}));
this.disposable.add(this.queryModelService.onRunQueryComplete(e => {
if (e === uri) {
this._displayValue(this.queryModelService.getQueryRunner(uri));
}
}));
}
}
}
private _displayValue(runner: QueryRunner) {
const rowCount = runner.batchSets.reduce((p, c) => {
return p + c.resultSetSummaries.reduce((rp, rc) => {
return rp + rc.rowCount;
}, 0);
}, 0);
const text = localize('rowCount', "{0} rows", rowCount);
this.statusItem.update({ text });
this.show();
}
}
export class QueryStatusStatusBarContributions extends Disposable implements IWorkbenchContribution {
private static readonly ID = 'status.query.status';
private visisbleUri: string | undefined;
constructor(
@IStatusbarService private readonly statusbarService: IStatusbarService,
@IEditorService private readonly editorService: IEditorService,
@IQueryModelService private readonly queryModelService: IQueryModelService
) {
super();
this._register(
this.statusbarService.addEntry({
text: localize('query.status.executing', "Executing query..."),
},
QueryStatusStatusBarContributions.ID,
localize('status.query.status', "Execution Status"),
StatusbarAlignment.RIGHT, 100)
);
this._register(Event.filter(this.queryModelService.onRunQueryStart, uri => uri === this.visisbleUri)(this.update, this));
this._register(Event.filter(this.queryModelService.onRunQueryComplete, uri => uri === this.visisbleUri)(this.update, this));
this._register(this.editorService.onDidActiveEditorChange(this.update, this));
this.update();
}
private update() {
this.hide();
this.visisbleUri = undefined;
const activeInput = this.editorService.activeEditor;
if (activeInput && activeInput instanceof QueryInput && activeInput.uri) {
this.visisbleUri = activeInput.uri;
const runner = this.queryModelService.getQueryRunner(this.visisbleUri);
if (runner && runner.isExecuting) {
this.show();
}
}
}
private hide() {
this.statusbarService.updateEntryVisibility(QueryStatusStatusBarContributions.ID, false);
}
private show() {
this.statusbarService.updateEntryVisibility(QueryStatusStatusBarContributions.ID, true);
}
}