diff --git a/src/sql/platform/query/common/queryModel.ts b/src/sql/platform/query/common/queryModel.ts index 139e3d27ba..94f9e39d2d 100644 --- a/src/sql/platform/query/common/queryModel.ts +++ b/src/sql/platform/query/common/queryModel.ts @@ -7,7 +7,7 @@ import QueryRunner, { IQueryMessage } from 'sql/platform/query/common/queryRunne import { DataService } from 'sql/workbench/contrib/grid/common/dataService'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Event } from 'vs/base/common/event'; -import { QueryInput } from 'sql/workbench/contrib/query/common/queryInput'; +import { QueryEditorInput } from 'sql/workbench/contrib/query/common/queryEditorInput'; import { ISelectionData, ResultSetSubset, @@ -52,9 +52,9 @@ export interface IQueryModelService { getQueryRunner(uri: string): QueryRunner | undefined; getQueryRows(uri: string, rowStart: number, numberOfRows: number, batchId: number, resultId: number): Promise; - runQuery(uri: string, selection: ISelectionData | undefined, queryInput: QueryInput, runOptions?: ExecutionPlanOptions): void; - runQueryStatement(uri: string, selection: ISelectionData | undefined, queryInput: QueryInput): void; - runQueryString(uri: string, selection: string | undefined, queryInput: QueryInput): void; + runQuery(uri: string, selection: ISelectionData | undefined, queryInput: QueryEditorInput, runOptions?: ExecutionPlanOptions): void; + runQueryStatement(uri: string, selection: ISelectionData | undefined, queryInput: QueryEditorInput): void; + runQueryString(uri: string, selection: string | undefined, queryInput: QueryEditorInput): void; cancelQuery(input: QueryRunner | string): void; disposeQuery(uri: string): void; isRunningQuery(uri: string): boolean; @@ -66,9 +66,6 @@ export interface IQueryModelService { onAngularLoaded(uri: string): void; copyResults(uri: string, selection: Slick.Range[], batchId: number, resultId: number, includeHeaders?: boolean): void; - setEditorSelection(uri: string, index: number): void; - showWarning(uri: string, message: string): void; - showError(uri: string, message: string): void; showCommitError(error: string): void; onRunQueryStart: Event; diff --git a/src/sql/platform/query/common/queryModelService.ts b/src/sql/platform/query/common/queryModelService.ts index 9ea1d218dc..57c949fe50 100644 --- a/src/sql/platform/query/common/queryModelService.ts +++ b/src/sql/platform/query/common/queryModelService.ts @@ -8,7 +8,6 @@ import * as LocalizedConstants from 'sql/workbench/contrib/query/common/localize import QueryRunner from 'sql/platform/query/common/queryRunner'; import { DataService } from 'sql/workbench/contrib/grid/common/dataService'; import { IQueryModelService, IQueryEvent } from 'sql/platform/query/common/queryModel'; -import { QueryInput } from 'sql/workbench/contrib/query/common/queryInput'; import * as azdata from 'azdata'; @@ -19,6 +18,7 @@ import * as strings from 'vs/base/common/strings'; import * as types from 'vs/base/common/types'; import { INotificationService } from 'vs/platform/notification/common/notification'; import Severity from 'vs/base/common/severity'; +import { QueryEditorInput } from 'sql/workbench/contrib/query/common/queryEditorInput'; const selectionSnippetMaxLen = 100; @@ -35,7 +35,7 @@ export class QueryInfo { public dataService: DataService; public queryEventQueue: QueryEvent[]; public selection: Array; - public queryInput: QueryInput; + public queryInput: QueryEditorInput; public selectionSnippet?: string; // Notes if the angular components have obtained the DataService. If not, all messages sent @@ -158,19 +158,6 @@ export class QueryModelService implements IQueryModelService { } } - public setEditorSelection(uri: string, index: number): void { - let info = this._queryInfoMap.get(uri); - if (info && info.queryInput) { - info.queryInput.updateSelection(info.selection[index]); - } - } - - public showWarning(uri: string, message: string): void { - } - - public showError(uri: string, message: string): void { - } - public showCommitError(error: string): void { this._notificationService.notify({ severity: Severity.Error, @@ -187,28 +174,28 @@ export class QueryModelService implements IQueryModelService { /** * Run a query for the given URI with the given text selection */ - public async runQuery(uri: string, selection: azdata.ISelectionData, queryInput: QueryInput, runOptions?: azdata.ExecutionPlanOptions): Promise { + public async runQuery(uri: string, selection: azdata.ISelectionData, queryInput: QueryEditorInput, runOptions?: azdata.ExecutionPlanOptions): Promise { return this.doRunQuery(uri, selection, queryInput, false, runOptions); } /** * Run the current SQL statement for the given URI */ - public async runQueryStatement(uri: string, selection: azdata.ISelectionData, queryInput: QueryInput): Promise { + public async runQueryStatement(uri: string, selection: azdata.ISelectionData, queryInput: QueryEditorInput): Promise { return this.doRunQuery(uri, selection, queryInput, true); } /** * Run the current SQL statement for the given URI */ - public async runQueryString(uri: string, selection: string, queryInput: QueryInput): Promise { + public async runQueryString(uri: string, selection: string, queryInput: QueryEditorInput): Promise { return this.doRunQuery(uri, selection, queryInput, true); } /** * Run Query implementation */ - private async doRunQuery(uri: string, selection: azdata.ISelectionData | string, queryInput: QueryInput, + private async doRunQuery(uri: string, selection: azdata.ISelectionData | string, queryInput: QueryEditorInput, runCurrentStatement: boolean, runOptions?: azdata.ExecutionPlanOptions): Promise { // Reuse existing query runner if it exists let queryRunner: QueryRunner | undefined; diff --git a/src/sql/platform/query/test/common/testQueryModelService.ts b/src/sql/platform/query/test/common/testQueryModelService.ts new file mode 100644 index 0000000000..f8fab5b9ce --- /dev/null +++ b/src/sql/platform/query/test/common/testQueryModelService.ts @@ -0,0 +1,116 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IQueryModelService, IQueryEvent } from 'sql/platform/query/common/queryModel'; +import QueryRunner from 'sql/platform/query/common/queryRunner'; +import * as azdata from 'azdata'; +import { QueryEditorInput } from 'sql/workbench/contrib/query/common/queryEditorInput'; +import { Event } from 'vs/base/common/event'; +import { QueryInfo } from 'sql/platform/query/common/queryModelService'; +import { DataService } from 'sql/workbench/contrib/grid/common/dataService'; + +export class TestQueryModelService implements IQueryModelService { + _serviceBrand: any; + onRunQueryUpdate: Event; + getQueryRunner(uri: string): QueryRunner { + throw new Error('Method not implemented.'); + } + getConfig(): Promise<{ [key: string]: any; }> { + throw new Error('Method not implemented.'); + } + getShortcuts(): Promise { + throw new Error('Method not implemented.'); + } + getQueryRows(uri: string, rowStart: number, numberOfRows: number, batchId: number, resultId: number): Promise { + throw new Error('Method not implemented.'); + } + runQuery(uri: string, selection: azdata.ISelectionData, queryInput: QueryEditorInput, runOptions?: azdata.ExecutionPlanOptions): void { + throw new Error('Method not implemented.'); + } + runQueryStatement(uri: string, selection: azdata.ISelectionData, queryInput: QueryEditorInput): void { + throw new Error('Method not implemented.'); + } + runQueryString(uri: string, selection: string, queryInput: QueryEditorInput) { + throw new Error('Method not implemented.'); + } + cancelQuery(input: string | QueryRunner): void { + throw new Error('Method not implemented.'); + } + disposeQuery(uri: string): void { + throw new Error('Method not implemented.'); + } + isRunningQuery(uri: string): boolean { + throw new Error('Method not implemented.'); + } + getDataService(uri: string): DataService { + throw new Error('Method not implemented.'); + } + refreshResultsets(uri: string): void { + throw new Error('Method not implemented.'); + } + sendGridContentEvent(uri: string, eventName: string): void { + throw new Error('Method not implemented.'); + } + resizeResultsets(uri: string): void { + throw new Error('Method not implemented.'); + } + onAngularLoaded(uri: string): void { + throw new Error('Method not implemented.'); + } + copyResults(uri: string, selection: Slick.Range[], batchId: number, resultId: number, includeHeaders?: boolean): void { + throw new Error('Method not implemented.'); + } + showWarning(uri: string, message: string): void { + throw new Error('Method not implemented.'); + } + showError(uri: string, message: string): void { + throw new Error('Method not implemented.'); + } + showCommitError(error: string): void { + throw new Error('Method not implemented.'); + } + get onRunQueryStart(): Event { + throw new Error('Method not implemented.'); + } + + get onRunQueryComplete(): Event { + throw new Error('Method not implemented.'); + } + + get onQueryEvent(): Event { + throw new Error('Method not implemented.'); + } + initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number, queryString: string): void { + throw new Error('Method not implemented.'); + } + disposeEdit(ownerUri: string): Promise { + throw new Error('Method not implemented.'); + } + updateCell(ownerUri: string, rowId: number, columnId: number, newValue: string): Promise { + throw new Error('Method not implemented.'); + } + commitEdit(ownerUri: any): Promise { + throw new Error('Method not implemented.'); + } + createRow(ownerUri: string): Promise { + throw new Error('Method not implemented.'); + } + deleteRow(ownerUri: string, rowId: number): Promise { + throw new Error('Method not implemented.'); + } + revertCell(ownerUri: string, rowId: number, columnId: number): Promise { + throw new Error('Method not implemented.'); + } + revertRow(ownerUri: string, rowId: number): Promise { + throw new Error('Method not implemented.'); + } + getEditRows(ownerUri: string, rowStart: number, numberOfRows: number): Promise { + throw new Error('Method not implemented.'); + } + _getQueryInfo(uri: string): QueryInfo { + throw new Error('Method not implemented.'); + } + onEditSessionReady: Event; +} diff --git a/src/sql/workbench/api/browser/mainThreadNotebookDocumentsAndEditors.ts b/src/sql/workbench/api/browser/mainThreadNotebookDocumentsAndEditors.ts index 5ff1124ed5..44c37fec44 100644 --- a/src/sql/workbench/api/browser/mainThreadNotebookDocumentsAndEditors.ts +++ b/src/sql/workbench/api/browser/mainThreadNotebookDocumentsAndEditors.ts @@ -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) { diff --git a/src/sql/workbench/browser/customInputConverter.ts b/src/sql/workbench/browser/customInputConverter.ts deleted file mode 100644 index 486ea1ba96..0000000000 --- a/src/sql/workbench/browser/customInputConverter.ts +++ /dev/null @@ -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 = input; - if (untitledCast) { - return untitledCast.getResource(); - } - } - - if (input instanceof FileEditorInput) { - let fileCast: FileEditorInput = input; - if (fileCast) { - return fileCast.getResource(); - } - } - - if (input instanceof ResourceEditorInput) { - let resourceCast: 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(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 = input; - return (untitledCast && untitledCast.getMode() === notebookModeId); - } - return false; -} - -function withService(instantiationService: IInstantiationService, serviceId: ServiceIdentifier, 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 = 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(instantiationService, INotebookService, notebookService => { - for (const editor of notebookService.listNotebookEditors()) { - if (editor.notebookParams.notebookUri === resource) { - return notebookModeId; - } - } - return sqlModeId; - }); -} diff --git a/src/sql/workbench/browser/parts/editor/editorStatusModeSelect.ts b/src/sql/workbench/browser/parts/editor/editorStatusModeSelect.ts new file mode 100644 index 0000000000..747048dd6f --- /dev/null +++ b/src/sql/workbench/browser/parts/editor/editorStatusModeSelect.ts @@ -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(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 { + 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) + } +} diff --git a/src/sql/workbench/browser/taskUtilities.ts b/src/sql/workbench/browser/taskUtilities.ts index 3007e1510d..94397e4a42 100644 --- a/src/sql/workbench/browser/taskUtilities.ts +++ b/src/sql/workbench/browser/taskUtilities.ts @@ -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 { return new Promise((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) { diff --git a/src/sql/workbench/common/editorReplacer.contribution.ts b/src/sql/workbench/common/editorReplacer.contribution.ts new file mode 100644 index 0000000000..bcfa5da426 --- /dev/null +++ b/src/sql/workbench/common/editorReplacer.contribution.ts @@ -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(WorkbenchExtensions.Workbench); +workbenchContributionsRegistry.registerWorkbenchContribution(EditorReplacementContribution, LifecyclePhase.Starting); diff --git a/src/sql/workbench/common/editorReplacerContribution.ts b/src/sql/workbench/common/editorReplacerContribution.ts new file mode 100644 index 0000000000..3d24444fd3 --- /dev/null +++ b/src/sql/workbench/common/editorReplacerContribution.ts @@ -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(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); + } +} diff --git a/src/sql/workbench/common/languageAssociation.ts b/src/sql/workbench/common/languageAssociation.ts new file mode 100644 index 0000000000..b9adf3b39f --- /dev/null +++ b/src/sql/workbench/common/languageAssociation.ts @@ -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()); \ No newline at end of file diff --git a/src/sql/workbench/common/sqlWorkbenchUtils.ts b/src/sql/workbench/common/sqlWorkbenchUtils.ts index d8396d24bb..1fff338885 100644 --- a/src/sql/workbench/common/sqlWorkbenchUtils.ts +++ b/src/sql/workbench/common/sqlWorkbenchUtils.ts @@ -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(workspaceConfigService: IConfigurationServi export function getEditorUri(input: IEditorInput): string { let uri: URI; - if (input instanceof QueryInput) { - let queryCast: QueryInput = input; - if (queryCast) { - uri = queryCast.getResource(); - } + if (input instanceof QueryEditorInput) { + uri = input.getResource(); } if (uri) { diff --git a/src/sql/workbench/contrib/charts/browser/actions.ts b/src/sql/workbench/contrib/charts/browser/actions.ts index e32ee49c0a..4b5f8cd8b8 100644 --- a/src/sql/workbench/contrib/charts/browser/actions.ts +++ b/src/sql/workbench/contrib/charts/browser/actions.ts @@ -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; diff --git a/src/sql/workbench/contrib/commandLine/electron-browser/commandLine.ts b/src/sql/workbench/contrib/commandLine/electron-browser/commandLine.ts index 54eb2d807e..e9d165fdc0 100644 --- a/src/sql/workbench/contrib/commandLine/electron-browser/commandLine.ts +++ b/src/sql/workbench/contrib/commandLine/electron-browser/commandLine.ts @@ -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 { 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); } } diff --git a/src/sql/workbench/contrib/commandLine/test/electron-browser/commandLine.test.ts b/src/sql/workbench/contrib/commandLine/test/electron-browser/commandLine.test.ts index d23cd2a007..ec07fbefab 100644 --- a/src/sql/workbench/contrib/commandLine/test/electron-browser/commandLine.test.ts +++ b/src/sql/workbench/contrib/commandLine/test/electron-browser/commandLine.test.ts @@ -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 { let configurationService = TypeMoq.Mock.ofType(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 = TypeMoq.Mock.ofType(QueryInput); + const querymodelService = TypeMoq.Mock.ofType(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 = TypeMoq.Mock.ofType(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(p => p.serverName === 'myserver' && p.authenticationType === Constants.sqlLogin), uri.toString(), - TypeMoq.It.is(i => i.params.input === queryInput.object && i.params.connectionType === ConnectionType.editor)) + TypeMoq.It.is(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(); }); diff --git a/src/sql/workbench/contrib/connection/browser/connectionActions.ts b/src/sql/workbench/contrib/connection/browser/connectionActions.ts index 3609c6e65f..0c14250bdf 100644 --- a/src/sql/workbench/contrib/connection/browser/connectionActions.ts +++ b/src/sql/workbench/contrib/connection/browser/connectionActions.ts @@ -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 { return new Promise((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); diff --git a/src/sql/workbench/contrib/editData/browser/editDataInput.ts b/src/sql/workbench/contrib/editData/browser/editDataInput.ts index dc312dc1a5..61dbf73059 100644 --- a/src/sql/workbench/contrib/editData/browser/editDataInput.ts +++ b/src/sql/workbench/contrib/editData/browser/editDataInput.ts @@ -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'; diff --git a/src/sql/workbench/contrib/notebook/browser/models/notebookInput.ts b/src/sql/workbench/contrib/notebook/browser/models/notebookInput.ts index 62844ab158..faa49b3484 100644 --- a/src/sql/workbench/contrib/notebook/browser/models/notebookInput.ts +++ b/src/sql/workbench/contrib/notebook/browser/models/notebookInput.ts @@ -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; @@ -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 { diff --git a/src/sql/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/sql/workbench/contrib/notebook/browser/notebook.contribution.ts index 382df5b2b4..063c57c059 100644 --- a/src/sql/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/sql/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -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(EditorInputFactoryExtensions.EditorInputFactories) + .registerEditorInputFactory(FileNotebookInput.ID, FileNoteBookEditorInputFactory); + +Registry.as(EditorInputFactoryExtensions.EditorInputFactories) + .registerEditorInputFactory(UntitledNotebookInput.ID, UntitledNoteBookEditorInputFactory); + +Registry.as(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(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 = Registry.as(Extensions.WorkbenchActions); +const actionRegistry = Registry.as(WorkbenchActionsExtensions.WorkbenchActions); actionRegistry.registerWorkbenchAction( new SyncActionDescriptor( @@ -145,7 +165,7 @@ registerAction({ } }); -const configurationRegistry = Registry.as(ConfigExtensions.Configuration); +const configurationRegistry = Registry.as(ConfigExtensions.Configuration); configurationRegistry.registerConfiguration({ 'id': 'notebook', 'title': 'Notebook', diff --git a/src/sql/workbench/contrib/notebook/browser/outputs/tableRenderers.ts b/src/sql/workbench/contrib/notebook/browser/outputs/tableRenderers.ts index b85b8d6723..a91c8cc864 100644 --- a/src/sql/workbench/contrib/notebook/browser/outputs/tableRenderers.ts +++ b/src/sql/workbench/contrib/notebook/browser/outputs/tableRenderers.ts @@ -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'; /** diff --git a/src/sql/workbench/contrib/notebook/common/models/fileNotebookInput.ts b/src/sql/workbench/contrib/notebook/common/models/fileNotebookInput.ts new file mode 100644 index 0000000000..5aeec05f89 --- /dev/null +++ b/src/sql/workbench/contrib/notebook/common/models/fileNotebookInput.ts @@ -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; + } +} diff --git a/src/sql/workbench/contrib/notebook/common/models/nodebookInputFactory.ts b/src/sql/workbench/contrib/notebook/common/models/nodebookInputFactory.ts new file mode 100644 index 0000000000..f27c67d542 --- /dev/null +++ b/src/sql/workbench/contrib/notebook/common/models/nodebookInputFactory.ts @@ -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(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); + } +} diff --git a/src/sql/workbench/contrib/notebook/common/models/untitledNotebookInput.ts b/src/sql/workbench/contrib/notebook/common/models/untitledNotebookInput.ts new file mode 100644 index 0000000000..adf0f50cbb --- /dev/null +++ b/src/sql/workbench/contrib/notebook/common/models/untitledNotebookInput.ts @@ -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; + } +} diff --git a/src/sql/workbench/contrib/query/browser/keyboardQueryActions.ts b/src/sql/workbench/contrib/query/browser/keyboardQueryActions.ts index 636864a91b..8e1e2b2737 100644 --- a/src/sql/workbench/contrib/query/browser/keyboardQueryActions.ts +++ b/src/sql/workbench/contrib/query/browser/keyboardQueryActions.ts @@ -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 { const editor = this.editorService.activeEditor; - if (editor instanceof QueryInput) { + if (editor instanceof QueryEditorInput) { this.connectionManagementService.rebuildIntelliSenseCache(editor.uri); } return Promise.resolve(null); diff --git a/src/sql/workbench/contrib/query/browser/query.contribution.ts b/src/sql/workbench/contrib/query/browser/query.contribution.ts index 0578c4a9ee..57db8768b5 100644 --- a/src/sql/workbench/contrib/query/browser/query.contribution.ts +++ b/src/sql/workbench/contrib/query/browser/query.contribution.ts @@ -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(EditorInputFactoryExtensions.EditorInputFactories) + .registerEditorInputFactory(FileQueryEditorInput.ID, FileQueryEditorInputFactory); + +Registry.as(EditorInputFactoryExtensions.EditorInputFactories) + .registerEditorInputFactory(UntitledQueryEditorInput.ID, UntitledQueryEditorInputFactory); + +Registry.as(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(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(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 = Registry.as(Extensions.WorkbenchActions); diff --git a/src/sql/workbench/contrib/query/browser/queryEditor.ts b/src/sql/workbench/contrib/query/browser/queryEditor.ts index 721821485b..d3781b4f60 100644 --- a/src/sql/workbench/contrib/query/browser/queryEditor.ts +++ b/src/sql/workbench/contrib/query/browser/queryEditor.ts @@ -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 { + public async setInput(newInput: QueryEditorInput, options: EditorOptions, token: CancellationToken): Promise { 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 } diff --git a/src/sql/workbench/contrib/query/browser/queryResultsEditor.ts b/src/sql/workbench/contrib/query/browser/queryResultsEditor.ts index 167c93dc08..c554b187ca 100644 --- a/src/sql/workbench/contrib/query/browser/queryResultsEditor.ts +++ b/src/sql/workbench/contrib/query/browser/queryResultsEditor.ts @@ -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('textCompareEditorVisible', false); diff --git a/src/sql/workbench/contrib/query/browser/statusBarItems.ts b/src/sql/workbench/contrib/query/browser/statusBarItems.ts index 96edc6f7ac..fde5fb6e01 100644 --- a/src/sql/workbench/contrib/query/browser/statusBarItems.ts +++ b/src/sql/workbench/contrib/query/browser/statusBarItems.ts @@ -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) { diff --git a/src/sql/workbench/contrib/query/common/fileQueryEditorInput.ts b/src/sql/workbench/contrib/query/common/fileQueryEditorInput.ts new file mode 100644 index 0000000000..67457a6d05 --- /dev/null +++ b/src/sql/workbench/contrib/query/common/fileQueryEditorInput.ts @@ -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 = { [K in keyof T]: T[K] }; + +export class FileQueryEditorInput extends QueryEditorInput implements PublicPart { + + 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 { + 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(); + } +} diff --git a/src/sql/workbench/contrib/query/common/queryInput.ts b/src/sql/workbench/contrib/query/common/queryEditorInput.ts similarity index 59% rename from src/sql/workbench/contrib/query/common/queryInput.ts rename to src/sql/workbench/contrib/query/common/queryEditorInput.ts index b4d71e0b11..ffcd0dd530 100644 --- a/src/sql/workbench/contrib/query/common/queryInput.ts +++ b/src/sql/workbench/contrib/query/common/queryEditorInput.ts @@ -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 = { [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, 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; - 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(); - 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 { return this._sql.revert(); } - public setMode(mode: string) { - this._sql.setMode(mode); - } + public revert(): Promise { 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 { return this._sql.onDidModelChangeContent; } - public get onDidModelChangeEncoding(): Event { return this._sql.onDidModelChangeEncoding; } - public resolve(): Promise { return this._sql.resolve(); } - public save(): Promise { return this._sql.save(); } - public isDirty(): boolean { return this._sql.isDirty(); } - public confirmSave(): Promise { 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 { return this._text.save(); } + public isDirty(): boolean { return this._text.isDirty(); } + public confirmSave(): Promise { 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 { @@ -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 { diff --git a/src/sql/workbench/contrib/query/common/queryInputFactory.ts b/src/sql/workbench/contrib/query/common/queryInputFactory.ts new file mode 100644 index 0000000000..e8790d78c6 --- /dev/null +++ b/src/sql/workbench/contrib/query/common/queryInputFactory.ts @@ -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(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); + } +} diff --git a/src/sql/workbench/contrib/query/common/resultsGridContribution.ts b/src/sql/workbench/contrib/query/common/resultsGrid.contribution.ts similarity index 100% rename from src/sql/workbench/contrib/query/common/resultsGridContribution.ts rename to src/sql/workbench/contrib/query/common/resultsGrid.contribution.ts diff --git a/src/sql/workbench/contrib/query/common/untitledQueryEditorInput.ts b/src/sql/workbench/contrib/query/common/untitledQueryEditorInput.ts new file mode 100644 index 0000000000..361683453e --- /dev/null +++ b/src/sql/workbench/contrib/query/common/untitledQueryEditorInput.ts @@ -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 = { [K in keyof T]: T[K] }; + +export class UntitledQueryEditorInput extends QueryEditorInput implements IEncodingSupport, PublicPart { + + 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 { + 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; + } +} diff --git a/src/sql/workbench/contrib/query/test/browser/queryActions.test.ts b/src/sql/workbench/contrib/query/test/browser/queryActions.test.ts index b759e96264..75ac627e21 100644 --- a/src/sql/workbench/contrib/query/test/browser/queryActions.test.ts +++ b/src/sql/workbench/contrib/query/test/browser/queryActions.test.ts @@ -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; let calledRunQueryOnInput: boolean = undefined; - let testQueryInput: TypeMoq.Mock; - let configurationService: TypeMoq.Mock; + let testQueryInput: TypeMoq.Mock; + let configurationService: TypeMoq.Mock; + let queryModelService: TypeMoq.Mock; + let connectionManagementService: TypeMoq.Mock; 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); + queryModelService.setup(q => q.onRunQueryStart).returns(() => Event.None); + queryModelService.setup(q => q.onRunQueryComplete).returns(() => Event.None); + connectionManagementService = TypeMoq.Mock.ofType(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 = 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(() => { 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(() => { databaseName: databaseName }); + connectionManagementService.setup(x => x.onConnectionChanged).returns(() => dbChangedEmitter.event); + connectionManagementService.setup(x => x.getConnectionProfile(TypeMoq.It.isAny())).returns(() => { 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(() => { databaseName: databaseName }); + connectionManagementService.setup(x => x.onConnectionChanged).returns(() => dbChangedEmitter.event); + connectionManagementService.setup(x => x.getConnectionProfile(TypeMoq.It.isAny())).returns(() => { 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(); // ... 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 = { diff --git a/src/sql/workbench/contrib/query/test/browser/queryEditor.test.ts b/src/sql/workbench/contrib/query/test/browser/queryEditor.test.ts index 60dd0f0a4d..ca88ff41a1 100644 --- a/src/sql/workbench/contrib/query/test/browser/queryEditor.test.ts +++ b/src/sql/workbench/contrib/query/test/browser/queryEditor.test.ts @@ -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; @@ -247,8 +248,8 @@ suite('SQL QueryEditor Tests', () => { suite('Action Tests', () => { let queryActionInstantiationService: TypeMoq.Mock; let queryConnectionService: TypeMoq.Mock; - let queryModelService: TypeMoq.Mock; - let queryInput: QueryInput; + let queryModelService: TypeMoq.Mock; + 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'); diff --git a/src/sql/workbench/contrib/queryPlan/browser/queryPlan.contribution.ts b/src/sql/workbench/contrib/queryPlan/browser/queryPlan.contribution.ts index 5c326837dd..8ae45e8d7b 100644 --- a/src/sql/workbench/contrib/queryPlan/browser/queryPlan.contribution.ts +++ b/src/sql/workbench/contrib/queryPlan/browser/queryPlan.contribution.ts @@ -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(Extensions.Editors) .registerEditor(queryPlanEditorDescriptor, [new SyncDescriptor(QueryPlanInput)]); + +Registry.as(LanguageAssociationExtensions.LanguageAssociations) + .registerLanguageAssociation('sqlplan', (accessor, editor) => { + const instantiationService = accessor.get(IInstantiationService); + return instantiationService.createInstance(QueryPlanInput, editor.getResource()); + }, (editor: QueryPlanInput) => undefined); diff --git a/src/sql/workbench/contrib/queryPlan/common/queryPlanInput.ts b/src/sql/workbench/contrib/queryPlan/common/queryPlanInput.ts index 098d4fd205..62a52db921 100644 --- a/src/sql/workbench/contrib/queryPlan/common/queryPlanInput.ts +++ b/src/sql/workbench/contrib/queryPlan/common/queryPlanInput.ts @@ -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; - } } diff --git a/src/sql/workbench/services/queryEditor/browser/queryEditorService.ts b/src/sql/workbench/services/queryEditor/browser/queryEditorService.ts index c7d1794ab1..7abb7c726b 100644 --- a/src/sql/workbench/services/queryEditor/browser/queryEditorService.ts +++ b/src/sql/workbench/services/queryEditor/browser/queryEditorService.ts @@ -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 = 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 { - 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 { - return this.createPrefixedSqlFilePath(untitledFilePrefix); + return this.createPrefixedSqlFilePath('SQLQuery'); } private async createPrefixedSqlFilePath(prefix: string): Promise { @@ -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 = 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 = control; - let newModel = codeEditor ? codeEditor.getModel() : undefined; - return newModel; - } } diff --git a/src/sql/workbench/services/queryEditor/common/queryEditorService.ts b/src/sql/workbench/services/queryEditor/common/queryEditorService.ts index 0a934b846d..e69b749c76 100644 --- a/src/sql/workbench/services/queryEditor/common/queryEditorService.ts +++ b/src/sql/workbench/services/queryEditor/common/queryEditorService.ts @@ -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; } diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index 349287a214..24ee3f3ca3 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -124,11 +124,6 @@ class UntitledEditorInputFactory implements IEditorInputFactory { const untitledEditorInput = editorInput; - // {{SQL CARBON EDIT}} @todo anthonydresser 4/12/19 investigate - if (!untitledEditorInput.getResource()) { - return undefined; - } - let resource = untitledEditorInput.getResource(); if (untitledEditorInput.hasAssociatedFilePath) { resource = toLocalResource(resource, this.environmentService.configuration.remoteAuthority); // untitled with associated file path use the local schema diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 6717e17d86..0ca8166512 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -32,7 +32,7 @@ import { RunOnceWorker } from 'vs/base/common/async'; import { EventType as TouchEventType, GestureEvent } from 'vs/base/browser/touch'; import { TitleControl } from 'vs/workbench/browser/parts/editor/titleControl'; import { IEditorGroupsAccessor, IEditorGroupView, IEditorPartOptionsChangeEvent, getActiveTextEditorOptions, IEditorOpeningEvent } from 'vs/workbench/browser/parts/editor/editor'; -// import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ActionRunner, IAction, Action } from 'vs/base/common/actions'; @@ -42,10 +42,6 @@ import { IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions' import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -// {{SQL CARBON EDIT}} -import { ICommandService } from 'vs/platform/commands/common/commands'; -import { GlobalNewUntitledFileAction } from 'vs/workbench/contrib/files/browser/fileActions'; -// {{SQL CARBON EDIT}} - End import { isErrorWithActions, IErrorWithActions } from 'vs/base/common/errorsWithActions'; import { IVisibleEditor } from 'vs/workbench/services/editor/common/editorService'; import { withNullAsUndefined, withUndefinedAsNull } from 'vs/base/common/types'; @@ -132,12 +128,10 @@ export class EditorGroupView extends Themable implements IEditorGroupView { @INotificationService private readonly notificationService: INotificationService, @IDialogService private readonly dialogService: IDialogService, @ITelemetryService private readonly telemetryService: ITelemetryService, - // @IUntitledEditorService private readonly untitledEditorService: IUntitledEditorService, {{SQL CARBON EDIT}} no unused + @IUntitledEditorService private readonly untitledEditorService: IUntitledEditorService, @IKeybindingService private readonly keybindingService: IKeybindingService, @IMenuService private readonly menuService: IMenuService, - @IContextMenuService private readonly contextMenuService: IContextMenuService, - // {{SQL CARBON EDIT}} - @ICommandService private commandService: ICommandService + @IContextMenuService private readonly contextMenuService: IContextMenuService ) { super(themeService); @@ -260,8 +254,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this._register(addDisposableListener(this.element, EventType.DBLCLICK, e => { if (this.isEmpty) { EventHelper.stop(e); - // {{SQL CARBON EDIT}} - this.commandService.executeCommand(GlobalNewUntitledFileAction.ID).then(undefined, err => this.notificationService.warn(err)); + + this.openEditor(this.untitledEditorService.createOrGet(), EditorOptions.create({ pinned: true })); } })); @@ -1463,7 +1457,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { inactiveReplacements.forEach(({ editor, replacement, options }) => { // Open inactive editor - this.doOpenEditor(replacement, options); + this.openEditor(replacement, options); // {{SQL CARBON EDIT}} use this.openEditor to allow us to override the open, we could potentially add this to vscode but i don't think they would care // Close replaced inactive editor unless they match if (!editor.matches(replacement)) { @@ -1476,7 +1470,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { if (activeReplacement) { // Open replacement as active editor - const openEditorResult = this.doOpenEditor(activeReplacement.replacement, activeReplacement.options); + const openEditorResult = this.openEditor(activeReplacement.replacement, activeReplacement.options); // {{SQL CARBON EDIT}} use this.openEditor to allow us to override the open, we could potentially add this to vscode but i don't think they would care // Close replaced active editor unless they match if (!activeReplacement.editor.matches(activeReplacement.replacement)) { diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index 095433bc24..6eae0cad11 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -50,9 +50,7 @@ import { Event } from 'vs/base/common/event'; import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment, IStatusbarEntry } from 'vs/workbench/services/statusbar/common/statusbar'; - -// {{SQL CARBON EDIT}} -import { IQueryEditorService } from 'sql/workbench/services/queryEditor/common/queryEditorService'; +import { setMode } from 'sql/workbench/browser/parts/editor/editorStatusModeSelect'; // {{SQL CARBON EDIT}} class SideBySideEditorEncodingSupport implements IEncodingSupport { constructor(private master: IEncodingSupport, private details: IEncodingSupport) { } @@ -872,8 +870,7 @@ export class ChangeModeAction extends Action { @IQuickInputService private readonly quickInputService: IQuickInputService, @IPreferencesService private readonly preferencesService: IPreferencesService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IUntitledEditorService private readonly untitledEditorService: IUntitledEditorService, - @IQueryEditorService private readonly queryEditorService: IQueryEditorService // {{ SQL CARBON EDIT }} + @IUntitledEditorService private readonly untitledEditorService: IUntitledEditorService ) { super(actionId, actionLabel); } @@ -973,16 +970,16 @@ export class ChangeModeAction extends Action { } // Change mode for active editor - const activeEditor = this.editorService.activeControl; // {{SQL CARBON EDIT}} @anthonydresser change to activeControl from active editor + const activeEditor = this.editorService.activeEditor; if (activeEditor) { - const modeSupport = toEditorWithModeSupport(activeEditor.input); // {{SQL CARBON EDIT}} @anthonydresser reference input rather than activeeditor directly + const modeSupport = toEditorWithModeSupport(activeEditor); if (modeSupport) { // Find mode let languageSelection: ILanguageSelection | undefined; if (pick === autoDetectMode) { if (textModel) { - const resource = toResource(activeEditor.input, { supportSideBySide: SideBySideEditor.MASTER }); // {{SQL CARBON EDIT}} @anthonydresser reference input rather than activeeditor directly + const resource = toResource(activeEditor, { supportSideBySide: SideBySideEditor.MASTER }); if (resource) { languageSelection = this.modeService.createByFilepathOrFirstLine(resource, textModel.getLineContent(1)); } @@ -991,14 +988,9 @@ export class ChangeModeAction extends Action { languageSelection = this.modeService.createByLanguageName(pick.label); } - // {{SQL CARBON EDIT}} @anthonydresser preform a check before we actuall set the mode // Change mode if (typeof languageSelection !== 'undefined') { - this.queryEditorService.sqlLanguageModeCheck(textModel, languageSelection, activeEditor).then(newTextModel => { - if (newTextModel) { - modeSupport.setMode(languageSelection.languageIdentifier.language); - } - }); + return this.instantiationService.invokeFunction(setMode, modeSupport, activeEditor, languageSelection.languageIdentifier.language); // {{SQL CARBON EDIT}} @anthonydresser use custom setMode } } } diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index 564bfb2395..c04eb4006c 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -32,7 +32,7 @@ import { Color } from 'vs/base/common/color'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { MergeGroupMode, IMergeGroupOptions } from 'vs/workbench/services/editor/common/editorGroupsService'; -// import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { addClass, addDisposableListener, hasClass, EventType, EventHelper, removeClass, Dimension, scheduleAtNextAnimationFrame, findParentWithClass, clearNode } from 'vs/base/browser/dom'; import { localize } from 'vs/nls'; import { IEditorGroupsAccessor, IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor'; @@ -44,10 +44,8 @@ import { withNullAsUndefined, assertAllDefined, assertIsDefined } from 'vs/base/ import { ILabelService } from 'vs/platform/label/common/label'; // {{SQL CARBON EDIT}} -- Display the editor's tab color -import { ICommandService } from 'vs/platform/commands/common/commands'; import * as QueryConstants from 'sql/workbench/contrib/query/common/constants'; import * as WorkbenchUtils from 'sql/workbench/common/sqlWorkbenchUtils'; -import { GlobalNewUntitledFileAction } from 'vs/workbench/contrib/files/browser/fileActions'; // {{SQL CARBON EDIT}} -- End interface IEditorInputLabel { @@ -81,7 +79,7 @@ export class TabsTitleControl extends TitleControl { group: IEditorGroupView, @IContextMenuService contextMenuService: IContextMenuService, @IInstantiationService instantiationService: IInstantiationService, - // @IUntitledEditorService private readonly untitledEditorService: IUntitledEditorService, {{SQL CARBON EDIT}} comment out inject + @IUntitledEditorService private readonly untitledEditorService: IUntitledEditorService, @IContextKeyService contextKeyService: IContextKeyService, @IKeybindingService keybindingService: IKeybindingService, @ITelemetryService telemetryService: ITelemetryService, @@ -92,9 +90,6 @@ export class TabsTitleControl extends TitleControl { @IExtensionService extensionService: IExtensionService, @IConfigurationService configurationService: IConfigurationService, @IFileService fileService: IFileService, - // {{SQL CARBON EDIT}} -- Display the editor's tab color - @ICommandService private commandService: ICommandService, - // {{SQL CARBON EDIT}} -- End @ILabelService labelService: ILabelService ) { super(parent, accessor, group, contextMenuService, instantiationService, contextKeyService, keybindingService, telemetryService, notificationService, menuService, quickOpenService, themeService, extensionService, configurationService, fileService, labelService); @@ -186,8 +181,8 @@ export class TabsTitleControl extends TitleControl { this._register(addDisposableListener(tabsContainer, EventType.DBLCLICK, e => { if (e.target === tabsContainer) { EventHelper.stop(e); - // {{SQL CARBON EDIT}} - this.commandService.executeCommand(GlobalNewUntitledFileAction.ID).then(undefined, err => this.notificationService.warn(err)); + + this.group.openEditor(this.untitledEditorService.createOrGet(), { pinned: true /* untitled is always pinned */, index: this.group.count /* always at the end */ }); } })); diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index da8a85f0be..67c927afc5 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -447,8 +447,7 @@ export abstract class EditorInput extends Disposable implements IEditorInput { * Subclasses can set this to false if it does not make sense to split the editor input. */ supportsSplitEditor(): boolean { - // {{SQL CARBON EDIT}} @anthonydresser 05/19/2019 investigate - return false; // TODO reenable when multiple Angular components of the same type can be open simultaneously + return true; } /** @@ -475,16 +474,6 @@ export abstract class EditorInput extends Disposable implements IEditorInput { super.dispose(); } - - // {{SQL CARBON EDIT}} @anthonydresser 05/19/2019 investigate - // Saving is not supported in the EditData query editor, so this can be overriden in its Input. - private _savingSupported: boolean = true; - public get savingSupported(): boolean { - return this._savingSupported; - } - public disableSaving() { - this._savingSupported = false; - } } export const enum ConfirmResult { diff --git a/src/vs/workbench/common/editor/editorGroup.ts b/src/vs/workbench/common/editor/editorGroup.ts index 0550c2de42..58199130e6 100644 --- a/src/vs/workbench/common/editor/editorGroup.ts +++ b/src/vs/workbench/common/editor/editorGroup.ts @@ -14,11 +14,9 @@ import { ResourceMap } from 'vs/base/common/map'; import { coalesce, firstIndex } from 'vs/base/common/arrays'; // {{SQL CARBON EDIT}} -import { QueryInput } from 'sql/workbench/contrib/query/common/queryInput'; import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput'; -import * as CustomInputConverter from 'sql/workbench/browser/customInputConverter'; -import { NotebookInput } from 'sql/workbench/contrib/notebook/browser/models/notebookInput'; import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; +import { QueryEditorInput } from 'sql/workbench/contrib/query/common/queryEditorInput'; const EditorOpenPositioning = { LEFT: 'left', @@ -643,16 +641,7 @@ export class EditorGroup extends Disposable { let serializableEditors: EditorInput[] = []; let serializedEditors: ISerializedEditorInput[] = []; let serializablePreviewIndex: number | undefined; - // {{SQL CARBON EDIT}} - const editors = this.editors.map(e => { - if (e instanceof QueryInput) { - return e.sql; - } else if (e instanceof NotebookInput) { - return e.textInput; - } - return e; - }); - editors.forEach(e => { + this.editors.forEach(e => { const factory = registry.getEditorInputFactory(e.getTypeId()); if (factory) { // {{SQL CARBON EDIT}} @@ -675,16 +664,7 @@ export class EditorGroup extends Disposable { } }); - // {{SQL CARBON EDIT}} - let mru = this.mru.map(e => { - if (e instanceof QueryInput) { - return e.sql; - } else if (e instanceof NotebookInput) { - return e.textInput; - } - return e; - }); - const serializableMru = mru.map(e => this.indexOf(e, serializableEditors)).filter(i => i >= 0); + const serializableMru = this.mru.map(e => this.indexOf(e, serializableEditors)).filter(i => i >= 0); return { id: this.id, @@ -714,8 +694,7 @@ export class EditorGroup extends Disposable { this.updateResourceMap(editor, false /* add */); } - // {{SQL CARBON EDIT}} - return CustomInputConverter.convertEditorInput(editor, undefined, this.instantiationService); + return editor; } return null; @@ -737,7 +716,7 @@ export class EditorGroup extends Disposable { let n = 0; while (n < this.editors.length) { let editor = this.editors[n]; - if (editor instanceof QueryInput && editor.matchInputInstanceType(FileEditorInput) && !editor.isDirty() && await editor.inputFileExists() === false && this.editors.length > 1) { + if (editor instanceof QueryEditorInput && editor.matchInputInstanceType(FileEditorInput) && !editor.isDirty() && await editor.inputFileExists() === false && this.editors.length > 1) { // remove from editors list so that they do not get restored this.editors.splice(n, 1); let index = firstIndex(this.mru, e => e.matches(editor)); diff --git a/src/vs/workbench/common/editor/untitledEditorInput.ts b/src/vs/workbench/common/editor/untitledEditorInput.ts index 2e743932e1..a9da560f87 100644 --- a/src/vs/workbench/common/editor/untitledEditorInput.ts +++ b/src/vs/workbench/common/editor/untitledEditorInput.ts @@ -125,11 +125,6 @@ export class UntitledEditorInput extends EditorInput implements IEncodingSupport } isDirty(): boolean { - // {{SQL CARBON EDIT}} - if (!this.savingSupported) { - return false; - } - if (this.cachedModel) { return this.cachedModel.isDirty(); } diff --git a/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts b/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts index f7b5e974a3..a76d73dfc7 100644 --- a/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts +++ b/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts @@ -30,7 +30,7 @@ import { withNullAsUndefined } from 'vs/base/common/types'; import { EditorActivation } from 'vs/platform/editor/common/editor'; // {{SQL CARBON EDIT}} -import { QueryInput } from 'sql/workbench/contrib/query/common/queryInput'; +import { QueryEditorInput } from 'sql/workbench/contrib/query/common/queryEditorInput'; export class FileEditorTracker extends Disposable implements IWorkbenchContribution { @@ -186,12 +186,12 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut } // {{SQL CARBON EDIT}} - Support FileEditorInput or QueryInput - private getOpenedFileEditors(dirtyState: boolean): (FileEditorInput | QueryInput)[] { - const editors: (FileEditorInput | QueryInput)[] = []; + private getOpenedFileEditors(dirtyState: boolean): (FileEditorInput | QueryEditorInput)[] { + const editors: (FileEditorInput | QueryEditorInput)[] = []; this.editorService.editors.forEach(editor => { // {{SQL CARBON EDIT}} - Support FileEditorInput or QueryInput - if (editor instanceof FileEditorInput || editor instanceof QueryInput) { + if (editor instanceof FileEditorInput || editor instanceof QueryEditorInput) { if (!!editor.isDirty() === dirtyState) { editors.push(editor); } @@ -220,7 +220,7 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut this.editorGroupService.groups.forEach(group => { group.editors.forEach(editor => { // {{SQL CARBON EDIT}} - Support FileEditorInput or QueryInput - if (editor instanceof FileEditorInput || editor instanceof QueryInput) { + if (editor instanceof FileEditorInput || editor instanceof QueryEditorInput) { const resource = editor.getResource(); // Update Editor if file (or any parent of the input) got renamed or moved diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index 332c9ff94f..dd8a33f374 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -46,9 +46,6 @@ import { AsyncDataTree } from 'vs/base/browser/ui/tree/asyncDataTree'; import { ExplorerItem, NewExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel'; import { onUnexpectedError, getErrorMessage } from 'vs/base/common/errors'; -// {{SQL CARBON EDIT}} -import { openNewQuery } from 'sql/workbench/contrib/query/browser/queryActions'; - export const NEW_FILE_COMMAND_ID = 'explorer.newFile'; export const NEW_FILE_LABEL = nls.localize('newFile', "New File"); @@ -155,15 +152,13 @@ export class GlobalNewUntitledFileAction extends Action { constructor( id: string, label: string, - // {{SQL CARBON EDIT}} - Make editorService protected and add other services - @IEditorService protected readonly editorService: IEditorService, - @IInstantiationService private readonly instantiationService: IInstantiationService + @IEditorService private readonly editorService: IEditorService ) { super(id, label); } run(): Promise { - return this.instantiationService.invokeFunction(openNewQuery); // {{SQL CARBON EDIT}} + return this.editorService.openEditor({ options: { pinned: true } }); // untitled are always pinned } } diff --git a/src/vs/workbench/contrib/files/browser/fileCommands.ts b/src/vs/workbench/contrib/files/browser/fileCommands.ts index c9642cd575..d9f683907a 100644 --- a/src/vs/workbench/contrib/files/browser/fileCommands.ts +++ b/src/vs/workbench/contrib/files/browser/fileCommands.ts @@ -5,7 +5,7 @@ import * as nls from 'vs/nls'; import { URI } from 'vs/base/common/uri'; -import { toResource, IEditorCommandsContext, SideBySideEditor, EditorInput } from 'vs/workbench/common/editor'; // {{SQL CARBON EDIT}} add edit input +import { toResource, IEditorCommandsContext, SideBySideEditor } from 'vs/workbench/common/editor'; import { IWindowOpenable, IOpenWindowOptions, isWorkspaceToOpen, IOpenEmptyWindowOptions } from 'vs/platform/windows/common/windows'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -128,14 +128,6 @@ async function save( return doSaveAs(resource, isSaveAs, options, editorService, fileService, untitledEditorService, textFileService, editorGroupService, queryEditorService, environmentService); // {{SQL CARBON EDIT}} add paramater } - if (resource && (fileService.canHandleResource(resource) || resource.scheme === Schemas.untitled)) { - // {{SQL CARBON EDIT}} - let editorInput = editorService.activeEditor; - if (editorInput instanceof EditorInput && !(editorInput).savingSupported) { - return; - } - } - // Save return doSave(resource, options, editorService, textFileService); } diff --git a/src/vs/workbench/services/editor/browser/editorService.ts b/src/vs/workbench/services/editor/browser/editorService.ts index 9eff28903b..ccdc691cb4 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -28,7 +28,6 @@ import { IEditorGroupView, IEditorOpeningEvent, EditorServiceImpl } from 'vs/wor import { ILabelService } from 'vs/platform/label/common/label'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { withNullAsUndefined } from 'vs/base/common/types'; -import { convertEditorInput, getFileMode } from 'sql/workbench/browser/customInputConverter'; //{{SQL CARBON EDIT}} type CachedEditorInput = ResourceEditorInput | IFileEditorInput | DataUriEditorInput; type OpenInEditorGroup = IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE; @@ -571,10 +570,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { // Untitled file support const untitledInput = input as IUntitledResourceInput; if (untitledInput.forceUntitled || !untitledInput.resource || (untitledInput.resource && untitledInput.resource.scheme === Schemas.untitled)) { - // {{SQL CARBON EDIT}} - // Need to get mode for QueryEditor and Notebook - let mode: string = untitledInput.mode ? untitledInput.mode : getFileMode(this.instantiationService, untitledInput.resource); - return convertEditorInput(this.untitledEditorService.createOrGet(untitledInput.resource, mode, untitledInput.contents, untitledInput.encoding), undefined, this.instantiationService); + return this.untitledEditorService.createOrGet(untitledInput.resource, untitledInput.mode, untitledInput.contents, untitledInput.encoding); } // Resource Editor Support @@ -585,9 +581,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { label = basename(resourceInput.resource); // derive the label from the path (but not for data URIs) } - // {{SQL CARBON EDIT}} - return convertEditorInput(this.createOrGet(resourceInput.resource, this.instantiationService, label, resourceInput.description, resourceInput.encoding, resourceInput.mode, resourceInput.forceFile) as EditorInput, - undefined, this.instantiationService); + return this.createOrGet(resourceInput.resource, this.instantiationService, label, resourceInput.description, resourceInput.encoding, resourceInput.mode, resourceInput.forceFile) as EditorInput; } throw new Error('Unknown input type'); diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 89e6637d88..f51c8d458e 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -363,13 +363,16 @@ import 'vs/workbench/contrib/userDataSync/browser/userDataSync.contribution'; // query import 'sql/workbench/contrib/query/browser/query.contribution'; -import 'sql/workbench/contrib/query/common/resultsGridContribution'; +import 'sql/workbench/contrib/query/common/resultsGrid.contribution'; // data explorer import 'sql/workbench/contrib/dataExplorer/browser/dataExplorer.contribution'; import 'sql/workbench/contrib/dataExplorer/browser/nodeActions.common.contribution'; // {{SQL CARBON EDIT}} +//editor replacement +import 'sql/workbench/common/editorReplacer.contribution'; + // tasks import 'sql/workbench/contrib/tasks/browser/tasks.contribution'; import 'sql/workbench/browser/actions.contribution';