Rework how we handle custom editors (#5696)

* update how we handle editors

* small edit

* handle changing languages

* implement generic language association

* implement notebook serializers

* fix tests

* formatting

* update how we handle editors

* small edit

* handle changing languages

* implement generic language association

* implement notebook serializers

* fix tests

* formatting

* fix broken

* fix compile

* fix tests

* add back in removed note book contributions

* fix layering

* fix compile errors

* fix workbench

* fix hanging promises

* idk why these changed

* fix change

* add comments to language change code

* fix a few bugs

* add query plan association
This commit is contained in:
Anthony Dresser
2019-11-24 19:22:11 -08:00
committed by GitHub
parent f3a6fc6f88
commit 43387f0d0b
50 changed files with 988 additions and 873 deletions

View File

@@ -30,9 +30,10 @@ import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilit
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { viewColumnToEditorGroup } from 'vs/workbench/api/common/shared/editor';
import { notebookModeId } from 'sql/workbench/browser/customInputConverter';
import { localize } from 'vs/nls';
import { IFileService } from 'vs/platform/files/common/files';
import { UntitledNotebookInput } from 'sql/workbench/contrib/notebook/common/models/untitledNotebookInput';
import { FileNotebookInput } from 'sql/workbench/contrib/notebook/common/models/fileNotebookInput';
import { find } from 'vs/base/common/arrays';
class MainThreadNotebookEditor extends Disposable {
@@ -453,13 +454,18 @@ export class MainThreadNotebookDocumentsAndEditors extends Disposable implements
};
let isUntitled: boolean = uri.scheme === Schemas.untitled;
const fileInput = isUntitled ? this._untitledEditorService.createOrGet(uri, notebookModeId, options.initialContent) :
this._editorService.createInput({ resource: uri, mode: notebookModeId });
let input = this._instantiationService.createInstance(NotebookInput, path.basename(uri.fsPath), uri, fileInput);
const fileInput = isUntitled ? this._untitledEditorService.createOrGet(uri, 'notebook', options.initialContent) :
this._editorService.createInput({ resource: uri, mode: 'notebook' });
let input: NotebookInput;
if (isUntitled) {
input = this._instantiationService.createInstance(UntitledNotebookInput, path.basename(uri.fsPath), uri, fileInput);
} else {
input = this._instantiationService.createInstance(FileNotebookInput, path.basename(uri.fsPath), uri, fileInput);
}
input.defaultKernel = options.defaultKernel;
input.connectionProfile = new ConnectionProfile(this._capabilitiesService, options.connectionProfile);
if (isUntitled) {
let untitledModel = await input.textInput.resolve();
let untitledModel = await (input as UntitledNotebookInput).textInput.resolve();
await untitledModel.load();
input.untitledEditorModel = untitledModel;
if (options.initialDirtyState === false) {

View File

@@ -1,246 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { EditorInput, IEditorInput } from 'vs/workbench/common/editor';
import { IInstantiationService, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
import { URI } from 'vs/base/common/uri';
import { QueryResultsInput } from 'sql/workbench/contrib/query/common/queryResultsInput';
import { QueryInput } from 'sql/workbench/contrib/query/common/queryInput';
import { IQueryEditorOptions } from 'sql/workbench/services/queryEditor/common/queryEditorService';
import { QueryPlanInput } from 'sql/workbench/contrib/queryPlan/common/queryPlanInput';
import { NotebookInput } from 'sql/workbench/contrib/notebook/browser/models/notebookInput';
import { INotebookService } from 'sql/workbench/services/notebook/browser/notebookService';
import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput';
import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput';
import { find } from 'vs/base/common/arrays';
////// Exported public functions/vars
// prefix for untitled sql editors
export const untitledFilePrefix = 'SQLQuery';
// mode identifier for SQL mode
export const sqlModeId = 'sql';
export const notebookModeId = 'notebook';
/**
* Checks if the specified input is supported by one our custom input types, and if so convert it
* to that type.
* @param input The input to check for conversion
* @param options Editor options for controlling the conversion
* @param instantiationService The instantiation service to use to create the new input types
*/
export function convertEditorInput(input: EditorInput, options: IQueryEditorOptions, instantiationService: IInstantiationService): EditorInput {
let denyQueryEditor: boolean = options && options.denyQueryEditor;
let untitledEditorInput: UntitledEditorInput = input as UntitledEditorInput;
let mode: string = (untitledEditorInput && untitledEditorInput.getMode) ? untitledEditorInput.getMode() : 'sql';
if (input && !denyQueryEditor) {
let uri: URI;
if (mode === 'sql') {
//QueryInput
uri = getQueryEditorFileUri(input);
if (uri) {
const queryResultsInput: QueryResultsInput = instantiationService.createInstance(QueryResultsInput, uri.toString(true));
let queryInput: QueryInput = instantiationService.createInstance(QueryInput, '', input, queryResultsInput, undefined);
return queryInput;
}
//QueryPlanInput
uri = getQueryPlanEditorUri(input);
if (uri) {
return instantiationService.createInstance(QueryPlanInput, uri, undefined);
}
}
//Notebook
uri = getNotebookEditorUri(input, instantiationService);
if (uri) {
let notebookInput: NotebookInput = instantiationService.createInstance(NotebookInput, input.getName(), uri, input);
return notebookInput;
}
}
return input;
}
/**
* Gets the resource of the input if it's one of the ones we support.
* @param input The IEditorInput to get the resource of
*/
export function getSupportedInputResource(input: IEditorInput): URI {
if (input instanceof UntitledEditorInput) {
let untitledCast: UntitledEditorInput = <UntitledEditorInput>input;
if (untitledCast) {
return untitledCast.getResource();
}
}
if (input instanceof FileEditorInput) {
let fileCast: FileEditorInput = <FileEditorInput>input;
if (fileCast) {
return fileCast.getResource();
}
}
if (input instanceof ResourceEditorInput) {
let resourceCast: ResourceEditorInput = <ResourceEditorInput>input;
if (resourceCast) {
return resourceCast.getResource();
}
}
return undefined;
}
////// Non-Exported Private functions/vars
// file extensions for the inputs we support (should be all upper case for comparison)
const sqlFileTypes = ['SQL'];
const sqlPlanFileTypes = ['SQLPLAN'];
/**
* If input is a supported query editor file, return it's URI. Otherwise return undefined.
* @param input The EditorInput to retrieve the URI of
*/
function getQueryEditorFileUri(input: EditorInput): URI {
if (!input || !input.getName()) {
return undefined;
}
// If this editor is not already of type queryinput
if (!(input instanceof QueryInput)) {
// If this editor has a URI
let uri: URI = getSupportedInputResource(input);
if (uri) {
let isValidUri: boolean = !!uri && !!uri.toString;
if (isValidUri && (hasFileExtension(sqlFileTypes, input, true) || hasSqlFileMode(input))) {
return uri;
}
}
}
return undefined;
}
/**
* If input is a supported query plan editor file (.sqlplan), return it's URI. Otherwise return undefined.
* @param input The EditorInput to get the URI of
*/
function getQueryPlanEditorUri(input: EditorInput): URI {
if (!input || !input.getName()) {
return undefined;
}
// If this editor is not already of type queryinput
if (!(input instanceof QueryPlanInput)) {
let uri: URI = getSupportedInputResource(input);
if (uri) {
if (hasFileExtension(sqlPlanFileTypes, input, false)) {
return uri;
}
}
}
return undefined;
}
/**
* If input is a supported notebook editor file (.ipynb), return it's URI. Otherwise return undefined.
* @param input The EditorInput to get the URI of.
*/
function getNotebookEditorUri(input: EditorInput, instantiationService: IInstantiationService): URI {
if (!input || !input.getName()) {
return undefined;
}
// If this editor is not already of type notebook input
if (!(input instanceof NotebookInput)) {
let uri: URI = getSupportedInputResource(input);
if (uri) {
if (hasFileExtension(getNotebookFileExtensions(instantiationService), input, false) || hasNotebookFileMode(input)) {
return uri;
}
}
}
return undefined;
}
function getNotebookFileExtensions(instantiationService: IInstantiationService): string[] {
return withService<INotebookService, string[]>(instantiationService, INotebookService, notebookService => {
return notebookService.getSupportedFileExtensions();
});
}
/**
* Checks whether the given EditorInput is set to either undefined or notebook mode
* @param input The EditorInput to check the mode of
*/
function hasNotebookFileMode(input: EditorInput): boolean {
if (input instanceof UntitledEditorInput) {
let untitledCast: UntitledEditorInput = <UntitledEditorInput>input;
return (untitledCast && untitledCast.getMode() === notebookModeId);
}
return false;
}
function withService<TService, TResult>(instantiationService: IInstantiationService, serviceId: ServiceIdentifier<TService>, action: (service: TService) => TResult, ): TResult {
return instantiationService.invokeFunction(accessor => {
let service = accessor.get(serviceId);
return action(service);
});
}
/**
* Checks whether the given EditorInput is set to either undefined or sql mode
* @param input The EditorInput to check the mode of
*/
function hasSqlFileMode(input: EditorInput): boolean {
if (input instanceof UntitledEditorInput) {
let untitledCast: UntitledEditorInput = <UntitledEditorInput>input;
return untitledCast && (untitledCast.getMode() === undefined || untitledCast.getMode() === sqlModeId);
}
return false;
}
/**
* Checks whether the name of the specified input has an extension that is
* @param extensions The extensions to check for
* @param input The input to check for the specified extensions
*/
function hasFileExtension(extensions: string[], input: EditorInput, checkUntitledFileType: boolean): boolean {
// Check the extension type
let lastPeriodIndex = input.getName().lastIndexOf('.');
if (lastPeriodIndex > -1) {
let extension: string = input.getName().substr(lastPeriodIndex + 1).toUpperCase();
return !!find(extensions, x => x === extension);
}
// Check for untitled file type
if (checkUntitledFileType && find(input.getName(), x => x === untitledFilePrefix)) {
return true;
}
// Return false if not a queryEditor file
return false;
}
// Returns file mode - notebookModeId or sqlModeId
export function getFileMode(instantiationService: IInstantiationService, resource: URI): string {
if (!resource) {
return sqlModeId;
}
return withService<INotebookService, string>(instantiationService, INotebookService, notebookService => {
for (const editor of notebookService.listNotebookEditors()) {
if (editor.notebookParams.notebookUri === resource) {
return notebookModeId;
}
}
return sqlModeId;
});
}

View File

@@ -0,0 +1,51 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IModeSupport, IEditorInput } from 'vs/workbench/common/editor';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { getCodeEditor } from 'vs/editor/browser/editorBrowser';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { localize } from 'vs/nls';
import { Registry } from 'vs/platform/registry/common/platform';
import { ILanguageAssociationRegistry, Extensions as LanguageAssociationExtensions } from 'sql/workbench/common/languageAssociation';
const languageAssociationRegistry = Registry.as<ILanguageAssociationRegistry>(LanguageAssociationExtensions.LanguageAssociations);
/**
* Handles setting a mode from the editor status and converts inputs if necessary
*/
export async function setMode(accessor: ServicesAccessor, modeSupport: IModeSupport, activeEditor: IEditorInput, language: string): Promise<void> {
const editorService = accessor.get(IEditorService);
const instantiationService = accessor.get(IInstantiationService);
const activeWidget = getCodeEditor(editorService.activeTextEditorWidget);
const activeControl = editorService.activeControl;
const textModel = activeWidget.getModel();
const oldLanguage = textModel.getLanguageIdentifier().language;
if (language !== oldLanguage) {
const oldInputCreator = languageAssociationRegistry.getAssociations().filter(e => e.language === oldLanguage)[0]; // who knows how to handle the current language
const newInputCreator = languageAssociationRegistry.getAssociations().filter(e => e.language === language)[0]; // who knows how to handle the requested language
if ((oldInputCreator || newInputCreator) && activeEditor.isDirty()) { // theres some issues with changing the language on a dirty file with one of our editors (we should look into this)
const notificationService = accessor.get(INotificationService);
notificationService.error(localize('languageChangeUnsupported', "Changing editor types on unsaved files is unsupported"));
return;
}
modeSupport.setMode(language);
let input: IEditorInput;
if (oldInputCreator) { // only transform the input if we have someone who knows how to deal with it (e.x QueryInput -> UntitledInput, etc)
input = oldInputCreator.baseInputCreator(activeEditor);
}
if (newInputCreator) { // if we know how to handle the new language, tranform the input and replace the editor (e.x notebook, sql, etc)
const newInput = instantiationService.invokeFunction(newInputCreator.creator, input || activeEditor);
if (newInput) { // the factory will return undefined if it doesn't know how to handle the input
await editorService.replaceEditors([{ editor: activeEditor, replacement: newInput }], activeControl.group);
}
} else if (oldInputCreator) { // if we don't know handle to handle the new language but we know how to handle the current language, replace the editor with the reverted input (e.x sql -> text)
await editorService.replaceEditors([{ editor: activeEditor, replacement: input }], activeControl.group);
} // otherwise we don't know the old language and we don't know the new langauge, so don't do anything and just let vscode handle it (e.x text -> powershell)
}
}

View File

@@ -12,12 +12,12 @@ import {
import { EditDataInput } from 'sql/workbench/contrib/editData/browser/editDataInput';
import { IInsightsDialogService } from 'sql/workbench/services/insights/browser/insightsDialogService';
import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/browser/objectExplorerService';
import { QueryInput } from 'sql/workbench/contrib/query/common/queryInput';
import { DashboardInput } from 'sql/workbench/contrib/dashboard/browser/dashboardInput';
import { ProfilerInput } from 'sql/workbench/contrib/profiler/browser/profilerInput';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IInsightsConfig } from 'sql/platform/dashboard/browser/insightRegistry';
import { QueryEditorInput } from 'sql/workbench/contrib/query/common/queryEditorInput';
export function replaceConnection(oldUri: string, newUri: string, connectionService: IConnectionManagementService): Promise<IConnectionResult> {
return new Promise<IConnectionResult>((resolve, reject) => {
@@ -87,7 +87,7 @@ export function getCurrentGlobalConnection(objectExplorerService: IObjectExplore
let activeInput = workbenchEditorService.activeEditor;
if (activeInput) {
if (activeInput instanceof QueryInput || activeInput instanceof EditDataInput || activeInput instanceof DashboardInput) {
if (activeInput instanceof QueryEditorInput || activeInput instanceof EditDataInput || activeInput instanceof DashboardInput) {
connection = connectionManagementService.getConnectionProfile(activeInput.uri);
}
else if (activeInput instanceof ProfilerInput) {

View File

@@ -0,0 +1,12 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { EditorReplacementContribution } from 'sql/workbench/common/editorReplacerContribution';
import { Registry } from 'vs/platform/registry/common/platform';
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
const workbenchContributionsRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
workbenchContributionsRegistry.registerWorkbenchContribution(EditorReplacementContribution, LifecyclePhase.Starting);

View File

@@ -0,0 +1,83 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IEditorService, IOpenEditorOverride } from 'vs/workbench/services/editor/common/editorService';
import { IEditorInput } from 'vs/workbench/common/editor';
import { IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor';
import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService';
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
import { IModeService } from 'vs/editor/common/services/modeService';
import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Registry } from 'vs/platform/registry/common/platform';
import { ILanguageAssociationRegistry, Extensions as LanguageAssociationExtensions } from 'sql/workbench/common/languageAssociation';
const languageAssociationRegistry = Registry.as<ILanguageAssociationRegistry>(LanguageAssociationExtensions.LanguageAssociations);
export class EditorReplacementContribution implements IWorkbenchContribution {
private editorOpeningListener: IDisposable;
constructor(
@IEditorService private readonly editorService: IEditorService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IModeService private readonly modeService: IModeService
) {
this.editorOpeningListener = this.editorService.overrideOpenEditor((editor, options, group) => this.onEditorOpening(editor, options, group));
}
private onEditorOpening(editor: IEditorInput, options: IEditorOptions | ITextEditorOptions | undefined, group: IEditorGroup): IOpenEditorOverride | undefined {
// If the resource was already opened before in the group, do not prevent
// the opening of that resource. Otherwise we would have the same settings
// opened twice (https://github.com/Microsoft/vscode/issues/36447)
// if (group.isOpened(editor)) {
// return undefined;
// }
if (!(editor instanceof FileEditorInput) && !(editor instanceof UntitledEditorInput)) {
return undefined;
}
let language: string;
if (editor instanceof FileEditorInput) {
language = editor.getPreferredMode();
} else if (editor instanceof UntitledEditorInput) {
language = editor.getMode();
} else {
return undefined;
}
if (!language) { // in the case the input doesn't have a preferred mode set we will attempt to guess the mode from the file path
language = this.modeService.getModeIdByFilepathOrFirstLine(editor.getResource());
}
if (!language) {
const defaultInputCreator = languageAssociationRegistry.getAssociations().filter(e => e.isDefault)[0];
if (defaultInputCreator) {
editor.setMode(defaultInputCreator.language);
const newInput = this.instantiationService.invokeFunction(defaultInputCreator.creator, editor);
if (newInput) {
return { override: this.editorService.openEditor(newInput, options, group) };
}
}
} else {
const inputCreator = languageAssociationRegistry.getAssociations().filter(e => e.language === language)[0];
if (inputCreator) {
const newInput = this.instantiationService.invokeFunction(inputCreator.creator, editor);
if (newInput) {
return { override: this.editorService.openEditor(newInput, options, group) };
}
}
}
return undefined;
}
dispose(): void {
dispose(this.editorOpeningListener);
}
}

View File

@@ -0,0 +1,34 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Registry } from 'vs/platform/registry/common/platform';
import { IEditorInput } from 'vs/workbench/common/editor';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
export type InputCreator = (servicesAccessor: ServicesAccessor, activeEditor: IEditorInput) => IEditorInput | undefined;
export type BaseInputCreator = (activeEditor: IEditorInput) => IEditorInput;
export interface ILanguageAssociationRegistry {
registerLanguageAssociation(language: string, creator: InputCreator, baseInputCreator: BaseInputCreator, isDefault?: boolean): void;
getAssociations(): Array<{ language: string, creator: InputCreator, baseInputCreator: BaseInputCreator, isDefault: boolean }>;
}
class LanguageAssociationRegistry implements ILanguageAssociationRegistry {
private associations = new Array<{ language: string, creator: InputCreator, baseInputCreator: BaseInputCreator, isDefault: boolean }>();
registerLanguageAssociation(language: string, creator: InputCreator, baseInputCreator: BaseInputCreator, isDefault: boolean = false): void {
this.associations.push({ language, creator, baseInputCreator, isDefault });
}
getAssociations(): Array<{ language: string, creator: InputCreator, baseInputCreator: BaseInputCreator, isDefault: boolean }> {
return this.associations.slice();
}
}
export const Extensions = {
LanguageAssociations: 'workbench.contributions.editor.languageAssociation'
};
Registry.add(Extensions.LanguageAssociations, new LanguageAssociationRegistry());

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as ConnectionConstants from 'sql/platform/connection/common/constants';
import { QueryInput } from 'sql/workbench/contrib/query/common/queryInput';
import { QueryEditorInput } from 'sql/workbench/contrib/query/common/queryEditorInput';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { URI } from 'vs/base/common/uri';
@@ -26,11 +26,8 @@ export function getSqlConfigValue<T>(workspaceConfigService: IConfigurationServi
export function getEditorUri(input: IEditorInput): string {
let uri: URI;
if (input instanceof QueryInput) {
let queryCast: QueryInput = <QueryInput>input;
if (queryCast) {
uri = queryCast.getResource();
}
if (input instanceof QueryEditorInput) {
uri = input.getResource();
}
if (uri) {

View File

@@ -13,9 +13,9 @@ import { INotificationService, Severity } from 'vs/platform/notification/common/
import { URI } from 'vs/base/common/uri';
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { QueryInput } from 'sql/workbench/contrib/query/common/queryInput';
import { IInsightsConfig } from 'sql/platform/dashboard/browser/insightRegistry';
import { IInsightOptions } from 'sql/workbench/contrib/charts/common/interfaces';
import { QueryEditorInput } from 'sql/workbench/contrib/query/common/queryEditorInput';
import { IFileService } from 'vs/platform/files/common/files';
import { IFileDialogService, FileFilter } from 'vs/platform/dialogs/common/dialogs';
import { VSBuffer } from 'vs/base/common/buffer';
@@ -89,7 +89,7 @@ export class CreateInsightAction extends Action {
private getActiveUriString(): string {
let editor = this.editorService.activeEditor;
if (editor instanceof QueryInput) {
if (editor instanceof QueryEditorInput) {
return editor.uri;
}
return undefined;

View File

@@ -20,7 +20,7 @@ import { ipcRenderer as ipc } from 'electron';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { localize } from 'vs/nls';
import { QueryInput } from 'sql/workbench/contrib/query/common/queryInput';
import { QueryEditorInput } from 'sql/workbench/contrib/query/common/queryEditorInput';
import { URI } from 'vs/base/common/uri';
import { ILogService } from 'vs/platform/log/common/log';
import { INotificationService } from 'vs/platform/notification/common/notification';
@@ -202,21 +202,18 @@ export class CommandLineWorkbenchContribution implements IWorkbenchContribution,
// If an open and connectable query editor exists for the given URI, attach it to the connection profile
private async processFile(uriString: string, profile: IConnectionProfile, warnOnConnectFailure: boolean): Promise<void> {
let activeEditor = this._editorService.editors.filter(v => v.getResource().toString() === uriString).pop();
if (activeEditor) {
let queryInput = activeEditor as QueryInput;
if (queryInput && queryInput.state.connected) {
let options: IConnectionCompletionOptions = {
params: { connectionType: ConnectionType.editor, runQueryOnCompletion: RunQueryOnConnectionMode.none, input: queryInput },
saveTheConnection: false,
showDashboard: false,
showConnectionDialogOnError: warnOnConnectFailure,
showFirewallRuleOnError: warnOnConnectFailure
};
if (this._notificationService) {
this._notificationService.status(localize('connectingQueryLabel', "Connecting query file"), { hideAfter: 2500 });
}
await this._connectionManagementService.connect(profile, uriString, options);
if (activeEditor instanceof QueryEditorInput && activeEditor.state.connected) {
let options: IConnectionCompletionOptions = {
params: { connectionType: ConnectionType.editor, runQueryOnCompletion: RunQueryOnConnectionMode.none, input: activeEditor },
saveTheConnection: false,
showDashboard: false,
showConnectionDialogOnError: warnOnConnectFailure,
showFirewallRuleOnError: warnOnConnectFailure
};
if (this._notificationService) {
this._notificationService.status(localize('connectingQueryLabel', "Connecting query file"), { hideAfter: 2500 });
}
await this._connectionManagementService.connect(profile, uriString, options);
}
}

View File

@@ -21,11 +21,16 @@ import { TestCommandService } from 'vs/editor/test/browser/editorTestServices';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { assertThrowsAsync } from 'sql/base/test/common/async';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { TestEditorService, TestDialogService } from 'vs/workbench/test/workbenchTestServices';
import { QueryInput, QueryEditorState } from 'sql/workbench/contrib/query/common/queryInput';
import { URI } from 'vs/base/common/uri';
import { ILogService, NullLogService } from 'vs/platform/log/common/log';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
import { TestEditorService, TestDialogService } from 'vs/workbench/test/workbenchTestServices';
import { URI } from 'vs/base/common/uri';
import { UntitledQueryEditorInput } from 'sql/workbench/contrib/query/common/untitledQueryEditorInput';
import { TestQueryModelService } from 'sql/platform/query/test/common/testQueryModelService';
import { Event } from 'vs/base/common/event';
import { IQueryModelService } from 'sql/platform/query/common/queryModel';
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService';
@@ -129,6 +134,7 @@ suite('commandLineService tests', () => {
function getConfigurationServiceMock(showConnectDialogOnStartup: boolean): TypeMoq.Mock<IConfigurationService> {
let configurationService = TypeMoq.Mock.ofType<IConfigurationService>(TestConfigurationService);
configurationService.setup((c) => c.getValue(TypeMoq.It.isAnyString())).returns((config: string) => showConnectDialogOnStartup);
configurationService.object.onDidChangeConfiguration = Event.None;
return configurationService;
}
@@ -377,23 +383,26 @@ suite('commandLineService tests', () => {
return Promise.resolve('unused');
}).verifiable(TypeMoq.Times.once());
connectionManagementService.setup(c => c.getConnectionProfileById(TypeMoq.It.isAnyString())).returns(() => originalProfile);
connectionManagementService.setup(c => c.onDisconnect).returns(() => Event.None);
connectionManagementService.setup(c => c.ensureDefaultLanguageFlavor(TypeMoq.It.isAny()));
const configurationService = getConfigurationServiceMock(true);
const queryInput: TypeMoq.Mock<QueryInput> = TypeMoq.Mock.ofType<QueryInput>(QueryInput);
const querymodelService = TypeMoq.Mock.ofType<IQueryModelService>(TestQueryModelService, TypeMoq.MockBehavior.Strict);
querymodelService.setup(c => c.onRunQueryStart).returns(() => Event.None);
querymodelService.setup(c => c.onRunQueryComplete).returns(() => Event.None);
const instantiationService = new TestInstantiationService();
let uri = URI.file(args._[0]);
const queryState = new QueryEditorState();
queryState.connected = true;
queryInput.setup(q => q.state).returns(() => queryState);
queryInput.setup(q => q.getResource()).returns(() => uri).verifiable(TypeMoq.Times.once());
const untitledEditorInput = new UntitledEditorInput(uri, false, '', '', '', instantiationService, undefined, undefined);
const queryInput = new UntitledQueryEditorInput(undefined, untitledEditorInput, undefined, connectionManagementService.object, querymodelService.object, configurationService.object, undefined);
queryInput.state.connected = true;
const editorService: TypeMoq.Mock<IEditorService> = TypeMoq.Mock.ofType<IEditorService>(TestEditorService, TypeMoq.MockBehavior.Strict);
editorService.setup(e => e.editors).returns(() => [queryInput.object]);
editorService.setup(e => e.editors).returns(() => [queryInput]);
connectionManagementService.setup(c =>
c.connect(TypeMoq.It.is<ConnectionProfile>(p => p.serverName === 'myserver' && p.authenticationType === Constants.sqlLogin),
uri.toString(),
TypeMoq.It.is<IConnectionCompletionOptions>(i => i.params.input === queryInput.object && i.params.connectionType === ConnectionType.editor))
TypeMoq.It.is<IConnectionCompletionOptions>(i => i.params.input === queryInput && i.params.connectionType === ConnectionType.editor))
).verifiable(TypeMoq.Times.once());
let contribution = getCommandLineContribution(connectionManagementService.object, configurationService.object, capabilitiesService, undefined, editorService.object);
await contribution.processCommandLine(args);
queryInput.verifyAll();
connectionManagementService.verifyAll();
});

View File

@@ -12,7 +12,7 @@ import { INotificationService, INotificationActions } from 'vs/platform/notifica
import Severity from 'vs/base/common/severity';
import { IDialogService, IConfirmation, IConfirmationResult } from 'vs/platform/dialogs/common/dialogs';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { QueryInput } from 'sql/workbench/contrib/query/common/queryInput';
import { QueryEditorInput } from 'sql/workbench/contrib/query/common/queryEditorInput';
import { EditDataInput } from 'sql/workbench/contrib/editData/browser/editDataInput';
import { DashboardInput } from 'sql/workbench/contrib/dashboard/browser/dashboardInput';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
@@ -156,7 +156,7 @@ export class GetCurrentConnectionStringAction extends Action {
public run(): Promise<void> {
return new Promise<void>((resolve, reject) => {
let activeInput = this._editorService.activeEditor;
if (activeInput && (activeInput instanceof QueryInput || activeInput instanceof EditDataInput || activeInput instanceof DashboardInput)
if (activeInput && (activeInput instanceof QueryEditorInput || activeInput instanceof EditDataInput || activeInput instanceof DashboardInput)
&& this._connectionManagementService.isConnected(activeInput.uri)) {
let includePassword = false;
let connectionProfile = this._connectionManagementService.getConnectionProfile(activeInput.uri);

View File

@@ -60,9 +60,7 @@ export class EditDataInput extends EditorInput implements IConnectableInput {
// re-emit sql editor events through this editor if it exists
if (this._sql) {
this._register(this._sql.onDidChangeDirty(() => this._onDidChangeDirty.fire()));
this._sql.disableSaving();
}
this.disableSaving();
//TODO determine is this is a table or a view
this._objectType = 'TABLE';

View File

@@ -29,6 +29,9 @@ import { Deferred } from 'sql/base/common/promise';
import { NotebookTextFileModel } from 'sql/workbench/contrib/notebook/browser/models/notebookTextFileModel';
import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration';
import { ResourceEditorModel } from 'vs/workbench/common/editor/resourceEditorModel';
import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput';
import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput';
import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel';
export type ModeViewSaveHandler = (handle: number) => Thenable<boolean>;
@@ -195,8 +198,9 @@ export class NotebookEditorModel extends EditorModel {
}
}
export class NotebookInput extends EditorInput {
public static ID: string = 'workbench.editorinputs.notebookInput';
type TextInput = ResourceEditorInput | UntitledEditorInput | FileEditorInput;
export abstract class NotebookInput extends EditorInput {
private _providerId: string;
private _providers: string[];
private _standardKernels: IStandardKernelWithProvider[];
@@ -217,7 +221,7 @@ export class NotebookInput extends EditorInput {
constructor(private _title: string,
private resource: URI,
private _textInput: UntitledEditorInput,
private _textInput: TextInput,
@ITextModelService private textModelService: ITextModelService,
@IInstantiationService private instantiationService: IInstantiationService,
@INotebookService private notebookService: INotebookService,
@@ -233,7 +237,7 @@ export class NotebookInput extends EditorInput {
}
}
public get textInput(): UntitledEditorInput {
public get textInput(): TextInput {
return this._textInput;
}
@@ -319,9 +323,7 @@ export class NotebookInput extends EditorInput {
this._layoutChanged.fire();
}
public getTypeId(): string {
return NotebookInput.ID;
}
public abstract getTypeId(): string;
getResource(): URI {
return this.resource;
@@ -352,7 +354,9 @@ export class NotebookInput extends EditorInput {
textOrUntitledEditorModel = this._untitledEditorModel;
} else {
let resolvedInput = await this._textInput.resolve();
resolvedInput.textEditorModel.onBeforeAttached();
if (!(resolvedInput instanceof BinaryEditorModel)) {
resolvedInput.textEditorModel.onBeforeAttached();
}
textOrUntitledEditorModel = resolvedInput;
}
} else {

View File

@@ -5,7 +5,17 @@
import { Registry } from 'vs/platform/registry/common/platform';
import { EditorDescriptor, IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/browser/editor';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput';
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
import { localize } from 'vs/nls';
import { IEditorInputFactoryRegistry, Extensions as EditorInputFactoryExtensions } from 'vs/workbench/common/editor';
import { ILanguageAssociationRegistry, Extensions as LanguageAssociationExtensions } from 'sql/workbench/common/languageAssociation';
import { UntitledNotebookInput } from 'sql/workbench/contrib/notebook/common/models/untitledNotebookInput';
import { FileNotebookInput } from 'sql/workbench/contrib/notebook/common/models/fileNotebookInput';
import { FileNoteBookEditorInputFactory, UntitledNoteBookEditorInputFactory } from 'sql/workbench/contrib/notebook/common/models/nodebookInputFactory';
import { IWorkbenchActionRegistry, Extensions as WorkbenchActionsExtensions } from 'vs/workbench/common/actions';
import { SyncActionDescriptor, registerAction, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
import { NotebookInput } from 'sql/workbench/contrib/notebook/browser/models/notebookInput';
@@ -14,7 +24,6 @@ import { NewNotebookAction } from 'sql/workbench/contrib/notebook/browser/notebo
import { KeyMod } from 'vs/editor/common/standalone/standaloneBase';
import { KeyCode } from 'vs/base/common/keyCodes';
import { IConfigurationRegistry, Extensions as ConfigExtensions } from 'vs/platform/configuration/common/configurationRegistry';
import { localize } from 'vs/nls';
import { GridOutputComponent } from 'sql/workbench/contrib/notebook/browser/outputs/gridOutput.component';
import { PlotlyOutputComponent } from 'sql/workbench/contrib/notebook/browser/outputs/plotlyOutput.component';
import { registerComponentType } from 'sql/workbench/contrib/notebook/browser/outputs/mimeRegistry';
@@ -30,7 +39,6 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { TreeViewItemHandleArg } from 'sql/workbench/common/views';
import { ConnectedContext } from 'azdata';
import { TreeNodeContextKey } from 'sql/workbench/contrib/objectExplorer/common/treeNodeContextKey';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ObjectExplorerActionsContext } from 'sql/workbench/contrib/objectExplorer/browser/objectExplorerActions';
import { ItemContextKey } from 'sql/workbench/contrib/dashboard/browser/widgets/explorer/explorerTreeContext';
import { ManageActionContext } from 'sql/workbench/browser/actions';
@@ -39,18 +47,30 @@ import { MarkdownOutputComponent } from 'sql/workbench/contrib/notebook/browser/
import { registerCellComponent } from 'sql/platform/notebooks/common/outputRegistry';
import { TextCellComponent } from 'sql/workbench/contrib/notebook/browser/cellViews/textCell.component';
// Model View editor registration
const viewModelEditorDescriptor = new EditorDescriptor(
NotebookEditor,
NotebookEditor.ID,
'Notebook'
);
Registry.as<IEditorInputFactoryRegistry>(EditorInputFactoryExtensions.EditorInputFactories)
.registerEditorInputFactory(FileNotebookInput.ID, FileNoteBookEditorInputFactory);
Registry.as<IEditorInputFactoryRegistry>(EditorInputFactoryExtensions.EditorInputFactories)
.registerEditorInputFactory(UntitledNotebookInput.ID, UntitledNoteBookEditorInputFactory);
Registry.as<ILanguageAssociationRegistry>(LanguageAssociationExtensions.LanguageAssociations)
.registerLanguageAssociation('notebook', (accessor, editor) => {
const instantiationService = accessor.get(IInstantiationService);
if (editor instanceof FileEditorInput) {
return instantiationService.createInstance(FileNotebookInput, editor.getName(), editor.getResource(), editor);
} else if (editor instanceof UntitledEditorInput) {
return instantiationService.createInstance(UntitledNotebookInput, editor.getName(), editor.getResource(), editor);
} else {
return undefined;
}
}, (editor: NotebookInput) => editor.textInput);
Registry.as<IEditorRegistry>(EditorExtensions.Editors)
.registerEditor(viewModelEditorDescriptor, [new SyncDescriptor(NotebookInput)]);
.registerEditor(new EditorDescriptor(NotebookEditor, NotebookEditor.ID, localize('notebookEditor.name', "Notebook Editor")), [new SyncDescriptor(UntitledNotebookInput), new SyncDescriptor(FileNotebookInput)]);
// Global Actions
let actionRegistry = <IWorkbenchActionRegistry>Registry.as(Extensions.WorkbenchActions);
const actionRegistry = Registry.as<IWorkbenchActionRegistry>(WorkbenchActionsExtensions.WorkbenchActions);
actionRegistry.registerWorkbenchAction(
new SyncActionDescriptor(
@@ -145,7 +165,7 @@ registerAction({
}
});
const configurationRegistry = <IConfigurationRegistry>Registry.as(ConfigExtensions.Configuration);
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigExtensions.Configuration);
configurationRegistry.registerConfiguration({
'id': 'notebook',
'title': 'Notebook',

View File

@@ -14,7 +14,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService';
import { MouseWheelSupport } from 'sql/base/browser/ui/table/plugins/mousewheelTableScroll.plugin';
import { AutoColumnSize } from 'sql/base/browser/ui/table/plugins/autoSizeColumns.plugin';
import { AdditionalKeyBindings } from 'sql/base/browser/ui/table/plugins/additionalKeyBindings.plugin';
import { RESULTS_GRID_DEFAULTS } from 'sql/workbench/contrib/query/common/resultsGridContribution';
import { RESULTS_GRID_DEFAULTS } from 'sql/workbench/contrib/query/common/resultsGrid.contribution';
import { values } from 'vs/base/common/collections';
/**

View File

@@ -0,0 +1,48 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput';
import { URI } from 'vs/base/common/uri';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { NotebookInput } from 'sql/workbench/contrib/notebook/browser/models/notebookInput';
import { INotebookService } from 'sql/workbench/services/notebook/browser/notebookService';
export class FileNotebookInput extends NotebookInput {
public static ID: string = 'workbench.editorinputs.fileNotebookInput';
constructor(
title: string,
resource: URI,
textInput: FileEditorInput,
@ITextModelService textModelService: ITextModelService,
@IInstantiationService instantiationService: IInstantiationService,
@INotebookService notebookService: INotebookService,
@IExtensionService extensionService: IExtensionService
) {
super(title, resource, textInput, textModelService, instantiationService, notebookService, extensionService);
}
public get textInput(): FileEditorInput {
return super.textInput as FileEditorInput;
}
public getPreferredMode(): string {
return this.textInput.getPreferredMode();
}
public setMode(mode: string): void {
this.textInput.setMode(mode);
}
public setPreferredMode(mode: string): void {
this.textInput.setPreferredMode(mode);
}
public getTypeId(): string {
return FileNotebookInput.ID;
}
}

View File

@@ -0,0 +1,47 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IEditorInputFactory, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions } from 'vs/workbench/common/editor';
import { Registry } from 'vs/platform/registry/common/platform';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
import { FILE_EDITOR_INPUT_ID } from 'vs/workbench/contrib/files/common/files';
import { FileNotebookInput } from 'sql/workbench/contrib/notebook/common/models/fileNotebookInput';
import { UntitledNotebookInput } from 'sql/workbench/contrib/notebook/common/models/untitledNotebookInput';
import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput';
const editorInputFactoryRegistry = Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactories);
export class FileNoteBookEditorInputFactory implements IEditorInputFactory {
serialize(editorInput: FileNotebookInput): string {
const factory = editorInputFactoryRegistry.getEditorInputFactory(FILE_EDITOR_INPUT_ID);
if (factory) {
return factory.serialize(editorInput.textInput); // serialize based on the underlying input
}
return undefined;
}
deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): FileNotebookInput | undefined {
const factory = editorInputFactoryRegistry.getEditorInputFactory(FILE_EDITOR_INPUT_ID);
const fileEditorInput = factory.deserialize(instantiationService, serializedEditorInput) as FileEditorInput;
return instantiationService.createInstance(FileNotebookInput, fileEditorInput.getName(), fileEditorInput.getResource(), fileEditorInput);
}
}
export class UntitledNoteBookEditorInputFactory implements IEditorInputFactory {
serialize(editorInput: UntitledNotebookInput): string {
const factory = editorInputFactoryRegistry.getEditorInputFactory(UntitledEditorInput.ID);
if (factory) {
return factory.serialize(editorInput.textInput); // serialize based on the underlying input
}
return undefined;
}
deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): UntitledNotebookInput | undefined {
const factory = editorInputFactoryRegistry.getEditorInputFactory(UntitledEditorInput.ID);
const untitledEditorInput = factory.deserialize(instantiationService, serializedEditorInput) as UntitledEditorInput;
return instantiationService.createInstance(UntitledNotebookInput, untitledEditorInput.getName(), untitledEditorInput.getResource(), untitledEditorInput);
}
}

View File

@@ -0,0 +1,40 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { URI } from 'vs/base/common/uri';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
import { NotebookInput } from 'sql/workbench/contrib/notebook/browser/models/notebookInput';
import { INotebookService } from 'sql/workbench/services/notebook/browser/notebookService';
export class UntitledNotebookInput extends NotebookInput {
public static ID: string = 'workbench.editorinputs.untitledNotebookInput';
constructor(
title: string,
resource: URI,
textInput: UntitledEditorInput,
@ITextModelService textModelService: ITextModelService,
@IInstantiationService instantiationService: IInstantiationService,
@INotebookService notebookService: INotebookService,
@IExtensionService extensionService: IExtensionService
) {
super(title, resource, textInput, textModelService, instantiationService, notebookService, extensionService);
}
public get textInput(): UntitledEditorInput {
return super.textInput as UntitledEditorInput;
}
public setMode(mode: string): void {
this.textInput.setMode(mode);
}
public getTypeId(): string {
return UntitledNotebookInput.ID;
}
}

View File

@@ -20,7 +20,7 @@ import * as ConnectionConstants from 'sql/platform/connection/common/constants';
import { EditDataEditor } from 'sql/workbench/contrib/editData/browser/editDataEditor';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { QueryInput } from 'sql/workbench/contrib/query/common/queryInput';
import { QueryEditorInput } from 'sql/workbench/contrib/query/common/queryEditorInput';
import { firstIndex } from 'vs/base/common/arrays';
const singleQuote = '\'';
@@ -199,7 +199,7 @@ export class RefreshIntellisenseKeyboardAction extends Action {
public run(): Promise<void> {
const editor = this.editorService.activeEditor;
if (editor instanceof QueryInput) {
if (editor instanceof QueryEditorInput) {
this.connectionManagementService.rebuildIntelliSenseCache(editor.uri);
}
return Promise.resolve(null);

View File

@@ -19,7 +19,6 @@ import { QueryEditor } from 'sql/workbench/contrib/query/browser/queryEditor';
import { QueryResultsEditor } from 'sql/workbench/contrib/query/browser/queryResultsEditor';
import { QueryResultsInput } from 'sql/workbench/contrib/query/common/queryResultsInput';
import * as queryContext from 'sql/workbench/contrib/query/common/queryContext';
import { QueryInput } from 'sql/workbench/contrib/query/common/queryInput';
import {
RunQueryKeyboardAction, RunCurrentQueryKeyboardAction, CancelQueryKeyboardAction, RefreshIntellisenseKeyboardAction, ToggleQueryResultsKeyboardAction,
RunQueryShortcutAction, RunCurrentQueryWithActualPlanKeyboardAction, FocusOnCurrentQueryKeyboardAction, ParseSyntaxAction
@@ -33,6 +32,14 @@ import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } fr
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import { TimeElapsedStatusBarContributions, RowCountStatusBarContributions, QueryStatusStatusBarContributions } from 'sql/workbench/contrib/query/browser/statusBarItems';
import { SqlFlavorStatusbarItem } from 'sql/workbench/contrib/query/browser/flavorStatus';
import { IEditorInputFactoryRegistry, Extensions as EditorInputFactoryExtensions } from 'vs/workbench/common/editor';
import { FileQueryEditorInput } from 'sql/workbench/contrib/query/common/fileQueryEditorInput';
import { FileQueryEditorInputFactory, UntitledQueryEditorInputFactory } from 'sql/workbench/contrib/query/common/queryInputFactory';
import { UntitledQueryEditorInput } from 'sql/workbench/contrib/query/common/untitledQueryEditorInput';
import { ILanguageAssociationRegistry, Extensions as LanguageAssociationExtensions } from 'sql/workbench/common/languageAssociation';
import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput';
import { QueryEditorInput } from 'sql/workbench/contrib/query/common/queryEditorInput';
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
import { NewQueryTask, OE_NEW_QUERY_ACTION_ID, DE_NEW_QUERY_COMMAND_ID } from 'sql/workbench/contrib/query/browser/queryActions';
import { TreeNodeContextKey } from 'sql/workbench/contrib/objectExplorer/common/treeNodeContextKey';
import { MssqlNodeContext } from 'sql/workbench/contrib/dataExplorer/browser/mssqlNodeContext';
@@ -44,25 +51,30 @@ export const QueryEditorVisibleCondition = ContextKeyExpr.has(queryContext.query
export const ResultsGridFocusCondition = ContextKeyExpr.and(ContextKeyExpr.has(queryContext.resultsVisibleId), ContextKeyExpr.has(queryContext.resultsGridFocussedId));
export const ResultsMessagesFocusCondition = ContextKeyExpr.and(ContextKeyExpr.has(queryContext.resultsVisibleId), ContextKeyExpr.has(queryContext.resultsMessagesFocussedId));
// Editor
const queryResultsEditorDescriptor = new EditorDescriptor(
QueryResultsEditor,
QueryResultsEditor.ID,
'QueryResults'
);
Registry.as<IEditorInputFactoryRegistry>(EditorInputFactoryExtensions.EditorInputFactories)
.registerEditorInputFactory(FileQueryEditorInput.ID, FileQueryEditorInputFactory);
Registry.as<IEditorInputFactoryRegistry>(EditorInputFactoryExtensions.EditorInputFactories)
.registerEditorInputFactory(UntitledQueryEditorInput.ID, UntitledQueryEditorInputFactory);
Registry.as<ILanguageAssociationRegistry>(LanguageAssociationExtensions.LanguageAssociations)
.registerLanguageAssociation('sql', (accessor, editor) => {
const instantiationService = accessor.get(IInstantiationService);
const queryResultsInput = instantiationService.createInstance(QueryResultsInput, editor.getResource().toString());
if (editor instanceof FileEditorInput) {
return instantiationService.createInstance(FileQueryEditorInput, '', editor, queryResultsInput);
} else if (editor instanceof UntitledEditorInput) {
return instantiationService.createInstance(UntitledQueryEditorInput, '', editor, queryResultsInput);
} else {
return undefined;
}
}, (editor: QueryEditorInput) => editor.text, true);
Registry.as<IEditorRegistry>(EditorExtensions.Editors)
.registerEditor(queryResultsEditorDescriptor, [new SyncDescriptor(QueryResultsInput)]);
// Editor
const queryEditorDescriptor = new EditorDescriptor(
QueryEditor,
QueryEditor.ID,
'Query'
);
.registerEditor(new EditorDescriptor(QueryResultsEditor, QueryResultsEditor.ID, localize('queryResultsEditor.name', "Query Results")), [new SyncDescriptor(QueryResultsInput)]);
Registry.as<IEditorRegistry>(EditorExtensions.Editors)
.registerEditor(queryEditorDescriptor, [new SyncDescriptor(QueryInput)]);
.registerEditor(new EditorDescriptor(QueryEditor, QueryEditor.ID, localize('queryEditor.name', "Query Editor")), [new SyncDescriptor(FileQueryEditorInput), new SyncDescriptor(UntitledQueryEditorInput)]);
const actionRegistry = <IWorkbenchActionRegistry>Registry.as(Extensions.WorkbenchActions);

View File

@@ -31,7 +31,7 @@ import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileE
import { URI } from 'vs/base/common/uri';
import { IFileService, FileChangesEvent } from 'vs/platform/files/common/files';
import { QueryInput, IQueryEditorStateChange } from 'sql/workbench/contrib/query/common/queryInput';
import { QueryEditorInput, IQueryEditorStateChange } from 'sql/workbench/contrib/query/common/queryEditorInput';
import { QueryResultsEditor } from 'sql/workbench/contrib/query/browser/queryResultsEditor';
import * as queryContext from 'sql/workbench/contrib/query/common/queryContext';
import { Taskbar, ITaskbarContent } from 'sql/base/browser/ui/taskbar/taskbar';
@@ -120,8 +120,8 @@ export class QueryEditor extends BaseEditor {
}
// PUBLIC METHODS ////////////////////////////////////////////////////////////
public get input(): QueryInput | null {
return this._input as QueryInput;
public get input(): QueryEditorInput | null {
return this._input as QueryEditorInput;
}
/**
@@ -278,7 +278,7 @@ export class QueryEditor extends BaseEditor {
this.taskbar.setContent(content);
}
public async setInput(newInput: QueryInput, options: EditorOptions, token: CancellationToken): Promise<void> {
public async setInput(newInput: QueryEditorInput, options: EditorOptions, token: CancellationToken): Promise<void> {
const oldInput = this.input;
if (newInput.matches(oldInput)) {
@@ -293,7 +293,7 @@ export class QueryEditor extends BaseEditor {
}
// If we're switching editor types switch out the views
const newTextEditor = newInput.sql instanceof FileEditorInput ? this.textFileEditor : this.textResourceEditor;
const newTextEditor = newInput.text instanceof FileEditorInput ? this.textFileEditor : this.textResourceEditor;
if (newTextEditor !== this.currentTextEditor) {
this.currentTextEditor = newTextEditor;
this.splitview.removeView(0, Sizing.Distribute);
@@ -309,7 +309,7 @@ export class QueryEditor extends BaseEditor {
await Promise.all([
super.setInput(newInput, options, token),
this.currentTextEditor.setInput(newInput.sql, options, token),
this.currentTextEditor.setInput(newInput.text, options, token),
this.resultsEditor.setInput(newInput.results, options)
]);
@@ -324,7 +324,7 @@ export class QueryEditor extends BaseEditor {
}
}
private saveQueryEditorViewState(input: QueryInput): void {
private saveQueryEditorViewState(input: QueryEditorInput): void {
if (!input) {
return; // ensure we have an input to handle view state for
}

View File

@@ -19,7 +19,7 @@ import { QueryResultsInput } from 'sql/workbench/contrib/query/common/queryResul
import { QueryResultsView } from 'sql/workbench/contrib/query/browser/queryResultsView';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { RESULTS_GRID_DEFAULTS } from 'sql/workbench/contrib/query/common/resultsGridContribution';
import { RESULTS_GRID_DEFAULTS } from 'sql/workbench/contrib/query/common/resultsGrid.contribution';
export const TextCompareEditorVisible = new RawContextKey<boolean>('textCompareEditorVisible', false);

View File

@@ -9,10 +9,10 @@ import { IntervalTimer } from 'vs/base/common/async';
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { localize } from 'vs/nls';
import { QueryInput } from 'sql/workbench/contrib/query/common/queryInput';
import QueryRunner from 'sql/platform/query/common/queryRunner';
import { parseNumAsTimeString } from 'sql/platform/connection/common/utils';
import { Event } from 'vs/base/common/event';
import { QueryEditorInput } from 'sql/workbench/contrib/query/common/queryEditorInput';
import { IStatusbarService, IStatusbarEntryAccessor, StatusbarAlignment } from 'vs/workbench/services/statusbar/common/statusbar';
export class TimeElapsedStatusBarContributions extends Disposable implements IWorkbenchContribution {
@@ -56,7 +56,7 @@ export class TimeElapsedStatusBarContributions extends Disposable implements IWo
this.disposable.clear();
this.hide();
const activeInput = this.editorService.activeEditor;
if (activeInput && activeInput instanceof QueryInput && activeInput.uri) {
if (activeInput && activeInput instanceof QueryEditorInput && activeInput.uri) {
const uri = activeInput.uri;
const runner = this.queryModelService.getQueryRunner(uri);
if (runner) {
@@ -148,7 +148,7 @@ export class RowCountStatusBarContributions extends Disposable implements IWorkb
this.disposable.clear();
this.hide();
const activeInput = this.editorService.activeEditor;
if (activeInput && activeInput instanceof QueryInput && activeInput.uri) {
if (activeInput && activeInput instanceof QueryEditorInput && activeInput.uri) {
const uri = activeInput.uri;
const runner = this.queryModelService.getQueryRunner(uri);
if (runner) {
@@ -227,7 +227,7 @@ export class QueryStatusStatusBarContributions extends Disposable implements IWo
this.hide();
this.visisbleUri = undefined;
const activeInput = this.editorService.activeEditor;
if (activeInput && activeInput instanceof QueryInput && activeInput.uri) {
if (activeInput && activeInput instanceof QueryEditorInput && activeInput.uri) {
this.visisbleUri = activeInput.uri;
const runner = this.queryModelService.getQueryRunner(this.visisbleUri);
if (runner && runner.isExecuting) {

View 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.
*--------------------------------------------------------------------------------------------*/
import { QueryEditorInput } from 'sql/workbench/contrib/query/common/queryEditorInput';
import { QueryResultsInput } from 'sql/workbench/contrib/query/common/queryResultsInput';
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
import { IQueryModelService } from 'sql/platform/query/common/queryModel';
import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { EncodingMode } from 'vs/workbench/common/editor';
import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel';
import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel';
import { IFileService } from 'vs/platform/files/common/files';
type PublicPart<T> = { [K in keyof T]: T[K] };
export class FileQueryEditorInput extends QueryEditorInput implements PublicPart<FileEditorInput> {
public static readonly ID = 'workbench.editorInput.fileQueryInput';
constructor(
description: string,
text: FileEditorInput,
results: QueryResultsInput,
@IConnectionManagementService connectionManagementService: IConnectionManagementService,
@IQueryModelService queryModelService: IQueryModelService,
@IConfigurationService configurationService: IConfigurationService,
@IFileService fileService: IFileService
) {
super(description, text, results, connectionManagementService, queryModelService, configurationService, fileService);
}
public resolve(): Promise<TextFileEditorModel | BinaryEditorModel> {
return this.text.resolve();
}
public get text(): FileEditorInput {
return this._text as FileEditorInput;
}
public getTypeId(): string {
return FileQueryEditorInput.ID;
}
public getEncoding(): string {
return this.text.getEncoding();
}
public setEncoding(encoding: string, mode: EncodingMode) {
this.text.setEncoding(encoding, mode);
}
public getPreferredEncoding(): string {
return this.text.getPreferredEncoding();
}
public setPreferredEncoding(encoding: string) {
this.text.setPreferredEncoding(encoding);
}
public getPreferredMode(): string {
return this.text.getPreferredMode();
}
public setMode(mode: string) {
this.text.setMode(mode);
}
public setPreferredMode(mode: string) {
this.text.setPreferredMode(mode);
}
public setForceOpenAsText() {
this.text.setForceOpenAsText();
}
public setForceOpenAsBinary() {
this.text.setForceOpenAsBinary();
}
public isResolved(): boolean {
return this.text.isResolved();
}
}

View File

@@ -5,10 +5,9 @@
import { localize } from 'vs/nls';
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
import { Event, Emitter } from 'vs/base/common/event';
import { Emitter } from 'vs/base/common/event';
import { URI } from 'vs/base/common/uri';
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
import { EditorInput, ConfirmResult, EncodingMode, IEncodingSupport } from 'vs/workbench/common/editor';
import { EditorInput, ConfirmResult } from 'vs/workbench/common/editor';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IFileService } from 'vs/platform/files/common/files';
@@ -17,14 +16,10 @@ import { QueryResultsInput } from 'sql/workbench/contrib/query/common/queryResul
import { IQueryModelService } from 'sql/platform/query/common/queryModel';
import { ISelectionData, ExecutionPlanOptions } from 'azdata';
import { UntitledEditorModel } from 'vs/workbench/common/editor/untitledEditorModel';
import { IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService';
import { startsWith } from 'vs/base/common/strings';
const MAX_SIZE = 13;
type PublicPart<T> = { [K in keyof T]: T[K] };
function trimTitle(title: string): string {
const length = title.length;
const diff = length - MAX_SIZE;
@@ -115,79 +110,48 @@ export class QueryEditorState extends Disposable {
* 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, PublicPart<UntitledEditorInput>, IDisposable {
export abstract class QueryEditorInput extends EditorInput implements IConnectableInput, IDisposable {
public static ID: string = 'workbench.editorinputs.queryInput';
public static SCHEMA: string = 'sql';
private _state = this._register(new QueryEditorState());
public get state(): QueryEditorState { return this._state; }
private _updateSelection: Emitter<ISelectionData>;
constructor(
private _description: string,
private _sql: UntitledEditorInput,
private _results: QueryResultsInput,
private _connectionProviderName: string,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
@IQueryModelService private _queryModelService: IQueryModelService,
@IConfigurationService private _configurationService: IConfigurationService,
protected _text: EditorInput,
protected _results: QueryResultsInput,
@IConnectionManagementService private readonly connectionManagementService: IConnectionManagementService,
@IQueryModelService private readonly queryModelService: IQueryModelService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IFileService private _fileService: IFileService
) {
super();
this._updateSelection = new Emitter<ISelectionData>();
this._register(this._sql);
this._register(this._text);
this._register(this._results);
// re-emit sql editor events through this editor if it exists
if (this._sql) {
this._register(this._sql.onDidChangeDirty(() => this._onDidChangeDirty.fire()));
}
this._text.onDidChangeDirty(() => this._onDidChangeDirty.fire());
// Attach to event callbacks
if (this._queryModelService) {
// Register callbacks for the Actions
this._register(
this._queryModelService.onRunQueryStart(uri => {
if (this.uri === uri) {
this.onRunQuery();
}
})
);
this._register(
this._queryModelService.onRunQueryComplete(uri => {
if (this.uri === uri) {
this.onQueryComplete();
}
})
);
}
if (this._connectionManagementService) {
this._register(this._connectionManagementService.onDisconnect(result => {
if (result.connectionUri === this.uri) {
this.onDisconnect();
}
}));
if (this.uri) {
if (this._connectionProviderName) {
this._connectionManagementService.doChangeLanguageFlavor(this.uri, 'sql', this._connectionProviderName);
} else {
this._connectionManagementService.ensureDefaultLanguageFlavor(this.uri);
this._register(
this.queryModelService.onRunQueryComplete(uri => {
if (this.uri === uri) {
this.onQueryComplete();
}
})
);
this._register(this.connectionManagementService.onDisconnect(result => {
if (result.connectionUri === this.uri) {
this.onDisconnect();
}
}
}));
if (this._configurationService) {
this._register(this._configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration('sql.showConnectionInfoInTitle')) {
this._onDidChangeLabel.fire();
}
}));
}
this._register(this.configurationService.onDidChangeConfiguration(e => {
if (e.affectedKeys.indexOf('sql.showConnectionInfoInTitle') > -1) {
this._onDidChangeLabel.fire();
}
}));
this.onDisconnect();
this.onQueryComplete();
@@ -195,47 +159,30 @@ export class QueryInput extends EditorInput implements IEncodingSupport, IConnec
// Getters for private properties
public get uri(): string { return this.getResource().toString(true); }
public get sql(): UntitledEditorInput { return this._sql; }
public get text(): EditorInput { return this._text; }
public get results(): QueryResultsInput { return this._results; }
public updateSelection(selection: ISelectionData): void { this._updateSelection.fire(selection); }
public getTypeId(): string { return QueryInput.ID; }
// Description is shown beside the tab name in the combobox of open editors
public getDescription(): string { return this._description; }
public supportsSplitEditor(): boolean { return false; }
public getMode(): string { return QueryInput.SCHEMA; }
public revert(): Promise<boolean> { return this._sql.revert(); }
public setMode(mode: string) {
this._sql.setMode(mode);
}
public revert(): Promise<boolean> { return this._text.revert(); }
public matches(otherInput: any): boolean {
if (otherInput instanceof QueryInput) {
return this._sql.matches(otherInput.sql);
// we want to be able to match against our underlying input as well, bascially we are our underlying input
if (otherInput instanceof QueryEditorInput) {
return this._text.matches(otherInput._text);
} else {
return this._text.matches(otherInput);
}
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(): Promise<UntitledEditorModel & IResolvedTextEditorModel> { return this._sql.resolve(); }
public save(): Promise<boolean> { return this._sql.save(); }
public isDirty(): boolean { return this._sql.isDirty(); }
public confirmSave(): Promise<ConfirmResult> { return this._sql.confirmSave(); }
public getResource(): URI { return this._sql.getResource(); }
public getEncoding(): string { return this._sql.getEncoding(); }
public suggestFileName(): string { return this._sql.suggestFileName(); }
hasBackup(): boolean {
if (this.sql) {
return this.sql.hasBackup();
}
return false;
}
public save(): Promise<boolean> { return this._text.save(); }
public isDirty(): boolean { return this._text.isDirty(); }
public confirmSave(): Promise<ConfirmResult> { return this._text.confirmSave(); }
public getResource(): URI { return this._text.getResource(); }
public matchInputInstanceType(inputType: any): boolean {
return (this._sql instanceof inputType);
return (this._text instanceof inputType);
}
public inputFileExists(): Promise<boolean> {
@@ -243,8 +190,8 @@ export class QueryInput extends EditorInput implements IEncodingSupport, IConnec
}
public getName(longForm?: boolean): string {
if (this._configurationService.getValue('sql.showConnectionInfoInTitle')) {
let profile = this._connectionManagementService.getConnectionProfile(this.uri);
if (this.configurationService.getValue('sql.showConnectionInfoInTitle')) {
let profile = this.connectionManagementService.getConnectionProfile(this.uri);
let title = '';
if (this._description && this._description !== '') {
title = this._description + ' ';
@@ -258,36 +205,30 @@ export class QueryInput extends EditorInput implements IEncodingSupport, IConnec
} else {
title += localize('disconnected', "disconnected");
}
return this._sql.getName() + (longForm ? (' - ' + title) : ` - ${trimTitle(title)}`);
return this._text.getName() + (longForm ? (' - ' + title) : ` - ${trimTitle(title)}`);
} else {
return this._sql.getName();
return this._text.getName();
}
}
// Called to get the tooltip of the tab
public getTitle() {
public getTitle(): string {
return this.getName(true);
}
public get hasAssociatedFilePath(): boolean { return this._sql.hasAssociatedFilePath; }
public setEncoding(encoding: string, mode: EncodingMode /* ignored, we only have Encode */): void {
this._sql.setEncoding(encoding, mode);
}
// State update funtions
public runQuery(selection?: ISelectionData, executePlanOptions?: ExecutionPlanOptions): void {
this._queryModelService.runQuery(this.uri, selection, this, executePlanOptions);
this.queryModelService.runQuery(this.uri, selection, this, executePlanOptions);
this.state.executing = true;
}
public runQueryStatement(selection?: ISelectionData): void {
this._queryModelService.runQueryStatement(this.uri, selection, this);
this.queryModelService.runQueryStatement(this.uri, selection, this);
this.state.executing = true;
}
public runQueryString(text: string): void {
this._queryModelService.runQueryString(this.uri, text, this);
this.queryModelService.runQueryString(this.uri, text, this);
this.state.executing = true;
}
@@ -314,7 +255,7 @@ export class QueryInput extends EditorInput implements IEncodingSupport, IConnec
this.state.connected = true;
this.state.connecting = false;
let isRunningQuery = this._queryModelService.isRunningQuery(this.uri);
let isRunningQuery = this.queryModelService.isRunningQuery(this.uri);
if (!isRunningQuery && params && params.runQueryOnCompletion) {
let selection: ISelectionData | undefined = params ? params.querySelection : undefined;
if (params.runQueryOnCompletion === RunQueryOnConnectionMode.executeCurrentQuery) {
@@ -345,10 +286,10 @@ export class QueryInput extends EditorInput implements IEncodingSupport, IConnec
}
public close(): void {
this._queryModelService.disposeQuery(this.uri);
this._connectionManagementService.disconnectEditor(this, true);
this.queryModelService.disposeQuery(this.uri);
this.connectionManagementService.disconnectEditor(this, true);
this._sql.close();
this._text.close();
this._results.close();
super.close();
}
@@ -357,7 +298,7 @@ export class QueryInput extends EditorInput implements IEncodingSupport, IConnec
* Get the color that should be displayed
*/
public get tabColor(): string {
return this._connectionManagementService.getTabColorForUri(this.uri);
return this.connectionManagementService.getTabColorForUri(this.uri);
}
public get isSharedSession(): boolean {

View File

@@ -0,0 +1,50 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IEditorInputFactory, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions } from 'vs/workbench/common/editor';
import { Registry } from 'vs/platform/registry/common/platform';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
import { QueryResultsInput } from 'sql/workbench/contrib/query/common/queryResultsInput';
import { FILE_EDITOR_INPUT_ID } from 'vs/workbench/contrib/files/common/files';
import { UntitledQueryEditorInput } from 'sql/workbench/contrib/query/common/untitledQueryEditorInput';
import { FileQueryEditorInput } from 'sql/workbench/contrib/query/common/fileQueryEditorInput';
import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput';
const editorInputFactoryRegistry = Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactories);
export class FileQueryEditorInputFactory implements IEditorInputFactory {
serialize(editorInput: FileQueryEditorInput): string {
const factory = editorInputFactoryRegistry.getEditorInputFactory(FILE_EDITOR_INPUT_ID);
if (factory) {
return factory.serialize(editorInput.text); // serialize based on the underlying input
}
return undefined;
}
deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): FileQueryEditorInput | undefined {
const factory = editorInputFactoryRegistry.getEditorInputFactory(FILE_EDITOR_INPUT_ID);
const fileEditorInput = factory.deserialize(instantiationService, serializedEditorInput) as FileEditorInput;
const queryResultsInput = instantiationService.createInstance(QueryResultsInput, fileEditorInput.getResource().toString());
return instantiationService.createInstance(FileQueryEditorInput, '', fileEditorInput, queryResultsInput);
}
}
export class UntitledQueryEditorInputFactory implements IEditorInputFactory {
serialize(editorInput: UntitledQueryEditorInput): string {
const factory = editorInputFactoryRegistry.getEditorInputFactory(UntitledEditorInput.ID);
if (factory) {
return factory.serialize(editorInput.text); // serialize based on the underlying input
}
return undefined;
}
deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): UntitledQueryEditorInput | undefined {
const factory = editorInputFactoryRegistry.getEditorInputFactory(UntitledEditorInput.ID);
const untitledEditorInput = factory.deserialize(instantiationService, serializedEditorInput) as UntitledEditorInput;
const queryResultsInput = instantiationService.createInstance(QueryResultsInput, untitledEditorInput.getResource().toString());
return instantiationService.createInstance(UntitledQueryEditorInput, '', untitledEditorInput, queryResultsInput);
}
}

View File

@@ -0,0 +1,82 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { QueryEditorInput } from 'sql/workbench/contrib/query/common/queryEditorInput';
import { QueryResultsInput } from 'sql/workbench/contrib/query/common/queryResultsInput';
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
import { IQueryModelService } from 'sql/platform/query/common/queryModel';
import { IEncodingSupport, EncodingMode } from 'vs/workbench/common/editor';
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { UntitledEditorModel } from 'vs/workbench/common/editor/untitledEditorModel';
import { IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService';
import { IFileService } from 'vs/platform/files/common/files';
type PublicPart<T> = { [K in keyof T]: T[K] };
export class UntitledQueryEditorInput extends QueryEditorInput implements IEncodingSupport, PublicPart<UntitledEditorInput> {
public static readonly ID = 'workbench.editorInput.untitledQueryInput';
public readonly onDidModelChangeContent = this.text.onDidModelChangeContent;
public readonly onDidModelChangeEncoding = this.text.onDidModelChangeEncoding;
constructor(
description: string,
text: UntitledEditorInput,
results: QueryResultsInput,
@IConnectionManagementService connectionManagementService: IConnectionManagementService,
@IQueryModelService queryModelService: IQueryModelService,
@IConfigurationService configurationService: IConfigurationService,
@IFileService fileService: IFileService
) {
super(description, text, results, connectionManagementService, queryModelService, configurationService, fileService);
}
public resolve(): Promise<UntitledEditorModel & IResolvedTextEditorModel> {
return this.text.resolve();
}
public get text(): UntitledEditorInput {
return this._text as UntitledEditorInput;
}
public get hasAssociatedFilePath(): boolean {
return this.text.hasAssociatedFilePath;
}
public suggestFileName(): string {
return this.text.suggestFileName();
}
public setMode(mode: string): void {
this.text.setMode(mode);
}
public getMode(): string {
return this.text.getMode();
}
public getTypeId(): string {
return UntitledQueryEditorInput.ID;
}
public getEncoding(): string {
return this.text.getEncoding();
}
public setEncoding(encoding: string, mode: EncodingMode): void {
this.text.setEncoding(encoding, mode);
}
hasBackup(): boolean {
if (this.text) {
return this.text.hasBackup();
}
return false;
}
}

View File

@@ -3,50 +3,48 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Emitter } from 'vs/base/common/event';
import { Emitter, Event } from 'vs/base/common/event';
import { ISelectionData } from 'azdata';
import {
IConnectionManagementService,
IConnectionParams,
INewConnectionParams,
ConnectionType,
RunQueryOnConnectionMode
} from 'sql/platform/connection/common/connectionManagement';
import { ConnectionDialogService } from 'sql/workbench/services/connection/browser/connectionDialogService';
import {
RunQueryAction, CancelQueryAction, ListDatabasesActionItem,
DisconnectDatabaseAction, ConnectDatabaseAction, QueryTaskbarAction
} from 'sql/workbench/contrib/query/browser/queryActions';
import { QueryInput } from 'sql/workbench/contrib/query/common/queryInput';
import { QueryEditor } from 'sql/workbench/contrib/query/browser/queryEditor';
import { QueryModelService } from 'sql/platform/query/common/queryModelService';
import { ConnectionManagementService } from 'sql/workbench/services/connection/browser/connectionManagementService';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import * as TypeMoq from 'typemoq';
import * as assert from 'assert';
import { TestStorageService, TestFileService } from 'vs/workbench/test/workbenchTestServices';
import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService';
import { UntitledQueryEditorInput } from 'sql/workbench/contrib/query/common/untitledQueryEditorInput';
import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
let none: void;
import { TestQueryModelService } from 'sql/platform/query/test/common/testQueryModelService';
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
import { URI } from 'vs/base/common/uri';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { TestConnectionManagementService } from 'sql/platform/connection/test/common/testConnectionManagementService';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
suite('SQL QueryAction Tests', () => {
let testUri: string = 'testURI';
let editor: TypeMoq.Mock<QueryEditor>;
let calledRunQueryOnInput: boolean = undefined;
let testQueryInput: TypeMoq.Mock<QueryInput>;
let configurationService: TypeMoq.Mock<IConfigurationService>;
let testQueryInput: TypeMoq.Mock<UntitledQueryEditorInput>;
let configurationService: TypeMoq.Mock<TestConfigurationService>;
let queryModelService: TypeMoq.Mock<TestQueryModelService>;
let connectionManagementService: TypeMoq.Mock<TestConnectionManagementService>;
setup(() => {
// Setup a reusable mock QueryInput
testQueryInput = TypeMoq.Mock.ofType(QueryInput, TypeMoq.MockBehavior.Strict);
testQueryInput.setup(x => x.uri).returns(() => testUri);
testQueryInput.setup(x => x.runQuery(undefined)).callback(() => { calledRunQueryOnInput = true; });
const contextkeyservice = new MockContextKeyService();
@@ -65,6 +63,17 @@ suite('SQL QueryAction Tests', () => {
configurationService.setup(x => x.getValue(TypeMoq.It.isAny())).returns(() => {
return {};
});
queryModelService = TypeMoq.Mock.ofType<TestQueryModelService>(TestQueryModelService);
queryModelService.setup(q => q.onRunQueryStart).returns(() => Event.None);
queryModelService.setup(q => q.onRunQueryComplete).returns(() => Event.None);
connectionManagementService = TypeMoq.Mock.ofType<TestConnectionManagementService>(TestConnectionManagementService);
connectionManagementService.setup(q => q.onDisconnect).returns(() => Event.None);
const instantiationService = new TestInstantiationService();
let fileInput = new UntitledEditorInput(URI.parse('file://testUri'), false, '', '', '', instantiationService, undefined, undefined);
// Setup a reusable mock QueryInput
testQueryInput = TypeMoq.Mock.ofType(UntitledQueryEditorInput, TypeMoq.MockBehavior.Strict, undefined, fileInput, undefined, connectionManagementService.object, queryModelService.object, configurationService.object);
testQueryInput.setup(x => x.uri).returns(() => testUri);
testQueryInput.setup(x => x.runQuery(undefined)).callback(() => { calledRunQueryOnInput = true; });
});
test('setClass sets child CSS class correctly', (done) => {
@@ -82,7 +91,6 @@ suite('SQL QueryAction Tests', () => {
let isConnectedReturnValue: boolean = false;
// ... Mock "isConnected in ConnectionManagementService
let connectionManagementService = TypeMoq.Mock.ofType(ConnectionManagementService, TypeMoq.MockBehavior.Loose, {}, {}, new TestStorageService());
connectionManagementService.setup(x => x.isConnected(TypeMoq.It.isAnyString())).returns(() => isConnectedReturnValue);
const contextkeyservice = new MockContextKeyService();
@@ -114,19 +122,15 @@ suite('SQL QueryAction Tests', () => {
let connectionParams: INewConnectionParams = undefined;
let countCalledShowDialog: number = 0;
// ... Mock "showDialog" ConnectionDialogService
let connectionDialogService = TypeMoq.Mock.ofType(ConnectionDialogService, TypeMoq.MockBehavior.Loose);
connectionDialogService.setup(x => x.showDialog(TypeMoq.It.isAny(), TypeMoq.It.isAny(), undefined, undefined, undefined))
.callback((service: IConnectionManagementService, params: INewConnectionParams) => {
// ... Mock "isConnected" in ConnectionManagementService
connectionManagementService.callBase = true;
connectionManagementService.setup(x => x.isConnected(TypeMoq.It.isAnyString())).returns(() => isConnected);
connectionManagementService.setup(x => x.showConnectionDialog(TypeMoq.It.isAny()))
.callback((params: INewConnectionParams) => {
connectionParams = params;
countCalledShowDialog++;
})
.returns(() => Promise.resolve(none));
// ... Mock "isConnected" in ConnectionManagementService
let connectionManagementService = TypeMoq.Mock.ofType(ConnectionManagementService, TypeMoq.MockBehavior.Loose, {}, undefined, connectionDialogService.object);
connectionManagementService.callBase = true;
connectionManagementService.setup(x => x.isConnected(TypeMoq.It.isAnyString())).returns(() => isConnected);
.returns(() => Promise.resolve());
// ... Mock QueryModelService
let queryModelService = TypeMoq.Mock.ofType(QueryModelService, TypeMoq.MockBehavior.Loose);
@@ -143,7 +147,6 @@ suite('SQL QueryAction Tests', () => {
testQueryInput.verify(x => x.runQuery(undefined), TypeMoq.Times.never());
// and the connection dialog should open with the correct parameter details
assert.equal(countCalledShowDialog, 1, 'run should call showDialog');
assert.equal(connectionParams.connectionType, ConnectionType.editor, 'connectionType should be queryEditor');
assert.equal(connectionParams.runQueryOnCompletion, RunQueryOnConnectionMode.executeQuery, 'runQueryOnCompletion should be true`');
assert.equal(connectionParams.input.uri, testUri, 'URI should be set to the test URI');
@@ -166,9 +169,18 @@ suite('SQL QueryAction Tests', () => {
let isSelectionEmpty: boolean = undefined;
let countCalledRunQuery: number = 0;
// ... Mock "isConnected" in ConnectionManagementService
connectionManagementService.setup(x => x.isConnected(TypeMoq.It.isAnyString())).returns(() => true);
// ... Mock QueryModelService
let queryModelService = TypeMoq.Mock.ofType(QueryModelService, TypeMoq.MockBehavior.Loose);
queryModelService.setup(x => x.onRunQueryStart).returns(() => Event.None);
queryModelService.setup(x => x.onRunQueryComplete).returns(() => Event.None);
const instantiationService = new TestInstantiationService();
let fileInput = new UntitledEditorInput(URI.parse('file://testUri'), false, '', '', '', instantiationService, undefined, undefined);
// ... Mock "isSelectionEmpty" in QueryEditor
let queryInput: TypeMoq.Mock<QueryInput> = TypeMoq.Mock.ofType(QueryInput, TypeMoq.MockBehavior.Strict);
queryInput = TypeMoq.Mock.ofType(QueryInput, TypeMoq.MockBehavior.Strict);
let queryInput = TypeMoq.Mock.ofType(UntitledQueryEditorInput, TypeMoq.MockBehavior.Strict, undefined, fileInput, undefined, connectionManagementService.object, queryModelService.object, configurationService.object);
queryInput.setup(x => x.uri).returns(() => testUri);
queryInput.setup(x => x.runQuery(undefined)).callback(() => {
countCalledRunQuery++;
@@ -183,14 +195,6 @@ suite('SQL QueryAction Tests', () => {
queryEditor.setup(x => x.getSelection(false)).returns(() => undefined);
queryEditor.setup(x => x.isSelectionEmpty()).returns(() => isSelectionEmpty);
// ... Mock "isConnected" in ConnectionManagementService
let connectionManagementService = TypeMoq.Mock.ofType(ConnectionManagementService, TypeMoq.MockBehavior.Loose, {});
connectionManagementService.callBase = true;
connectionManagementService.setup(x => x.isConnected(TypeMoq.It.isAnyString())).returns(() => true);
// ... Mock QueryModelService
let queryModelService = TypeMoq.Mock.ofType(QueryModelService, TypeMoq.MockBehavior.Loose);
// If I call run on RunQueryAction when I have a non empty selection
let queryAction: RunQueryAction = new RunQueryAction(queryEditor.object, queryModelService.object, connectionManagementService.object);
isSelectionEmpty = false;
@@ -221,17 +225,11 @@ suite('SQL QueryAction Tests', () => {
let selectionToReturnInGetSelection: ISelectionData = undefined;
let predefinedSelection: ISelectionData = { startLine: 1, startColumn: 2, endLine: 3, endColumn: 4 };
// ... Mock "showDialog" ConnectionDialogService
let connectionDialogService = TypeMoq.Mock.ofType(ConnectionDialogService, TypeMoq.MockBehavior.Loose);
connectionDialogService.setup(x => x.showDialog(TypeMoq.It.isAny(), TypeMoq.It.isAny(), undefined, undefined, undefined))
.callback((service: IConnectionManagementService, params: INewConnectionParams) => {
showDialogConnectionParams = params;
countCalledShowDialog++;
})
.returns(() => Promise.resolve(none));
// ... Mock "getSelection" in QueryEditor
let queryInput = TypeMoq.Mock.ofType(QueryInput, TypeMoq.MockBehavior.Loose);
const instantiationService = new TestInstantiationService();
let fileInput = new UntitledEditorInput(URI.parse('file://testUri'), false, '', '', '', instantiationService, undefined, undefined);
let queryInput = TypeMoq.Mock.ofType(UntitledQueryEditorInput, TypeMoq.MockBehavior.Loose, undefined, fileInput, undefined, connectionManagementService.object, queryModelService.object, configurationService.object);
queryInput.setup(x => x.uri).returns(() => testUri);
queryInput.setup(x => x.runQuery(TypeMoq.It.isAny())).callback((selection: ISelectionData) => {
runQuerySelection = selection;
@@ -256,9 +254,13 @@ suite('SQL QueryAction Tests', () => {
});
// ... Mock "isConnected" in ConnectionManagementService
let connectionManagementService = TypeMoq.Mock.ofType(ConnectionManagementService, TypeMoq.MockBehavior.Loose, {}, undefined, connectionDialogService.object);
connectionManagementService.callBase = true;
connectionManagementService.setup(x => x.isConnected(TypeMoq.It.isAnyString())).returns(() => isConnected);
connectionManagementService.setup(x => x.showConnectionDialog(TypeMoq.It.isAny()))
.callback((params: INewConnectionParams) => {
showDialogConnectionParams = params;
countCalledShowDialog++;
})
.returns(() => Promise.resolve());
/// End Setup Test ///
@@ -320,7 +322,6 @@ suite('SQL QueryAction Tests', () => {
let calledCancelQuery: boolean = false;
// ... Mock "isConnected" in ConnectionManagementService
let connectionManagementService = TypeMoq.Mock.ofType(ConnectionManagementService, TypeMoq.MockBehavior.Loose, {});
connectionManagementService.setup(x => x.isConnected(TypeMoq.It.isAnyString())).returns(() => isConnected);
// ... Mock QueryModelService
@@ -353,7 +354,6 @@ suite('SQL QueryAction Tests', () => {
let countCalledDisconnectEditor: number = 0;
// ... Mock "isConnected" and "disconnectEditor" in ConnectionManagementService
let connectionManagementService = TypeMoq.Mock.ofType(ConnectionManagementService, TypeMoq.MockBehavior.Loose, {});
connectionManagementService.setup(x => x.isConnected(TypeMoq.It.isAnyString())).returns(() => isConnected);
connectionManagementService.setup(x => x.disconnectEditor(TypeMoq.It.isAny())).callback(() => {
countCalledDisconnectEditor++;
@@ -382,19 +382,14 @@ suite('SQL QueryAction Tests', () => {
let connectionParams: INewConnectionParams = undefined;
let countCalledShowDialog: number = 0;
// ... Mock "showDialog" ConnectionDialogService
let connectionDialogService = TypeMoq.Mock.ofType(ConnectionDialogService, TypeMoq.MockBehavior.Loose);
connectionDialogService.setup(x => x.showDialog(TypeMoq.It.isAny(), TypeMoq.It.isAny(), undefined, undefined, undefined))
.callback((service: IConnectionManagementService, params: INewConnectionParams) => {
// ... Mock "isConnected" in ConnectionManagementService
connectionManagementService.setup(x => x.isConnected(TypeMoq.It.isAnyString())).returns(() => isConnected);
connectionManagementService.setup(x => x.showConnectionDialog(TypeMoq.It.isAny()))
.callback((params: INewConnectionParams) => {
connectionParams = params;
countCalledShowDialog++;
})
.returns(() => Promise.resolve(none));
// ... Mock "isConnected" in ConnectionManagementService
let connectionManagementService = TypeMoq.Mock.ofType(ConnectionManagementService, TypeMoq.MockBehavior.Loose, {}, undefined, connectionDialogService.object);
connectionManagementService.callBase = true;
connectionManagementService.setup(x => x.isConnected(TypeMoq.It.isAnyString())).returns(() => isConnected);
.returns(() => Promise.resolve());
// If I call run on ConnectDatabaseAction when I am not connected
let queryAction: ConnectDatabaseAction = new ConnectDatabaseAction(editor.object, false, connectionManagementService.object);
@@ -428,19 +423,13 @@ suite('SQL QueryAction Tests', () => {
let connectionParams: INewConnectionParams = undefined;
let calledShowDialog: number = 0;
// ... Mock "showDialog" ConnectionDialogService
let connectionDialogService = TypeMoq.Mock.ofType(ConnectionDialogService, TypeMoq.MockBehavior.Loose);
connectionDialogService.setup(x => x.showDialog(TypeMoq.It.isAny(), TypeMoq.It.isAny(), undefined, undefined, undefined))
.callback((service: IConnectionManagementService, params: INewConnectionParams) => {
// ... Mock "isConnected" in ConnectionManagementService
connectionManagementService.setup(x => x.isConnected(TypeMoq.It.isAnyString())).returns(() => isConnected);
connectionManagementService.setup(x => x.showConnectionDialog(TypeMoq.It.isAny()))
.callback((params: INewConnectionParams) => {
calledShowDialog++;
connectionParams = params;
})
.returns(() => Promise.resolve(none));
// ... Mock "isConnected" in ConnectionManagementService
let connectionManagementService = TypeMoq.Mock.ofType(ConnectionManagementService, TypeMoq.MockBehavior.Loose, {}, undefined, connectionDialogService.object);
connectionManagementService.callBase = true;
connectionManagementService.setup(x => x.isConnected(TypeMoq.It.isAnyString())).returns(() => isConnected);
}).returns(() => Promise.resolve());
// If I call run on ChangeConnectionAction when I am not connected
queryAction = new ConnectDatabaseAction(editor.object, false, connectionManagementService.object);
@@ -473,9 +462,8 @@ suite('SQL QueryAction Tests', () => {
let databaseName: string = undefined;
// ... Mock "isConnected" in ConnectionManagementService
let connectionManagementService = TypeMoq.Mock.ofType(ConnectionManagementService, TypeMoq.MockBehavior.Loose, {});
connectionManagementService.callBase = true;
connectionManagementService.setup(x => x.isConnected(TypeMoq.It.isAnyString())).returns(() => isConnected);
connectionManagementService.setup(x => x.onConnectionChanged).returns(() => Event.None);
connectionManagementService.setup(x => x.getConnectionProfile(TypeMoq.It.isAny())).returns(() => <IConnectionProfile>{
databaseName: databaseName
});
@@ -510,13 +498,11 @@ suite('SQL QueryAction Tests', () => {
// ... Create mock connection management service
let databaseName = 'foobar';
let cms = TypeMoq.Mock.ofType(ConnectionManagementService, TypeMoq.MockBehavior.Loose, {});
cms.callBase = true;
cms.setup(x => x.onConnectionChanged).returns(() => dbChangedEmitter.event);
cms.setup(x => x.getConnectionProfile(TypeMoq.It.isAny())).returns(() => <IConnectionProfile>{ databaseName: databaseName });
connectionManagementService.setup(x => x.onConnectionChanged).returns(() => dbChangedEmitter.event);
connectionManagementService.setup(x => x.getConnectionProfile(TypeMoq.It.isAny())).returns(() => <IConnectionProfile>{ databaseName: databaseName });
// ... Create a database dropdown that has been connected
let listItem = new ListDatabasesActionItem(editor.object, undefined, cms.object, undefined, configurationService.object);
let listItem = new ListDatabasesActionItem(editor.object, undefined, connectionManagementService.object, undefined, configurationService.object);
listItem.onConnected();
// If: I raise a connection changed event
@@ -534,13 +520,11 @@ suite('SQL QueryAction Tests', () => {
// ... Create mock connection management service that will not claim it's connected
let databaseName = 'foobar';
let cms = TypeMoq.Mock.ofType(ConnectionManagementService, TypeMoq.MockBehavior.Loose, {}, {}, new TestStorageService());
cms.callBase = true;
cms.setup(x => x.onConnectionChanged).returns(() => dbChangedEmitter.event);
cms.setup(x => x.getConnectionProfile(TypeMoq.It.isAny())).returns(() => <IConnectionProfile>{ databaseName: databaseName });
connectionManagementService.setup(x => x.onConnectionChanged).returns(() => dbChangedEmitter.event);
connectionManagementService.setup(x => x.getConnectionProfile(TypeMoq.It.isAny())).returns(() => <IConnectionProfile>{ databaseName: databaseName });
// ... Create a database dropdown that has been connected
let listItem = new ListDatabasesActionItem(editor.object, undefined, cms.object, undefined, configurationService.object);
let listItem = new ListDatabasesActionItem(editor.object, undefined, connectionManagementService.object, undefined, configurationService.object);
listItem.onConnected();
// If: I raise a connection changed event for the 'wrong' URI
@@ -562,12 +546,10 @@ suite('SQL QueryAction Tests', () => {
let dbChangedEmitter = new Emitter<IConnectionParams>();
// ... Create mock connection management service
let cms = TypeMoq.Mock.ofType(ConnectionManagementService, TypeMoq.MockBehavior.Loose, {}, {}, new TestStorageService());
cms.callBase = true;
cms.setup(x => x.onConnectionChanged).returns(() => dbChangedEmitter.event);
connectionManagementService.setup(x => x.onConnectionChanged).returns(() => dbChangedEmitter.event);
// ... Create a database dropdown
let listItem = new ListDatabasesActionItem(editor.object, undefined, cms.object, undefined, configurationService.object);
let listItem = new ListDatabasesActionItem(editor.object, undefined, connectionManagementService.object, undefined, configurationService.object);
// If: I raise a connection changed event
let eventParams = <IConnectionParams>{

View File

@@ -10,8 +10,6 @@ import { Memento } from 'vs/workbench/common/memento';
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
import { QueryResultsInput } from 'sql/workbench/contrib/query/common/queryResultsInput';
import { QueryModelService } from 'sql/platform/query/common/queryModelService';
import { QueryInput } from 'sql/workbench/contrib/query/common/queryInput';
import { INewConnectionParams, ConnectionType, RunQueryOnConnectionMode } from 'sql/platform/connection/common/connectionManagement';
import { ConnectionManagementService } from 'sql/workbench/services/connection/browser/connectionManagementService';
import { RunQueryAction, ListDatabasesActionItem } from 'sql/workbench/contrib/query/browser/queryActions';
@@ -23,6 +21,9 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
import { TestStorageService } from 'vs/workbench/test/workbenchTestServices';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
import { UntitledQueryEditorInput } from 'sql/workbench/contrib/query/common/untitledQueryEditorInput';
import { TestQueryModelService } from 'sql/platform/query/test/common/testQueryModelService';
import { Event } from 'vs/base/common/event';
suite('SQL QueryEditor Tests', () => {
let instantiationService: TypeMoq.Mock<InstantiationService>;
@@ -247,8 +248,8 @@ suite('SQL QueryEditor Tests', () => {
suite('Action Tests', () => {
let queryActionInstantiationService: TypeMoq.Mock<InstantiationService>;
let queryConnectionService: TypeMoq.Mock<ConnectionManagementService>;
let queryModelService: TypeMoq.Mock<QueryModelService>;
let queryInput: QueryInput;
let queryModelService: TypeMoq.Mock<TestQueryModelService>;
let queryInput: UntitledQueryEditorInput;
setup(() => {
// Mock ConnectionManagementService but don't set connected state
@@ -284,17 +285,17 @@ suite('SQL QueryEditor Tests', () => {
});
let fileInput = new UntitledEditorInput(URI.parse('file://testUri'), false, '', '', '', instantiationService.object, undefined, undefined);
queryModelService = TypeMoq.Mock.ofType(QueryModelService, TypeMoq.MockBehavior.Loose, undefined, undefined);
queryModelService.callBase = true;
queryModelService.setup(x => x.disposeQuery(TypeMoq.It.isAny())).returns(() => void 0);
queryInput = new QueryInput(
queryModelService = TypeMoq.Mock.ofType(TestQueryModelService, TypeMoq.MockBehavior.Strict);
queryModelService.setup(x => x.disposeQuery(TypeMoq.It.isAny()));
queryModelService.setup(x => x.onRunQueryComplete).returns(() => Event.None);
queryModelService.setup(x => x.onRunQueryStart).returns(() => Event.None);
queryInput = new UntitledQueryEditorInput(
'',
fileInput,
undefined,
undefined,
connectionManagementService.object,
queryModelService.object,
undefined,
configurationService.object,
undefined
);
});
@@ -311,8 +312,8 @@ suite('SQL QueryEditor Tests', () => {
test('Taskbar buttons are set correctly upon connect', () => {
let params: INewConnectionParams = { connectionType: ConnectionType.editor, runQueryOnCompletion: RunQueryOnConnectionMode.none };
queryInput.onConnectSuccess(params);
queryModelService.setup(x => x.isRunningQuery(TypeMoq.It.isAny())).returns(() => false);
queryInput.onConnectSuccess(params);
assert.equal(queryInput.state.connected, true, 'query state should be not connected');
assert.equal(queryInput.state.executing, false, 'query state should be not executing');
assert.equal(queryInput.state.connecting, false, 'query state should be not connecting');

View File

@@ -8,6 +8,8 @@ import { EditorDescriptor, IEditorRegistry, Extensions } from 'vs/workbench/brow
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { Registry } from 'vs/platform/registry/common/platform';
import { QueryPlanEditor } from 'sql/workbench/contrib/queryPlan/browser/queryPlanEditor';
import { ILanguageAssociationRegistry, Extensions as LanguageAssociationExtensions } from 'sql/workbench/common/languageAssociation';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
// Query Plan editor registration
@@ -19,3 +21,9 @@ const queryPlanEditorDescriptor = new EditorDescriptor(
Registry.as<IEditorRegistry>(Extensions.Editors)
.registerEditor(queryPlanEditorDescriptor, [new SyncDescriptor(QueryPlanInput)]);
Registry.as<ILanguageAssociationRegistry>(LanguageAssociationExtensions.LanguageAssociations)
.registerLanguageAssociation('sqlplan', (accessor, editor) => {
const instantiationService = accessor.get(IInstantiationService);
return instantiationService.createInstance(QueryPlanInput, editor.getResource());
}, (editor: QueryPlanInput) => undefined);

View File

@@ -6,7 +6,6 @@
import { EditorInput, EditorModel } from 'vs/workbench/common/editor';
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { ConnectionManagementInfo } from 'sql/platform/connection/common/connectionManagementInfo';
import { IFileService } from 'vs/platform/files/common/files';
import { URI } from 'vs/base/common/uri';
@@ -19,7 +18,7 @@ export class QueryPlanInput extends EditorInput {
private _xml: string;
constructor(
private _uri: URI, private _connection: ConnectionManagementInfo,
private _uri: URI,
@IFileService private readonly fileService: IFileService
) {
super();
@@ -68,8 +67,4 @@ export class QueryPlanInput extends EditorInput {
public get uniqueSelector(): string {
return this._uniqueSelector;
}
public getConnectionInfo(): ConnectionManagementInfo {
return this._connection;
}
}

View File

@@ -4,33 +4,23 @@
*--------------------------------------------------------------------------------------------*/
import { QueryResultsInput } from 'sql/workbench/contrib/query/common/queryResultsInput';
import { QueryInput } from 'sql/workbench/contrib/query/common/queryInput';
import { QueryEditorInput } from 'sql/workbench/contrib/query/common/queryEditorInput';
import { EditDataInput } from 'sql/workbench/contrib/editData/browser/editDataInput';
import { IConnectableInput, IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
import { IQueryEditorService, IQueryEditorOptions } from 'sql/workbench/services/queryEditor/common/queryEditorService';
import { sqlModeId, untitledFilePrefix, getSupportedInputResource } from 'sql/workbench/browser/customInputConverter';
import * as TaskUtilities from 'sql/workbench/browser/taskUtilities';
import { IQueryEditorService } from 'sql/workbench/services/queryEditor/common/queryEditorService';
import { UntitledQueryEditorInput } from 'sql/workbench/contrib/query/common/untitledQueryEditorInput';
import { ITextModel } from 'vs/editor/common/model';
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import Severity from 'vs/base/common/severity';
import nls = require('vs/nls');
import { URI } from 'vs/base/common/uri';
import paths = require('vs/base/common/extpath');
import * as paths from 'vs/base/common/extpath';
import { isLinux } from 'vs/base/common/platform';
import { Schemas } from 'vs/base/common/network';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { EditDataResultsInput } from 'sql/workbench/contrib/editData/browser/editDataResultsInput';
import { IEditorInput, IEditor } from 'vs/workbench/common/editor';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { ILanguageSelection } from 'vs/editor/common/services/modeService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService';
import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput';
import { replaceConnection } from 'sql/workbench/browser/taskUtilities';
import { EditDataResultsInput } from 'sql/workbench/contrib/editData/browser/editDataResultsInput';
import { ILogService } from 'vs/platform/log/common/log';
import { assign } from 'vs/base/common/objects';
/**
* Service wrapper for opening and creating SQL documents as sql editor inputs
@@ -39,18 +29,7 @@ export class QueryEditorService implements IQueryEditorService {
public _serviceBrand: undefined;
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"
);
constructor(
@INotificationService private _notificationService: INotificationService,
@IUntitledEditorService private _untitledEditorService: IUntitledEditorService,
@IInstantiationService private _instantiationService: IInstantiationService,
@IEditorService private _editorService: IEditorService,
@@ -83,12 +62,11 @@ export class QueryEditorService implements IQueryEditorService {
}
const queryResultsInput: QueryResultsInput = this._instantiationService.createInstance(QueryResultsInput, docUri.toString());
let queryInput: QueryInput = this._instantiationService.createInstance(QueryInput, objectName, fileInput, queryResultsInput, connectionProviderName);
let queryInput = this._instantiationService.createInstance(UntitledQueryEditorInput, objectName, fileInput, queryResultsInput);
this._editorService.openEditor(queryInput, { pinned: true })
.then((editor) => {
let params = <QueryInput>editor.input;
resolve(params);
resolve(editor.input as UntitledQueryEditorInput);
}, (error) => {
reject(error);
});
@@ -128,14 +106,14 @@ export class QueryEditorService implements IQueryEditorService {
let oldResourceString: string = oldResource.toString();
this._editorService.editors.forEach(input => {
if (input instanceof QueryInput) {
if (input instanceof QueryEditorInput) {
const resource = input.getResource();
// Update Editor if file (or any parent of the input) got renamed or moved
// Note: must check the new file name for this since this method is called after the rename is completed
if (paths.isEqualOrParent(resource.fsPath, newResource.fsPath, !isLinux /* ignorecase */)) {
// In this case, we know that this is a straight rename so support this as a rename / replace operation
TaskUtilities.replaceConnection(oldResourceString, newResource.toString(), this._connectionManagementService).then(result => {
replaceConnection(oldResourceString, newResource.toString(), this._connectionManagementService).then(result => {
if (result && result.connected) {
input.onConnectSuccess();
} else {
@@ -147,84 +125,10 @@ export class QueryEditorService implements IQueryEditorService {
});
}
////// 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 async sqlLanguageModeCheck(model: ITextModel, languageSelection: ILanguageSelection, editor: IEditor): Promise<ITextModel> {
if (!model || !languageSelection || !editor) {
return undefined;
}
let newLanguage: string = languageSelection.languageIdentifier.language;
let oldLanguage: string = model.getLanguageIdentifier().language;
let changingToSql = sqlModeId === newLanguage;
let changingFromSql = sqlModeId === oldLanguage;
let changingLanguage = newLanguage !== oldLanguage;
if (!changingLanguage) {
return model;
}
if (!changingFromSql && !changingToSql) {
return model;
}
let uri: URI = QueryEditorService._getEditorChangeUri(editor.input, changingToSql);
if (uri.scheme === Schemas.untitled && (editor.input instanceof QueryInput || editor.input instanceof EditDataInput)) {
this._notificationService.notify({
severity: Severity.Error,
message: QueryEditorService.CHANGE_UNSUPPORTED_ERROR_MESSAGE
});
return 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()) {
this._notificationService.notify({
severity: Severity.Error,
message: QueryEditorService.CHANGE_ERROR_MESSAGE
});
return undefined;
}
let group: IEditorGroup = editor.group;
let index: number = group.editors.indexOf(editor.input);
let options: IQueryEditorOptions = editor.options ? editor.options : {};
options = assign(options, { index: index });
// Return a promise that will resovle when the old editor has been replaced by a new editor
let newEditorInput = this.getNewEditorInput(changingToSql, editor.input, uri);
// Override queryEditorCheck to not open this file in a QueryEditor
if (!changingToSql) {
options.denyQueryEditor = true;
}
await group.closeEditor(editor.input);
// Reopen a new editor in the same position/index
const newEditor = await this._editorService.openEditor(newEditorInput, options, group);
return QueryEditorService._onEditorOpened(newEditor, uri.toString(), undefined, options.pinned);
}
////// Private functions
private createUntitledSqlFilePath(): Promise<string> {
return this.createPrefixedSqlFilePath(untitledFilePrefix);
return this.createPrefixedSqlFilePath('SQLQuery');
}
private async createPrefixedSqlFilePath(prefix: string): Promise<string> {
@@ -242,69 +146,4 @@ export class QueryEditorService implements IQueryEditorService {
return filePath;
}
////// Private static functions
/**
* Returns a QueryInput if we are changingToSql. Returns a FileEditorInput if we are !changingToSql.
*/
private getNewEditorInput(changingToSql: boolean, input: IEditorInput, uri: URI): IEditorInput {
if (!uri) {
return undefined;
}
let newEditorInput: IEditorInput = undefined;
if (changingToSql) {
const queryResultsInput: QueryResultsInput = this._instantiationService.createInstance(QueryResultsInput, uri.toString());
let queryInput: QueryInput = this._instantiationService.createInstance(QueryInput, '', 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 = this._instantiationService.createInstance(FileEditorInput, uriCopy, undefined, 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): ITextModel {
// 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);
// }
// @SQLTODO do we need the below
// else {
// QueryEditorService.editorGroupService.p .unpinEditor(group, editor.input);
// }
// Grab and returns the IModel that will be used to resolve the sqlLanguageModeCheck promise.
let control = editor.getControl();
let codeEditor: ICodeEditor = <ICodeEditor>control;
let newModel = codeEditor ? codeEditor.getModel() : undefined;
return newModel;
}
}

View File

@@ -8,9 +8,6 @@ import { IConnectableInput } from 'sql/platform/connection/common/connectionMana
import { IEditorOptions } from 'vs/platform/editor/common/editor';
import { URI } from 'vs/base/common/uri';
import { ITextModel } from 'vs/editor/common/model';
import { ILanguageSelection } from 'vs/editor/common/services/modeService';
import { IEditor } from 'vs/workbench/common/editor';
export interface IQueryEditorOptions extends IEditorOptions {
@@ -38,6 +35,4 @@ export interface IQueryEditorService {
* @param newResource URI of the file after the save as operation was completed
*/
onSaveAsCompleted(oldResource: URI, newResource: URI): void;
sqlLanguageModeCheck(model: ITextModel, languageSelection: ILanguageSelection, editor: IEditor): Promise<ITextModel>;
}