mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-03-24 05:40:29 -04:00
SQL Operations Studio Public Preview 1 (0.23) release source code
This commit is contained in:
10
src/sql/parts/query/common/constants.ts
Normal file
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
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
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
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
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
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
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
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
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
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);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user