SQL Operations Studio Public Preview 1 (0.23) release source code
10
src/sql/parts/query/common/constants.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export const copyIncludeHeaders = 'copyIncludeHeaders';
|
||||
export const configSaveAsCsv = 'saveAsCsv';
|
||||
export const configCopyRemoveNewLine = 'copyRemoveNewLine';
|
||||
export const configShowBatchTime = 'showBatchTime';
|
||||
|
||||
218
src/sql/parts/query/common/flavorStatus.ts
Normal file
@@ -0,0 +1,218 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { $, append, show, hide } from 'vs/base/browser/dom';
|
||||
import { IDisposable, combinedDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar';
|
||||
import { IEditorCloseEvent } from 'vs/workbench/common/editor';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
|
||||
import { IQuickOpenService, IPickOpenEntry } from 'vs/platform/quickOpen/common/quickOpen';
|
||||
import { IMessageService, Severity } from 'vs/platform/message/common/message';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import errors = require('vs/base/common/errors');
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { getCodeEditor as getEditorWidget } from 'vs/editor/common/services/codeEditorService';
|
||||
import nls = require('vs/nls');
|
||||
|
||||
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
|
||||
import * as WorkbenchUtils from 'sql/workbench/common/sqlWorkbenchUtils';
|
||||
|
||||
import { DidChangeLanguageFlavorParams } from 'data';
|
||||
|
||||
export interface ISqlProviderEntry extends IPickOpenEntry {
|
||||
providerId: string;
|
||||
}
|
||||
|
||||
// Query execution status
|
||||
class SqlProviderEntry implements ISqlProviderEntry {
|
||||
constructor(public providerId: string, private _providerDisplayName?: string) {
|
||||
}
|
||||
|
||||
public get label(): string {
|
||||
// If display name is provided, use it. Else use default
|
||||
if (this._providerDisplayName) {
|
||||
return this._providerDisplayName;
|
||||
}
|
||||
|
||||
if (!this.providerId) {
|
||||
return SqlProviderEntry.getDefaultLabel();
|
||||
}
|
||||
// Note: consider adding API to connection management service to
|
||||
// support getting display name for provider so this is consistent
|
||||
switch (this.providerId) {
|
||||
case 'MSSQL':
|
||||
return 'MSSQL';
|
||||
default:
|
||||
return this.providerId;
|
||||
}
|
||||
}
|
||||
|
||||
public static getDefaultLabel(): string {
|
||||
return nls.localize('chooseSqlLang', 'Choose SQL Language');
|
||||
}
|
||||
}
|
||||
|
||||
// Shows SQL flavor status in the editor
|
||||
export class SqlFlavorStatusbarItem implements IStatusbarItem {
|
||||
|
||||
private _element: HTMLElement;
|
||||
private _flavorElement: HTMLElement;
|
||||
private _sqlStatusEditors: { [editorUri: string]: SqlProviderEntry };
|
||||
private _toDispose: IDisposable[];
|
||||
|
||||
constructor(
|
||||
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
|
||||
@IWorkbenchEditorService private _editorService: IWorkbenchEditorService,
|
||||
@IEditorGroupService private _editorGroupService: IEditorGroupService,
|
||||
@IQuickOpenService private _quickOpenService: IQuickOpenService,
|
||||
@IInstantiationService private _instantiationService: IInstantiationService,
|
||||
) {
|
||||
this._sqlStatusEditors = {};
|
||||
}
|
||||
|
||||
public render(container: HTMLElement): IDisposable {
|
||||
this._element = append(container, $('.query-statusbar-group'));
|
||||
this._flavorElement = append(this._element, $('a.editor-status-selection'));
|
||||
this._flavorElement.title = nls.localize('changeProvider', "Change SQL language provider");
|
||||
this._flavorElement.onclick = () => this._onSelectionClick();
|
||||
hide(this._flavorElement);
|
||||
|
||||
this._toDispose = [];
|
||||
this._toDispose.push(
|
||||
this._connectionManagementService.onLanguageFlavorChanged((changeParams: DidChangeLanguageFlavorParams) => this._onFlavorChanged(changeParams)),
|
||||
this._editorGroupService.onEditorsChanged(() => this._onEditorsChanged()),
|
||||
this._editorGroupService.getStacksModel().onEditorClosed(event => this._onEditorClosed(event))
|
||||
);
|
||||
|
||||
return combinedDisposable(this._toDispose);
|
||||
}
|
||||
|
||||
private _onSelectionClick() {
|
||||
const action = this._instantiationService.createInstance(ChangeFlavorAction, ChangeFlavorAction.ID, ChangeFlavorAction.LABEL);
|
||||
|
||||
action.run().done(null, errors.onUnexpectedError);
|
||||
action.dispose();
|
||||
}
|
||||
|
||||
private _onEditorClosed(event: IEditorCloseEvent): void {
|
||||
let uri = WorkbenchUtils.getEditorUri(event.editor);
|
||||
if (uri && uri in this._sqlStatusEditors) {
|
||||
// If active editor is being closed, hide the query status.
|
||||
let activeEditor = this._editorService.getActiveEditor();
|
||||
if (activeEditor) {
|
||||
let currentUri = WorkbenchUtils.getEditorUri(activeEditor.input);
|
||||
if (uri === currentUri) {
|
||||
hide(this._flavorElement);
|
||||
}
|
||||
}
|
||||
// note: intentionally not removing language flavor. This is preserved across close/open events at present
|
||||
// delete this._sqlStatusEditors[uri];
|
||||
}
|
||||
}
|
||||
|
||||
private _onEditorsChanged(): void {
|
||||
let activeEditor = this._editorService.getActiveEditor();
|
||||
if (activeEditor) {
|
||||
let uri = WorkbenchUtils.getEditorUri(activeEditor.input);
|
||||
|
||||
// Show active editor's language flavor status
|
||||
if (uri) {
|
||||
this._showStatus(uri);
|
||||
} else {
|
||||
hide(this._flavorElement);
|
||||
}
|
||||
} else {
|
||||
hide(this._flavorElement);
|
||||
}
|
||||
}
|
||||
|
||||
private _onFlavorChanged(changeParams: DidChangeLanguageFlavorParams): void {
|
||||
if (changeParams) {
|
||||
this._updateStatus(changeParams.uri, new SqlProviderEntry(changeParams.flavor));
|
||||
}
|
||||
}
|
||||
|
||||
// Update query status for the editor
|
||||
private _updateStatus(uri: string, newStatus: SqlProviderEntry): void {
|
||||
if (uri) {
|
||||
this._sqlStatusEditors[uri] = newStatus;
|
||||
this._showStatus(uri);
|
||||
}
|
||||
}
|
||||
|
||||
// Show/hide query status for active editor
|
||||
private _showStatus(uri: string): void {
|
||||
let activeEditor = this._editorService.getActiveEditor();
|
||||
if (activeEditor) {
|
||||
let currentUri = WorkbenchUtils.getEditorUri(activeEditor.input);
|
||||
if (uri === currentUri) {
|
||||
let flavor: SqlProviderEntry = this._sqlStatusEditors[uri];
|
||||
if (flavor) {
|
||||
this._flavorElement.textContent = flavor.label;
|
||||
} else {
|
||||
this._flavorElement.textContent = SqlProviderEntry.getDefaultLabel();
|
||||
}
|
||||
show(this._flavorElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class ChangeFlavorAction extends Action {
|
||||
|
||||
public static ID = 'sql.action.editor.changeProvider';
|
||||
public static LABEL = nls.localize('changeSqlProvider', "Change SQL Engine Provider");
|
||||
|
||||
constructor(
|
||||
actionId: string,
|
||||
actionLabel: string,
|
||||
@IWorkbenchEditorService private _editorService: IWorkbenchEditorService,
|
||||
@IQuickOpenService private _quickOpenService: IQuickOpenService,
|
||||
@IMessageService private _messageService: IMessageService,
|
||||
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService
|
||||
) {
|
||||
super(actionId, actionLabel);
|
||||
}
|
||||
|
||||
public run(): TPromise<any> {
|
||||
let activeEditor = this._editorService.getActiveEditor();
|
||||
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 = getEditorWidget(activeEditor);
|
||||
if (!editorWidget) {
|
||||
return this._showMessage(Severity.Info, nls.localize('noEditor', "No text editor active at this time"));
|
||||
}
|
||||
|
||||
// TODO #1334 use connectionManagementService.GetProviderNames here. The challenge is that the credentials provider is returned
|
||||
// so we need a way to filter this using a capabilities check, with isn't yet implemented
|
||||
const ProviderOptions: ISqlProviderEntry[] = [
|
||||
new SqlProviderEntry('MSSQL')
|
||||
];
|
||||
|
||||
// TODO: select the current language flavor
|
||||
return this._quickOpenService.pick(ProviderOptions, { placeHolder: nls.localize('pickSqlProvider', "Select SQL Language Provider"), autoFocus: { autoFocusIndex: 0 } }).then(provider => {
|
||||
if (provider) {
|
||||
activeEditor = this._editorService.getActiveEditor();
|
||||
const editorWidget = getEditorWidget(activeEditor);
|
||||
if (editorWidget) {
|
||||
if (currentUri) {
|
||||
this._connectionManagementService.doChangeLanguageFlavor(currentUri, 'sql', provider.providerId);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _showMessage(sev: Severity, message: string): TPromise<any> {
|
||||
this._messageService.show(sev, message);
|
||||
return TPromise.as(undefined);
|
||||
}
|
||||
}
|
||||
49
src/sql/parts/query/common/localizedConstants.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
// localizable strings
|
||||
|
||||
export const runQueryBatchStartMessage = localize('runQueryBatchStartMessage', 'Started executing query at ');
|
||||
export const runQueryBatchStartLine = localize('runQueryBatchStartLine', 'Line {0}');
|
||||
|
||||
export const msgCancelQueryFailed = localize('msgCancelQueryFailed', 'Canceling the query failed: {0}');
|
||||
|
||||
export const filepathPrompt = localize('filepathPrompt', 'File path');
|
||||
export const filepathPlaceholder = localize('filepathPlaceholder', 'File name');
|
||||
export const filepathMessage = localize('filepathMessage', 'File name');
|
||||
export const overwritePrompt = localize('overwritePrompt', 'A file with this name already exists. Do you want to replace the existing file?');
|
||||
export const overwritePlaceholder = localize('overwritePlaceholder', 'A file with this name already exists');
|
||||
|
||||
export const msgSaveResultInProgress = localize('msgSaveResultInProgress', 'A save request is already executing. Please wait for its completion.');
|
||||
export const msgCannotOpenContent = localize('msgCannotOpenContent', 'Error occurred opening content in editor.');
|
||||
export const msgSaveStarted = localize('msgSaveStarted', 'Started saving results to ');
|
||||
export const msgSaveFailed = localize('msgSaveFailed', 'Failed to save results. ');
|
||||
export const msgSaveSucceeded = localize('msgSaveSucceeded', 'Successfully saved results to ');
|
||||
|
||||
|
||||
export const msgStatusRunQueryInProgress = localize('msgStatusRunQueryInProgress', 'Executing query...');
|
||||
|
||||
// /** Results Pane Labels */
|
||||
export const maximizeLabel = localize('maximizeLabel', 'Maximize');
|
||||
export const restoreLabel = localize('restoreLabel', 'Restore');
|
||||
export const saveCSVLabel = localize('saveCSVLabel', 'Save as CSV');
|
||||
export const saveJSONLabel = localize('saveJSONLabel', 'Save as JSON');
|
||||
export const saveExcelLabel = localize('saveExcelLabel', 'Save as Excel');
|
||||
export const viewChartLabel = localize('viewChartLabel', 'View as Chart');
|
||||
export const resultPaneLabel = localize('resultPaneLabel', 'Results');
|
||||
export const selectAll = localize('selectAll', 'Select all');
|
||||
export const copyLabel = localize('copyLabel', 'Copy');
|
||||
export const executeQueryLabel = localize('executeQueryLabel', 'Executing query ');
|
||||
|
||||
/** Messages Pane Labels */
|
||||
export const messagePaneLabel = localize('messagePaneLabel', 'Messages');
|
||||
export const elapsedTimeLabel = localize('elapsedTimeLabel', 'Total execution time: {0}');
|
||||
|
||||
/** Warning message for save icons */
|
||||
export const msgCannotSaveMultipleSelections = localize('msgCannotSaveMultipleSelections', 'Save results command cannot be used with multiple selections.');
|
||||
|
||||
|
||||
262
src/sql/parts/query/common/query.contribution.ts
Normal file
@@ -0,0 +1,262 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { EditorDescriptor } from 'vs/workbench/browser/parts/editor/baseEditor';
|
||||
import { IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/common/editor';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actionRegistry';
|
||||
import { IConfigurationRegistry, Extensions as ConfigExtensions } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
|
||||
import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes';
|
||||
import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
|
||||
import { QueryEditor } from 'sql/parts/query/editor/queryEditor';
|
||||
import { QueryResultsEditor } from 'sql/parts/query/editor/queryResultsEditor';
|
||||
import { QueryResultsInput } from 'sql/parts/query/common/queryResultsInput';
|
||||
import * as queryContext from 'sql/parts/query/common/queryContext';
|
||||
import { QueryInput } from 'sql/parts/query/common/queryInput';
|
||||
import { EditDataEditor } from 'sql/parts/editData/editor/editDataEditor';
|
||||
import { EditDataInput } from 'sql/parts/editData/common/editDataInput';
|
||||
import { RunQueryKeyboardAction, RunCurrentQueryKeyboardAction, CancelQueryKeyboardAction, RefreshIntellisenseKeyboardAction } from 'sql/parts/query/execution/keyboardQueryActions';
|
||||
import * as gridActions from 'sql/parts/grid/views/gridActions';
|
||||
import * as gridCommands from 'sql/parts/grid/views/gridCommands';
|
||||
import { QueryPlanEditor } from 'sql/parts/queryPlan/queryPlanEditor';
|
||||
import { QueryPlanInput } from 'sql/parts/queryPlan/queryPlanInput';
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
const gridCommandsWeightBonus = 100; // give our commands a little bit more weight over other default list/tree commands
|
||||
|
||||
export const QueryEditorVisibleCondition = ContextKeyExpr.has(queryContext.queryEditorVisibleId);
|
||||
export const ResultsGridFocusCondition = ContextKeyExpr.and(ContextKeyExpr.has(queryContext.resultsVisibleId), ContextKeyExpr.has(queryContext.resultsGridFocussedId));
|
||||
export const ResultsMessagesFocusCondition = ContextKeyExpr.and(ContextKeyExpr.has(queryContext.resultsVisibleId), ContextKeyExpr.has(queryContext.resultsMessagesFocussedId));
|
||||
|
||||
// Editor
|
||||
const queryResultsEditorDescriptor = new EditorDescriptor(
|
||||
QueryResultsEditor.ID,
|
||||
'QueryResults',
|
||||
'sql/parts/query/editor/queryResultsEditor',
|
||||
'QueryResultsEditor'
|
||||
);
|
||||
|
||||
Registry.as<IEditorRegistry>(EditorExtensions.Editors)
|
||||
.registerEditor(queryResultsEditorDescriptor, [new SyncDescriptor(QueryResultsInput)]);
|
||||
|
||||
// Editor
|
||||
const queryEditorDescriptor = new EditorDescriptor(
|
||||
QueryEditor.ID,
|
||||
'Query',
|
||||
'sql/parts/query/editor/queryEditor',
|
||||
'QueryEditor'
|
||||
);
|
||||
|
||||
Registry.as<IEditorRegistry>(EditorExtensions.Editors)
|
||||
.registerEditor(queryEditorDescriptor, [new SyncDescriptor(QueryInput)]);
|
||||
|
||||
// Query Plan editor registration
|
||||
|
||||
const createLoginEditorDescriptor = new EditorDescriptor(
|
||||
QueryPlanEditor.ID,
|
||||
'QueryPlan',
|
||||
'sql/parts/queryPlan/queryPlanEditor',
|
||||
'QueryPlanEditor'
|
||||
);
|
||||
|
||||
Registry.as<IEditorRegistry>(EditorExtensions.Editors)
|
||||
.registerEditor(createLoginEditorDescriptor, [new SyncDescriptor(QueryPlanInput)]);
|
||||
|
||||
// Editor
|
||||
const editDataEditorDescriptor = new EditorDescriptor(
|
||||
EditDataEditor.ID,
|
||||
'EditData',
|
||||
'sql/parts/editData/editor/editDataEditor',
|
||||
'EditDataEditor'
|
||||
);
|
||||
|
||||
Registry.as<IEditorRegistry>(EditorExtensions.Editors)
|
||||
.registerEditor(editDataEditorDescriptor, [new SyncDescriptor(EditDataInput)]);
|
||||
|
||||
let actionRegistry = <IWorkbenchActionRegistry>Registry.as(Extensions.WorkbenchActions);
|
||||
|
||||
// Query Actions
|
||||
actionRegistry.registerWorkbenchAction(
|
||||
new SyncActionDescriptor(
|
||||
RunQueryKeyboardAction,
|
||||
RunQueryKeyboardAction.ID,
|
||||
RunQueryKeyboardAction.LABEL,
|
||||
{ primary: KeyCode.F5 }
|
||||
),
|
||||
RunQueryKeyboardAction.LABEL
|
||||
);
|
||||
|
||||
actionRegistry.registerWorkbenchAction(
|
||||
new SyncActionDescriptor(
|
||||
RunCurrentQueryKeyboardAction,
|
||||
RunCurrentQueryKeyboardAction.ID,
|
||||
RunCurrentQueryKeyboardAction.LABEL,
|
||||
{ primary:KeyMod.CtrlCmd | KeyCode.F5 }
|
||||
),
|
||||
RunCurrentQueryKeyboardAction.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
|
||||
);
|
||||
|
||||
// Grid actions
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: gridActions.GRID_COPY_ID,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(gridCommandsWeightBonus),
|
||||
when: ResultsGridFocusCondition,
|
||||
primary: KeyMod.CtrlCmd | KeyCode.KEY_C,
|
||||
handler: gridCommands.copySelection
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: gridActions.MESSAGES_SELECTALL_ID,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(gridCommandsWeightBonus),
|
||||
when: ResultsMessagesFocusCondition,
|
||||
primary: KeyMod.CtrlCmd | KeyCode.KEY_A,
|
||||
handler: gridCommands.selectAllMessages
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: gridActions.GRID_SELECTALL_ID,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(gridCommandsWeightBonus),
|
||||
when: ResultsGridFocusCondition,
|
||||
primary: KeyMod.CtrlCmd | KeyCode.KEY_A,
|
||||
handler: gridCommands.selectAll
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: gridActions.MESSAGES_COPY_ID,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(gridCommandsWeightBonus),
|
||||
when: ResultsMessagesFocusCondition,
|
||||
primary: KeyMod.CtrlCmd | KeyCode.KEY_C,
|
||||
handler: gridCommands.copyMessagesSelection
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: gridActions.GRID_SAVECSV_ID,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(gridCommandsWeightBonus),
|
||||
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: KeybindingsRegistry.WEIGHT.workbenchContrib(gridCommandsWeightBonus),
|
||||
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: KeybindingsRegistry.WEIGHT.workbenchContrib(gridCommandsWeightBonus),
|
||||
when: ResultsGridFocusCondition,
|
||||
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_R, KeyMod.CtrlCmd | KeyCode.KEY_E),
|
||||
handler: gridCommands.saveAsExcel
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: gridActions.TOGGLERESULTS_ID,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(gridCommandsWeightBonus),
|
||||
when: QueryEditorVisibleCondition,
|
||||
primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_R,
|
||||
handler: gridCommands.toggleResultsPane
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: gridActions.TOGGLEMESSAGES_ID,
|
||||
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(gridCommandsWeightBonus),
|
||||
when: QueryEditorVisibleCondition,
|
||||
primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_Y,
|
||||
handler: gridCommands.toggleMessagePane
|
||||
});
|
||||
|
||||
// Intellisense and other configuration options
|
||||
|
||||
let configurationRegistry = <IConfigurationRegistry>Registry.as(ConfigExtensions.Configuration);
|
||||
configurationRegistry.registerConfiguration({
|
||||
'id': 'sqlEditor',
|
||||
'title': 'SQL Editor',
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'sql.messagesDefaultOpen': {
|
||||
'type': 'boolean',
|
||||
'description': localize('sql.messagesDefaultOpen', 'True for the messages pane to be open by default; false for closed'),
|
||||
'default': true
|
||||
},
|
||||
'sql.saveAsCsv.includeHeaders': {
|
||||
'type': 'boolean',
|
||||
'description': localize('sql.saveAsCsv.includeHeaders', '[Optional] When true, column headers are included when saving results as CSV'),
|
||||
'default': true
|
||||
},
|
||||
'sql.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.intelliSense.enableIntelliSense': {
|
||||
'type': 'boolean',
|
||||
'default': true,
|
||||
'description': localize('sql.intelliSense.enableIntelliSense', 'Should IntelliSense be enabled')
|
||||
},
|
||||
'sql.intelliSense.enableErrorChecking': {
|
||||
'type': 'boolean',
|
||||
'default': true,
|
||||
'description': localize('sql.intelliSense.enableErrorChecking', 'Should IntelliSense error checking be enabled')
|
||||
},
|
||||
'sql.intelliSense.enableSuggestions': {
|
||||
'type': 'boolean',
|
||||
'default': true,
|
||||
'description': localize('sql.intelliSense.enableSuggestions', 'Should IntelliSense suggestions be enabled')
|
||||
},
|
||||
'sql.intelliSense.enableQuickInfo': {
|
||||
'type': 'boolean',
|
||||
'default': true,
|
||||
'description': localize('sql.intelliSense.enableQuickInfo', 'Should IntelliSense quick info be enabled')
|
||||
},
|
||||
'sql.intelliSense.lowerCaseSuggestions': {
|
||||
'type': 'boolean',
|
||||
'default': false,
|
||||
'description': localize('sql.intelliSense.lowerCaseSuggestions', 'Should IntelliSense suggestions be lowercase')
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
21
src/sql/parts/query/common/queryContext.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
|
||||
/**
|
||||
* Context Keys to use with keybindings for the results grid and messages used in query and edit data views
|
||||
*/
|
||||
export const queryEditorVisibleId = 'queryEditorVisible';
|
||||
export const resultsVisibleId = 'resultsVisible';
|
||||
export const resultsGridFocussedId = 'resultsGridFocussed';
|
||||
export const resultsMessagesFocussedId = 'resultsMessagesFocussed';
|
||||
|
||||
export const QueryEditorVisibleContext = new RawContextKey<boolean>(queryEditorVisibleId, false);
|
||||
export const ResultsVisibleContext = new RawContextKey<boolean>(resultsVisibleId, false);
|
||||
export const ResultsGridFocussedContext = new RawContextKey<boolean>(resultsGridFocussedId, false);
|
||||
export const ResultsMessagesFocussedContext = new RawContextKey<boolean>(resultsMessagesFocussedId, false);
|
||||
35
src/sql/parts/query/common/queryEditorService.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IConnectableInput } from 'sql/parts/connection/common/connectionManagement';
|
||||
import { IEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
import { QueryEditorService } from 'sql/parts/query/services/queryEditorService';
|
||||
|
||||
export interface IQueryEditorOptions extends IEditorOptions {
|
||||
|
||||
// Tells IQueryEditorService.queryEditorCheck to not open this input in the QueryEditor.
|
||||
// Used when the user changes the Language Mode to not-SQL for files with .sql extensions.
|
||||
denyQueryEditor?: boolean;
|
||||
}
|
||||
|
||||
export const IQueryEditorService = createDecorator<QueryEditorService>('QueryEditorService');
|
||||
|
||||
export interface IQueryEditorService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
// Creates new untitled document for SQL queries and opens it in a new editor tab
|
||||
newSqlEditor(sqlContent?: string, connectionProviderName?: string): Promise<IConnectableInput>;
|
||||
|
||||
// Creates a new query plan document
|
||||
newQueryPlanEditor(xmlShowPlan: string): Promise<any>;
|
||||
|
||||
// Creates new edit data session
|
||||
newEditDataEditor(schemaName: string, tableName: string): Promise<IConnectableInput>;
|
||||
|
||||
// Clears any QueryEditor data for the given URI held by this service
|
||||
onQueryInputClosed(uri: string): void;
|
||||
}
|
||||
249
src/sql/parts/query/common/queryInput.ts
Normal file
@@ -0,0 +1,249 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { EditorInput, EditorModel, ConfirmResult, EncodingMode, IEncodingSupport } from 'vs/workbench/common/editor';
|
||||
import { IConnectionManagementService, IConnectableInput, INewConnectionParams, RunQueryOnConnectionMode } from 'sql/parts/connection/common/connectionManagement';
|
||||
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
|
||||
import { QueryResultsInput } from 'sql/parts/query/common/queryResultsInput';
|
||||
import { IQueryModelService } from 'sql/parts/query/execution/queryModel';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { ISelectionData, ExecutionPlanOptions } from 'data';
|
||||
import { IQueryEditorService } from 'sql/parts/query/common/queryEditorService';
|
||||
|
||||
/**
|
||||
* Input for the QueryEditor. This input is simply a wrapper around a QueryResultsInput for the QueryResultsEditor
|
||||
* and a UntitledEditorInput for the SQL File Editor.
|
||||
*/
|
||||
export class QueryInput extends EditorInput implements IEncodingSupport, IConnectableInput, IDisposable {
|
||||
|
||||
public static ID: string = 'workbench.editorinputs.queryInput';
|
||||
public static SCHEMA: string = 'sql';
|
||||
|
||||
private _runQueryEnabled: boolean;
|
||||
private _cancelQueryEnabled: boolean;
|
||||
private _connectEnabled: boolean;
|
||||
private _disconnectEnabled: boolean;
|
||||
private _changeConnectionEnabled: boolean;
|
||||
private _listDatabasesConnected: boolean;
|
||||
|
||||
private _updateTaskbar: Emitter<void>;
|
||||
private _showQueryResultsEditor: Emitter<void>;
|
||||
private _updateSelection: Emitter<ISelectionData>;
|
||||
|
||||
private _toDispose: IDisposable[];
|
||||
private _currentEventCallbacks: IDisposable[];
|
||||
|
||||
constructor(
|
||||
private _name: string,
|
||||
private _description: string,
|
||||
private _sql: UntitledEditorInput,
|
||||
private _results: QueryResultsInput,
|
||||
private _connectionProviderName: string,
|
||||
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
|
||||
@IQueryModelService private _queryModelService: IQueryModelService,
|
||||
@IQueryEditorService private _queryEditorService: IQueryEditorService
|
||||
) {
|
||||
super();
|
||||
let self = this;
|
||||
this._updateTaskbar = new Emitter<void>();
|
||||
this._showQueryResultsEditor = new Emitter<void>();
|
||||
this._updateSelection = new Emitter<ISelectionData>();
|
||||
|
||||
this._toDispose = [];
|
||||
this._currentEventCallbacks = [];
|
||||
// re-emit sql editor events through this editor if it exists
|
||||
if (this._sql) {
|
||||
this._toDispose.push(this._sql.onDidChangeDirty(() => this._onDidChangeDirty.fire()));
|
||||
}
|
||||
|
||||
// Attach to event callbacks
|
||||
if (this._queryModelService) {
|
||||
// Register callbacks for the Actions
|
||||
this._toDispose.push(
|
||||
this._queryModelService.onRunQueryStart(uri => {
|
||||
if (self.uri === uri) {
|
||||
self.onRunQuery();
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
this._toDispose.push(
|
||||
this._queryModelService.onRunQueryComplete(uri => {
|
||||
if (self.uri === uri) {
|
||||
self.onQueryComplete();
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (this._connectionManagementService) {
|
||||
this._toDispose.push(self._connectionManagementService.onDisconnect(result => {
|
||||
if (result.connectionUri === self.uri) {
|
||||
self.onDisconnect();
|
||||
}
|
||||
}));
|
||||
if (self.uri) {
|
||||
if (this._connectionProviderName) {
|
||||
this._connectionManagementService.doChangeLanguageFlavor(self.uri, 'sql', this._connectionProviderName);
|
||||
} else {
|
||||
this._connectionManagementService.ensureDefaultLanguageFlavor(self.uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.onDisconnect();
|
||||
this.onQueryComplete();
|
||||
}
|
||||
|
||||
// Getters for private properties
|
||||
public get uri(): string { return this.getResource().toString(); }
|
||||
public get sql(): UntitledEditorInput { return this._sql; }
|
||||
public get results(): QueryResultsInput { return this._results; }
|
||||
public get updateTaskbarEvent(): Event<void> { return this._updateTaskbar.event; }
|
||||
public get showQueryResultsEditorEvent(): Event<void> { return this._showQueryResultsEditor.event; }
|
||||
public get updateSelectionEvent(): Event<ISelectionData> { return this._updateSelection.event; }
|
||||
public get runQueryEnabled(): boolean { return this._runQueryEnabled; }
|
||||
public get cancelQueryEnabled(): boolean { return this._cancelQueryEnabled; }
|
||||
public get connectEnabled(): boolean { return this._connectEnabled; }
|
||||
public get disconnectEnabled(): boolean { return this._disconnectEnabled; }
|
||||
public get changeConnectionEnabled(): boolean { return this._changeConnectionEnabled; }
|
||||
public get listDatabasesConnected(): boolean { return this._listDatabasesConnected; }
|
||||
public getQueryResultsInputResource(): string { return this._results.uri; }
|
||||
public showQueryResultsEditor(): void { this._showQueryResultsEditor.fire(); }
|
||||
public updateSelection(selection: ISelectionData): void { this._updateSelection.fire(selection); }
|
||||
public getTypeId(): string { return UntitledEditorInput.ID; }
|
||||
public getDescription(): string { return this._description; }
|
||||
public supportsSplitEditor(): boolean { return false; }
|
||||
public getModeId(): string { return QueryInput.SCHEMA; }
|
||||
public revert(): TPromise<boolean> { return this._sql.revert(); }
|
||||
|
||||
public matches(otherInput: any): boolean {
|
||||
if (otherInput instanceof QueryInput) {
|
||||
return this._sql.matches(otherInput.sql);
|
||||
}
|
||||
|
||||
return this._sql.matches(otherInput);
|
||||
}
|
||||
|
||||
// Forwarding resource functions to the inline sql file editor
|
||||
public get onDidModelChangeContent(): Event<void> { return this._sql.onDidModelChangeContent; }
|
||||
public get onDidModelChangeEncoding(): Event<void> { return this._sql.onDidModelChangeEncoding; }
|
||||
public resolve(refresh?: boolean): TPromise<EditorModel> { return this._sql.resolve(); }
|
||||
public save(): TPromise<boolean> { return this._sql.save(); }
|
||||
public isDirty(): boolean { return this._sql.isDirty(); }
|
||||
public confirmSave(): ConfirmResult { return this._sql.confirmSave(); }
|
||||
public getResource(): URI { return this._sql.getResource(); }
|
||||
public getEncoding(): string { return this._sql.getEncoding(); }
|
||||
public suggestFileName(): string { return this._sql.suggestFileName(); }
|
||||
public getName(): string { return this._sql.getName(); }
|
||||
public hasAssociatedFilePath(): boolean { return this._sql.hasAssociatedFilePath; }
|
||||
|
||||
public setEncoding(encoding: string, mode: EncodingMode /* ignored, we only have Encode */): void {
|
||||
this._sql.setEncoding(encoding, mode);
|
||||
}
|
||||
|
||||
// State update funtions
|
||||
public runQuery(selection: ISelectionData, executePlanOptions?: ExecutionPlanOptions): void {
|
||||
this._queryModelService.runQuery(this.uri, selection, this.uri, this, executePlanOptions);
|
||||
this.showQueryResultsEditor();
|
||||
}
|
||||
|
||||
public runQueryStatement(selection: ISelectionData): void {
|
||||
this._queryModelService.runQueryStatement(this.uri, selection, this.uri, this);
|
||||
this.showQueryResultsEditor();
|
||||
}
|
||||
|
||||
public onConnectStart(): void {
|
||||
this._runQueryEnabled = false;
|
||||
this._cancelQueryEnabled = false;
|
||||
this._connectEnabled = false;
|
||||
this._disconnectEnabled = true;
|
||||
this._changeConnectionEnabled = false;
|
||||
this._listDatabasesConnected = false;
|
||||
this._updateTaskbar.fire();
|
||||
}
|
||||
|
||||
public onConnectReject(): void {
|
||||
this.onDisconnect();
|
||||
this._updateTaskbar.fire();
|
||||
}
|
||||
|
||||
public onConnectSuccess(params?: INewConnectionParams): void {
|
||||
this._runQueryEnabled = true;
|
||||
this._connectEnabled = false;
|
||||
this._disconnectEnabled = true;
|
||||
this._changeConnectionEnabled = true;
|
||||
this._listDatabasesConnected = true;
|
||||
|
||||
let isRunningQuery = this._queryModelService.isRunningQuery(this.uri);
|
||||
if (!isRunningQuery && params && params.runQueryOnCompletion) {
|
||||
let selection: ISelectionData = params ? params.querySelection : undefined;
|
||||
if (params.runQueryOnCompletion === RunQueryOnConnectionMode.executeCurrentQuery) {
|
||||
this.runQueryStatement(selection);
|
||||
} else if (params.runQueryOnCompletion === RunQueryOnConnectionMode.executeQuery) {
|
||||
this.runQuery(selection);
|
||||
} else if (params.runQueryOnCompletion === RunQueryOnConnectionMode.estimatedQueryPlan) {
|
||||
this.runQuery(selection, { displayEstimatedQueryPlan: true });
|
||||
}
|
||||
}
|
||||
this._updateTaskbar.fire();
|
||||
}
|
||||
|
||||
public onDisconnect(): void {
|
||||
this._runQueryEnabled = true;
|
||||
this._cancelQueryEnabled = false;
|
||||
this._connectEnabled = true;
|
||||
this._disconnectEnabled = false;
|
||||
this._changeConnectionEnabled = false;
|
||||
this._listDatabasesConnected = false;
|
||||
this._updateTaskbar.fire();
|
||||
}
|
||||
|
||||
public onRunQuery(): void {
|
||||
this._runQueryEnabled = false;
|
||||
this._cancelQueryEnabled = true;
|
||||
this._updateTaskbar.fire();
|
||||
}
|
||||
|
||||
public onQueryComplete(): void {
|
||||
this._runQueryEnabled = true;
|
||||
this._cancelQueryEnabled = false;
|
||||
this._updateTaskbar.fire();
|
||||
}
|
||||
|
||||
// Clean up functions
|
||||
public dispose(): void {
|
||||
this._queryModelService.disposeQuery(this.uri);
|
||||
this._sql.dispose();
|
||||
this._results.dispose();
|
||||
this._toDispose = dispose(this._toDispose);
|
||||
this._currentEventCallbacks = dispose(this._currentEventCallbacks);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
public close(): void {
|
||||
this._queryEditorService.onQueryInputClosed(this.uri);
|
||||
this._connectionManagementService.disconnectEditor(this, true);
|
||||
|
||||
this._sql.close();
|
||||
this._results.close();
|
||||
super.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribe all events in _currentEventCallbacks and set the new callbacks
|
||||
* to be unsubscribed the next time this method is called.
|
||||
*
|
||||
* This method is used to ensure that all callbacks point to the current QueryEditor
|
||||
* in the case that this QueryInput is moved between different QueryEditors.
|
||||
*/
|
||||
public setEventCallbacks(callbacks: IDisposable[]): void {
|
||||
this._currentEventCallbacks = dispose(this._currentEventCallbacks);
|
||||
this._currentEventCallbacks = callbacks;
|
||||
}
|
||||
}
|
||||
306
src/sql/parts/query/common/queryManagement.ts
Normal file
@@ -0,0 +1,306 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import QueryRunner from 'sql/parts/query/execution/queryRunner';
|
||||
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import data = require('data');
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as TelemetryKeys from 'sql/common/telemetryKeys';
|
||||
import * as TelemetryUtils from 'sql/common/telemetryUtilities';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
|
||||
export const SERVICE_ID = 'queryManagementService';
|
||||
|
||||
export const IQueryManagementService = createDecorator<IQueryManagementService>(SERVICE_ID);
|
||||
|
||||
export interface IQueryManagementService {
|
||||
_serviceBrand: any;
|
||||
|
||||
addQueryRequestHandler(queryType: string, runner: QueryRequestHandler): IDisposable;
|
||||
registerRunner(runner: QueryRunner, uri: string): void;
|
||||
|
||||
cancelQuery(ownerUri: string): Thenable<data.QueryCancelResult>;
|
||||
runQuery(ownerUri: string, selection: data.ISelectionData, runOptions?: data.ExecutionPlanOptions): Thenable<void>;
|
||||
runQueryStatement(ownerUri: string, line: number, column: number): Thenable<void>;
|
||||
runQueryString(ownerUri: string, queryString: string): Thenable<void>;
|
||||
runQueryAndReturn(ownerUri: string, queryString: string): Thenable<data.SimpleExecuteResult>;
|
||||
getQueryRows(rowData: data.QueryExecuteSubsetParams): Thenable<data.QueryExecuteSubsetResult>;
|
||||
disposeQuery(ownerUri: string): Thenable<void>;
|
||||
saveResults(requestParams: data.SaveResultsRequestParams): Thenable<data.SaveResultRequestResult>;
|
||||
|
||||
// Callbacks
|
||||
onQueryComplete(result: data.QueryExecuteCompleteNotificationResult): void;
|
||||
onBatchStart(batchInfo: data.QueryExecuteBatchNotificationParams): void;
|
||||
onBatchComplete(batchInfo: data.QueryExecuteBatchNotificationParams): void;
|
||||
onResultSetComplete(resultSetInfo: data.QueryExecuteResultSetCompleteNotificationParams): void;
|
||||
onMessage(message: data.QueryExecuteMessageParams): void;
|
||||
|
||||
// Edit Data Callbacks
|
||||
onEditSessionReady(ownerUri: string, success: boolean, message: string): void;
|
||||
|
||||
// Edit Data Functions
|
||||
initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number): Thenable<void>;
|
||||
disposeEdit(ownerUri: string): Thenable<void>;
|
||||
updateCell(ownerUri: string, rowId: number, columnId: number, newValue: string): Thenable<data.EditUpdateCellResult>;
|
||||
commitEdit(ownerUri): Thenable<void>;
|
||||
createRow(ownerUri: string): Thenable<data.EditCreateRowResult>;
|
||||
deleteRow(ownerUri: string, rowId: number): Thenable<void>;
|
||||
revertCell(ownerUri: string, rowId: number, columnId: number): Thenable<data.EditRevertCellResult>;
|
||||
revertRow(ownerUri: string, rowId: number): Thenable<void>;
|
||||
getEditRows(rowData: data.EditSubsetParams): Thenable<data.EditSubsetResult>;
|
||||
}
|
||||
|
||||
/*
|
||||
* An object that can handle basic request-response actions related to queries
|
||||
*/
|
||||
export interface QueryRequestHandler {
|
||||
cancelQuery(ownerUri: string): Thenable<data.QueryCancelResult>;
|
||||
runQuery(ownerUri: string, selection: data.ISelectionData, runOptions?: data.ExecutionPlanOptions): Thenable<void>;
|
||||
runQueryStatement(ownerUri: string, line: number, column: number): Thenable<void>;
|
||||
runQueryString(ownerUri: string, queryString: string): Thenable<void>;
|
||||
runQueryAndReturn(ownerUri: string, queryString: string): Thenable<data.SimpleExecuteResult>;
|
||||
getQueryRows(rowData: data.QueryExecuteSubsetParams): Thenable<data.QueryExecuteSubsetResult>;
|
||||
disposeQuery(ownerUri: string): Thenable<void>;
|
||||
saveResults(requestParams: data.SaveResultsRequestParams): Thenable<data.SaveResultRequestResult>;
|
||||
|
||||
// Edit Data actions
|
||||
initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number): Thenable<void>;
|
||||
disposeEdit(ownerUri: string): Thenable<void>;
|
||||
updateCell(ownerUri: string, rowId: number, columnId: number, newValue: string): Thenable<data.EditUpdateCellResult>;
|
||||
commitEdit(ownerUri): Thenable<void>;
|
||||
createRow(ownerUri: string): Thenable<data.EditCreateRowResult>;
|
||||
deleteRow(ownerUri: string, rowId: number): Thenable<void>;
|
||||
revertCell(ownerUri: string, rowId: number, columnId: number): Thenable<data.EditRevertCellResult>;
|
||||
revertRow(ownerUri: string, rowId: number): Thenable<void>;
|
||||
getEditRows(rowData: data.EditSubsetParams): Thenable<data.EditSubsetResult>;
|
||||
}
|
||||
|
||||
export class QueryManagementService implements IQueryManagementService {
|
||||
public static readonly DefaultQueryType = 'MSSQL';
|
||||
public _serviceBrand: any;
|
||||
|
||||
private _requestHandlers = new Map<string, QueryRequestHandler>();
|
||||
// public for testing only
|
||||
public _queryRunners = new Map<string, QueryRunner>();
|
||||
|
||||
// public for testing only
|
||||
public _handlerCallbackQueue: ((run: QueryRunner) => void)[] = [];
|
||||
|
||||
constructor(
|
||||
@IConnectionManagementService private _connectionService: IConnectionManagementService,
|
||||
@ITelemetryService private _telemetryService: ITelemetryService
|
||||
) {
|
||||
}
|
||||
|
||||
// Registers queryRunners with their uris to distribute notifications.
|
||||
// Ensures that notifications are handled in the correct order by handling
|
||||
// enqueued handlers first.
|
||||
// public for testing only
|
||||
public registerRunner(runner: QueryRunner, uri: string): void {
|
||||
// If enqueueOrRun was called before registerRunner for the current query,
|
||||
// _handlerCallbackQueue will be non-empty. Run all handlers in the queue first
|
||||
// so that notifications are handled in order they arrived
|
||||
while (this._handlerCallbackQueue.length > 0) {
|
||||
let handler = this._handlerCallbackQueue.shift();
|
||||
handler(runner);
|
||||
}
|
||||
|
||||
// Set the runner for any other handlers if the runner is in use by the
|
||||
// current query or a subsequent query
|
||||
if (!runner.hasCompleted) {
|
||||
this._queryRunners.set(uri, runner);
|
||||
}
|
||||
}
|
||||
|
||||
// Handles logic to run the given handlerCallback at the appropriate time. If the given runner is
|
||||
// undefined, the handlerCallback is put on the _handlerCallbackQueue to be run once the runner is set
|
||||
// public for testing only
|
||||
private enqueueOrRun(handlerCallback: (runnerParam: QueryRunner) => void, runner: QueryRunner): void {
|
||||
if (runner === undefined) {
|
||||
this._handlerCallbackQueue.push(handlerCallback);
|
||||
} else {
|
||||
handlerCallback(runner);
|
||||
}
|
||||
}
|
||||
|
||||
private _notify(ownerUri: string, sendNotification: (runner: QueryRunner) => void): void {
|
||||
let runner = this._queryRunners.get(ownerUri);
|
||||
this.enqueueOrRun(sendNotification, runner);
|
||||
}
|
||||
|
||||
public addQueryRequestHandler(queryType: string, handler: QueryRequestHandler): IDisposable {
|
||||
this._requestHandlers.set(queryType, handler);
|
||||
|
||||
return {
|
||||
dispose: () => {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private addTelemetry(eventName: string, ownerUri: string, runOptions?: data.ExecutionPlanOptions): void {
|
||||
let providerId: string = this._connectionService.getProviderIdFromUri(ownerUri);
|
||||
let data: TelemetryUtils.IConnectionTelemetryData = {
|
||||
provider: providerId,
|
||||
};
|
||||
if (runOptions) {
|
||||
data = Object.assign({}, data, {
|
||||
displayEstimatedQueryPlan: runOptions.displayEstimatedQueryPlan,
|
||||
displayActualQueryPlan: runOptions.displayActualQueryPlan
|
||||
});
|
||||
}
|
||||
TelemetryUtils.addTelemetry(this._telemetryService, eventName, data);
|
||||
}
|
||||
|
||||
private _runAction<T>(uri: string, action: (handler: QueryRequestHandler) => Thenable<T>): Thenable<T> {
|
||||
let providerId: string = this._connectionService.getProviderIdFromUri(uri);
|
||||
|
||||
if (!providerId) {
|
||||
return TPromise.wrapError(new Error('Connection is required in order to interact with queries'));
|
||||
}
|
||||
let handler = this._requestHandlers.get(providerId);
|
||||
if (handler) {
|
||||
return action(handler);
|
||||
} else {
|
||||
return TPromise.wrapError(new Error('No Handler Registered'));
|
||||
}
|
||||
}
|
||||
|
||||
public cancelQuery(ownerUri: string): Thenable<data.QueryCancelResult> {
|
||||
this.addTelemetry(TelemetryKeys.CancelQuery, ownerUri);
|
||||
return this._runAction(ownerUri, (runner) => {
|
||||
return runner.cancelQuery(ownerUri);
|
||||
});
|
||||
}
|
||||
public runQuery(ownerUri: string, selection: data.ISelectionData, runOptions?: data.ExecutionPlanOptions): Thenable<void> {
|
||||
this.addTelemetry(TelemetryKeys.RunQuery, ownerUri, runOptions);
|
||||
return this._runAction(ownerUri, (runner) => {
|
||||
return runner.runQuery(ownerUri, selection, runOptions);
|
||||
});
|
||||
}
|
||||
public runQueryStatement(ownerUri: string, line: number, column: number): Thenable<void> {
|
||||
this.addTelemetry(TelemetryKeys.RunQueryStatement, ownerUri);
|
||||
return this._runAction(ownerUri, (runner) => {
|
||||
return runner.runQueryStatement(ownerUri, line, column);
|
||||
});
|
||||
}
|
||||
public runQueryString(ownerUri: string, queryString: string): Thenable<void> {
|
||||
return this._runAction(ownerUri, (runner) => {
|
||||
return runner.runQueryString(ownerUri, queryString);
|
||||
});
|
||||
}
|
||||
public runQueryAndReturn(ownerUri: string, queryString: string): Thenable<data.SimpleExecuteResult> {
|
||||
return this._runAction(ownerUri, (runner) => {
|
||||
return runner.runQueryAndReturn(ownerUri, queryString);
|
||||
});
|
||||
}
|
||||
public getQueryRows(rowData: data.QueryExecuteSubsetParams): Thenable<data.QueryExecuteSubsetResult> {
|
||||
return this._runAction(rowData.ownerUri, (runner) => {
|
||||
return runner.getQueryRows(rowData);
|
||||
});
|
||||
}
|
||||
public disposeQuery(ownerUri: string): Thenable<void> {
|
||||
return this._runAction(ownerUri, (runner) => {
|
||||
return runner.disposeQuery(ownerUri);
|
||||
});
|
||||
}
|
||||
|
||||
public saveResults(requestParams: data.SaveResultsRequestParams): Thenable<data.SaveResultRequestResult> {
|
||||
return this._runAction(requestParams.ownerUri, (runner) => {
|
||||
return runner.saveResults(requestParams);
|
||||
});
|
||||
}
|
||||
|
||||
public onQueryComplete(result: data.QueryExecuteCompleteNotificationResult): void {
|
||||
this._notify(result.ownerUri, (runner: QueryRunner) => {
|
||||
runner.handleQueryComplete(result);
|
||||
});
|
||||
}
|
||||
public onBatchStart(batchInfo: data.QueryExecuteBatchNotificationParams): void {
|
||||
this._notify(batchInfo.ownerUri, (runner: QueryRunner) => {
|
||||
runner.handleBatchStart(batchInfo);
|
||||
});
|
||||
}
|
||||
|
||||
public onBatchComplete(batchInfo: data.QueryExecuteBatchNotificationParams): void {
|
||||
this._notify(batchInfo.ownerUri, (runner: QueryRunner) => {
|
||||
runner.handleBatchComplete(batchInfo);
|
||||
});
|
||||
}
|
||||
|
||||
public onResultSetComplete(resultSetInfo: data.QueryExecuteResultSetCompleteNotificationParams): void {
|
||||
this._notify(resultSetInfo.ownerUri, (runner: QueryRunner) => {
|
||||
runner.handleResultSetComplete(resultSetInfo);
|
||||
});
|
||||
}
|
||||
|
||||
public onMessage(message: data.QueryExecuteMessageParams): void {
|
||||
this._notify(message.ownerUri, (runner: QueryRunner) => {
|
||||
runner.handleMessage(message);
|
||||
});
|
||||
}
|
||||
|
||||
// Edit Data Functions
|
||||
public initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number): Thenable<void> {
|
||||
return this._runAction(ownerUri, (runner) => {
|
||||
return runner.initializeEdit(ownerUri, schemaName, objectName, objectType, rowLimit);
|
||||
});
|
||||
}
|
||||
|
||||
public onEditSessionReady(ownerUri: string, success: boolean, message: string): void {
|
||||
this._notify(ownerUri, (runner: QueryRunner) => {
|
||||
runner.handleEditSessionReady(ownerUri, success, message);
|
||||
});
|
||||
}
|
||||
|
||||
public updateCell(ownerUri: string, rowId: number, columnId: number, newValue: string): Thenable<data.EditUpdateCellResult> {
|
||||
return this._runAction(ownerUri, (runner) => {
|
||||
return runner.updateCell(ownerUri, rowId, columnId, newValue);
|
||||
});
|
||||
}
|
||||
|
||||
public commitEdit(ownerUri: string): Thenable<void> {
|
||||
return this._runAction(ownerUri, (runner) => {
|
||||
return runner.commitEdit(ownerUri);
|
||||
});
|
||||
}
|
||||
|
||||
public createRow(ownerUri: string): Thenable<data.EditCreateRowResult> {
|
||||
return this._runAction(ownerUri, (runner) => {
|
||||
return runner.createRow(ownerUri);
|
||||
});
|
||||
}
|
||||
|
||||
public deleteRow(ownerUri: string, rowId: number): Thenable<void> {
|
||||
return this._runAction(ownerUri, (runner) => {
|
||||
return runner.deleteRow(ownerUri, rowId);
|
||||
});
|
||||
}
|
||||
|
||||
public disposeEdit(ownerUri: string): Thenable<void> {
|
||||
return this._runAction(ownerUri, (runner) => {
|
||||
return runner.disposeEdit(ownerUri);
|
||||
});
|
||||
}
|
||||
|
||||
public revertCell(ownerUri: string, rowId: number, columnId: number): Thenable<data.EditRevertCellResult> {
|
||||
return this._runAction(ownerUri, (runner) => {
|
||||
return runner.revertCell(ownerUri, rowId, columnId);
|
||||
});
|
||||
}
|
||||
|
||||
public revertRow(ownerUri: string, rowId: number): Thenable<void> {
|
||||
return this._runAction(ownerUri, (runner) => {
|
||||
return runner.revertRow(ownerUri, rowId);
|
||||
});
|
||||
}
|
||||
|
||||
public getEditRows(rowData: data.EditSubsetParams): Thenable<data.EditSubsetResult> {
|
||||
return this._runAction(rowData.ownerUri, (runner) => {
|
||||
return runner.getEditRows(rowData);
|
||||
});
|
||||
}
|
||||
}
|
||||
109
src/sql/parts/query/common/queryResultsInput.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { EditorInput } from 'vs/workbench/common/editor';
|
||||
|
||||
/**
|
||||
* Input for the QueryResultsEditor. This input helps with logic for the viewing and editing of
|
||||
* data in the results grid.
|
||||
*/
|
||||
export class QueryResultsInput extends EditorInput {
|
||||
|
||||
// Tracks if the editor that holds this input should be visible (i.e. true if a query has been run)
|
||||
private _visible: boolean;
|
||||
|
||||
// Tracks if the editor has holds this input has has bootstrapped angular yet
|
||||
private _hasBootstrapped: boolean;
|
||||
|
||||
// Holds the HTML content for the editor when the editor discards this input and loads another
|
||||
private _editorContainer: HTMLElement;
|
||||
|
||||
constructor(private _uri: string) {
|
||||
super();
|
||||
this._visible = false;
|
||||
this._hasBootstrapped = false;
|
||||
}
|
||||
|
||||
getTypeId(): string {
|
||||
return QueryResultsInput.ID;
|
||||
}
|
||||
|
||||
getName(): string {
|
||||
return localize('extensionsInputName', 'Extension');
|
||||
}
|
||||
|
||||
matches(other: any): boolean {
|
||||
if (other instanceof QueryResultsInput) {
|
||||
return (other._uri === this._uri);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
resolve(refresh?: boolean): TPromise<any> {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
supportsSplitEditor(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
public setVisibleTrue(): void {
|
||||
this._visible = true;
|
||||
}
|
||||
|
||||
public setBootstrappedTrue(): void {
|
||||
this._hasBootstrapped = true;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._disposeContainer();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
private _disposeContainer() {
|
||||
if (!this._editorContainer) {
|
||||
return;
|
||||
}
|
||||
|
||||
let parentContainer = this._editorContainer.parentNode;
|
||||
if (parentContainer) {
|
||||
parentContainer.removeChild(this._editorContainer);
|
||||
this._editorContainer = null;
|
||||
}
|
||||
}
|
||||
|
||||
//// Properties
|
||||
|
||||
static get ID() {
|
||||
return 'workbench.query.queryResultsInput';
|
||||
}
|
||||
|
||||
set container(container: HTMLElement) {
|
||||
this._disposeContainer();
|
||||
this._editorContainer = container;
|
||||
}
|
||||
|
||||
get container(): HTMLElement {
|
||||
return this._editorContainer;
|
||||
}
|
||||
|
||||
get hasBootstrapped(): boolean {
|
||||
return this._hasBootstrapped;
|
||||
}
|
||||
|
||||
get visible(): boolean {
|
||||
return this._visible;
|
||||
}
|
||||
|
||||
get uri(): string {
|
||||
return this._uri;
|
||||
}
|
||||
|
||||
}
|
||||
300
src/sql/parts/query/common/resultSerializer.ts
Normal file
@@ -0,0 +1,300 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as ConnectionConstants from 'sql/parts/connection/common/constants';
|
||||
import * as Constants from 'sql/parts/query/common/constants';
|
||||
import * as LocalizedConstants from 'sql/parts/query/common/localizedConstants';
|
||||
import * as WorkbenchUtils from 'sql/workbench/common/sqlWorkbenchUtils';
|
||||
import { SaveResultsRequestParams } from 'data';
|
||||
import { IQueryManagementService } from 'sql/parts/query/common/queryManagement';
|
||||
import { ISaveRequest, SaveFormat } from 'sql/parts/grid/common/interfaces';
|
||||
import * as PathUtilities from 'sql/common/pathUtilities';
|
||||
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IMessageService, Severity } from 'vs/platform/message/common/message';
|
||||
import { IOutputService, IOutputChannel, IOutputChannelRegistry, Extensions as OutputExtensions } from 'vs/workbench/parts/output/common/output';
|
||||
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { IWindowsService, IWindowService } from 'vs/platform/windows/common/windows';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { IUntitledEditorService, UNTITLED_SCHEMA } from 'vs/workbench/services/untitled/common/untitledEditorService';
|
||||
import * as paths from 'vs/base/common/paths';
|
||||
import * as nls from 'vs/nls';
|
||||
import * as pretty from 'pretty-data';
|
||||
|
||||
import { ISlickRange } from 'angular2-slickgrid';
|
||||
import * as path from 'path';
|
||||
|
||||
/**
|
||||
* Handles save results request from the context menu of slickGrid
|
||||
*/
|
||||
export class ResultSerializer {
|
||||
public static tempFileCount: number = 1;
|
||||
private static MAX_FILENAMES = 100;
|
||||
|
||||
private _uri: string;
|
||||
private _filePath: string;
|
||||
|
||||
constructor(
|
||||
@IInstantiationService private _instantiationService: IInstantiationService,
|
||||
@IMessageService private _messageService: IMessageService,
|
||||
@IOutputService private _outputService: IOutputService,
|
||||
@IQueryManagementService private _queryManagementService: IQueryManagementService,
|
||||
@IWorkspaceConfigurationService private _workspaceConfigurationService: IWorkspaceConfigurationService,
|
||||
@IWorkbenchEditorService private _editorService: IWorkbenchEditorService,
|
||||
@IWorkspaceContextService private _contextService: IWorkspaceContextService,
|
||||
@IWindowsService private _windowsService: IWindowsService,
|
||||
@IWindowService private _windowService: IWindowService,
|
||||
@IUntitledEditorService private _untitledEditorService: IUntitledEditorService
|
||||
) { }
|
||||
|
||||
/**
|
||||
* Handle save request by getting filename from user and sending request to service
|
||||
*/
|
||||
public saveResults(uri: string, saveRequest: ISaveRequest): Thenable<void> {
|
||||
const self = this;
|
||||
this._uri = uri;
|
||||
|
||||
// prompt for filepath
|
||||
let filePath = self.promptForFilepath(saveRequest);
|
||||
if (filePath) {
|
||||
return self.sendRequestToService(filePath, saveRequest.batchIndex, saveRequest.resultSetNumber, saveRequest.format, saveRequest.selection ? saveRequest.selection[0] : undefined);
|
||||
}
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a xml/json link - Opens the content in a new editor pane
|
||||
*/
|
||||
public openLink(content: string, columnName: string, linkType: string): void {
|
||||
let fileMode: string = undefined;
|
||||
let fileUri = this.getUntitledFileUri(columnName);
|
||||
|
||||
if (linkType === SaveFormat.XML) {
|
||||
fileMode = SaveFormat.XML;
|
||||
try {
|
||||
content = pretty.pd.xml(content);
|
||||
} catch (e) {
|
||||
// If Xml fails to parse, fall back on original Xml content
|
||||
}
|
||||
} else if (linkType === SaveFormat.JSON) {
|
||||
let jsonContent: string = undefined;
|
||||
fileMode = SaveFormat.JSON;
|
||||
try {
|
||||
jsonContent = JSON.parse(content);
|
||||
} catch (e) {
|
||||
// If Json fails to parse, fall back on original Json content
|
||||
}
|
||||
if (jsonContent) {
|
||||
// If Json content was valid and parsed, pretty print content to a string
|
||||
content = JSON.stringify(jsonContent, undefined, 4);
|
||||
}
|
||||
}
|
||||
|
||||
this.openUntitledFile(fileMode, content, fileUri);
|
||||
}
|
||||
|
||||
private getUntitledFileUri(columnName: string): URI {
|
||||
let fileName = columnName;
|
||||
|
||||
let uri: URI = URI.from({ scheme: UNTITLED_SCHEMA, path: fileName });
|
||||
|
||||
// If the current filename is taken, try another up to a max number
|
||||
if (this._untitledEditorService.exists(uri)) {
|
||||
let i = 1;
|
||||
while (i < ResultSerializer.MAX_FILENAMES
|
||||
&& this._untitledEditorService.exists(uri)) {
|
||||
fileName = [columnName, i.toString()].join('-');
|
||||
uri = URI.from({ scheme: UNTITLED_SCHEMA, path: fileName });
|
||||
i++;
|
||||
}
|
||||
if (this._untitledEditorService.exists(uri)) {
|
||||
// If this fails, return undefined and let the system figure out the right name
|
||||
uri = undefined;
|
||||
}
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
|
||||
private ensureOutputChannelExists(): void {
|
||||
Registry.as<IOutputChannelRegistry>(OutputExtensions.OutputChannels)
|
||||
.registerChannel(ConnectionConstants.outputChannelName, ConnectionConstants.outputChannelName);
|
||||
}
|
||||
|
||||
private get outputChannel(): IOutputChannel {
|
||||
this.ensureOutputChannelExists();
|
||||
return this._outputService.getChannel(ConnectionConstants.outputChannelName);
|
||||
}
|
||||
|
||||
private get rootPath(): string {
|
||||
return PathUtilities.getRootPath(this._contextService);
|
||||
}
|
||||
|
||||
private logToOutputChannel(message: string): void {
|
||||
this.outputChannel.append(message);
|
||||
}
|
||||
|
||||
private promptForFilepath(saveRequest: ISaveRequest): string {
|
||||
let filepathPlaceHolder = PathUtilities.resolveCurrentDirectory(this._uri, this.rootPath);
|
||||
filepathPlaceHolder = path.join(filepathPlaceHolder, this.getResultsDefaultFilename(saveRequest));
|
||||
|
||||
let filePath: string = this._windowService.showSaveDialog({
|
||||
title: nls.localize('saveAsFileTitle', 'Choose Results File'),
|
||||
defaultPath: paths.normalize(filepathPlaceHolder, true)
|
||||
});
|
||||
return filePath;
|
||||
}
|
||||
|
||||
private getResultsDefaultFilename(saveRequest: ISaveRequest): string {
|
||||
let fileName = 'Results';
|
||||
switch (saveRequest.format) {
|
||||
case SaveFormat.CSV:
|
||||
fileName = fileName + '.csv';
|
||||
break;
|
||||
case SaveFormat.JSON:
|
||||
fileName = fileName + '.json';
|
||||
break;
|
||||
case SaveFormat.EXCEL:
|
||||
fileName = fileName + '.xlsx';
|
||||
break;
|
||||
case SaveFormat.XML:
|
||||
fileName = fileName + '.xml';
|
||||
break;
|
||||
default:
|
||||
fileName = fileName + '.txt';
|
||||
}
|
||||
return fileName;
|
||||
}
|
||||
|
||||
private getConfigForCsv(): SaveResultsRequestParams {
|
||||
let saveResultsParams = <SaveResultsRequestParams>{ resultFormat: SaveFormat.CSV as string };
|
||||
|
||||
// get save results config from vscode config
|
||||
let saveConfig = WorkbenchUtils.getSqlConfigSection(this._workspaceConfigurationService, Constants.configSaveAsCsv);
|
||||
// if user entered config, set options
|
||||
if (saveConfig) {
|
||||
if (saveConfig.includeHeaders !== undefined) {
|
||||
saveResultsParams.includeHeaders = saveConfig.includeHeaders;
|
||||
}
|
||||
}
|
||||
return saveResultsParams;
|
||||
}
|
||||
|
||||
private getConfigForJson(): SaveResultsRequestParams {
|
||||
// JSON does not currently have special conditions
|
||||
let saveResultsParams = <SaveResultsRequestParams>{ resultFormat: SaveFormat.JSON as string };
|
||||
return saveResultsParams;
|
||||
}
|
||||
|
||||
private getConfigForExcel(): SaveResultsRequestParams {
|
||||
// get save results config from vscode config
|
||||
// Note: we are currently using the configSaveAsCsv setting since it has the option mssql.saveAsCsv.includeHeaders
|
||||
// and we want to have just 1 setting that lists this.
|
||||
let config = this.getConfigForCsv();
|
||||
config.resultFormat = SaveFormat.EXCEL;
|
||||
return config;
|
||||
}
|
||||
|
||||
private getParameters(filePath: string, batchIndex: number, resultSetNo: number, format: string, selection: ISlickRange): SaveResultsRequestParams {
|
||||
let saveResultsParams: SaveResultsRequestParams;
|
||||
if (!path.isAbsolute(filePath)) {
|
||||
this._filePath = PathUtilities.resolveFilePath(this._uri, filePath, this.rootPath);
|
||||
} else {
|
||||
this._filePath = filePath;
|
||||
}
|
||||
|
||||
if (format === SaveFormat.CSV) {
|
||||
saveResultsParams = this.getConfigForCsv();
|
||||
} else if (format === SaveFormat.JSON) {
|
||||
saveResultsParams = this.getConfigForJson();
|
||||
} else if (format === SaveFormat.EXCEL) {
|
||||
saveResultsParams = this.getConfigForExcel();
|
||||
}
|
||||
|
||||
saveResultsParams.filePath = this._filePath;
|
||||
saveResultsParams.ownerUri = this._uri;
|
||||
saveResultsParams.resultSetIndex = resultSetNo;
|
||||
saveResultsParams.batchIndex = batchIndex;
|
||||
if (this.isSelected(selection)) {
|
||||
saveResultsParams.rowStartIndex = selection.fromRow;
|
||||
saveResultsParams.rowEndIndex = selection.toRow;
|
||||
saveResultsParams.columnStartIndex = selection.fromCell;
|
||||
saveResultsParams.columnEndIndex = selection.toCell;
|
||||
}
|
||||
return saveResultsParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a range of cells were selected.
|
||||
*/
|
||||
private isSelected(selection: ISlickRange): boolean {
|
||||
return (selection && !((selection.fromCell === selection.toCell) && (selection.fromRow === selection.toRow)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Send request to sql tools service to save a result set
|
||||
*/
|
||||
private sendRequestToService(filePath: string, batchIndex: number, resultSetNo: number, format: string, selection: ISlickRange): Thenable<void> {
|
||||
let saveResultsParams = this.getParameters(filePath, batchIndex, resultSetNo, format, selection);
|
||||
|
||||
this.logToOutputChannel(LocalizedConstants.msgSaveStarted + this._filePath);
|
||||
|
||||
// send message to the sqlserverclient for converting results to the requested format and saving to filepath
|
||||
return this._queryManagementService.saveResults(saveResultsParams).then(result => {
|
||||
if (result.messages) {
|
||||
this._messageService.show(Severity.Error, LocalizedConstants.msgSaveFailed + result.messages);
|
||||
this.logToOutputChannel(LocalizedConstants.msgSaveFailed + result.messages);
|
||||
} else {
|
||||
this._messageService.show(Severity.Info, LocalizedConstants.msgSaveSucceeded + this._filePath);
|
||||
this.logToOutputChannel(LocalizedConstants.msgSaveSucceeded + filePath);
|
||||
this.openSavedFile(this._filePath, format);
|
||||
}
|
||||
// TODO telemetry for save results
|
||||
// Telemetry.sendTelemetryEvent('SavedResults', { 'type': format });
|
||||
|
||||
}, error => {
|
||||
this._messageService.show(Severity.Error, LocalizedConstants.msgSaveFailed + error);
|
||||
this.logToOutputChannel(LocalizedConstants.msgSaveFailed + error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the saved file in a new vscode editor pane
|
||||
*/
|
||||
private openSavedFile(filePath: string, format: string): void {
|
||||
if (format === SaveFormat.EXCEL) {
|
||||
// This will not open in VSCode as it's treated as binary. Use the native file opener instead
|
||||
// Note: must use filePath here, URI does not open correctly
|
||||
// TODO see if there is an alternative opener that includes error handling
|
||||
let fileUri = URI.from({ scheme: PathUtilities.FILE_SCHEMA, path: filePath });
|
||||
this._windowsService.openExternal(fileUri.toString());
|
||||
} else {
|
||||
let uri = URI.file(filePath);
|
||||
this._editorService.openEditor({ resource: uri }).then((result) => {
|
||||
|
||||
}, (error: any) => {
|
||||
this._messageService.show(Severity.Error, error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the saved file in a new vscode editor pane
|
||||
*/
|
||||
private openUntitledFile(fileMode: string, contents: string, fileUri: URI = undefined): void {
|
||||
const input = this._untitledEditorService.createOrGet(fileUri, fileMode, contents);
|
||||
|
||||
this._editorService.openEditor(input, { pinned: true })
|
||||
.then(
|
||||
(success) => {
|
||||
},
|
||||
(error: any) => {
|
||||
this._messageService.show(Severity.Error, error);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
26
src/sql/parts/query/editor/editorDescriptorService.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { EditorInput, Extensions, IEditorRegistry, IEditorDescriptor } from 'vs/workbench/common/editor';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
export interface IEditorDescriptorService {
|
||||
getEditor(input: EditorInput): IEditorDescriptor;
|
||||
}
|
||||
|
||||
export class EditorDescriptorService implements IEditorDescriptorService {
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
public getEditor(input: EditorInput): IEditorDescriptor {
|
||||
return Registry.as<IEditorRegistry>(Extensions.Editors).getEditor(input);
|
||||
}
|
||||
}
|
||||
|
||||
export const SERVICE_ID = 'editorDescriptorService';
|
||||
|
||||
export const IEditorDescriptorService = createDecorator<IEditorDescriptorService >(SERVICE_ID);
|
||||
20
src/sql/parts/query/editor/media/binarydiffeditor.css
Normal file
@@ -0,0 +1,20 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-workbench .binarydiff-left {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.monaco-workbench .binarydiff-right {
|
||||
border-left: 3px solid #DDD;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench .binarydiff-right {
|
||||
border-left: 3px solid rgb(20, 20, 20);
|
||||
}
|
||||
|
||||
.hc-black .monaco-workbench .binarydiff-right {
|
||||
border-left: 3px solid #6FC3DF;
|
||||
}
|
||||
1
src/sql/parts/query/editor/media/close-dirty-inverse.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" enable-background="new 0 0 16 16" height="16" width="16"><circle fill="#C5C5C5" cx="8" cy="8" r="4"/></svg>
|
||||
|
After Width: | Height: | Size: 167 B |
1
src/sql/parts/query/editor/media/close-dirty.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" enable-background="new 0 0 16 16" height="16" width="16"><circle fill="#424242" cx="8" cy="8" r="4"/></svg>
|
||||
|
After Width: | Height: | Size: 167 B |
1
src/sql/parts/query/editor/media/close-inverse.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="#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/parts/query/editor/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 |
152
src/sql/parts/query/editor/media/editorGroupsControl.css
Normal file
@@ -0,0 +1,152 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.vs .monaco-workbench > .editor > .content.drag {
|
||||
background-color: #ECECEC;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench > .editor > .content.drag {
|
||||
background-color: #2D2D2D;
|
||||
}
|
||||
|
||||
.monaco-workbench > .editor > .content.dragging > .monaco-sash {
|
||||
display: none; /* hide sashes while dragging editors around */
|
||||
}
|
||||
|
||||
#monaco-workbench-editor-move-overlay,
|
||||
#monaco-workbench-editor-drop-overlay {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
z-index: 10000;
|
||||
}
|
||||
|
||||
#monaco-workbench-editor-drop-overlay {
|
||||
opacity: 0; /* initially not visible until moving around */
|
||||
}
|
||||
|
||||
.vs #monaco-workbench-editor-drop-overlay,
|
||||
.vs .monaco-workbench > .editor.empty > .content.dropfeedback {
|
||||
background-color: rgba(51,153,255, 0.18);
|
||||
}
|
||||
|
||||
.vs-dark #monaco-workbench-editor-drop-overlay,
|
||||
.vs-dark .monaco-workbench > .editor.empty > .content.dropfeedback {
|
||||
background-color: rgba(83, 89, 93, 0.5);
|
||||
}
|
||||
|
||||
.hc-black #monaco-workbench-editor-drop-overlay,
|
||||
.hc-black .monaco-workbench > .editor.empty > .content.dropfeedback {
|
||||
background: none !important;
|
||||
outline: 2px dashed #f38518;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .editor > .content > .one-editor-silo {
|
||||
position: absolute;
|
||||
box-sizing: border-box; /* use border box to be able to draw a border as separator between editors */
|
||||
}
|
||||
|
||||
.monaco-workbench > .editor > .content > .one-editor-silo.editor-one {
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.monaco-workbench > .editor > .content.vertical-layout > .one-editor-silo.editor-three {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.monaco-workbench > .editor > .content.horizontal-layout > .one-editor-silo.editor-three {
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.monaco-workbench > .editor > .content > .one-editor-silo.dragging {
|
||||
z-index: 70;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
.vs .monaco-workbench > .editor > .content.vertical-layout > .one-editor-silo.dragging {
|
||||
border-left: 1px solid #E7E7E7;
|
||||
border-right: 1px solid #E7E7E7;
|
||||
}
|
||||
|
||||
.vs .monaco-workbench > .editor > .content.horizontal-layout > .one-editor-silo.dragging {
|
||||
border-top: 1px solid #E7E7E7;
|
||||
border-bottom: 1px solid #E7E7E7;
|
||||
}
|
||||
|
||||
.vs .monaco-workbench > .editor > .content.vertical-layout > .one-editor-silo.editor-two,
|
||||
.vs .monaco-workbench > .editor > .content.vertical-layout > .one-editor-silo.editor-three {
|
||||
border-left: 1px solid #E7E7E7;
|
||||
}
|
||||
|
||||
.vs .monaco-workbench > .editor > .content.horizontal-layout > .one-editor-silo.editor-two,
|
||||
.vs .monaco-workbench > .editor > .content.horizontal-layout > .one-editor-silo.editor-three {
|
||||
border-top: 1px solid #E7E7E7;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench > .editor > .content.vertical-layout > .one-editor-silo.dragging {
|
||||
border-left: 1px solid #444;
|
||||
border-right: 1px solid #444;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench > .editor > .content.horizontal-layout > .one-editor-silo.dragging {
|
||||
border-top: 1px solid #444;
|
||||
border-bottom: 1px solid #444;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench > .editor > .content.vertical-layout > .one-editor-silo.editor-two,
|
||||
.vs-dark .monaco-workbench > .editor > .content.vertical-layout > .one-editor-silo.editor-three {
|
||||
border-left: 1px solid #444;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench > .editor > .content.horizontal-layout > .one-editor-silo.editor-two,
|
||||
.vs-dark .monaco-workbench > .editor > .content.horizontal-layout > .one-editor-silo.editor-three {
|
||||
border-top: 1px solid #444;
|
||||
}
|
||||
|
||||
.hc-black .monaco-workbench > .editor > .content.vertical-layout > .one-editor-silo.dragging {
|
||||
border-left: 1px solid #6FC3DF;
|
||||
border-right: 1px solid #6FC3DF;
|
||||
}
|
||||
|
||||
.hc-black .monaco-workbench > .editor > .content.horizontal-layout > .one-editor-silo.dragging {
|
||||
border-top: 1px solid #6FC3DF;
|
||||
border-bottom: 1px solid #6FC3DF;
|
||||
}
|
||||
|
||||
.hc-black .monaco-workbench > .editor > .content.vertical-layout > .one-editor-silo.editor-two,
|
||||
.hc-black .monaco-workbench > .editor > .content.vertical-layout > .one-editor-silo.editor-three {
|
||||
border-left: 1px solid #6FC3DF;
|
||||
}
|
||||
|
||||
.hc-black .monaco-workbench > .editor > .content.horizontal-layout > .one-editor-silo.editor-two,
|
||||
.hc-black .monaco-workbench > .editor > .content.horizontal-layout > .one-editor-silo.editor-three {
|
||||
border-top: 1px solid #6FC3DF;
|
||||
}
|
||||
|
||||
.monaco-workbench > .editor > .content.vertical-layout > .one-editor-silo.draggedunder {
|
||||
transition: left 200ms ease-out;
|
||||
}
|
||||
|
||||
.monaco-workbench > .editor > .content.vertical-layout > .editor-three.draggedunder {
|
||||
transition-property: right;
|
||||
}
|
||||
|
||||
.monaco-workbench > .editor > .content.horizontal-layout > .one-editor-silo.draggedunder {
|
||||
transition: top 200ms ease-out;
|
||||
}
|
||||
|
||||
.monaco-workbench > .editor > .content.horizontal-layout > .editor-three.draggedunder {
|
||||
transition-property: bottom;
|
||||
}
|
||||
|
||||
.monaco-workbench > .editor > .content > .one-editor-silo > .container {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.monaco-workbench > .editor > .content > .one-editor-silo > .container > .editor-container {
|
||||
height: calc(100% - 35px); /* Editor is below editor title */
|
||||
}
|
||||
41
src/sql/parts/query/editor/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;
|
||||
}
|
||||
}
|
||||
8
src/sql/parts/query/editor/media/editorpicker.css
Normal file
@@ -0,0 +1,8 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-workbench .quick-open-widget .quick-open-tree .quick-open-entry.editor-preview {
|
||||
font-style: italic;
|
||||
}
|
||||
26
src/sql/parts/query/editor/media/editorstatus.css
Normal file
@@ -0,0 +1,26 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-workbench .editor-statusbar-item > a:not(:first-child) {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.monaco-workbench .editor-statusbar-item > .editor-status-mode,
|
||||
.monaco-workbench .editor-statusbar-item > .editor-status-encoding,
|
||||
.monaco-workbench .editor-statusbar-item > .editor-status-eol,
|
||||
.monaco-workbench .editor-statusbar-item > .editor-status-selection,
|
||||
.monaco-workbench .editor-statusbar-item > .editor-status-indentation,
|
||||
.monaco-workbench .editor-statusbar-item > .editor-status-metadata {
|
||||
padding: 0 5px 0 5px;
|
||||
}
|
||||
|
||||
.monaco-workbench .editor-statusbar-item > .editor-status-metadata {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.monaco-workbench .editor-statusbar-item > .editor-status-tabfocusmode {
|
||||
padding: 0 5px 0 5px;
|
||||
background-color: brown !important;
|
||||
}
|
||||
1
src/sql/parts/query/editor/media/letterpress-dark.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" width="260" height="260" viewBox="0 0 260 260"><style>.st0{opacity:.25}.st1{opacity:3e-2}.st2{fill:#fff}</style><path d="M194 2L92.4 103.6 27.6 53.2 2 66v128l25.6 12.8 64.8-50.4L194 258l64-25.6V27.6L194 2zM27 169V91l39 39-39 39zm99.4-39L194 77v106l-67.6-53z" class="st0"/><path class="st1 st2" d="M194 2l64 25.6v204.8L194 258 92.4 156.4l-64.8 50.4L2 194V66l25.6-12.8 64.8 50.4L194 2m0 181V77l-67.6 53 67.6 53M27 169l39-39-39-39v78M193.8.8l-.5.5-101 101-64.1-49.9-.5-.4-.6.3L1.6 65.1l-.6.3v129.2l.6.3 25.6 12.8.6.3.5-.4 64.1-49.9 101 101 .5.5.6-.2 64-25.6.6-.3V26.9l-.6-.3-64-25.6-.7-.2zM128 130l65-50.9V181l-65-51zM28 166.6V93.4L64.6 130 28 166.6z"/></svg>
|
||||
|
After Width: | Height: | Size: 709 B |
1
src/sql/parts/query/editor/media/letterpress-hc.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" width="260" height="260" viewBox="0 0 260 260"><style>.st0{fill:#fff;fill-opacity:.13;enable-background:new}</style><path class="st0" d="M194 2L92.4 103.6 27.6 53.2 2 66v128l25.6 12.8 64.8-50.4L194 258l64-25.6V27.6L194 2zM27 169V91l39 39-39 39zm99.4-39L194 77v106l-67.6-53z"/></svg>
|
||||
|
After Width: | Height: | Size: 335 B |
1
src/sql/parts/query/editor/media/letterpress.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" width="260" height="260" viewBox="0 0 260 260"><style>.st0{opacity:.1}.st1{opacity:5e-2}.st2{fill:#231f20}</style><path d="M194 2L92.4 103.6 27.6 53.2 2 66v128l25.6 12.8 64.8-50.4L194 258l64-25.6V27.6L194 2zM27 169V91l39 39-39 39zm99.4-39L194 77v106l-67.6-53z" class="st0"/><path class="st1 st2" d="M194 2l64 25.6v204.8L194 258 92.4 156.4l-64.8 50.4L2 194V66l25.6-12.8 64.8 50.4L194 2m0 181V77l-67.6 53 67.6 53M27 169l39-39-39-39v78M193.8.8l-.5.5-101 101-64.1-49.9-.5-.4-.6.3L1.6 65.1l-.6.3v129.2l.6.3 25.6 12.8.6.3.5-.4 64.1-49.9 101 101 .5.5.6-.2 64-25.6.6-.3V26.9l-.6-.3-64-25.6-.7-.2zM128 130l65-50.9V181l-65-51zM28 166.6V93.4L64.6 130 28 166.6z"/></svg>
|
||||
|
After Width: | Height: | Size: 711 B |
1
src/sql/parts/query/editor/media/next-diff-inverse.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="-1 -3 16 16" enable-background="new -1 -3 16 16"><path fill="#C5C5C5" d="M1 4h7l-3-3h3l4 4-4 4h-3l3-3h-7v-2z"/></svg>
|
||||
|
After Width: | Height: | Size: 189 B |
1
src/sql/parts/query/editor/media/next-diff.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="-1 -3 16 16" enable-background="new -1 -3 16 16"><path fill="#656565" d="M1 4h7l-3-3h3l4 4-4 4h-3l3-3h-7v-2z"/></svg>
|
||||
|
After Width: | Height: | Size: 189 B |
47
src/sql/parts/query/editor/media/notabstitle.css
Normal file
@@ -0,0 +1,47 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/* Title Label */
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .title-label {
|
||||
line-height: 35px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
position: relative;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .monaco-icon-label::before {
|
||||
height: 35px; /* tweak the icon size of the editor labels when icons are enabled */
|
||||
}
|
||||
|
||||
/* Title Actions */
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .title-actions {
|
||||
display: flex;
|
||||
flex: initial;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.active .title-actions {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.dirty .title-actions .close-editor-action {
|
||||
background: url('close-dirty.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.dirty .title-actions .close-editor-action,
|
||||
.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.dirty .title-actions .close-editor-action {
|
||||
background: url('close-dirty-inverse.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.dirty .title-actions .close-editor-action:hover {
|
||||
background: url('close.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.dirty .title-actions .close-editor-action:hover,
|
||||
.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.dirty .title-actions .close-editor-action:hover {
|
||||
background: url('close-inverse.svg') center center no-repeat;
|
||||
}
|
||||
1
src/sql/parts/query/editor/media/parseQuery.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><title>ParseQuery_16x</title><rect width="16" height="16" fill="#f6f6f6" opacity="0"/><polygon points="4.382 15 0.382 7 5.618 7 6.5 8.764 10.382 1 15.618 1 8.618 15 4.382 15" fill="#f6f6f6"/><polygon points="11 2 6.5 11 5 8 2 8 5 14 8 14 14 2 11 2" fill="#424242"/></svg>
|
||||
|
After Width: | Height: | Size: 331 B |
@@ -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 |
1
src/sql/parts/query/editor/media/previous-diff.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="-1 -3 16 16" enable-background="new -1 -3 16 16"><polygon fill="#656565" points="13,4 6,4 9,1 6,1 2,5 6,9 9,9 6,6 13,6"/></svg>
|
||||
|
After Width: | Height: | Size: 199 B |
24
src/sql/parts/query/editor/media/queryEditor.css
Normal file
@@ -0,0 +1,24 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.vs-dark .side-by-side-editor > .master-editor-container {
|
||||
box-shadow: -6px 0 5px -5px black;
|
||||
}
|
||||
|
||||
.side-by-side-editor > .master-editor-container {
|
||||
box-shadow: -6px 0 5px -5px #DDD;
|
||||
}
|
||||
|
||||
.vs-dark .side-by-side-editor > .master-editor-container-horizontal {
|
||||
box-shadow: 0 -6px 5px -5px black;
|
||||
}
|
||||
|
||||
.side-by-side-editor > .master-editor-container-horizontal {
|
||||
box-shadow: 0 -6px 5px -5px #DDD;
|
||||
}
|
||||
|
||||
.editDataEditor {
|
||||
height: inherit
|
||||
}
|
||||
1
src/sql/parts/query/editor/media/runWithoutDebug.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1,.cls-2{fill:#f6f6f6;}.cls-1{opacity:0;}.cls-3{fill:#388a34;}.cls-4{fill:#f0eff1;}</style></defs><title>StartWithoutDebug@2x</title><g id="Layer_2" data-name="Layer 2"><g id="outline"><rect class="cls-1" width="16" height="16"/><path class="cls-2" d="M3,0,13.67,8,3,16Z"/></g><g id="color_action"><path class="cls-3" d="M6,6,8.67,8,6,10V6M4,2V14l8-6L4,2Z"/></g><g id="icon_fg"><path class="cls-4" d="M8.67,8,6,10V6Z"/></g></g></svg>
|
||||
|
After Width: | Height: | Size: 511 B |
12
src/sql/parts/query/editor/media/sidebysideEditor.css
Normal file
@@ -0,0 +1,12 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.vs-dark .side-by-side-editor > .master-editor-container {
|
||||
box-shadow: -6px 0 5px -5px black;
|
||||
}
|
||||
|
||||
.side-by-side-editor > .master-editor-container {
|
||||
box-shadow: -6px 0 5px -5px #DDD;
|
||||
}
|
||||
@@ -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 |
20
src/sql/parts/query/editor/media/stackview-inverse.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:#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/parts/query/editor/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/parts/query/editor/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;
|
||||
}
|
||||
22
src/sql/parts/query/editor/media/textdiffeditor.css
Normal file
@@ -0,0 +1,22 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.vs .monaco-workbench .textdiff-editor-action.next {
|
||||
background: url('next-diff.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs .monaco-workbench .textdiff-editor-action.previous {
|
||||
background: url('previous-diff.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench .textdiff-editor-action.next,
|
||||
.hc-black .monaco-workbench .textdiff-editor-action.next {
|
||||
background: url('next-diff-inverse.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench .textdiff-editor-action.previous,
|
||||
.hc-black .monaco-workbench .textdiff-editor-action.previous {
|
||||
background: url('previous-diff-inverse.svg') center center no-repeat;
|
||||
}
|
||||
127
src/sql/parts/query/editor/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;
|
||||
}
|
||||
855
src/sql/parts/query/editor/queryEditor.ts
Normal file
@@ -0,0 +1,855 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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/parts/query/editor/media/queryEditor';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { Builder, Dimension, withElementById } from 'vs/base/browser/builder';
|
||||
|
||||
import { EditorInput, EditorOptions } from 'vs/workbench/common/editor';
|
||||
import { BaseEditor, EditorDescriptor } from 'vs/workbench/browser/parts/editor/baseEditor';
|
||||
import { IEditorControl, Position, IEditor } from 'vs/platform/editor/common/editor';
|
||||
import { VerticalFlexibleSash, HorizontalFlexibleSash, IFlexibleSash } from 'sql/parts/query/views/flexibleSash';
|
||||
import { Orientation } from 'vs/base/browser/ui/sash/sash';
|
||||
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
|
||||
import { TextResourceEditor } from 'vs/workbench/browser/parts/editor/textResourceEditor';
|
||||
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IActionItem } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { ISelectionData } from 'data';
|
||||
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
|
||||
import { CodeEditor } from 'vs/editor/browser/codeEditor';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IRange } from 'vs/editor/common/core/range';
|
||||
|
||||
import { QueryResultsInput } from 'sql/parts/query/common/queryResultsInput';
|
||||
import { QueryInput } from 'sql/parts/query/common/queryInput';
|
||||
import { QueryResultsEditor } from 'sql/parts/query/editor/queryResultsEditor';
|
||||
import * as queryContext from 'sql/parts/query/common/queryContext';
|
||||
import { Taskbar, ITaskbarContent } from 'sql/base/browser/ui/taskbar/taskbar';
|
||||
import { ITextFileService, TextFileModelChangeEvent } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import {
|
||||
RunQueryAction, CancelQueryAction, ListDatabasesAction, ListDatabasesActionItem,
|
||||
ConnectDatabaseAction, ToggleConnectDatabaseAction, EstimatedQueryPlanAction
|
||||
} from 'sql/parts/query/execution/queryActions';
|
||||
import { IQueryModelService } from 'sql/parts/query/execution/queryModel';
|
||||
import { IEditorDescriptorService } from 'sql/parts/query/editor/editorDescriptorService';
|
||||
import * as TaskUtilities from 'sql/workbench/common/taskUtilities';
|
||||
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
|
||||
import { attachEditableDropdownStyler } from 'sql/common/theme/styler';
|
||||
|
||||
/**
|
||||
* Editor that hosts 2 sub-editors: A TextResourceEditor for SQL file editing, and a QueryResultsEditor
|
||||
* for viewing and editing query results. This editor is based off SideBySideEditor.
|
||||
*/
|
||||
export class QueryEditor extends BaseEditor {
|
||||
|
||||
public static ID: string = 'workbench.editor.queryEditor';
|
||||
|
||||
// The height of the tabs above the editor
|
||||
private readonly _tabHeight: number = 35;
|
||||
|
||||
// The height of the taskbar above the editor
|
||||
private readonly _taskbarHeight: number = 28;
|
||||
|
||||
// The minimum width/height of the editors hosted in the QueryEditor
|
||||
private readonly _minEditorSize: number = 220;
|
||||
|
||||
private _sash: IFlexibleSash;
|
||||
private _editorTopOffset: number;
|
||||
private _orientation: Orientation;
|
||||
private _dimension: Dimension;
|
||||
|
||||
private _resultsEditor: QueryResultsEditor;
|
||||
private _resultsEditorContainer: HTMLElement;
|
||||
|
||||
private _sqlEditor: TextResourceEditor;
|
||||
private _sqlEditorContainer: HTMLElement;
|
||||
|
||||
private _taskbar: Taskbar;
|
||||
private _taskbarContainer: HTMLElement;
|
||||
private _listDatabasesActionItem: ListDatabasesActionItem;
|
||||
|
||||
|
||||
private queryEditorVisible: IContextKey<boolean>;
|
||||
|
||||
private _runQueryAction: RunQueryAction;
|
||||
private _cancelQueryAction: CancelQueryAction;
|
||||
private _toggleConnectDatabaseAction: ToggleConnectDatabaseAction;
|
||||
private _changeConnectionAction: ConnectDatabaseAction;
|
||||
private _listDatabasesAction: ListDatabasesAction;
|
||||
private _estimatedQueryPlanAction: EstimatedQueryPlanAction;
|
||||
|
||||
constructor(
|
||||
@ITelemetryService _telemetryService: ITelemetryService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IInstantiationService private _instantiationService: IInstantiationService,
|
||||
@IWorkbenchEditorService private _editorService: IWorkbenchEditorService,
|
||||
@IContextMenuService private _contextMenuService: IContextMenuService,
|
||||
@IQueryModelService private _queryModelService: IQueryModelService,
|
||||
@IEditorDescriptorService private _editorDescriptorService: IEditorDescriptorService,
|
||||
@IEditorGroupService private _editorGroupService: IEditorGroupService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@ITextFileService private _textFileService: ITextFileService,
|
||||
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
|
||||
editorOrientation?: Orientation
|
||||
) {
|
||||
super(QueryEditor.ID, _telemetryService, themeService);
|
||||
|
||||
if (editorOrientation) {
|
||||
this._orientation = editorOrientation;
|
||||
} else {
|
||||
this._orientation = Orientation.HORIZONTAL;
|
||||
}
|
||||
|
||||
if (contextKeyService) {
|
||||
this.queryEditorVisible = queryContext.QueryEditorVisibleContext.bindTo(contextKeyService);
|
||||
}
|
||||
|
||||
if (this._textFileService && this._textFileService.models) {
|
||||
this._textFileService.models.onModelSaved(event => this._onModelSaved(event));
|
||||
}
|
||||
}
|
||||
|
||||
// PROPERTIES //////////////////////////////////////////////////////////
|
||||
/**
|
||||
* Returns the URI of this editor if it is connected.
|
||||
* @returns {string} URI of the editor if connected, undefined otherwise
|
||||
*/
|
||||
public get connectedUri(): string {
|
||||
return this._connectionManagementService.isConnected(this.uri)
|
||||
? this.uri
|
||||
: undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URI of this editor if an input is associated with it
|
||||
* @return {string} URI of this if input is associated, undefined otherwise
|
||||
*/
|
||||
get uri(): string {
|
||||
let input: QueryInput = <QueryInput>this.input;
|
||||
return input
|
||||
? input.getQueryResultsInputResource()
|
||||
: undefined;
|
||||
}
|
||||
|
||||
private _onModelSaved(event: TextFileModelChangeEvent): void {
|
||||
if (event.resource.toString() !== this.uri) {
|
||||
TaskUtilities.replaceConnection(this.uri, event.resource.toString(), this._connectionManagementService).then(result => {
|
||||
if (result && result.connected) {
|
||||
this.currentQueryInput.onConnectSuccess();
|
||||
} else {
|
||||
this.currentQueryInput.onConnectReject();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// PUBLIC METHODS ////////////////////////////////////////////////////////////
|
||||
public get currentQueryInput(): QueryInput {
|
||||
return <QueryInput>this.input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to create the editor in the parent builder.
|
||||
*/
|
||||
public createEditor(parent: Builder): void {
|
||||
const parentElement = parent.getHTMLElement();
|
||||
DOM.addClass(parentElement, 'side-by-side-editor');
|
||||
this._createTaskbar(parentElement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the input data for this editor.
|
||||
*/
|
||||
public setInput(newInput: QueryInput, options?: EditorOptions): TPromise<void> {
|
||||
const oldInput = <QueryInput>this.input;
|
||||
|
||||
if (newInput.matches(oldInput)) {
|
||||
return TPromise.as(undefined);
|
||||
}
|
||||
|
||||
// Make sure all event callbacks will be sent to this QueryEditor in the case that this QueryInput was moved from
|
||||
// another QueryEditor
|
||||
let taskbarCallback: IDisposable = newInput.updateTaskbarEvent(() => this._updateTaskbar());
|
||||
let showResultsCallback: IDisposable = newInput.showQueryResultsEditorEvent(() => this._showQueryResultsEditor());
|
||||
let selectionCallback: IDisposable = newInput.updateSelectionEvent((selection) => this._setSelection(selection));
|
||||
newInput.setEventCallbacks([taskbarCallback, showResultsCallback, selectionCallback]);
|
||||
|
||||
return super.setInput(newInput, options)
|
||||
.then(() => this._updateInput(oldInput, newInput, options));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets this editor and the 2 sub-editors to visible.
|
||||
*/
|
||||
public setEditorVisible(visible: boolean, position: Position): void {
|
||||
if (this._resultsEditor) {
|
||||
this._resultsEditor.setVisible(visible, position);
|
||||
}
|
||||
if (this._sqlEditor) {
|
||||
this._sqlEditor.setVisible(visible, position);
|
||||
}
|
||||
super.setEditorVisible(visible, position);
|
||||
|
||||
// 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.getVisibleEditors()].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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Changes the position of the editor.
|
||||
*/
|
||||
public changePosition(position: Position): void {
|
||||
if (this._resultsEditor) {
|
||||
this._resultsEditor.changePosition(position);
|
||||
}
|
||||
if (this._sqlEditor) {
|
||||
this._sqlEditor.changePosition(position);
|
||||
}
|
||||
super.changePosition(position);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to indicate to the editor that the input should be cleared and resources associated with the
|
||||
* input should be freed.
|
||||
*/
|
||||
public clearInput(): void {
|
||||
if (this._resultsEditor) {
|
||||
this._resultsEditor.clearInput();
|
||||
}
|
||||
if (this._sqlEditor) {
|
||||
this._sqlEditor.clearInput();
|
||||
}
|
||||
this._disposeEditors();
|
||||
super.clearInput();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets focus on this editor. Specifically, it sets the focus on the hosted text editor.
|
||||
*/
|
||||
public focus(): void {
|
||||
if (this._sqlEditor) {
|
||||
this._sqlEditor.focus();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the internal variable keeping track of the editor's size, and re-calculates the sash position.
|
||||
* To be called when the container of this editor changes size.
|
||||
*/
|
||||
public layout(dimension: Dimension): void {
|
||||
this._dimension = dimension;
|
||||
|
||||
if (this._sash) {
|
||||
this._setSashDimension();
|
||||
this.sash.layout();
|
||||
}
|
||||
|
||||
this._doLayout();
|
||||
this._resizeGridContents();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the editor control for the text editor.
|
||||
*/
|
||||
public getControl(): IEditorControl {
|
||||
if (this._sqlEditor) {
|
||||
return this._sqlEditor.getControl();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public getQueryResultsEditor(): QueryResultsEditor {
|
||||
return this._resultsEditor;
|
||||
}
|
||||
|
||||
public getSqlEditor(): TextResourceEditor {
|
||||
return this._sqlEditor;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._disposeEditors();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
public close(): void {
|
||||
let queryInput: QueryInput = <QueryInput>this.input;
|
||||
queryInput.sql.close();
|
||||
queryInput.results.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes visible the QueryResultsEditor for the current QueryInput (if it is not
|
||||
* already visible).
|
||||
*/
|
||||
public _showQueryResultsEditor(): void {
|
||||
if (this._isResultsEditorVisible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._editorGroupService.pinEditor(this.position, this.input);
|
||||
|
||||
let input = <QueryInput>this.input;
|
||||
this._createResultsEditorContainer();
|
||||
|
||||
this._createEditor(<QueryResultsInput>input.results, this._resultsEditorContainer)
|
||||
.then(result => {
|
||||
this._onResultsEditorCreated(<QueryResultsEditor>result, input.results, this.options);
|
||||
this._setResultsEditorVisible();
|
||||
this._doLayout();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the underlying SQL editor's text selection in a 0-indexed format. Returns undefined if there
|
||||
* is no selected text.
|
||||
*/
|
||||
public getSelection(checkIfRange: boolean = true): ISelectionData {
|
||||
if (this._sqlEditor && this._sqlEditor.getControl()) {
|
||||
let vscodeSelection = this._sqlEditor.getControl().getSelection();
|
||||
|
||||
// If the selection is a range of characters rather than just a cursor position, return the range
|
||||
let isRange: boolean =
|
||||
!(vscodeSelection.getStartPosition().lineNumber === vscodeSelection.getEndPosition().lineNumber &&
|
||||
vscodeSelection.getStartPosition().column === vscodeSelection.getEndPosition().column);
|
||||
if (!checkIfRange || isRange) {
|
||||
let sqlToolsServiceSelection: ISelectionData = {
|
||||
startLine: vscodeSelection.getStartPosition().lineNumber - 1,
|
||||
startColumn: vscodeSelection.getStartPosition().column - 1,
|
||||
endLine: vscodeSelection.getEndPosition().lineNumber - 1,
|
||||
endColumn: vscodeSelection.getEndPosition().column - 1,
|
||||
};
|
||||
return sqlToolsServiceSelection;
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise return undefined because there is no selected text
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public isSelectionEmpty(): boolean {
|
||||
if (this._sqlEditor && this._sqlEditor.getControl()) {
|
||||
let control = this._sqlEditor.getControl();
|
||||
let codeEditor: CodeEditor = <CodeEditor>control;
|
||||
|
||||
if (codeEditor) {
|
||||
let value = codeEditor.getValue();
|
||||
if (value !== undefined && value.length > 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the run method of this editor's RunQueryAction
|
||||
*/
|
||||
public runQuery(): void {
|
||||
this._runQueryAction.run();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the runCurrent method of this editor's RunQueryAction
|
||||
*/
|
||||
public runCurrentQuery(): void {
|
||||
this._runQueryAction.runCurrent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the run method of this editor's CancelQueryAction
|
||||
*/
|
||||
public cancelQuery(): void {
|
||||
this._cancelQueryAction.run();
|
||||
}
|
||||
|
||||
public rebuildIntelliSenseCache(): void {
|
||||
this._connectionManagementService.rebuildIntelliSenseCache(this.connectedUri);
|
||||
}
|
||||
|
||||
// PRIVATE METHODS ////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Creates the query execution taskbar that appears at the top of the QueryEditor
|
||||
*/
|
||||
private _createTaskbar(parentElement: HTMLElement): void {
|
||||
// Create QueryTaskbar
|
||||
this._taskbarContainer = DOM.append(parentElement, DOM.$('div'));
|
||||
this._taskbar = new Taskbar(this._taskbarContainer, this._contextMenuService, {
|
||||
actionItemProvider: (action: Action) => this._getActionItemForAction(action),
|
||||
});
|
||||
|
||||
// Create Actions for the toolbar
|
||||
this._runQueryAction = this._instantiationService.createInstance(RunQueryAction, this);
|
||||
this._cancelQueryAction = this._instantiationService.createInstance(CancelQueryAction, this);
|
||||
this._toggleConnectDatabaseAction = this._instantiationService.createInstance(ToggleConnectDatabaseAction, this, false);
|
||||
this._changeConnectionAction = this._instantiationService.createInstance(ConnectDatabaseAction, this, true);
|
||||
this._listDatabasesAction = this._instantiationService.createInstance(ListDatabasesAction, this);
|
||||
this._estimatedQueryPlanAction = this._instantiationService.createInstance(EstimatedQueryPlanAction, this);
|
||||
|
||||
// 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 },
|
||||
];
|
||||
this._taskbar.setContent(content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the IActionItem for the List Databases dropdown if provided the associated Action.
|
||||
* Otherwise returns null.
|
||||
*/
|
||||
private _getActionItemForAction(action: Action): IActionItem {
|
||||
if (action.id === ListDatabasesAction.ID) {
|
||||
return this.listDatabasesActionItem;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public for testing purposes only
|
||||
*/
|
||||
public get listDatabasesActionItem(): ListDatabasesActionItem {
|
||||
if (!this._listDatabasesActionItem) {
|
||||
this._listDatabasesActionItem = this._instantiationService.createInstance(ListDatabasesActionItem, this, this._listDatabasesAction);
|
||||
this._register(attachEditableDropdownStyler(this._listDatabasesActionItem, this.themeService));
|
||||
}
|
||||
return this._listDatabasesActionItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles setting input for this editor.
|
||||
*/
|
||||
private _updateInput(oldInput: QueryInput, newInput: QueryInput, options?: EditorOptions): TPromise<void> {
|
||||
|
||||
if (oldInput) {
|
||||
this._disposeEditors();
|
||||
}
|
||||
|
||||
this._createSqlEditorContainer();
|
||||
if (this._isResultsEditorVisible()) {
|
||||
this._createResultsEditorContainer();
|
||||
|
||||
let uri: string = newInput.getQueryResultsInputResource();
|
||||
if (uri) {
|
||||
this._queryModelService.refreshResultsets(uri);
|
||||
}
|
||||
}
|
||||
|
||||
if (this._sash) {
|
||||
if (this._isResultsEditorVisible()) {
|
||||
this._sash.show();
|
||||
} else {
|
||||
this._sash.hide();
|
||||
}
|
||||
}
|
||||
|
||||
this._updateTaskbar();
|
||||
return this._setNewInput(newInput, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles setting input and creating editors when this QueryEditor is either:
|
||||
* - Opened for the first time
|
||||
* - Opened with a new QueryInput
|
||||
* This will create only the SQL editor if the results editor does not yet exist for the
|
||||
* given QueryInput.
|
||||
*/
|
||||
private _setNewInput(newInput: QueryInput, options?: EditorOptions): TPromise<any> {
|
||||
|
||||
// Promises that will ensure proper ordering of editor creation logic
|
||||
let createEditors: () => TPromise<any>;
|
||||
let onEditorsCreated: (result) => TPromise<any>;
|
||||
|
||||
// If both editors exist, create joined promises - one for each editor
|
||||
if (this._isResultsEditorVisible()) {
|
||||
createEditors = () => {
|
||||
return TPromise.join([
|
||||
this._createEditor(<QueryResultsInput>newInput.results, this._resultsEditorContainer),
|
||||
this._createEditor(<UntitledEditorInput>newInput.sql, this._sqlEditorContainer)
|
||||
]);
|
||||
};
|
||||
onEditorsCreated = (result: IEditor[]) => {
|
||||
return TPromise.join([
|
||||
this._onResultsEditorCreated(<QueryResultsEditor>result[0], newInput.results, options),
|
||||
this._onSqlEditorCreated(<TextResourceEditor>result[1], newInput.sql, options)
|
||||
]);
|
||||
};
|
||||
|
||||
// If only the sql editor exists, create a promise and wait for the sql editor to be created
|
||||
} else {
|
||||
createEditors = () => {
|
||||
return this._createEditor(<UntitledEditorInput>newInput.sql, this._sqlEditorContainer);
|
||||
};
|
||||
onEditorsCreated = (result: TextResourceEditor) => {
|
||||
return this._onSqlEditorCreated(result, newInput.sql, options);
|
||||
};
|
||||
}
|
||||
|
||||
// Create a promise to re render the layout after the editor creation logic
|
||||
let doLayout: () => TPromise<any> = () => {
|
||||
this._doLayout();
|
||||
return TPromise.as(undefined);
|
||||
};
|
||||
|
||||
// Run all three steps synchronously
|
||||
return createEditors()
|
||||
.then(onEditorsCreated)
|
||||
.then(doLayout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a single editor based on the type of the given EditorInput.
|
||||
*/
|
||||
private _createEditor(editorInput: EditorInput, container: HTMLElement): TPromise<BaseEditor> {
|
||||
const descriptor = this._editorDescriptorService.getEditor(editorInput);
|
||||
if (!descriptor) {
|
||||
return TPromise.wrapError(new Error(strings.format('Can not find a registered editor for the input {0}', editorInput)));
|
||||
}
|
||||
return this._instantiationService.createInstance(<EditorDescriptor>descriptor)
|
||||
.then((editor: BaseEditor) => {
|
||||
editor.create(new Builder(container));
|
||||
editor.setVisible(this.isVisible(), this.position);
|
||||
return editor;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets input for the SQL editor after it has been created.
|
||||
*/
|
||||
private _onSqlEditorCreated(sqlEditor: TextResourceEditor, sqlInput: UntitledEditorInput, options: EditorOptions): TPromise<void> {
|
||||
this._sqlEditor = sqlEditor;
|
||||
return this._sqlEditor.setInput(sqlInput, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets input for the results editor after it has been created.
|
||||
*/
|
||||
private _onResultsEditorCreated(resultsEditor: QueryResultsEditor, resultsInput: QueryResultsInput, options: EditorOptions): TPromise<void> {
|
||||
this._resultsEditor = resultsEditor;
|
||||
return this._resultsEditor.setInput(resultsInput, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the HTML for the SQL editor. Creates new HTML every time.
|
||||
*/
|
||||
private _createSqlEditorContainer() {
|
||||
const parentElement = this.getContainer().getHTMLElement();
|
||||
this._sqlEditorContainer = DOM.append(parentElement, DOM.$('.details-editor-container'));
|
||||
this._sqlEditorContainer.style.position = 'absolute';
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the HTML for the QueryResultsEditor to the QueryEditor. If the HTML has not yet been
|
||||
* created, it creates it and appends it. If it has already been created, it locates it and
|
||||
* appends it.
|
||||
*/
|
||||
private _createResultsEditorContainer() {
|
||||
this._createSash();
|
||||
|
||||
const parentElement = this.getContainer().getHTMLElement();
|
||||
let input = <QueryInput>this.input;
|
||||
|
||||
if (!input.results.container) {
|
||||
let cssClass: string = '.master-editor-container';
|
||||
if (this._orientation === Orientation.HORIZONTAL) {
|
||||
cssClass = '.master-editor-container-horizontal';
|
||||
}
|
||||
|
||||
this._resultsEditorContainer = DOM.append(parentElement, DOM.$(cssClass));
|
||||
this._resultsEditorContainer.style.position = 'absolute';
|
||||
|
||||
input.results.container = this._resultsEditorContainer;
|
||||
} else {
|
||||
this._resultsEditorContainer = DOM.append(parentElement, input.results.container);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the sash with the requested orientation and registers sash callbacks
|
||||
*/
|
||||
private _createSash(): void {
|
||||
if (!this._sash) {
|
||||
let parentElement: HTMLElement = this.getContainer().getHTMLElement();
|
||||
|
||||
if (this._orientation === Orientation.HORIZONTAL) {
|
||||
this._sash = this._register(new HorizontalFlexibleSash(parentElement, this._minEditorSize));
|
||||
} else {
|
||||
this._sash = this._register(new VerticalFlexibleSash(parentElement, this._minEditorSize));
|
||||
this._sash.setEdge(this._taskbarHeight + this._tabHeight);
|
||||
}
|
||||
this._setSashDimension();
|
||||
|
||||
this._register(this._sash.onPositionChange(position => this._doLayout()));
|
||||
}
|
||||
|
||||
this.sash.show();
|
||||
}
|
||||
|
||||
private _setSashDimension(): void {
|
||||
if (!this._dimension) {
|
||||
return;
|
||||
}
|
||||
if (this._orientation === Orientation.HORIZONTAL) {
|
||||
this._sash.setDimenesion(this._dimension);
|
||||
} else {
|
||||
this._sash.setDimenesion(new Dimension(this._dimension.width, this._dimension.height - this._taskbarHeight));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the size of the 2 sub-editors. Uses agnostic dimensions due to the fact that
|
||||
* the IFlexibleSash could be horizontal or vertical. The same logic is used for horizontal
|
||||
* and vertical sashes.
|
||||
*/
|
||||
private _doLayout(): void {
|
||||
if (!this._isResultsEditorVisible() && this._sqlEditor) {
|
||||
this._doLayoutSql();
|
||||
return;
|
||||
}
|
||||
if (!this._sqlEditor || !this._resultsEditor || !this._dimension || !this._sash) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._orientation === Orientation.HORIZONTAL) {
|
||||
this._doLayoutHorizontal();
|
||||
} else {
|
||||
this._doLayoutVertical();
|
||||
}
|
||||
|
||||
this._resizeGridContents();
|
||||
}
|
||||
|
||||
private _doLayoutHorizontal(): void {
|
||||
let splitPointTop: number = this._sash.getSplitPoint();
|
||||
let parent: ClientRect = this.getContainer().getHTMLElement().getBoundingClientRect();
|
||||
|
||||
let sqlEditorHeight = splitPointTop - (parent.top + this._taskbarHeight);
|
||||
|
||||
let titleBar = withElementById('workbench.parts.titlebar');
|
||||
if (titleBar) {
|
||||
sqlEditorHeight += DOM.getContentHeight(titleBar.getHTMLElement());
|
||||
}
|
||||
|
||||
let queryResultsEditorHeight = parent.bottom - splitPointTop;
|
||||
|
||||
this._sqlEditorContainer.style.height = `${sqlEditorHeight}px`;
|
||||
this._sqlEditorContainer.style.width = `${this._dimension.width}px`;
|
||||
this._sqlEditorContainer.style.top = `${this._editorTopOffset}px`;
|
||||
|
||||
this._resultsEditorContainer.style.height = `${queryResultsEditorHeight}px`;
|
||||
this._resultsEditorContainer.style.width = `${this._dimension.width}px`;
|
||||
this._resultsEditorContainer.style.top = `${splitPointTop}px`;
|
||||
|
||||
this._sqlEditor.layout(new Dimension(this._dimension.width, sqlEditorHeight));
|
||||
this._resultsEditor.layout(new Dimension(this._dimension.width, queryResultsEditorHeight));
|
||||
}
|
||||
|
||||
private _doLayoutVertical(): void {
|
||||
let splitPointLeft: number = this._sash.getSplitPoint();
|
||||
let parent: ClientRect = this.getContainer().getHTMLElement().getBoundingClientRect();
|
||||
|
||||
let sqlEditorWidth = splitPointLeft;
|
||||
let queryResultsEditorWidth = parent.width - splitPointLeft;
|
||||
|
||||
this._sqlEditorContainer.style.width = `${sqlEditorWidth}px`;
|
||||
this._sqlEditorContainer.style.height = `${this._dimension.height - this._taskbarHeight}px`;
|
||||
this._sqlEditorContainer.style.left = `0px`;
|
||||
|
||||
this._resultsEditorContainer.style.width = `${queryResultsEditorWidth}px`;
|
||||
this._resultsEditorContainer.style.height = `${this._dimension.height - this._taskbarHeight}px`;
|
||||
this._resultsEditorContainer.style.left = `${splitPointLeft}px`;
|
||||
|
||||
this._sqlEditor.layout(new Dimension(sqlEditorWidth, this._dimension.height - this._taskbarHeight));
|
||||
this._resultsEditor.layout(new Dimension(queryResultsEditorWidth, this._dimension.height - this._taskbarHeight));
|
||||
}
|
||||
|
||||
private _doLayoutSql() {
|
||||
if (this._dimension) {
|
||||
this._sqlEditor.layout(new Dimension(this._dimension.width, this._dimension.height - this._taskbarHeight));
|
||||
}
|
||||
}
|
||||
|
||||
private _resizeGridContents(): void {
|
||||
if (this._isResultsEditorVisible()) {
|
||||
let queryInput: QueryInput = <QueryInput>this.input;
|
||||
let uri: string = queryInput.getQueryResultsInputResource();
|
||||
if (uri) {
|
||||
this._queryModelService.resizeResultsets(uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _disposeEditors(): void {
|
||||
if (this._sqlEditor) {
|
||||
this._sqlEditor.dispose();
|
||||
this._sqlEditor = null;
|
||||
}
|
||||
if (this._resultsEditor) {
|
||||
this._resultsEditor.dispose();
|
||||
this._resultsEditor = null;
|
||||
}
|
||||
|
||||
let thisEditorParent: HTMLElement = this.getContainer().getHTMLElement();
|
||||
|
||||
if (this._sqlEditorContainer) {
|
||||
let sqlEditorParent: HTMLElement = this._sqlEditorContainer.parentElement;
|
||||
if (sqlEditorParent && sqlEditorParent === thisEditorParent) {
|
||||
this._sqlEditorContainer.parentElement.removeChild(this._sqlEditorContainer);
|
||||
}
|
||||
this._sqlEditorContainer = null;
|
||||
}
|
||||
|
||||
if (this._resultsEditorContainer) {
|
||||
let resultsEditorParent: HTMLElement = this._resultsEditorContainer.parentElement;
|
||||
if (resultsEditorParent && resultsEditorParent === thisEditorParent) {
|
||||
this._resultsEditorContainer.parentElement.removeChild(this._resultsEditorContainer);
|
||||
}
|
||||
this._resultsEditorContainer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the QueryResultsInput has denoted that the results editor
|
||||
* should be visible.
|
||||
* Public for testing only.
|
||||
*/
|
||||
public _isResultsEditorVisible(): boolean {
|
||||
let input: QueryInput = <QueryInput>this.input;
|
||||
|
||||
if (!input) {
|
||||
return false;
|
||||
}
|
||||
return input.results.visible;
|
||||
}
|
||||
|
||||
private _setResultsEditorVisible(): void {
|
||||
let input: QueryInput = <QueryInput>this.input;
|
||||
input.results.setVisibleTrue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the buttons on the taskbar to reflect the state of the current input.
|
||||
*/
|
||||
private _updateTaskbar(): void {
|
||||
let queryInput: QueryInput = <QueryInput>this.input;
|
||||
|
||||
if (queryInput) {
|
||||
this._cancelQueryAction.enabled = queryInput.cancelQueryEnabled;
|
||||
this._changeConnectionAction.enabled = queryInput.changeConnectionEnabled;
|
||||
|
||||
// For the toggle database action, it should always be enabled since it's a toggle.
|
||||
// We use inverse of connect enabled state for now, should refactor queryInput in the future to
|
||||
// define connected as a boolean instead of using the enabled flag
|
||||
this._toggleConnectDatabaseAction.enabled = true;
|
||||
this._toggleConnectDatabaseAction.connected = !queryInput.connectEnabled;
|
||||
this._runQueryAction.enabled = queryInput.runQueryEnabled;
|
||||
if (queryInput.listDatabasesConnected) {
|
||||
this.listDatabasesActionItem.onConnected();
|
||||
} else {
|
||||
this.listDatabasesActionItem.onDisconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the text selection for the SQL editor based on the given ISelectionData.
|
||||
*/
|
||||
private _setSelection(selection: ISelectionData): void {
|
||||
let rangeConversion: IRange = {
|
||||
startLineNumber: selection.startLine + 1,
|
||||
startColumn: selection.startColumn + 1,
|
||||
endLineNumber: selection.endLine + 1,
|
||||
endColumn: selection.endColumn + 1
|
||||
};
|
||||
let editor = this._sqlEditor.getControl();
|
||||
editor.revealRange(rangeConversion);
|
||||
editor.setSelection(rangeConversion);
|
||||
editor.focus();
|
||||
}
|
||||
|
||||
// TESTING PROPERTIES ////////////////////////////////////////////////////////////
|
||||
|
||||
public get resultsEditor(): QueryResultsEditor {
|
||||
return this._resultsEditor;
|
||||
}
|
||||
|
||||
public get sqlEditor(): TextResourceEditor {
|
||||
return this._sqlEditor;
|
||||
}
|
||||
|
||||
public get taskbar(): Taskbar {
|
||||
return this._taskbar;
|
||||
}
|
||||
|
||||
public get sash(): IFlexibleSash {
|
||||
return this._sash;
|
||||
}
|
||||
|
||||
public get resultsEditorContainer(): HTMLElement {
|
||||
return this._resultsEditorContainer;
|
||||
}
|
||||
|
||||
public get sqlEditorContainer(): HTMLElement {
|
||||
return this._sqlEditorContainer;
|
||||
}
|
||||
|
||||
public get taskbarContainer(): HTMLElement {
|
||||
return this._taskbarContainer;
|
||||
}
|
||||
|
||||
public get runQueryAction(): RunQueryAction {
|
||||
return this._runQueryAction;
|
||||
}
|
||||
|
||||
public get cancelQueryAction(): CancelQueryAction {
|
||||
return this._cancelQueryAction;
|
||||
}
|
||||
|
||||
public get changeConnectionAction(): ConnectDatabaseAction {
|
||||
return this._changeConnectionAction;
|
||||
}
|
||||
}
|
||||
87
src/sql/parts/query/editor/queryResultsEditor.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Builder, Dimension } from 'vs/base/browser/builder';
|
||||
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 { QueryResultsInput } from 'sql/parts/query/common/queryResultsInput';
|
||||
import { IQueryModelService } from 'sql/parts/query/execution/queryModel';
|
||||
import { IBootstrapService } from 'sql/services/bootstrap/bootstrapService';
|
||||
import { QueryComponentParams } from 'sql/services/bootstrap/bootstrapParams';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { QueryOutputModule } from 'sql/parts/query/views/queryOutput.module';
|
||||
import { QUERY_OUTPUT_SELECTOR } from 'sql/parts/query/views/queryOutput.component';
|
||||
|
||||
export const TextCompareEditorVisible = new RawContextKey<boolean>('textCompareEditorVisible', false);
|
||||
|
||||
/**
|
||||
* 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';
|
||||
|
||||
constructor(
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IQueryModelService private _queryModelService: IQueryModelService,
|
||||
@IBootstrapService private _bootstrapService: IBootstrapService
|
||||
) {
|
||||
super(QueryResultsEditor.ID, telemetryService, themeService);
|
||||
}
|
||||
|
||||
createEditor(parent: Builder): void {
|
||||
}
|
||||
|
||||
layout(dimension: Dimension): void {
|
||||
}
|
||||
|
||||
setInput(input: QueryResultsInput, options: EditorOptions): TPromise<void> {
|
||||
super.setInput(input, options);
|
||||
if (!input.hasBootstrapped) {
|
||||
this._bootstrapAngular();
|
||||
}
|
||||
return TPromise.as<void>(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the angular components and record for this input that we have done so
|
||||
*/
|
||||
private _bootstrapAngular(): void {
|
||||
let input = <QueryResultsInput>this.input;
|
||||
let uri = input.uri;
|
||||
|
||||
// Pass the correct DataService to the new angular component
|
||||
let dataService = this._queryModelService.getDataService(uri);
|
||||
if (!dataService) {
|
||||
throw new Error('DataService not found for URI: ' + uri);
|
||||
}
|
||||
|
||||
// Mark that we have bootstrapped
|
||||
input.setBootstrappedTrue();
|
||||
|
||||
// Get the bootstrap params and perform the bootstrap
|
||||
// Note: pass in input so on disposal this is cleaned up.
|
||||
// Otherwise many components will be left around and be subscribed
|
||||
// to events from the backing data service
|
||||
let params: QueryComponentParams = { dataService: dataService };
|
||||
this._bootstrapService.bootstrap(
|
||||
QueryOutputModule,
|
||||
this.getContainer().getHTMLElement(),
|
||||
QUERY_OUTPUT_SELECTOR,
|
||||
params,
|
||||
input);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
117
src/sql/parts/query/execution/keyboardQueryActions.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import nls = require('vs/nls');
|
||||
|
||||
import { Action} from 'vs/base/common/actions';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { QueryEditor } from 'sql/parts/query/editor/queryEditor';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
|
||||
/**
|
||||
* 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,
|
||||
@IWorkbenchEditorService private _editorService: IWorkbenchEditorService
|
||||
) {
|
||||
super(id, label);
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public run(): TPromise<void> {
|
||||
let editor = this._editorService.getActiveEditor();
|
||||
if (editor && editor instanceof QueryEditor) {
|
||||
let queryEditor: QueryEditor = editor;
|
||||
queryEditor.runQuery();
|
||||
}
|
||||
return TPromise.as(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,
|
||||
@IWorkbenchEditorService private _editorService: IWorkbenchEditorService
|
||||
) {
|
||||
super(id, label);
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public run(): TPromise<void> {
|
||||
let editor = this._editorService.getActiveEditor();
|
||||
if (editor && editor instanceof QueryEditor) {
|
||||
let queryEditor: QueryEditor = editor;
|
||||
queryEditor.runCurrentQuery();
|
||||
}
|
||||
return TPromise.as(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,
|
||||
@IWorkbenchEditorService private _editorService: IWorkbenchEditorService
|
||||
) {
|
||||
super(id, label);
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public run(): TPromise<void> {
|
||||
let editor = this._editorService.getActiveEditor();
|
||||
if (editor && editor instanceof QueryEditor) {
|
||||
let queryEditor: QueryEditor = editor;
|
||||
queryEditor.cancelQuery();
|
||||
}
|
||||
return TPromise.as(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,
|
||||
@IWorkbenchEditorService private _editorService: IWorkbenchEditorService
|
||||
) {
|
||||
super(id, label);
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public run(): TPromise<void> {
|
||||
let editor = this._editorService.getActiveEditor();
|
||||
if (editor && editor instanceof QueryEditor) {
|
||||
let queryEditor: QueryEditor = editor;
|
||||
queryEditor.rebuildIntelliSenseCache();
|
||||
}
|
||||
return TPromise.as(null);
|
||||
}
|
||||
}
|
||||
543
src/sql/parts/query/execution/queryActions.ts
Normal file
@@ -0,0 +1,543 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { Builder, $ } from 'vs/base/browser/builder';
|
||||
import { Dropdown } from 'sql/base/browser/ui/editableDropdown/dropdown';
|
||||
import { Action, IActionItem, IActionRunner } from 'vs/base/common/actions';
|
||||
import { EventEmitter } from 'vs/base/common/eventEmitter';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IMessageService, Severity } from 'vs/platform/message/common/message';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
|
||||
import { ISelectionData } from 'data';
|
||||
import {
|
||||
IConnectionManagementService,
|
||||
IConnectionParams,
|
||||
INewConnectionParams,
|
||||
ConnectionType,
|
||||
RunQueryOnConnectionMode
|
||||
} from 'sql/parts/connection/common/connectionManagement';
|
||||
import { QueryEditor } from 'sql/parts/query/editor/queryEditor';
|
||||
import { IQueryModelService } from 'sql/parts/query/execution/queryModel';
|
||||
|
||||
/**
|
||||
* Action class that query-based Actions will extend. This base class automatically handles activating and
|
||||
* deactivating the button when a SQL file is opened.
|
||||
*/
|
||||
export abstract class QueryTaskbarAction extends Action {
|
||||
|
||||
private _classes: string[];
|
||||
|
||||
constructor(
|
||||
protected _connectionManagementService: IConnectionManagementService,
|
||||
protected editor: QueryEditor,
|
||||
id: string,
|
||||
enabledClass: string
|
||||
) {
|
||||
super(id);
|
||||
this.enabled = true;
|
||||
this._setCssClass(enabledClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is executed when the button is clicked.
|
||||
*/
|
||||
public abstract run(): TPromise<void>;
|
||||
|
||||
protected updateCssClass(enabledClass: string): void {
|
||||
// set the class, useful on change of label or icon
|
||||
this._setCssClass(enabledClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the CSS classes combining the parent and child classes.
|
||||
* Public for testing only.
|
||||
*/
|
||||
private _setCssClass(enabledClass: string): void {
|
||||
this._classes = [];
|
||||
|
||||
if (enabledClass) {
|
||||
this._classes.push(enabledClass);
|
||||
}
|
||||
this.class = this._classes.join(' ');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URI of the given editor if it is not undefined and is connected.
|
||||
* Public for testing only.
|
||||
*/
|
||||
public isConnected(editor: QueryEditor): boolean {
|
||||
if (!editor || !editor.currentQueryInput) {
|
||||
return false;
|
||||
}
|
||||
return this._connectionManagementService.isConnected(editor.currentQueryInput.uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects the given editor to it's current URI.
|
||||
* Public for testing only.
|
||||
*/
|
||||
protected connectEditor(editor: QueryEditor, runQueryOnCompletion?: RunQueryOnConnectionMode, selection?: ISelectionData): void {
|
||||
let params: INewConnectionParams = {
|
||||
input: editor.currentQueryInput,
|
||||
connectionType: ConnectionType.editor,
|
||||
runQueryOnCompletion: runQueryOnCompletion ? runQueryOnCompletion : RunQueryOnConnectionMode.none,
|
||||
querySelection: selection
|
||||
};
|
||||
this._connectionManagementService.showConnectionDialog(params);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Action class that runs a query in the active SQL text document.
|
||||
*/
|
||||
export class RunQueryAction extends QueryTaskbarAction {
|
||||
|
||||
public static EnabledClass = 'start';
|
||||
public static ID = 'runQueryAction';
|
||||
|
||||
constructor(
|
||||
editor: QueryEditor,
|
||||
@IQueryModelService private _queryModelService: IQueryModelService,
|
||||
@IConnectionManagementService connectionManagementService: IConnectionManagementService
|
||||
) {
|
||||
super(connectionManagementService, editor, RunQueryAction.ID, RunQueryAction.EnabledClass);
|
||||
this.label = nls.localize('runQueryLabel', 'Run');
|
||||
}
|
||||
|
||||
public run(): TPromise<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 TPromise.as(null);
|
||||
}
|
||||
|
||||
public runCurrent(): TPromise<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 TPromise.as(null);
|
||||
}
|
||||
|
||||
public runQuery(editor: QueryEditor, runCurrentStatement: boolean = false) {
|
||||
if (!editor) {
|
||||
editor = this.editor;
|
||||
}
|
||||
|
||||
if (this.isConnected(editor)) {
|
||||
// if the selection isn't empty then execute the selection
|
||||
// otherwise, either run the statement or the script depending on parameter
|
||||
let selection: ISelectionData = editor.getSelection(false);
|
||||
if (runCurrentStatement && selection && this.isCursorPosition(selection)) {
|
||||
editor.currentQueryInput.runQueryStatement(selection);
|
||||
} else {
|
||||
// get the selection again this time with trimming
|
||||
selection = editor.getSelection();
|
||||
editor.currentQueryInput.runQuery(selection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private isCursorPosition(selection: ISelectionData) {
|
||||
return selection.startLine === selection.endLine
|
||||
&& selection.startColumn === selection.endColumn;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Action class that cancels the running query in the current SQL text document.
|
||||
*/
|
||||
export class CancelQueryAction extends QueryTaskbarAction {
|
||||
|
||||
public static EnabledClass = 'stop';
|
||||
public static ID = 'cancelQueryAction';
|
||||
|
||||
constructor(
|
||||
editor: QueryEditor,
|
||||
@IQueryModelService private _queryModelService: IQueryModelService,
|
||||
@IConnectionManagementService connectionManagementService: IConnectionManagementService
|
||||
) {
|
||||
super(connectionManagementService, editor, CancelQueryAction.ID, CancelQueryAction.EnabledClass);
|
||||
this.enabled = false;
|
||||
this.label = nls.localize('cancelQueryLabel', 'Cancel');
|
||||
}
|
||||
|
||||
public run(): TPromise<void> {
|
||||
if (this.isConnected(this.editor)) {
|
||||
this._queryModelService.cancelQuery(this.editor.currentQueryInput.uri);
|
||||
}
|
||||
return TPromise.as(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Action class that runs a query in the active SQL text document.
|
||||
*/
|
||||
export class EstimatedQueryPlanAction extends QueryTaskbarAction {
|
||||
|
||||
public static EnabledClass = 'estimatedQueryPlan';
|
||||
public static ID = 'estimatedQueryPlanAction';
|
||||
|
||||
constructor(
|
||||
editor: QueryEditor,
|
||||
@IQueryModelService private _queryModelService: IQueryModelService,
|
||||
@IConnectionManagementService connectionManagementService: IConnectionManagementService
|
||||
) {
|
||||
super(connectionManagementService, editor, EstimatedQueryPlanAction.ID, EstimatedQueryPlanAction.EnabledClass);
|
||||
this.label = nls.localize('estimatedQueryPlan', 'Explain');
|
||||
}
|
||||
|
||||
public run(): TPromise<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 TPromise.as(null);
|
||||
}
|
||||
|
||||
public runQuery(editor: QueryEditor) {
|
||||
if (!editor) {
|
||||
editor = this.editor;
|
||||
}
|
||||
|
||||
if (this.isConnected(editor)) {
|
||||
editor.currentQueryInput.runQuery(editor.getSelection(), {
|
||||
displayEstimatedQueryPlan: true
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Action class that disconnects the connection associated with the current query file.
|
||||
*/
|
||||
export class DisconnectDatabaseAction extends QueryTaskbarAction {
|
||||
|
||||
public static EnabledClass = 'disconnect';
|
||||
public static ID = 'disconnectDatabaseAction';
|
||||
|
||||
constructor(
|
||||
editor: QueryEditor,
|
||||
@IConnectionManagementService connectionManagementService: IConnectionManagementService
|
||||
) {
|
||||
super(connectionManagementService, editor, DisconnectDatabaseAction.ID, DisconnectDatabaseAction.EnabledClass);
|
||||
this.label = nls.localize('disconnectDatabaseLabel', 'Disconnect');
|
||||
}
|
||||
|
||||
public run(): TPromise<void> {
|
||||
// Call disconnectEditor regardless of the connection state and let the ConnectionManagementService
|
||||
// determine if we need to disconnect, cancel an in-progress conneciton, or do nothing
|
||||
this._connectionManagementService.disconnectEditor(this.editor.currentQueryInput);
|
||||
return TPromise.as(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Action class that launches a connection dialogue for the current query file
|
||||
*/
|
||||
export class ConnectDatabaseAction extends QueryTaskbarAction {
|
||||
|
||||
public static EnabledDefaultClass = 'connect';
|
||||
public static EnabledChangeClass = 'changeConnection';
|
||||
public static ID = 'connectDatabaseAction';
|
||||
|
||||
constructor(
|
||||
editor: QueryEditor,
|
||||
isChangeConnectionAction: boolean,
|
||||
@IConnectionManagementService connectionManagementService: IConnectionManagementService
|
||||
) {
|
||||
let label: string;
|
||||
let enabledClass: string;
|
||||
|
||||
if (isChangeConnectionAction) {
|
||||
enabledClass = ConnectDatabaseAction.EnabledChangeClass;
|
||||
label = nls.localize('changeConnectionDatabaseLabel', 'Change Connection');
|
||||
} else {
|
||||
enabledClass = ConnectDatabaseAction.EnabledDefaultClass;
|
||||
label = nls.localize('connectDatabaseLabel', 'Connect');
|
||||
}
|
||||
|
||||
super(connectionManagementService, editor, ConnectDatabaseAction.ID, enabledClass);
|
||||
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
public run(): TPromise<void> {
|
||||
this.connectEditor(this.editor);
|
||||
return TPromise.as(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Action class that either launches a connection dialogue for the current query file,
|
||||
* or disconnects the active connection
|
||||
*/
|
||||
export class ToggleConnectDatabaseAction extends QueryTaskbarAction {
|
||||
|
||||
public static ConnectClass = 'connect';
|
||||
public static DisconnectClass = 'disconnect';
|
||||
public static ID = 'toggleConnectDatabaseAction';
|
||||
|
||||
private _connected: boolean;
|
||||
private _connectLabel: string;
|
||||
private _disconnectLabel: string;
|
||||
constructor(
|
||||
editor: QueryEditor,
|
||||
isConnected: boolean,
|
||||
@IConnectionManagementService connectionManagementService: IConnectionManagementService
|
||||
) {
|
||||
let enabledClass: string;
|
||||
|
||||
super(connectionManagementService, editor, ToggleConnectDatabaseAction.ID, enabledClass);
|
||||
|
||||
this._connectLabel = nls.localize('connectDatabaseLabel', 'Connect');
|
||||
this._disconnectLabel = nls.localize('disconnectDatabaseLabel', 'Disconnect');
|
||||
|
||||
this.connected = isConnected;
|
||||
}
|
||||
|
||||
public get connected(): boolean {
|
||||
return this._connected;
|
||||
}
|
||||
|
||||
public set connected(value: boolean) {
|
||||
// intentionally always updating, since parent class handles skipping if values
|
||||
this._connected = value;
|
||||
this.updateLabelAndIcon();
|
||||
}
|
||||
|
||||
private updateLabelAndIcon(): void {
|
||||
if (this._connected) {
|
||||
// We are connected, so show option to disconnect
|
||||
this.label = this._disconnectLabel;
|
||||
this.updateCssClass(ToggleConnectDatabaseAction.DisconnectClass);
|
||||
} else {
|
||||
this.label = this._connectLabel;
|
||||
this.updateCssClass(ToggleConnectDatabaseAction.ConnectClass);
|
||||
}
|
||||
}
|
||||
|
||||
public run(): TPromise<void> {
|
||||
if (this.connected) {
|
||||
// Call disconnectEditor regardless of the connection state and let the ConnectionManagementService
|
||||
// determine if we need to disconnect, cancel an in-progress connection, or do nothing
|
||||
this._connectionManagementService.disconnectEditor(this.editor.currentQueryInput);
|
||||
} else {
|
||||
this.connectEditor(this.editor);
|
||||
}
|
||||
return TPromise.as(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Action class that is tied with ListDatabasesActionItem.
|
||||
*/
|
||||
export class ListDatabasesAction extends QueryTaskbarAction {
|
||||
|
||||
public static EnabledClass = '';
|
||||
public static ID = 'listDatabaseQueryAction';
|
||||
|
||||
constructor(
|
||||
editor: QueryEditor,
|
||||
@IConnectionManagementService connectionManagementService: IConnectionManagementService
|
||||
) {
|
||||
super(connectionManagementService, editor, ListDatabasesAction.ID, undefined);
|
||||
this.enabled = false;
|
||||
this.class = ListDatabasesAction.EnabledClass;
|
||||
}
|
||||
|
||||
public run(): TPromise<void> {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Action item that handles the dropdown (combobox) that lists the available databases.
|
||||
* Based off StartDebugActionItem.
|
||||
*/
|
||||
export class ListDatabasesActionItem extends EventEmitter implements IActionItem {
|
||||
public static ID = 'listDatabaseQueryActionItem';
|
||||
|
||||
public actionRunner: IActionRunner;
|
||||
private _toDispose: IDisposable[];
|
||||
private _context: any;
|
||||
private _currentDatabaseName: string;
|
||||
private _isConnected: boolean;
|
||||
private $databaseListDropdown: Builder;
|
||||
private _dropdown: Dropdown;
|
||||
|
||||
// CONSTRUCTOR /////////////////////////////////////////////////////////
|
||||
constructor(
|
||||
private _editor: QueryEditor,
|
||||
private _action: ListDatabasesAction,
|
||||
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
|
||||
@IMessageService private _messageService: IMessageService,
|
||||
@IContextViewService contextViewProvider: IContextViewService,
|
||||
@IThemeService themeService: IThemeService
|
||||
) {
|
||||
super();
|
||||
this._toDispose = [];
|
||||
this.$databaseListDropdown = $('.databaseListDropdown');
|
||||
this._dropdown = new Dropdown(this.$databaseListDropdown.getHTMLElement(), contextViewProvider, themeService, {
|
||||
strictSelection: true,
|
||||
placeholder: nls.localize("selectDatabase", "Select Database")
|
||||
});
|
||||
this._dropdown.onValueChange(s => this.databaseSelected(s));
|
||||
|
||||
// Register event handlers
|
||||
let self = this;
|
||||
this._toDispose.push(this._dropdown.onFocus(() => { self.onDropdownFocus(); }));
|
||||
this._toDispose.push(this._connectionManagementService.onConnectionChanged(params => { self.onConnectionChanged(params); }));
|
||||
}
|
||||
|
||||
// PUBLIC METHODS //////////////////////////////////////////////////////
|
||||
public render(container: HTMLElement): void {
|
||||
this.$databaseListDropdown.appendTo(container);
|
||||
}
|
||||
|
||||
public style(styles) {
|
||||
this._dropdown.style(styles);
|
||||
}
|
||||
|
||||
public setActionContext(context: any): void {
|
||||
this._context = context;
|
||||
}
|
||||
|
||||
public isEnabled(): boolean {
|
||||
return !!this._isConnected;
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
this._dropdown.focus();
|
||||
}
|
||||
|
||||
public blur(): void {
|
||||
this._dropdown.blur();
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._toDispose = dispose(this._toDispose);
|
||||
}
|
||||
|
||||
// EVENT HANDLERS FROM EDITOR //////////////////////////////////////////
|
||||
public onConnected(): void {
|
||||
let dbName = this.getCurrentDatabaseName();
|
||||
this.updateConnection(dbName);
|
||||
}
|
||||
|
||||
public onDisconnect(): void {
|
||||
this._isConnected = false;
|
||||
this._dropdown.enabled = false;
|
||||
this._currentDatabaseName = undefined;
|
||||
this._dropdown.value = '';
|
||||
}
|
||||
|
||||
// PRIVATE HELPERS /////////////////////////////////////////////////////
|
||||
private databaseSelected(dbName: string): void {
|
||||
let uri = this._editor.connectedUri;
|
||||
if (!uri) {
|
||||
return;
|
||||
}
|
||||
|
||||
let profile = this._connectionManagementService.getConnectionProfile(uri);
|
||||
if (!profile) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._connectionManagementService.changeDatabase(this._editor.uri, dbName)
|
||||
.then(
|
||||
result => {
|
||||
if (!result) {
|
||||
this.resetDatabaseName();
|
||||
this._messageService.show(Severity.Error, nls.localize('changeDatabase.failed', "Failed to change database"));
|
||||
}
|
||||
},
|
||||
error => {
|
||||
this.resetDatabaseName();
|
||||
this._messageService.show(Severity.Error, nls.localize('changeDatabase.failedWithError', "Failed to change database {0}", error));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private getCurrentDatabaseName() {
|
||||
let uri = this._editor.connectedUri;
|
||||
if (uri) {
|
||||
let profile = this._connectionManagementService.getConnectionProfile(uri);
|
||||
if (profile) {
|
||||
return profile.databaseName;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private resetDatabaseName() {
|
||||
this._dropdown.value = this.getCurrentDatabaseName();
|
||||
}
|
||||
|
||||
private onConnectionChanged(connParams: IConnectionParams): void {
|
||||
if (!connParams) {
|
||||
return;
|
||||
}
|
||||
|
||||
let uri = this._editor.connectedUri;
|
||||
if (uri !== connParams.connectionUri) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateConnection(connParams.connectionProfile.databaseName);
|
||||
}
|
||||
|
||||
private onDropdownFocus(): void {
|
||||
let self = this;
|
||||
|
||||
let uri = self._editor.connectedUri;
|
||||
if (!uri) {
|
||||
return;
|
||||
}
|
||||
|
||||
self._connectionManagementService.listDatabases(uri)
|
||||
.then(result => {
|
||||
if (result && result.databaseNames) {
|
||||
this._dropdown.values = result.databaseNames;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private updateConnection(databaseName: string) {
|
||||
this._isConnected = true;
|
||||
this._dropdown.enabled = true;
|
||||
this._currentDatabaseName = databaseName;
|
||||
this._dropdown.value = databaseName;
|
||||
}
|
||||
|
||||
// TESTING PROPERTIES //////////////////////////////////////////////////
|
||||
public get currentDatabaseName(): string {
|
||||
return this._currentDatabaseName;
|
||||
}
|
||||
|
||||
}
|
||||
71
src/sql/parts/query/execution/queryModel.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import QueryRunner from 'sql/parts/query/execution/queryRunner';
|
||||
import { DataService } from 'sql/parts/grid/services/dataService';
|
||||
import { ISlickRange } from 'angular2-slickgrid';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import Event from 'vs/base/common/event';
|
||||
import { QueryInput } from 'sql/parts/query/common/queryInput';
|
||||
import {
|
||||
ISelectionData,
|
||||
ResultSetSubset,
|
||||
EditUpdateCellResult,
|
||||
EditSessionReadyParams,
|
||||
EditSubsetResult,
|
||||
EditCreateRowResult,
|
||||
EditRevertCellResult,
|
||||
ExecutionPlanOptions
|
||||
} from 'data';
|
||||
|
||||
export const SERVICE_ID = 'queryModelService';
|
||||
|
||||
export const IQueryModelService = createDecorator<IQueryModelService>(SERVICE_ID);
|
||||
|
||||
/**
|
||||
* Interface for the logic of handling running queries and grid interactions for all URIs.
|
||||
*/
|
||||
export interface IQueryModelService {
|
||||
_serviceBrand: any;
|
||||
|
||||
getConfig(): Promise<{ [key: string]: any }>;
|
||||
getShortcuts(): Promise<any>;
|
||||
getQueryRows(uri: string, rowStart: number, numberOfRows: number, batchId: number, resultId: number): Thenable<ResultSetSubset>;
|
||||
runQuery(uri: string, selection: ISelectionData, title: string, queryInput: QueryInput, runOptions?: ExecutionPlanOptions): void;
|
||||
runQueryStatement(uri: string, selection: ISelectionData, title: string, queryInput: QueryInput): void;
|
||||
cancelQuery(input: QueryRunner | string): void;
|
||||
disposeQuery(uri: string): Thenable<void>;
|
||||
isRunningQuery(uri: string): boolean;
|
||||
|
||||
getDataService(uri: string): DataService;
|
||||
refreshResultsets(uri: string): void;
|
||||
sendGridContentEvent(uri: string, eventName: string): void;
|
||||
resizeResultsets(uri: string): void;
|
||||
onAngularLoaded(uri: string): void;
|
||||
|
||||
copyResults(uri: string, selection: ISlickRange[], batchId: number, resultId: number, includeHeaders?: boolean): void;
|
||||
setEditorSelection(uri: string, index: number): void;
|
||||
showWarning(uri: string, message: string): void;
|
||||
showError(uri: string, message: string): void;
|
||||
showCommitError(error: string): void;
|
||||
|
||||
onRunQueryStart: Event<string>;
|
||||
onRunQueryComplete: Event<string>;
|
||||
|
||||
|
||||
// Edit Data Functions
|
||||
initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number): void;
|
||||
disposeEdit(ownerUri: string): Thenable<void>;
|
||||
updateCell(ownerUri: string, rowId: number, columnId: number, newValue: string): Thenable<EditUpdateCellResult>;
|
||||
commitEdit(ownerUri): Thenable<void>;
|
||||
createRow(ownerUri: string): Thenable<EditCreateRowResult>;
|
||||
deleteRow(ownerUri: string, rowId: number): Thenable<void>;
|
||||
revertCell(ownerUri: string, rowId: number, columnId: number): Thenable<EditRevertCellResult>;
|
||||
revertRow(ownerUri: string, rowId: number): Thenable<void>;
|
||||
getEditRows(ownerUri: string, rowStart: number, numberOfRows: number): Thenable<EditSubsetResult>;
|
||||
|
||||
// Edit Data Callbacks
|
||||
onEditSessionReady: Event<EditSessionReadyParams>;
|
||||
}
|
||||
529
src/sql/parts/query/execution/queryModelService.ts
Normal file
@@ -0,0 +1,529 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as GridContentEvents from 'sql/parts/grid/common/gridContentEvents';
|
||||
import * as LocalizedConstants from 'sql/parts/query/common/localizedConstants';
|
||||
import QueryRunner from 'sql/parts/query/execution/queryRunner';
|
||||
import { DataService } from 'sql/parts/grid/services/dataService';
|
||||
import { IQueryModelService } from 'sql/parts/query/execution/queryModel';
|
||||
import { QueryInput } from 'sql/parts/query/common/queryInput';
|
||||
import { QueryStatusbarItem } from 'sql/parts/query/execution/queryStatus';
|
||||
import { SqlFlavorStatusbarItem } from 'sql/parts/query/common/flavorStatus';
|
||||
|
||||
import {
|
||||
ISelectionData, ResultSetSubset, EditSubsetResult, ExecutionPlanOptions,
|
||||
EditUpdateCellResult, EditSessionReadyParams, EditCreateRowResult, EditRevertCellResult
|
||||
} from 'data';
|
||||
import { ISlickRange } from 'angular2-slickgrid';
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import * as statusbar from 'vs/workbench/browser/parts/statusbar/statusbar';
|
||||
import * as platform from 'vs/platform/registry/common/platform';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IMessageService, Severity } from 'vs/platform/message/common/message';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
|
||||
interface QueryEvent {
|
||||
type: string;
|
||||
data: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds information about the state of a query runner
|
||||
*/
|
||||
class QueryInfo {
|
||||
public queryRunner: QueryRunner;
|
||||
public dataService: DataService;
|
||||
public queryEventQueue: QueryEvent[];
|
||||
public selection: Array<ISelectionData>;
|
||||
public queryInput: QueryInput;
|
||||
|
||||
// Notes if the angular components have obtained the DataService. If not, all messages sent
|
||||
// via the data service will be lost.
|
||||
public dataServiceReady: boolean;
|
||||
|
||||
constructor() {
|
||||
this.dataServiceReady = false;
|
||||
this.queryEventQueue = [];
|
||||
this.selection = [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles running queries and grid interactions for all URIs. Interacts with each URI's results grid via a DataService instance
|
||||
*/
|
||||
export class QueryModelService implements IQueryModelService {
|
||||
_serviceBrand: any;
|
||||
|
||||
// MEMBER VARIABLES ////////////////////////////////////////////////////
|
||||
private _queryInfoMap: Map<string, QueryInfo>;
|
||||
private _onRunQueryStart: Emitter<string>;
|
||||
private _onRunQueryComplete: Emitter<string>;
|
||||
private _onEditSessionReady: Emitter<EditSessionReadyParams>;
|
||||
|
||||
// EVENTS /////////////////////////////////////////////////////////////
|
||||
public get onRunQueryStart(): Event<string> { return this._onRunQueryStart.event; }
|
||||
public get onRunQueryComplete(): Event<string> { return this._onRunQueryComplete.event; }
|
||||
public get onEditSessionReady(): Event<EditSessionReadyParams> { return this._onEditSessionReady.event; }
|
||||
|
||||
// CONSTRUCTOR /////////////////////////////////////////////////////////
|
||||
constructor(
|
||||
@IInstantiationService private _instantiationService: IInstantiationService,
|
||||
@IMessageService private _messageService: IMessageService
|
||||
) {
|
||||
this._queryInfoMap = new Map<string, QueryInfo>();
|
||||
this._onRunQueryStart = new Emitter<string>();
|
||||
this._onRunQueryComplete = new Emitter<string>();
|
||||
this._onEditSessionReady = new Emitter<EditSessionReadyParams>();
|
||||
|
||||
// Register Statusbar items
|
||||
(<statusbar.IStatusbarRegistry>platform.Registry.as(statusbar.Extensions.Statusbar)).registerStatusbarItem(new statusbar.StatusbarItemDescriptor(
|
||||
QueryStatusbarItem,
|
||||
statusbar.StatusbarAlignment.RIGHT,
|
||||
100 /* High Priority */
|
||||
));
|
||||
|
||||
// (<statusbar.IStatusbarRegistry>platform.Registry.as(statusbar.Extensions.Statusbar)).registerStatusbarItem(new statusbar.StatusbarItemDescriptor(
|
||||
// SqlFlavorStatusbarItem,
|
||||
// statusbar.StatusbarAlignment.RIGHT,
|
||||
// 90 /* Should appear to the right of the SQL editor status */
|
||||
// ));
|
||||
}
|
||||
|
||||
// IQUERYMODEL /////////////////////////////////////////////////////////
|
||||
public getDataService(uri: string): DataService {
|
||||
let dataService = this._getQueryInfo(uri).dataService;
|
||||
if (!dataService) {
|
||||
throw new Error('Could not find data service for uri: ' + uri);
|
||||
}
|
||||
|
||||
return dataService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Force all grids to re-render. This is needed to re-render the grids when switching
|
||||
* between different URIs.
|
||||
*/
|
||||
public refreshResultsets(uri: string): void {
|
||||
this._fireGridContentEvent(uri, GridContentEvents.RefreshContents);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resize the grid UI to fit the current screen size.
|
||||
*/
|
||||
public resizeResultsets(uri: string): void {
|
||||
this._fireGridContentEvent(uri, GridContentEvents.ResizeContents);
|
||||
}
|
||||
|
||||
public sendGridContentEvent(uri: string, eventName: string): void {
|
||||
this._fireGridContentEvent(uri, eventName);
|
||||
}
|
||||
|
||||
/**
|
||||
* To be called by an angular component's DataService when the component has finished loading.
|
||||
* Sends all previously enqueued query events to the DataService and signals to stop enqueuing
|
||||
* any further events. This prevents QueryEvents from getting lost if they are sent before
|
||||
* angular is listening for them.
|
||||
*/
|
||||
public onAngularLoaded(uri: string) {
|
||||
let info = this._getQueryInfo(uri);
|
||||
info.dataServiceReady = true;
|
||||
this._sendQueuedEvents(uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get more data rows from the current resultSets from the service layer
|
||||
*/
|
||||
public getQueryRows(uri: string, rowStart: number, numberOfRows: number, batchId: number, resultId: number): Thenable<ResultSetSubset> {
|
||||
return this._getQueryInfo(uri).queryRunner.getQueryRows(rowStart, numberOfRows, batchId, resultId).then(results => {
|
||||
return results.resultSubset;
|
||||
});
|
||||
}
|
||||
|
||||
public getEditRows(uri: string, rowStart: number, numberOfRows: number): Thenable<EditSubsetResult> {
|
||||
return this._queryInfoMap.get(uri).queryRunner.getEditRows(rowStart, numberOfRows).then(results => {
|
||||
return results;
|
||||
});
|
||||
}
|
||||
|
||||
public getConfig(): Promise<{ [key: string]: any }> {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public getShortcuts(): Promise<any> {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public copyResults(uri: string, selection: ISlickRange[], batchId: number, resultId: number, includeHeaders?: boolean): void {
|
||||
this._queryInfoMap.get(uri).queryRunner.copyResults(selection, batchId, resultId, includeHeaders);
|
||||
}
|
||||
|
||||
public setEditorSelection(uri: string, index: number): void {
|
||||
let info: QueryInfo = this._queryInfoMap.get(uri);
|
||||
if (info && info.queryInput) {
|
||||
info.queryInput.updateSelection(info.selection[index]);
|
||||
}
|
||||
}
|
||||
|
||||
public showWarning(uri: string, message: string): void {
|
||||
}
|
||||
|
||||
public showError(uri: string, message: string): void {
|
||||
}
|
||||
|
||||
public showCommitError(error: string): void {
|
||||
this._messageService.show(Severity.Error, nls.localize('commitEditFailed', 'Commit row failed: ') + error);
|
||||
}
|
||||
|
||||
public isRunningQuery(uri: string): boolean {
|
||||
return !this._queryInfoMap.has(uri)
|
||||
? false
|
||||
: this._getQueryInfo(uri).queryRunner.isExecuting;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a query for the given URI with the given text selection
|
||||
*/
|
||||
public runQuery(uri: string, selection: ISelectionData,
|
||||
title: string, queryInput: QueryInput, runOptions?: ExecutionPlanOptions): void {
|
||||
this.doRunQuery(uri, selection, title, queryInput, false, runOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the current SQL statement for the given URI
|
||||
*/
|
||||
public runQueryStatement(uri: string, selection: ISelectionData,
|
||||
title: string, queryInput: QueryInput): void {
|
||||
this.doRunQuery(uri, selection, title, queryInput, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run Query implementation
|
||||
*/
|
||||
private doRunQuery(uri: string, selection: ISelectionData,
|
||||
title: string, queryInput: QueryInput,
|
||||
runCurrentStatement: boolean, runOptions?: ExecutionPlanOptions): void {
|
||||
// Reuse existing query runner if it exists
|
||||
let queryRunner: QueryRunner;
|
||||
let info: QueryInfo;
|
||||
|
||||
if (this._queryInfoMap.has(uri)) {
|
||||
info = this._getQueryInfo(uri);
|
||||
let existingRunner: QueryRunner = info.queryRunner;
|
||||
|
||||
// If the query is already in progress, don't attempt to send it
|
||||
if (existingRunner.isExecuting) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the query is not in progress, we can reuse the query runner
|
||||
queryRunner = existingRunner;
|
||||
info.selection = [];
|
||||
} else {
|
||||
// We do not have a query runner for this editor, so create a new one
|
||||
// and map it to the results uri
|
||||
info = new QueryInfo();
|
||||
queryRunner = this.initQueryRunner(uri, title, info);
|
||||
}
|
||||
|
||||
this._getQueryInfo(uri).queryInput = queryInput;
|
||||
|
||||
if (runCurrentStatement) {
|
||||
queryRunner.runQueryStatement(selection);
|
||||
} else {
|
||||
queryRunner.runQuery(selection, runOptions);
|
||||
}
|
||||
}
|
||||
|
||||
private initQueryRunner(uri: string, title: string, info: QueryInfo): QueryRunner {
|
||||
let queryRunner: QueryRunner;
|
||||
queryRunner = this._instantiationService.createInstance(QueryRunner, uri, title);
|
||||
queryRunner.eventEmitter.on('resultSet', (resultSet) => {
|
||||
this._fireQueryEvent(uri, 'resultSet', resultSet);
|
||||
});
|
||||
queryRunner.eventEmitter.on('batchStart', (batch) => {
|
||||
let link = undefined;
|
||||
if (batch.selection) {
|
||||
link = {
|
||||
text: strings.format(LocalizedConstants.runQueryBatchStartLine, batch.selection.startLine + 1)
|
||||
};
|
||||
}
|
||||
let message = {
|
||||
message: LocalizedConstants.runQueryBatchStartMessage,
|
||||
batchId: batch.id,
|
||||
isError: false,
|
||||
time: new Date().toLocaleTimeString(),
|
||||
link: link
|
||||
};
|
||||
this._fireQueryEvent(uri, 'message', message);
|
||||
info.selection.push(this._validateSelection(batch.selection));
|
||||
});
|
||||
queryRunner.eventEmitter.on('message', (message) => {
|
||||
this._fireQueryEvent(uri, 'message', message);
|
||||
});
|
||||
queryRunner.eventEmitter.on('complete', (totalMilliseconds) => {
|
||||
this._onRunQueryComplete.fire(uri);
|
||||
this._fireQueryEvent(uri, 'complete', totalMilliseconds);
|
||||
});
|
||||
queryRunner.eventEmitter.on('start', () => {
|
||||
this._onRunQueryStart.fire(uri);
|
||||
this._fireQueryEvent(uri, 'start');
|
||||
});
|
||||
|
||||
info = new QueryInfo();
|
||||
info.queryRunner = queryRunner;
|
||||
info.dataService = this._instantiationService.createInstance(DataService, uri);
|
||||
this._queryInfoMap.set(uri, info);
|
||||
return queryRunner;
|
||||
}
|
||||
|
||||
public cancelQuery(input: QueryRunner | string): void {
|
||||
let queryRunner: QueryRunner;
|
||||
|
||||
if (typeof input === 'string') {
|
||||
if (this._queryInfoMap.has(input)) {
|
||||
queryRunner = this._getQueryInfo(input).queryRunner;
|
||||
}
|
||||
} else {
|
||||
queryRunner = input;
|
||||
}
|
||||
|
||||
if (queryRunner === undefined || !queryRunner.isExecuting) {
|
||||
// TODO: Cannot cancel query as no query is running.
|
||||
return;
|
||||
}
|
||||
|
||||
// Switch the spinner to canceling, which will be reset when the query execute sends back its completed event
|
||||
// TODO indicate on the status bar that the query is being canceled
|
||||
|
||||
// Cancel the query
|
||||
queryRunner.cancelQuery().then(success => undefined, error => {
|
||||
// On error, show error message and notify that the query is complete so that buttons and other status indicators
|
||||
// can be correct
|
||||
this._messageService.show(Severity.Error, strings.format(LocalizedConstants.msgCancelQueryFailed, error));
|
||||
this._fireQueryEvent(queryRunner.uri, 'complete', 0);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public disposeQuery(ownerUri: string): Thenable<void> {
|
||||
// Get existing query runner
|
||||
let queryRunner = this._getQueryRunner(ownerUri);
|
||||
if (queryRunner) {
|
||||
return queryRunner.dispose();
|
||||
}
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
// EDIT DATA METHODS /////////////////////////////////////////////////////
|
||||
initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number): void {
|
||||
// Reuse existing query runner if it exists
|
||||
let queryRunner: QueryRunner;
|
||||
let info: QueryInfo;
|
||||
|
||||
if (this._queryInfoMap.has(ownerUri)) {
|
||||
info = this._getQueryInfo(ownerUri);
|
||||
let existingRunner: QueryRunner = info.queryRunner;
|
||||
|
||||
// If the initialization is already in progress
|
||||
if (existingRunner.isExecuting) {
|
||||
return;
|
||||
}
|
||||
|
||||
queryRunner = existingRunner;
|
||||
} else {
|
||||
// We do not have a query runner for this editor, so create a new one
|
||||
// and map it to the results uri
|
||||
queryRunner = this._instantiationService.createInstance(QueryRunner, ownerUri, ownerUri);
|
||||
queryRunner.eventEmitter.on('resultSet', (resultSet) => {
|
||||
this._fireQueryEvent(ownerUri, 'resultSet', resultSet);
|
||||
});
|
||||
queryRunner.eventEmitter.on('batchStart', (batch) => {
|
||||
let link = undefined;
|
||||
if (batch.selection) {
|
||||
link = {
|
||||
text: strings.format(LocalizedConstants.runQueryBatchStartLine, batch.selection.startLine + 1),
|
||||
uri: ''
|
||||
};
|
||||
}
|
||||
let message = {
|
||||
message: LocalizedConstants.runQueryBatchStartMessage,
|
||||
batchId: undefined,
|
||||
isError: false,
|
||||
time: new Date().toLocaleTimeString(),
|
||||
link: link
|
||||
};
|
||||
this._fireQueryEvent(ownerUri, 'message', message);
|
||||
});
|
||||
queryRunner.eventEmitter.on('message', (message) => {
|
||||
this._fireQueryEvent(ownerUri, 'message', message);
|
||||
});
|
||||
queryRunner.eventEmitter.on('complete', (totalMilliseconds) => {
|
||||
this._onRunQueryComplete.fire(ownerUri);
|
||||
this._fireQueryEvent(ownerUri, 'complete', totalMilliseconds);
|
||||
});
|
||||
queryRunner.eventEmitter.on('start', () => {
|
||||
this._onRunQueryStart.fire(ownerUri);
|
||||
this._fireQueryEvent(ownerUri, 'start');
|
||||
});
|
||||
queryRunner.eventEmitter.on('editSessionReady', (ownerUri, success, message) => {
|
||||
this._onEditSessionReady.fire({ ownerUri: ownerUri, success: success, message: message });
|
||||
this._fireQueryEvent(ownerUri, 'editSessionReady');
|
||||
});
|
||||
|
||||
info = new QueryInfo();
|
||||
info.queryRunner = queryRunner;
|
||||
info.dataService = this._instantiationService.createInstance(DataService, ownerUri);
|
||||
this._queryInfoMap.set(ownerUri, info);
|
||||
}
|
||||
|
||||
queryRunner.initializeEdit(ownerUri, schemaName, objectName, objectType, rowLimit);
|
||||
}
|
||||
|
||||
public cancelInitializeEdit(input: QueryRunner | string): void {
|
||||
// TODO: Implement query cancellation service
|
||||
}
|
||||
|
||||
public disposeEdit(ownerUri: string): Thenable<void> {
|
||||
// Get existing query runner
|
||||
let queryRunner = this._getQueryRunner(ownerUri);
|
||||
if (queryRunner) {
|
||||
return queryRunner.disposeEdit(ownerUri);
|
||||
}
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
public updateCell(ownerUri: string, rowId: number, columnId: number, newValue: string): Thenable<EditUpdateCellResult> {
|
||||
// Get existing query runner
|
||||
let queryRunner = this._getQueryRunner(ownerUri);
|
||||
if (queryRunner) {
|
||||
return queryRunner.updateCell(ownerUri, rowId, columnId, newValue).then((result) => result, error => {
|
||||
this._messageService.show(Severity.Error, nls.localize('updateCellFailed', 'Update cell failed: ') + error.message);
|
||||
return Promise.reject(error);
|
||||
});
|
||||
}
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
public commitEdit(ownerUri): Thenable<void> {
|
||||
// Get existing query runner
|
||||
let queryRunner = this._getQueryRunner(ownerUri);
|
||||
if (queryRunner) {
|
||||
return queryRunner.commitEdit(ownerUri).then(() => { }, error => {
|
||||
this._messageService.show(Severity.Error, nls.localize('commitEditFailed', 'Commit row failed: ') + error.message);
|
||||
return Promise.reject(error);
|
||||
});
|
||||
}
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
public createRow(ownerUri: string): Thenable<EditCreateRowResult> {
|
||||
// Get existing query runner
|
||||
let queryRunner = this._getQueryRunner(ownerUri);
|
||||
if (queryRunner) {
|
||||
return queryRunner.createRow(ownerUri);
|
||||
}
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
public deleteRow(ownerUri: string, rowId: number): Thenable<void> {
|
||||
// Get existing query runner
|
||||
let queryRunner = this._getQueryRunner(ownerUri);
|
||||
if (queryRunner) {
|
||||
return queryRunner.deleteRow(ownerUri, rowId);
|
||||
}
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
public revertCell(ownerUri: string, rowId: number, columnId: number): Thenable<EditRevertCellResult> {
|
||||
// Get existing query runner
|
||||
let queryRunner = this._getQueryRunner(ownerUri);
|
||||
if (queryRunner) {
|
||||
return queryRunner.revertCell(ownerUri, rowId, columnId);
|
||||
}
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
public revertRow(ownerUri: string, rowId: number): Thenable<void> {
|
||||
// Get existing query runner
|
||||
let queryRunner = this._getQueryRunner(ownerUri);
|
||||
if (queryRunner) {
|
||||
return queryRunner.revertRow(ownerUri, rowId);
|
||||
}
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
// PRIVATE METHODS //////////////////////////////////////////////////////
|
||||
|
||||
private _getQueryRunner(ownerUri): QueryRunner {
|
||||
let queryRunner: QueryRunner = undefined;
|
||||
if (this._queryInfoMap.has(ownerUri)) {
|
||||
let existingRunner = this._getQueryInfo(ownerUri).queryRunner;
|
||||
// If the query is not already executing then set it up
|
||||
if (!existingRunner.isExecuting) {
|
||||
queryRunner = this._getQueryInfo(ownerUri).queryRunner;
|
||||
}
|
||||
}
|
||||
// return undefined if not found or is already executing
|
||||
return queryRunner;
|
||||
}
|
||||
|
||||
private _fireGridContentEvent(uri: string, type: string): void {
|
||||
let info: QueryInfo = this._getQueryInfo(uri);
|
||||
|
||||
if (info && info.dataServiceReady) {
|
||||
let service: DataService = this.getDataService(uri);
|
||||
if (service) {
|
||||
// There is no need to queue up these events like there is for the query events because
|
||||
// if the DataService is not yet ready there will be no grid content to update
|
||||
service.gridContentObserver.next(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _fireQueryEvent(uri: string, type: string, data?: any) {
|
||||
let info: QueryInfo = this._getQueryInfo(uri);
|
||||
|
||||
if (info.dataServiceReady) {
|
||||
let service: DataService = this.getDataService(uri);
|
||||
service.queryEventObserver.next({
|
||||
type: type,
|
||||
data: data
|
||||
});
|
||||
} else {
|
||||
let queueItem: QueryEvent = { type: type, data: data };
|
||||
info.queryEventQueue.push(queueItem);
|
||||
}
|
||||
}
|
||||
|
||||
private _sendQueuedEvents(uri: string): void {
|
||||
let info: QueryInfo = this._getQueryInfo(uri);
|
||||
while (info.queryEventQueue.length > 0) {
|
||||
let event: QueryEvent = info.queryEventQueue.shift();
|
||||
this._fireQueryEvent(uri, event.type, event.data);
|
||||
}
|
||||
}
|
||||
|
||||
private _getQueryInfo(uri: string): QueryInfo {
|
||||
return this._queryInfoMap.get(uri);
|
||||
}
|
||||
|
||||
// TODO remove this funciton and its usages when #821 in vscode-mssql is fixed and
|
||||
// the SqlToolsService version is updated in this repo - coquagli 4/19/2017
|
||||
private _validateSelection(selection: ISelectionData): ISelectionData {
|
||||
if (!selection) {
|
||||
selection = <ISelectionData>{};
|
||||
}
|
||||
selection.endColumn = selection ? Math.max(0, selection.endColumn) : 0;
|
||||
selection.endLine = selection ? Math.max(0, selection.endLine) : 0;
|
||||
selection.startColumn = selection ? Math.max(0, selection.startColumn) : 0;
|
||||
selection.startLine = selection ? Math.max(0, selection.startLine) : 0;
|
||||
return selection;
|
||||
}
|
||||
}
|
||||
525
src/sql/parts/query/execution/queryRunner.ts
Normal file
@@ -0,0 +1,525 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import {
|
||||
BatchSummary,
|
||||
QueryCancelResult,
|
||||
QueryExecuteBatchNotificationParams,
|
||||
QueryExecuteCompleteNotificationResult,
|
||||
QueryExecuteResultSetCompleteNotificationParams,
|
||||
QueryExecuteMessageParams,
|
||||
QueryExecuteSubsetParams, QueryExecuteSubsetResult,
|
||||
EditSubsetParams, EditSubsetResult, EditUpdateCellResult, EditCreateRowResult,
|
||||
EditRevertCellResult, ISelectionData, IResultMessage, ExecutionPlanOptions
|
||||
} from 'data';
|
||||
|
||||
import { EventEmitter } from 'events';
|
||||
import * as Constants from 'sql/parts/query/common/constants';
|
||||
import * as WorkbenchUtils from 'sql/workbench/common/sqlWorkbenchUtils';
|
||||
import { IQueryManagementService } from 'sql/parts/query/common/queryManagement';
|
||||
import { ISlickRange } from 'angular2-slickgrid';
|
||||
import * as Utils from 'sql/parts/connection/common/utils';
|
||||
import { error as consoleError } from 'sql/base/common/log';
|
||||
|
||||
import { IMessageService } from 'vs/platform/message/common/message';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
|
||||
import * as nls from 'vs/nls';
|
||||
|
||||
import * as os from 'os';
|
||||
|
||||
/*
|
||||
* Query Runner class which handles running a query, reports the results to the content manager,
|
||||
* and handles getting more rows from the service layer and disposing when the content is closed.
|
||||
*/
|
||||
export default class QueryRunner {
|
||||
// MEMBER VARIABLES ////////////////////////////////////////////////////
|
||||
private _batchSets: BatchSummary[] = [];
|
||||
private _isExecuting: boolean;
|
||||
private _uri: string;
|
||||
private _title: string;
|
||||
private _resultLineOffset: number;
|
||||
private _totalElapsedMilliseconds: number;
|
||||
private _hasCompleted: boolean;
|
||||
public eventEmitter: EventEmitter = new EventEmitter();
|
||||
|
||||
// CONSTRUCTOR /////////////////////////////////////////////////////////
|
||||
|
||||
constructor(private _ownerUri: string,
|
||||
private _editorTitle: string,
|
||||
@IQueryManagementService private _queryManagementService: IQueryManagementService,
|
||||
@IMessageService private _messageService: IMessageService,
|
||||
@IWorkspaceConfigurationService private _workspaceConfigurationService: IWorkspaceConfigurationService
|
||||
) {
|
||||
|
||||
// Store the state
|
||||
this._uri = _ownerUri;
|
||||
this._title = _editorTitle;
|
||||
this._isExecuting = false;
|
||||
this._totalElapsedMilliseconds = 0;
|
||||
this._hasCompleted = false;
|
||||
}
|
||||
|
||||
// PROPERTIES //////////////////////////////////////////////////////////
|
||||
|
||||
get uri(): string {
|
||||
return this._uri;
|
||||
}
|
||||
|
||||
set uri(uri: string) {
|
||||
this._uri = uri;
|
||||
}
|
||||
|
||||
get title(): string {
|
||||
return this._title;
|
||||
}
|
||||
|
||||
set title(title: string) {
|
||||
this._title = title;
|
||||
}
|
||||
|
||||
get batchSets(): BatchSummary[] {
|
||||
return this._batchSets;
|
||||
}
|
||||
|
||||
set batchSets(batchSets: BatchSummary[]) {
|
||||
this._batchSets = batchSets;
|
||||
}
|
||||
|
||||
get isExecuting(): boolean {
|
||||
return this._isExecuting;
|
||||
}
|
||||
|
||||
get hasCompleted(): boolean {
|
||||
return this._hasCompleted;
|
||||
}
|
||||
|
||||
// PUBLIC METHODS ======================================================
|
||||
|
||||
/**
|
||||
* Cancels the running query, if there is one
|
||||
*/
|
||||
public cancelQuery(): Thenable<QueryCancelResult> {
|
||||
return this._queryManagementService.cancelQuery(this._uri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the query with the provided query
|
||||
* @param input Query string to execute
|
||||
*/
|
||||
public runQuery(input: string, runOptions?: ExecutionPlanOptions): Thenable<void>;
|
||||
/**
|
||||
* Runs the query by pulling the query from the document using the provided selection data
|
||||
* @param input selection data
|
||||
*/
|
||||
public runQuery(input: ISelectionData, runOptions?: ExecutionPlanOptions): Thenable<void>;
|
||||
public runQuery(input, runOptions?: ExecutionPlanOptions): Thenable<void> {
|
||||
return this.doRunQuery(input, false, runOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the current SQL statement by pulling the query from the document using the provided selection data
|
||||
* @param input selection data
|
||||
*/
|
||||
public runQueryStatement(input: ISelectionData): Thenable<void> {
|
||||
return this.doRunQuery(input, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation that runs the query with the provided query
|
||||
* @param input Query string to execute
|
||||
*/
|
||||
private doRunQuery(input: string, runCurrentStatement: boolean, runOptions?: ExecutionPlanOptions): Thenable<void>;
|
||||
private doRunQuery(input: ISelectionData, runCurrentStatement: boolean, runOptions?: ExecutionPlanOptions): Thenable<void>;
|
||||
private doRunQuery(input, runCurrentStatement: boolean, runOptions?: ExecutionPlanOptions): Thenable<void> {
|
||||
let ownerUri = this._uri;
|
||||
this.batchSets = [];
|
||||
this._hasCompleted = false;
|
||||
if (typeof input === 'object' || input === undefined) {
|
||||
// Update internal state to show that we're executing the query
|
||||
this._resultLineOffset = input ? input.startLine : 0;
|
||||
this._isExecuting = true;
|
||||
this._totalElapsedMilliseconds = 0;
|
||||
// TODO issue #228 add statusview callbacks here
|
||||
|
||||
// Send the request to execute the query
|
||||
return runCurrentStatement
|
||||
? this._queryManagementService.runQueryStatement(ownerUri, input.startLine, input.startColumn).then(this.handleSuccessRunQueryResult(), this.handleFailureRunQueryResult())
|
||||
: this._queryManagementService.runQuery(ownerUri, input, runOptions).then(this.handleSuccessRunQueryResult(), this.handleFailureRunQueryResult());
|
||||
} else if (typeof input === 'string') {
|
||||
// Update internal state to show that we're executing the query
|
||||
this._isExecuting = true;
|
||||
this._totalElapsedMilliseconds = 0;
|
||||
|
||||
return this._queryManagementService.runQueryString(ownerUri, input).then(this.handleSuccessRunQueryResult(), this.handleFailureRunQueryResult());
|
||||
} else {
|
||||
return Promise.reject('Unknown input');
|
||||
}
|
||||
}
|
||||
|
||||
private handleSuccessRunQueryResult() {
|
||||
let self = this;
|
||||
return () => {
|
||||
// The query has started, so lets fire up the result pane
|
||||
self.eventEmitter.emit('start');
|
||||
self._queryManagementService.registerRunner(self, self._uri);
|
||||
};
|
||||
}
|
||||
|
||||
private handleFailureRunQueryResult() {
|
||||
let self = this;
|
||||
return (error: any) => {
|
||||
// Attempting to launch the query failed, show the error message
|
||||
|
||||
// TODO issue #228 add statusview callbacks here
|
||||
self._isExecuting = false;
|
||||
|
||||
self._messageService.show(Severity.Error, nls.localize('query.ExecutionFailedError', 'Execution failed: {0}', error));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a QueryComplete from the service layer
|
||||
*/
|
||||
public handleQueryComplete(result: QueryExecuteCompleteNotificationResult): void {
|
||||
|
||||
// Store the batch sets we got back as a source of "truth"
|
||||
this._isExecuting = false;
|
||||
this._hasCompleted = true;
|
||||
this._batchSets = result.batchSummaries ? result.batchSummaries : [];
|
||||
|
||||
this._batchSets.map((batch) => {
|
||||
if (batch.selection) {
|
||||
batch.selection.startLine = batch.selection.startLine + this._resultLineOffset;
|
||||
batch.selection.endLine = batch.selection.endLine + this._resultLineOffset;
|
||||
}
|
||||
});
|
||||
|
||||
// We're done with this query so shut down any waiting mechanisms
|
||||
this.eventEmitter.emit('complete', Utils.parseNumAsTimeString(this._totalElapsedMilliseconds));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a BatchStart from the service layer
|
||||
*/
|
||||
public handleBatchStart(result: QueryExecuteBatchNotificationParams): void {
|
||||
let batch = result.batchSummary;
|
||||
|
||||
// Recalculate the start and end lines, relative to the result line offset
|
||||
if (batch.selection) {
|
||||
batch.selection.startLine += this._resultLineOffset;
|
||||
batch.selection.endLine += this._resultLineOffset;
|
||||
}
|
||||
|
||||
// Set the result sets as an empty array so that as result sets complete we can add to the list
|
||||
batch.resultSetSummaries = [];
|
||||
|
||||
// Store the batch
|
||||
this._batchSets[batch.id] = batch;
|
||||
this.eventEmitter.emit('batchStart', batch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a BatchComplete from the service layer
|
||||
*/
|
||||
public handleBatchComplete(result: QueryExecuteBatchNotificationParams): void {
|
||||
let batch: BatchSummary = result.batchSummary;
|
||||
|
||||
// Store the batch again to get the rest of the data
|
||||
this._batchSets[batch.id] = batch;
|
||||
let executionTime = <number>(Utils.parseTimeString(batch.executionElapsed) || 0);
|
||||
this._totalElapsedMilliseconds += executionTime;
|
||||
if (executionTime > 0) {
|
||||
// send a time message in the format used for query complete
|
||||
this.sendBatchTimeMessage(batch.id, Utils.parseNumAsTimeString(executionTime));
|
||||
}
|
||||
this.eventEmitter.emit('batchComplete', batch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a ResultSetComplete from the service layer
|
||||
*/
|
||||
public handleResultSetComplete(result: QueryExecuteResultSetCompleteNotificationParams): void {
|
||||
if (result && result.resultSetSummary) {
|
||||
let resultSet = result.resultSetSummary;
|
||||
let batchSet: BatchSummary;
|
||||
if (!resultSet.batchId) {
|
||||
// Missing the batchId. In this case, default to always using the first batch in the list
|
||||
// or create one in the case the DMP extension didn't obey the contract perfectly
|
||||
if (this._batchSets.length > 0) {
|
||||
batchSet = this._batchSets[0];
|
||||
} else {
|
||||
batchSet = <BatchSummary>{
|
||||
id: 0,
|
||||
selection: undefined,
|
||||
hasError: false,
|
||||
resultSetSummaries: []
|
||||
};
|
||||
this._batchSets[0] = batchSet;
|
||||
}
|
||||
} else {
|
||||
batchSet = this._batchSets[resultSet.batchId];
|
||||
}
|
||||
if (batchSet) {
|
||||
// Store the result set in the batch and emit that a result set has completed
|
||||
batchSet.resultSetSummaries[resultSet.id] = resultSet;
|
||||
this.eventEmitter.emit('resultSet', resultSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a Mssage from the service layer
|
||||
*/
|
||||
public handleMessage(obj: QueryExecuteMessageParams): void {
|
||||
let message = obj.message;
|
||||
message.time = new Date(message.time).toLocaleTimeString();
|
||||
|
||||
// Send the message to the results pane
|
||||
this.eventEmitter.emit('message', message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get more data rows from the current resultSets from the service layer
|
||||
*/
|
||||
public getQueryRows(rowStart: number, numberOfRows: number, batchIndex: number, resultSetIndex: number): Thenable<QueryExecuteSubsetResult> {
|
||||
const self = this;
|
||||
let rowData: QueryExecuteSubsetParams = <QueryExecuteSubsetParams>{
|
||||
ownerUri: this.uri,
|
||||
resultSetIndex: resultSetIndex,
|
||||
rowsCount: numberOfRows,
|
||||
rowsStartIndex: rowStart,
|
||||
batchIndex: batchIndex
|
||||
};
|
||||
|
||||
return new Promise<QueryExecuteSubsetResult>((resolve, reject) => {
|
||||
self._queryManagementService.getQueryRows(rowData).then(result => {
|
||||
resolve(result);
|
||||
}, error => {
|
||||
self._messageService.show(Severity.Error, nls.localize('query.gettingRowsFailedError', 'Something went wrong getting more rows: {0}', error));
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle a session ready event for Edit Data
|
||||
*/
|
||||
public initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number): Thenable<void> {
|
||||
const self = this;
|
||||
|
||||
// Update internal state to show that we're executing the query
|
||||
this._isExecuting = true;
|
||||
this._totalElapsedMilliseconds = 0;
|
||||
// TODO issue #228 add statusview callbacks here
|
||||
|
||||
return this._queryManagementService.initializeEdit(ownerUri, schemaName, objectName, objectType, rowLimit).then(result => {
|
||||
// The query has started, so lets fire up the result pane
|
||||
self.eventEmitter.emit('start');
|
||||
self._queryManagementService.registerRunner(self, ownerUri);
|
||||
}, error => {
|
||||
// Attempting to launch the query failed, show the error message
|
||||
|
||||
// TODO issue #228 add statusview callbacks here
|
||||
self._isExecuting = false;
|
||||
|
||||
self._messageService.show(Severity.Error, nls.localize('query.initEditExecutionFailed', 'Init Edit Execution failed: ') + error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a number of rows from an edit session
|
||||
* @param rowStart The index of the row to start returning (inclusive)
|
||||
* @param numberOfRows The number of rows to return
|
||||
*/
|
||||
public getEditRows(rowStart: number, numberOfRows: number): Thenable<EditSubsetResult> {
|
||||
const self = this;
|
||||
let rowData: EditSubsetParams = {
|
||||
ownerUri: this.uri,
|
||||
rowCount: numberOfRows,
|
||||
rowStartIndex: rowStart
|
||||
};
|
||||
|
||||
return new Promise<EditSubsetResult>((resolve, reject) => {
|
||||
self._queryManagementService.getEditRows(rowData).then(result => {
|
||||
if (!result.hasOwnProperty('rowCount')) {
|
||||
let error = `Nothing returned from subset query`;
|
||||
self._messageService.show(Severity.Error, error);
|
||||
reject(error);
|
||||
}
|
||||
resolve(result);
|
||||
}, error => {
|
||||
let errorMessage = nls.localize('query.moreRowsFailedError', 'Something went wrong getting more rows:');
|
||||
self._messageService.show(Severity.Error, `${errorMessage} ${error}`);
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public handleEditSessionReady(ownerUri: string, success: boolean, message: string): void {
|
||||
this.eventEmitter.emit('editSessionReady', ownerUri, success, message);
|
||||
}
|
||||
|
||||
public updateCell(ownerUri: string, rowId: number, columnId: number, newValue: string): Thenable<EditUpdateCellResult> {
|
||||
return this._queryManagementService.updateCell(ownerUri, rowId, columnId, newValue);
|
||||
}
|
||||
|
||||
public commitEdit(ownerUri): Thenable<void> {
|
||||
return this._queryManagementService.commitEdit(ownerUri);
|
||||
}
|
||||
|
||||
public createRow(ownerUri: string): Thenable<EditCreateRowResult> {
|
||||
return this._queryManagementService.createRow(ownerUri).then(result => {
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
public deleteRow(ownerUri: string, rowId: number): Thenable<void> {
|
||||
return this._queryManagementService.deleteRow(ownerUri, rowId);
|
||||
}
|
||||
|
||||
public revertCell(ownerUri: string, rowId: number, columnId: number): Thenable<EditRevertCellResult> {
|
||||
return this._queryManagementService.revertCell(ownerUri, rowId, columnId).then(result => {
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
public revertRow(ownerUri: string, rowId: number): Thenable<void> {
|
||||
return this._queryManagementService.revertRow(ownerUri, rowId);
|
||||
}
|
||||
|
||||
public disposeEdit(ownerUri: string): Thenable<void> {
|
||||
return this._queryManagementService.disposeEdit(ownerUri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes the Query from the service client
|
||||
* @returns A promise that will be rejected if a problem occured
|
||||
*/
|
||||
public dispose(): Promise<void> {
|
||||
const self = this;
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
self._queryManagementService.disposeQuery(self.uri).then(result => {
|
||||
resolve();
|
||||
}, error => {
|
||||
consoleError('Failed disposing query: ' + error);
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
get totalElapsedMilliseconds(): number {
|
||||
return this._totalElapsedMilliseconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a copy request
|
||||
* @param selection The selection range to copy
|
||||
* @param batchId The batch id of the result to copy from
|
||||
* @param resultId The result id of the result to copy from
|
||||
* @param includeHeaders [Optional]: Should column headers be included in the copy selection
|
||||
*/
|
||||
copyResults(selection: ISlickRange[], batchId: number, resultId: number, includeHeaders?: boolean): void {
|
||||
const self = this;
|
||||
let copyString = '';
|
||||
|
||||
// create a mapping of the ranges to get promises
|
||||
let tasks = selection.map((range, i) => {
|
||||
return () => {
|
||||
return self.getQueryRows(range.fromRow, range.toRow - range.fromRow + 1, batchId, resultId).then((result) => {
|
||||
if (self.shouldIncludeHeaders(includeHeaders)) {
|
||||
let columnHeaders = self.getColumnHeaders(batchId, resultId, range);
|
||||
if (columnHeaders !== undefined) {
|
||||
copyString += columnHeaders.join('\t') + os.EOL;
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate over the rows to paste into the copy string
|
||||
for (let rowIndex: number = 0; rowIndex < result.resultSubset.rows.length; rowIndex++) {
|
||||
let row = result.resultSubset.rows[rowIndex];
|
||||
let cellObjects = row.slice(range.fromCell, (range.toCell + 1));
|
||||
// Remove newlines if requested
|
||||
let cells = self.shouldRemoveNewLines()
|
||||
? cellObjects.map(x => self.removeNewLines(x.displayValue))
|
||||
: cellObjects.map(x => x.displayValue);
|
||||
copyString += cells.join('\t');
|
||||
if (rowIndex < result.resultSubset.rows.length - 1) {
|
||||
copyString += os.EOL;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
if (tasks.length > 0) {
|
||||
let p = tasks[0]();
|
||||
for (let i = 1; i < tasks.length; i++) {
|
||||
p = p.then(tasks[i]);
|
||||
}
|
||||
p.then(() => {
|
||||
WorkbenchUtils.executeCopy(copyString);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private shouldIncludeHeaders(includeHeaders: boolean): boolean {
|
||||
if (includeHeaders !== undefined) {
|
||||
// Respect the value explicity passed into the method
|
||||
return includeHeaders;
|
||||
}
|
||||
// else get config option from vscode config
|
||||
includeHeaders = WorkbenchUtils.getSqlConfigValue<boolean>(this._workspaceConfigurationService, Constants.copyIncludeHeaders);
|
||||
return !!includeHeaders;
|
||||
}
|
||||
|
||||
private shouldRemoveNewLines(): boolean {
|
||||
// get config copyRemoveNewLine option from vscode config
|
||||
let removeNewLines: boolean = WorkbenchUtils.getSqlConfigValue<boolean>(this._workspaceConfigurationService, Constants.configCopyRemoveNewLine);
|
||||
return !!removeNewLines;
|
||||
}
|
||||
|
||||
private getColumnHeaders(batchId: number, resultId: number, range: ISlickRange): string[] {
|
||||
let headers: string[] = undefined;
|
||||
let batchSummary: BatchSummary = this.batchSets[batchId];
|
||||
if (batchSummary !== undefined) {
|
||||
let resultSetSummary = batchSummary.resultSetSummaries[resultId];
|
||||
headers = resultSetSummary.columnInfo.slice(range.fromCell, range.toCell + 1).map((info, i) => {
|
||||
return info.columnName;
|
||||
});
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
private removeNewLines(inputString: string): string {
|
||||
// This regex removes all newlines in all OS types
|
||||
// Windows(CRLF): \r\n
|
||||
// Linux(LF)/Modern MacOS: \n
|
||||
// Old MacOs: \r
|
||||
if (!inputString) {
|
||||
return 'null';
|
||||
}
|
||||
|
||||
let outputString: string = inputString.replace(/(\r\n|\n|\r)/gm, '');
|
||||
return outputString;
|
||||
}
|
||||
|
||||
private sendBatchTimeMessage(batchId: number, executionTime: string): void {
|
||||
// get config copyRemoveNewLine option from vscode config
|
||||
let showBatchTime: boolean = WorkbenchUtils.getSqlConfigValue<boolean>(this._workspaceConfigurationService, Constants.configShowBatchTime);
|
||||
if (showBatchTime) {
|
||||
let message: IResultMessage = {
|
||||
batchId: batchId,
|
||||
message: nls.localize('elapsedBatchTime', 'Batch execution time: {0}', executionTime),
|
||||
time: undefined,
|
||||
isError: false
|
||||
};
|
||||
// Send the message to the results pane
|
||||
this.eventEmitter.emit('message', message);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
118
src/sql/parts/query/execution/queryStatus.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { $, append, show, hide } from 'vs/base/browser/dom';
|
||||
import { IDisposable, combinedDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar';
|
||||
import { IEditorCloseEvent } from 'vs/workbench/common/editor';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
|
||||
import { IQueryModelService } from 'sql/parts/query/execution/queryModel';
|
||||
import LocalizedConstants = require('sql/parts/query/common/localizedConstants');
|
||||
import * as WorkbenchUtils from 'sql/workbench/common/sqlWorkbenchUtils';
|
||||
|
||||
// Query execution status
|
||||
enum QueryExecutionStatus{
|
||||
Executing,
|
||||
Completed
|
||||
}
|
||||
|
||||
// Shows query status in the editor
|
||||
export class QueryStatusbarItem implements IStatusbarItem {
|
||||
|
||||
private _element: HTMLElement;
|
||||
private _queryElement: HTMLElement;
|
||||
private _queryStatusEditors: { [editorUri: string]: QueryExecutionStatus };
|
||||
private _toDispose: IDisposable[];
|
||||
|
||||
constructor(
|
||||
@IQueryModelService private _queryModelService: IQueryModelService,
|
||||
@IWorkbenchEditorService private _editorService: IWorkbenchEditorService,
|
||||
@IEditorGroupService private _editorGroupService: IEditorGroupService,
|
||||
) {
|
||||
this._queryStatusEditors = {};
|
||||
}
|
||||
|
||||
public render(container: HTMLElement): IDisposable {
|
||||
this._element = append(container, $('.query-statusbar-group'));
|
||||
this._queryElement = append(this._element, $('div.query-statusbar-item'));
|
||||
hide(this._queryElement);
|
||||
|
||||
this._toDispose = [];
|
||||
this._toDispose.push(
|
||||
this._queryModelService.onRunQueryStart((uri:string) => this._onRunQueryStart(uri)),
|
||||
this._queryModelService.onRunQueryComplete((uri:string) => this._onRunQueryComplete(uri)),
|
||||
this._editorGroupService.onEditorsChanged(() => this._onEditorsChanged()),
|
||||
this._editorGroupService.getStacksModel().onEditorClosed(event => this._onEditorClosed(event))
|
||||
);
|
||||
|
||||
return combinedDisposable(this._toDispose);
|
||||
}
|
||||
|
||||
private _onEditorClosed(event: IEditorCloseEvent): void{
|
||||
let uri = WorkbenchUtils.getEditorUri(event.editor);
|
||||
if (uri && uri in this._queryStatusEditors) {
|
||||
// If active editor is being closed, hide the query status.
|
||||
let activeEditor = this._editorService.getActiveEditor();
|
||||
if (activeEditor) {
|
||||
let currentUri = WorkbenchUtils.getEditorUri(activeEditor.input);
|
||||
if (uri === currentUri) {
|
||||
hide(this._queryElement);
|
||||
}
|
||||
}
|
||||
delete this._queryStatusEditors[uri];
|
||||
}
|
||||
}
|
||||
|
||||
private _onEditorsChanged(): void{
|
||||
let activeEditor = this._editorService.getActiveEditor();
|
||||
if (activeEditor) {
|
||||
let uri = WorkbenchUtils.getEditorUri(activeEditor.input);
|
||||
|
||||
// Show active editor's query status
|
||||
if (uri && uri in this._queryStatusEditors){
|
||||
this._showStatus(uri);
|
||||
} else {
|
||||
hide(this._queryElement);
|
||||
}
|
||||
} else {
|
||||
hide(this._queryElement);
|
||||
}
|
||||
}
|
||||
|
||||
private _onRunQueryStart(uri: string): void {
|
||||
this._updateStatus(uri, QueryExecutionStatus.Executing);
|
||||
}
|
||||
|
||||
private _onRunQueryComplete(uri: string): void {
|
||||
this._updateStatus(uri, QueryExecutionStatus.Completed);
|
||||
}
|
||||
|
||||
// Update query status for the editor
|
||||
private _updateStatus(uri: string, newStatus: QueryExecutionStatus){
|
||||
if (uri) {
|
||||
this._queryStatusEditors[uri] = newStatus;
|
||||
this._showStatus(uri);
|
||||
}
|
||||
}
|
||||
|
||||
// Show/hide query status for active editor
|
||||
private _showStatus(uri: string): void{
|
||||
let activeEditor = this._editorService.getActiveEditor();
|
||||
if (activeEditor) {
|
||||
let currentUri = WorkbenchUtils.getEditorUri(activeEditor.input);
|
||||
if (uri === currentUri) {
|
||||
switch(this._queryStatusEditors[uri]){
|
||||
case QueryExecutionStatus.Executing:
|
||||
this._queryElement.textContent = LocalizedConstants.msgStatusRunQueryInProgress;
|
||||
show(this._queryElement);
|
||||
break;
|
||||
default:
|
||||
hide(this._queryElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
337
src/sql/parts/query/services/queryEditorService.ts
Normal file
@@ -0,0 +1,337 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IEditorGroup } from 'vs/workbench/common/editor';
|
||||
import { IUntitledEditorService, UNTITLED_SCHEMA } from 'vs/workbench/services/untitled/common/untitledEditorService';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { FileEditorInput } from 'vs/workbench/parts/files/common/editors/fileEditorInput';
|
||||
import { QueryResultsInput } from 'sql/parts/query/common/queryResultsInput';
|
||||
import { QueryInput } from 'sql/parts/query/common/queryInput';
|
||||
import { EditDataInput } from 'sql/parts/editData/common/editDataInput';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { IConnectableInput } from 'sql/parts/connection/common/connectionManagement';
|
||||
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
|
||||
import { IMode } from 'vs/editor/common/modes';
|
||||
import { IModel } from 'vs/editor/common/editorCommon';
|
||||
import { IEditor, IEditorInput, Position } from 'vs/platform/editor/common/editor';
|
||||
import { CodeEditor } from 'vs/editor/browser/codeEditor';
|
||||
import { IQueryEditorService, IQueryEditorOptions } from 'sql/parts/query/common/queryEditorService';
|
||||
import { IMessageService } from 'vs/platform/message/common/message';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import nls = require('vs/nls');
|
||||
import { QueryPlanInput } from 'sql/parts/queryPlan/queryPlanInput';
|
||||
import { sqlModeId, untitledFilePrefix, getSupportedInputResource } from 'sql/parts/common/customInputConverter';
|
||||
|
||||
const fs = require('fs');
|
||||
|
||||
/**
|
||||
* Service wrapper for opening and creating SQL documents as sql editor inputs
|
||||
*/
|
||||
export class QueryEditorService implements IQueryEditorService {
|
||||
|
||||
public _serviceBrand: any;
|
||||
|
||||
private static CHANGE_UNSUPPORTED_ERROR_MESSAGE = nls.localize(
|
||||
'queryEditorServiceChangeUnsupportedError',
|
||||
'Change Language Mode is not supported for unsaved queries'
|
||||
);
|
||||
|
||||
private static CHANGE_ERROR_MESSAGE = nls.localize(
|
||||
'queryEditorServiceChangeError',
|
||||
'Please save or discard changes before switching to/from the SQL Language Mode'
|
||||
);
|
||||
|
||||
// service references for static functions
|
||||
private static editorService: IWorkbenchEditorService;
|
||||
private static instantiationService: IInstantiationService;
|
||||
private static editorGroupService: IEditorGroupService;
|
||||
private static messageService: IMessageService;
|
||||
|
||||
constructor(
|
||||
@IUntitledEditorService private _untitledEditorService: IUntitledEditorService,
|
||||
@IInstantiationService private _instantiationService: IInstantiationService,
|
||||
@IWorkbenchEditorService private _editorService: IWorkbenchEditorService,
|
||||
@IEditorGroupService private _editorGroupService: IEditorGroupService,
|
||||
@IMessageService private _messageService: IMessageService,
|
||||
) {
|
||||
QueryEditorService.editorService = _editorService;
|
||||
QueryEditorService.instantiationService = _instantiationService;
|
||||
QueryEditorService.editorGroupService = _editorGroupService;
|
||||
QueryEditorService.messageService = _messageService;
|
||||
}
|
||||
|
||||
////// Public functions
|
||||
|
||||
/**
|
||||
* Creates new untitled document for SQL query and opens in new editor tab
|
||||
*/
|
||||
public newSqlEditor(sqlContent?: string, connectionProviderName?: string): Promise<IConnectableInput> {
|
||||
return new Promise<IConnectableInput>((resolve, reject) => {
|
||||
try {
|
||||
// Create file path and file URI
|
||||
let filePath = this.createUntitledSqlFilePath();
|
||||
let docUri: URI = URI.from({ scheme: UNTITLED_SCHEMA, path: filePath });
|
||||
|
||||
// Create a sql document pane with accoutrements
|
||||
const fileInput = this._untitledEditorService.createOrGet(docUri, 'sql');
|
||||
fileInput.resolve().then(m => {
|
||||
if (sqlContent) {
|
||||
m.textEditorModel.setValue(sqlContent);
|
||||
}
|
||||
});
|
||||
|
||||
//input.resolve().then(model => this.backupFileService.backupResource(resource, model.getValue(), model.getVersionId())).done(null, errors.onUnexpectedError);
|
||||
|
||||
const queryResultsInput: QueryResultsInput = this._instantiationService.createInstance(QueryResultsInput, docUri.toString());
|
||||
let queryInput: QueryInput = this._instantiationService.createInstance(QueryInput, fileInput.getName(), '', fileInput, queryResultsInput, connectionProviderName);
|
||||
|
||||
this._editorService.openEditor(queryInput, { pinned: true })
|
||||
.then((editor) => {
|
||||
let params = <QueryInput>editor.input;
|
||||
resolve(params);
|
||||
}, (error) => {
|
||||
reject(error);
|
||||
});
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Creates a new query plan document
|
||||
public newQueryPlanEditor(xmlShowPlan: string): Promise<any> {
|
||||
const self = this;
|
||||
return new Promise<any>((resolve, reject) => {
|
||||
let queryPlanInput: QueryPlanInput = self._instantiationService.createInstance(QueryPlanInput, xmlShowPlan, 'aaa', undefined);
|
||||
self._editorService.openEditor(queryPlanInput, { pinned: true }, false);
|
||||
resolve(true);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new edit data session
|
||||
*/
|
||||
public newEditDataEditor(schemaName: string, tableName: string): Promise<IConnectableInput> {
|
||||
|
||||
return new Promise<IConnectableInput>((resolve, reject) => {
|
||||
try {
|
||||
// Create file path and file URI
|
||||
let objectName = schemaName ? schemaName + '.' + tableName : tableName;
|
||||
let filePath = this.createEditDataFileName(objectName);
|
||||
let docUri: URI = URI.from({ scheme: UNTITLED_SCHEMA, path: filePath });
|
||||
|
||||
// Create an EditDataInput for editing
|
||||
let editDataInput: EditDataInput = this._instantiationService.createInstance(EditDataInput, docUri, schemaName, tableName);
|
||||
|
||||
this._editorService.openEditor(editDataInput, { pinned: true })
|
||||
.then((editor) => {
|
||||
let params = <EditDataInput>editor.input;
|
||||
resolve(params);
|
||||
}, (error) => {
|
||||
reject(error);
|
||||
});
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears any QueryEditor data for the given URI held by this service
|
||||
*/
|
||||
public onQueryInputClosed(uri: string): void {
|
||||
}
|
||||
|
||||
////// Public static functions
|
||||
// These functions are static to reduce extra lines needed in the vscode code base
|
||||
|
||||
/**
|
||||
* Checks if the Language Mode is being changed to/from SQL. If so, swaps out the input of the
|
||||
* given editor with a new input, opens a new editor, then returns the new editor's IModel.
|
||||
*
|
||||
* Returns an immediately resolved promise if the SQL Language mode is not involved. In this case,
|
||||
* the calling function in editorStatus.ts will handle the language change normally.
|
||||
*
|
||||
* Returns an immediately resolved promise with undefined if SQL is involved in the language change
|
||||
* and the editor is dirty. In this case, the calling function in editorStatus.ts will not perform
|
||||
* the language change. TODO: change this - tracked by issue #727
|
||||
*
|
||||
* In all other cases (when SQL is involved in the language change and the editor is not dirty),
|
||||
* returns a promise that will resolve when the old editor has been replaced by a new editor.
|
||||
*/
|
||||
public static sqlLanguageModeCheck(model: IModel, mode: IMode, editor: IEditor): Promise<IModel> {
|
||||
if (!model || !mode || !editor) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
let newLanguage: string = mode.getLanguageIdentifier().language;
|
||||
let oldLanguage: string = model.getLanguageIdentifier().language;
|
||||
let changingToSql = sqlModeId === newLanguage;
|
||||
let changingFromSql = sqlModeId === oldLanguage;
|
||||
let changingLanguage = newLanguage !== oldLanguage;
|
||||
|
||||
if (!changingLanguage) {
|
||||
return Promise.resolve(model);
|
||||
}
|
||||
if (!changingFromSql && !changingToSql) {
|
||||
return Promise.resolve(model);
|
||||
}
|
||||
|
||||
let uri: URI = QueryEditorService._getEditorChangeUri(editor.input, changingToSql);
|
||||
if(uri.scheme === UNTITLED_SCHEMA && editor.input instanceof QueryInput)
|
||||
{
|
||||
QueryEditorService.messageService.show(Severity.Error, QueryEditorService.CHANGE_UNSUPPORTED_ERROR_MESSAGE);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
// Return undefined to notify the calling funciton to not perform the language change
|
||||
// TODO change this - tracked by issue #727
|
||||
if (editor.input.isDirty()) {
|
||||
QueryEditorService.messageService.show(Severity.Error, QueryEditorService.CHANGE_ERROR_MESSAGE);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
let group: IEditorGroup = QueryEditorService.editorGroupService.getStacksModel().groupAt(editor.position);
|
||||
let index: number = group.indexOf(editor.input);
|
||||
let position: Position = editor.position;
|
||||
let options: IQueryEditorOptions = editor.options ? editor.options : {};
|
||||
options.index = index;
|
||||
options.pinned = group.isPinned(index);
|
||||
|
||||
// Return a promise that will resovle when the old editor has been replaced by a new editor
|
||||
return new Promise<IModel>((resolve, reject) => {
|
||||
let newEditorInput = QueryEditorService._getNewEditorInput(changingToSql, editor.input, uri);
|
||||
|
||||
// Override queryEditorCheck to not open this file in a QueryEditor
|
||||
if (!changingToSql) {
|
||||
options.denyQueryEditor = true;
|
||||
}
|
||||
|
||||
// Close the current editor
|
||||
QueryEditorService.editorService.closeEditor(position, editor.input).then(() => {
|
||||
|
||||
// Reopen a new editor in the same position/index
|
||||
QueryEditorService.editorService.openEditor(newEditorInput, options, position).then((editor) => {
|
||||
resolve(QueryEditorService._onEditorOpened(editor, uri.toString(), position, options.pinned));
|
||||
},
|
||||
(error) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
////// Private functions
|
||||
|
||||
private createUntitledSqlFilePath(): string {
|
||||
let sqlFileName = (counter: number): string => {
|
||||
return `${untitledFilePrefix}${counter}`;
|
||||
};
|
||||
|
||||
let counter = 1;
|
||||
// Get document name and check if it exists
|
||||
let filePath = sqlFileName(counter);
|
||||
while (fs.existsSync(filePath)) {
|
||||
counter++;
|
||||
filePath = sqlFileName(counter);
|
||||
}
|
||||
|
||||
// check if this document name already exists in any open documents
|
||||
let untitledEditors = this._untitledEditorService.getAll();
|
||||
while (untitledEditors.find(x => x.getName().toUpperCase() === filePath.toUpperCase())) {
|
||||
counter++;
|
||||
filePath = sqlFileName(counter);
|
||||
}
|
||||
|
||||
return filePath;
|
||||
}
|
||||
|
||||
private createEditDataFileName(tableName: string): string {
|
||||
let editDataFileName = (counter: number): string => {
|
||||
return encodeURIComponent(`${tableName}_${counter}`);
|
||||
};
|
||||
|
||||
let counter = 1;
|
||||
// Get document name and check if it exists
|
||||
let filePath = editDataFileName(counter);
|
||||
while (fs.existsSync(filePath)) {
|
||||
counter++;
|
||||
filePath = editDataFileName(counter);
|
||||
}
|
||||
|
||||
// TODO: check if this document name already exists in any open documents tabs
|
||||
let fileNames: string[] = [];
|
||||
this._editorGroupService.getStacksModel().groups.map(group => group.getEditors().map(editor => fileNames.push(editor.getName())));
|
||||
while (fileNames.find(x => x.toUpperCase() === filePath.toUpperCase())) {
|
||||
counter++;
|
||||
filePath = editDataFileName(counter);
|
||||
}
|
||||
|
||||
return filePath;
|
||||
}
|
||||
|
||||
////// Private static functions
|
||||
|
||||
/**
|
||||
* Returns a QueryInput if we are changingToSql. Returns a FileEditorInput if we are !changingToSql.
|
||||
*/
|
||||
private static _getNewEditorInput(changingToSql: boolean, input: IEditorInput, uri: URI): IEditorInput {
|
||||
if (!uri) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let newEditorInput: IEditorInput = undefined;
|
||||
if (changingToSql) {
|
||||
const queryResultsInput: QueryResultsInput = QueryEditorService.instantiationService.createInstance(QueryResultsInput, uri.toString());
|
||||
let queryInput: QueryInput = QueryEditorService.instantiationService.createInstance(QueryInput, input.getName(), '', input, queryResultsInput, undefined);
|
||||
newEditorInput = queryInput;
|
||||
} else {
|
||||
let uriCopy: URI = URI.from( { scheme: uri.scheme, authority: uri.authority, path: uri.path, query: uri.query, fragment: uri.fragment } );
|
||||
newEditorInput = QueryEditorService.instantiationService.createInstance(FileEditorInput, uriCopy, undefined);
|
||||
}
|
||||
|
||||
return newEditorInput;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the URI for this IEditorInput or returns undefined if one does not exist.
|
||||
*/
|
||||
private static _getEditorChangeUri(input: IEditorInput, changingToSql: boolean): URI {
|
||||
let uriSource: IEditorInput = input;
|
||||
|
||||
// It is assumed that if we got here, !changingToSql is logically equivalent to changingFromSql
|
||||
let changingFromSql = !changingToSql;
|
||||
if (input instanceof QueryInput && changingFromSql) {
|
||||
let queryInput: QueryInput = <QueryInput> input;
|
||||
uriSource = queryInput.sql;
|
||||
}
|
||||
return getSupportedInputResource(uriSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle all cleanup actions that need to wait until the editor is fully open.
|
||||
*/
|
||||
private static _onEditorOpened(editor: IEditor, uri: string, position: Position, isPinned: boolean): IModel {
|
||||
|
||||
// Reset the editor pin state
|
||||
// TODO: change this so it happens automatically in openEditor in sqlLanguageModeCheck. Performing this here
|
||||
// causes the text on the tab to slightly flicker for unpinned files (from non-italic to italic to non-italic).
|
||||
// This is currently unavoidable because vscode ignores "pinned" on IEditorOptions if "index" is not undefined,
|
||||
// and we need to specify "index"" so the editor tab remains in the same place
|
||||
let group: IEditorGroup = QueryEditorService.editorGroupService.getStacksModel().groupAt(position);
|
||||
if (isPinned) {
|
||||
QueryEditorService.editorGroupService.pinEditor(group, editor.input);
|
||||
} else {
|
||||
QueryEditorService.editorGroupService.unpinEditor(group, editor.input);
|
||||
}
|
||||
|
||||
// Grab and returns the IModel that will be used to resolve the sqlLanguageModeCheck promise.
|
||||
let control = editor.getControl();
|
||||
let codeEditor: CodeEditor = <CodeEditor> control;
|
||||
let newModel = codeEditor ? codeEditor.getModel() : undefined;
|
||||
return newModel;
|
||||
}
|
||||
}
|
||||
271
src/sql/parts/query/views/flexibleSash.ts
Normal file
@@ -0,0 +1,271 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { Dimension } from 'vs/base/browser/builder';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { IHorizontalSashLayoutProvider, IVerticalSashLayoutProvider,
|
||||
ISashEvent, Orientation, VSash, Sash } from 'vs/base/browser/ui/sash/sash';
|
||||
// There is no need to import the sash CSS - 'vs/base/browser/ui/sash/sash' already includes it
|
||||
|
||||
/**
|
||||
* Interface describing a sash that could be horizontal or vertical. This interface allows classes
|
||||
* using the sash to have UI logic that is agnostic of the orientation of the sash.
|
||||
*/
|
||||
export interface IFlexibleSash {
|
||||
|
||||
// Get the value of the CSS property denoted by getMajorPosition()
|
||||
getSplitPoint(): number;
|
||||
|
||||
// Sets the Dimension containing the height and width of the editor this sash will separate
|
||||
setDimenesion(dimension: Dimension);
|
||||
|
||||
// Re-calculates the width and height of the sash
|
||||
layout(): void;
|
||||
|
||||
// Hides the sash
|
||||
hide(): void;
|
||||
|
||||
// Shows/unhides the sash
|
||||
show(): void;
|
||||
|
||||
// Sets the top or left property of this sash
|
||||
setEdge(edge: number);
|
||||
|
||||
// Fired when the position of this sash changes
|
||||
onPositionChange: Event<number>;
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple Vertical Sash that computes the position of the sash when it is moved between the given dimension.
|
||||
* Triggers onPositionChange event when the position is changed. Implements IFlexibleSash to enable classes to be
|
||||
* agnostic of the fact that this sash is vertical.
|
||||
*/
|
||||
|
||||
|
||||
export class VerticalFlexibleSash extends Disposable implements IVerticalSashLayoutProvider, IFlexibleSash {
|
||||
|
||||
private sash: Sash;
|
||||
private ratio: number;
|
||||
private startPosition: number;
|
||||
private position: number;
|
||||
private dimension: Dimension;
|
||||
private top: number;
|
||||
|
||||
private _onPositionChange: Emitter<number> = new Emitter<number>();
|
||||
public get onPositionChange(): Event<number> { return this._onPositionChange.event; }
|
||||
|
||||
constructor(container: HTMLElement, private minWidth: number) {
|
||||
super();
|
||||
this.ratio = 0.5;
|
||||
this.top = 0;
|
||||
this.sash = new Sash(container, this);
|
||||
|
||||
this._register(this.sash.addListener('start', () => this.onSashDragStart()));
|
||||
this._register(this.sash.addListener('change', (e: ISashEvent) => this.onSashDrag(e)));
|
||||
this._register(this.sash.addListener('end', () => this.onSashDragEnd()));
|
||||
this._register(this.sash.addListener('reset', () => this.onSashReset()));
|
||||
}
|
||||
|
||||
public getSplitPoint(): number {
|
||||
return this.getVerticalSashLeft();
|
||||
}
|
||||
|
||||
public layout(): void {
|
||||
this.sash.layout();
|
||||
}
|
||||
|
||||
public show(): void {
|
||||
this.sash.show();
|
||||
}
|
||||
|
||||
public hide(): void {
|
||||
this.sash.hide();
|
||||
}
|
||||
|
||||
public getVerticalSashTop(): number {
|
||||
return this.top;
|
||||
}
|
||||
|
||||
public getVerticalSashLeft(): number {
|
||||
return this.position;
|
||||
}
|
||||
|
||||
public getVerticalSashHeight(): number {
|
||||
return this.dimension.height;
|
||||
}
|
||||
|
||||
public setDimenesion(dimension: Dimension) {
|
||||
this.dimension = dimension;
|
||||
this.compute(this.ratio);
|
||||
}
|
||||
|
||||
public setEdge(edge: number) {
|
||||
this.top = edge;
|
||||
}
|
||||
|
||||
private onSashDragStart(): void {
|
||||
this.startPosition = this.position;
|
||||
}
|
||||
|
||||
private onSashDrag(e: ISashEvent): void {
|
||||
this.compute((this.startPosition + (e.currentX - e.startX)) / this.dimension.width);
|
||||
}
|
||||
|
||||
private compute(ratio: number) {
|
||||
this.computeSashPosition(ratio);
|
||||
this.ratio = this.position / this.dimension.width;
|
||||
this._onPositionChange.fire(this.position);
|
||||
}
|
||||
|
||||
private onSashDragEnd(): void {
|
||||
this.sash.layout();
|
||||
}
|
||||
|
||||
private onSashReset(): void {
|
||||
this.ratio = 0.5;
|
||||
this._onPositionChange.fire(this.position);
|
||||
this.sash.layout();
|
||||
}
|
||||
|
||||
private computeSashPosition(sashRatio: number = this.ratio) {
|
||||
let contentWidth = this.dimension.width;
|
||||
let sashPosition = Math.floor((sashRatio || 0.5) * contentWidth);
|
||||
let midPoint = Math.floor(0.5 * contentWidth);
|
||||
|
||||
if (contentWidth > this.minWidth * 2) {
|
||||
if (sashPosition < this.minWidth) {
|
||||
sashPosition = this.minWidth;
|
||||
}
|
||||
if (sashPosition > contentWidth - this.minWidth) {
|
||||
sashPosition = contentWidth - this.minWidth;
|
||||
}
|
||||
} else {
|
||||
sashPosition = midPoint;
|
||||
}
|
||||
if (this.position !== sashPosition) {
|
||||
this.position = sashPosition;
|
||||
this.sash.layout();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple Horizontal Sash that computes the position of the sash when it is moved between the given dimension.
|
||||
* Triggers onPositionChange event when the position is changed. Implements IFlexibleSash to enable classes to be
|
||||
* agnostic of the fact that this sash is horizontal. Based off the VSash class.
|
||||
*/
|
||||
export class HorizontalFlexibleSash extends Disposable implements IHorizontalSashLayoutProvider, IFlexibleSash {
|
||||
|
||||
private 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 = 0.5;
|
||||
this.left = 0;
|
||||
this.sash = new Sash(container, this, { orientation: Orientation.HORIZONTAL });
|
||||
|
||||
this._register(this.sash.addListener('start', () => this.onSashDragStart()));
|
||||
this._register(this.sash.addListener('change', (e: ISashEvent) => this.onSashDrag(e)));
|
||||
this._register(this.sash.addListener('end', () => this.onSashDragEnd()));
|
||||
this._register(this.sash.addListener('reset', () => 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 = 0.5;
|
||||
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 * 2) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
33
src/sql/parts/query/views/queryOutput.component.html
Normal file
@@ -0,0 +1,33 @@
|
||||
<!--
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
-->
|
||||
|
||||
<panel class="fullsize" [options]="panelOpt">
|
||||
<tab [title]="queryComponentTitle" class="fullsize" [identifier]="resultsTabIdentifier">
|
||||
<div id="queryDiv" class="fullsize">
|
||||
<query-component id="queryComp" #queryComponent class="fullsize" [queryParameters]="queryParameters"></query-component>
|
||||
</div>
|
||||
</tab>
|
||||
|
||||
<tab *ngIf="hasQueryPlan" [title]="queryPlanTitle" [identifier]="queryPlanTabIdentifier">
|
||||
<div id="queryPlanDiv" class="headersVisible fullsize" style=" overflow: auto; margin-left: 2px">
|
||||
<queryplan-component #queryPlanComponent class="fullsize" style="display: block"></queryplan-component>
|
||||
</div>
|
||||
</tab>
|
||||
|
||||
<tab *ngIf="hasQueryPlan" class="fullsize" [title]="topOperationsTitle">
|
||||
<div id="topOperationsDiv" class="fullsize">
|
||||
<top-operations-component #topOperationsComponent class="fullsize" style="display: block" [queryParameters]="queryParameters"></top-operations-component>
|
||||
</div>
|
||||
</tab>
|
||||
|
||||
<tab *ngIf="showChartView" [title]="chartViewerTitle" [identifier]="chartViewerTabIdentifier">
|
||||
<div id="chartViewerDiv" class="headersVisible fullsize" >
|
||||
<chart-viewer #chartViewerComponent class="fullsize" style="display: block">
|
||||
</chart-viewer>
|
||||
</div>
|
||||
</tab>
|
||||
</panel>
|
||||
110
src/sql/parts/query/views/queryOutput.component.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import 'vs/css!sql/parts/grid/media/slickColorTheme';
|
||||
import 'vs/css!sql/parts/grid/media/flexbox';
|
||||
import 'vs/css!sql/parts/grid/media/styles';
|
||||
import 'vs/css!sql/parts/grid/media/slick.grid';
|
||||
import 'vs/css!sql/parts/grid/media/slickGrid';
|
||||
|
||||
import { ElementRef, ChangeDetectorRef, OnInit, OnDestroy, Component, Inject, forwardRef, ViewChild } from '@angular/core';
|
||||
import { IBootstrapService, BOOTSTRAP_SERVICE_ID } from 'sql/services/bootstrap/bootstrapService';
|
||||
import { QueryComponentParams } from 'sql/services/bootstrap/bootstrapParams';
|
||||
import { QueryComponent } from 'sql/parts/grid/views/query/query.component';
|
||||
import { QueryPlanComponent } from 'sql/parts/queryPlan/queryPlan.component';
|
||||
import { TopOperationsComponent } from 'sql/parts/queryPlan/topOperations.component';
|
||||
import { ChartViewerComponent } from 'sql/parts/grid/views/query/chartViewer.component';
|
||||
import { toDisposableSubscription } from 'sql/parts/common/rxjsUtils';
|
||||
import { PanelComponent, IPanelOptions } from 'sql/base/browser/ui/panel/panel.component';
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
export const QUERY_OUTPUT_SELECTOR: string = 'query-output-component';
|
||||
|
||||
declare type PaneType = 'messages' | 'results';
|
||||
|
||||
@Component({
|
||||
selector: QUERY_OUTPUT_SELECTOR,
|
||||
templateUrl: decodeURI(require.toUrl('sql/parts/query/views/queryOutput.component.html'))
|
||||
})
|
||||
export class QueryOutputComponent implements OnInit, OnDestroy {
|
||||
|
||||
@ViewChild('queryComponent') queryComponent: QueryComponent;
|
||||
|
||||
@ViewChild('queryPlanComponent') queryPlanComponent: QueryPlanComponent;
|
||||
|
||||
@ViewChild('topOperationsComponent') topOperationsComponent: TopOperationsComponent;
|
||||
|
||||
@ViewChild('chartViewerComponent') chartViewerComponent: ChartViewerComponent;
|
||||
|
||||
@ViewChild(PanelComponent) private _panel: PanelComponent;
|
||||
|
||||
// tslint:disable:no-unused-variable
|
||||
private readonly queryComponentTitle: string = nls.localize('results', 'Results');
|
||||
private readonly queryPlanTitle: string = nls.localize('queryPlan', 'Query Plan');
|
||||
private readonly topOperationsTitle: string = nls.localize('topOperations', 'Top Operations');
|
||||
private readonly chartViewerTitle: string = nls.localize('chartViewer', 'Chart Viewer');
|
||||
|
||||
private readonly resultsTabIdentifier = 'results';
|
||||
private readonly queryPlanTabIdentifier = 'queryPlan';
|
||||
private readonly chartViewerTabIdentifier = 'chartViewer';
|
||||
// tslint:enable:no-unused-variable
|
||||
|
||||
private hasQueryPlan = false;
|
||||
private showChartView = false;
|
||||
|
||||
// tslint:disable-next-line:no-unused-variable
|
||||
private readonly panelOpt: IPanelOptions = {
|
||||
showTabsWhenOne: false
|
||||
};
|
||||
|
||||
public queryParameters: QueryComponentParams;
|
||||
|
||||
private _disposables: Array<IDisposable> = [];
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => ElementRef)) el: ElementRef,
|
||||
@Inject(forwardRef(() => ChangeDetectorRef)) private _cd: ChangeDetectorRef,
|
||||
@Inject(BOOTSTRAP_SERVICE_ID) bootstrapService: IBootstrapService
|
||||
) {
|
||||
this.queryParameters = bootstrapService.getBootstrapParams(el.nativeElement.tagName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by Angular when the object is initialized
|
||||
*/
|
||||
public ngOnInit(): void {
|
||||
this._disposables.push(toDisposableSubscription(this.queryComponent.queryPlanAvailable.subscribe((xml) => {
|
||||
this.hasQueryPlan = true;
|
||||
this._cd.detectChanges();
|
||||
this._panel.selectTab(this.queryPlanTabIdentifier);
|
||||
this.queryPlanComponent.planXml = xml;
|
||||
this.topOperationsComponent.planXml = xml;
|
||||
})));
|
||||
|
||||
this._disposables.push(toDisposableSubscription(this.queryComponent.showChartRequested.subscribe((dataSet) => {
|
||||
this.showChartView = true;
|
||||
this._cd.detectChanges();
|
||||
this.chartViewerComponent.dataSet = dataSet;
|
||||
this._panel.selectTab(this.chartViewerTabIdentifier);
|
||||
})));
|
||||
|
||||
this._disposables.push(toDisposableSubscription(this.queryComponent.queryExecutionStatus.subscribe(status => {
|
||||
if (status === 'start') {
|
||||
this._panel.selectTab(this.resultsTabIdentifier);
|
||||
this.hasQueryPlan = false;
|
||||
this.showChartView = false;
|
||||
this._cd.detectChanges();
|
||||
}
|
||||
})));
|
||||
}
|
||||
|
||||
public ngOnDestroy(): void {
|
||||
this._disposables.forEach(i => i.dispose());
|
||||
}
|
||||
}
|
||||
75
src/sql/parts/query/views/queryOutput.module.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
import { ApplicationRef, ComponentFactoryResolver, forwardRef, NgModule, Inject } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { SlickGrid } from 'angular2-slickgrid';
|
||||
import { ChartsModule } from 'ng2-charts/ng2-charts';
|
||||
|
||||
const BrowserAnimationsModule = (<any>require.__$__nodeRequire('@angular/platform-browser/animations')).BrowserAnimationsModule;
|
||||
|
||||
import { IBootstrapService, BOOTSTRAP_SERVICE_ID } from 'sql/services/bootstrap/bootstrapService';
|
||||
import { Extensions, IInsightRegistry } from 'sql/platform/dashboard/common/insightRegistry';
|
||||
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
|
||||
|
||||
import { QueryOutputComponent, QUERY_OUTPUT_SELECTOR } from 'sql/parts/query/views/queryOutput.component';
|
||||
import { QueryPlanComponent, } from 'sql/parts/queryPlan/queryPlan.component';
|
||||
import { QueryComponent } from 'sql/parts/grid/views/query/query.component';
|
||||
import { TopOperationsComponent } from 'sql/parts/queryPlan/topOperations.component';
|
||||
|
||||
import { ChartViewerComponent } from 'sql/parts/grid/views/query/chartViewer.component';
|
||||
|
||||
import { PanelModule } from 'sql/base/browser/ui/panel/panel.module';
|
||||
|
||||
/* Directives */
|
||||
import { ComponentHostDirective } from 'sql/parts/dashboard/common/componentHost.directive';
|
||||
import { MouseDownDirective } from 'sql/parts/grid/directives/mousedown.directive';
|
||||
import { ScrollDirective } from 'sql/parts/grid/directives/scroll.directive';
|
||||
|
||||
let baseComponents = [QueryComponent, ComponentHostDirective, QueryOutputComponent, QueryPlanComponent, TopOperationsComponent, ChartViewerComponent];
|
||||
/* Insights */
|
||||
let insightComponents = Registry.as<IInsightRegistry>(Extensions.InsightContribution).getAllCtors();
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
BrowserModule,
|
||||
FormsModule,
|
||||
BrowserAnimationsModule,
|
||||
ChartsModule,
|
||||
PanelModule
|
||||
],
|
||||
declarations: [
|
||||
...baseComponents,
|
||||
...insightComponents,
|
||||
SlickGrid,
|
||||
ScrollDirective,
|
||||
MouseDownDirective
|
||||
],
|
||||
entryComponents: [
|
||||
QueryOutputComponent,
|
||||
...insightComponents
|
||||
]
|
||||
})
|
||||
export class QueryOutputModule {
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => ComponentFactoryResolver)) private _resolver: ComponentFactoryResolver,
|
||||
@Inject(BOOTSTRAP_SERVICE_ID) private _bootstrapService: IBootstrapService
|
||||
) {
|
||||
}
|
||||
|
||||
ngDoBootstrap(appRef: ApplicationRef) {
|
||||
const factory = this._resolver.resolveComponentFactory(QueryOutputComponent);
|
||||
const uniqueSelector: string = this._bootstrapService.getUniqueSelector(QUERY_OUTPUT_SELECTOR);
|
||||
(<any>factory).factory.selector = uniqueSelector;
|
||||
appRef.bootstrap(factory);
|
||||
}
|
||||
}
|
||||