move code from parts to contrib (#8319)
251
src/sql/workbench/contrib/query/browser/actions.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
220
src/sql/workbench/contrib/query/browser/flavorStatus.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
157
src/sql/workbench/contrib/query/browser/flexibleSash.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
800
src/sql/workbench/contrib/query/browser/gridPanel.ts
Normal 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),
|
||||
];
|
||||
}
|
||||
}
|
||||
450
src/sql/workbench/contrib/query/browser/keyboardQueryActions.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 |
@@ -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 |
@@ -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 |
1
src/sql/workbench/contrib/query/browser/media/close.svg
Normal 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 |
@@ -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 */
|
||||
}
|
||||
41
src/sql/workbench/contrib/query/browser/media/editorpart.css
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
15
src/sql/workbench/contrib/query/browser/media/gridPanel.css
Normal 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;
|
||||
}
|
||||
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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;
|
||||
}
|
||||
@@ -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 |
@@ -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 |
@@ -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;
|
||||
}
|
||||
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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;
|
||||
}
|
||||
@@ -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%;
|
||||
}
|
||||
@@ -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 |
@@ -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;
|
||||
}
|
||||
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
20
src/sql/workbench/contrib/query/browser/media/stackview.svg
Normal 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 |
220
src/sql/workbench/contrib/query/browser/media/tabstitle.css
Normal 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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
127
src/sql/workbench/contrib/query/browser/media/titlecontrol.css
Normal 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;
|
||||
}
|
||||
408
src/sql/workbench/contrib/query/browser/messagePanel.ts
Normal 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() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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%;
|
||||
}
|
||||
@@ -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 |
@@ -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 |
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
566
src/sql/workbench/contrib/query/browser/query.contribution.ts
Normal 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);
|
||||
810
src/sql/workbench/contrib/query/browser/queryActions.ts
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
613
src/sql/workbench/contrib/query/browser/queryEditor.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
157
src/sql/workbench/contrib/query/browser/queryResultsEditor.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
432
src/sql/workbench/contrib/query/browser/queryResultsView.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
246
src/sql/workbench/contrib/query/browser/statusBarItems.ts
Normal 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);
|
||||
}
|
||||
}
|
||||