diff --git a/src/sql/platform/capabilities/test/common/testCapabilitiesService.ts b/src/sql/platform/capabilities/test/common/testCapabilitiesService.ts index f3dc734ad9..9e6747ee80 100644 --- a/src/sql/platform/capabilities/test/common/testCapabilitiesService.ts +++ b/src/sql/platform/capabilities/test/common/testCapabilitiesService.ts @@ -14,6 +14,7 @@ import { mssqlProviderName } from 'sql/platform/connection/common/constants'; export class TestCapabilitiesService implements ICapabilitiesService { + private pgsqlProviderName = 'PGSQL'; public _serviceBrand: undefined; public capabilities: { [id: string]: ProviderFeatures } = {}; @@ -99,7 +100,13 @@ export class TestCapabilitiesService implements ICapabilitiesService { displayName: 'MSSQL', connectionOptions: connectionProvider, }; + let pgSQLCapabilities = { + providerId: this.pgsqlProviderName, + displayName: this.pgsqlProviderName, + connectionOptions: connectionProvider, + }; this.capabilities[mssqlProviderName] = { connection: msSQLCapabilities }; + this.capabilities[this.pgsqlProviderName] = { connection: pgSQLCapabilities }; } /** diff --git a/src/sql/platform/connection/common/connectionManagement.ts b/src/sql/platform/connection/common/connectionManagement.ts index f1f0342e1e..7c980a8d43 100644 --- a/src/sql/platform/connection/common/connectionManagement.ts +++ b/src/sql/platform/connection/common/connectionManagement.ts @@ -74,6 +74,9 @@ export interface IConnectionManagementService { onConnectionChanged: Event; onLanguageFlavorChanged: Event; + // Properties + providerNameToDisplayNameMap: { [providerDisplayName: string]: string }; + /** * Opens the connection dialog to create new connection */ @@ -180,6 +183,8 @@ export interface IConnectionManagementService { getDefaultProviderId(): string; + getUniqueConnectionProvidersByNameMap(providerNameToDisplayNameMap: { [providerDisplayName: string]: string }): { [providerDisplayName: string]: string }; + /** * Cancels the connection */ diff --git a/src/sql/platform/connection/test/common/testConnectionManagementService.ts b/src/sql/platform/connection/test/common/testConnectionManagementService.ts index b1f1ebc161..a781aafbf7 100644 --- a/src/sql/platform/connection/test/common/testConnectionManagementService.ts +++ b/src/sql/platform/connection/test/common/testConnectionManagementService.ts @@ -32,6 +32,10 @@ export class TestConnectionManagementService implements IConnectionManagementSer return conEvent.event; } + public get providerNameToDisplayNameMap(): { [providerDisplayName: string]: string } { + return {}; + } + registerProvider(providerId: string, provider: azdata.ConnectionProvider): void { } @@ -213,9 +217,14 @@ export class TestConnectionManagementService implements IConnectionManagementSer return Promise.resolve(); } + getUniqueConnectionProvidersByNameMap(providerNameToDisplayNameMap: { [providerDisplayName: string]: string }): { [providerDisplayName: string]: string } { + return {}; + } + getProviderIdFromUri(ownerUri: string): string { return undefined; } + hasRegisteredServers(): boolean { return true; } @@ -235,10 +244,6 @@ export class TestConnectionManagementService implements IConnectionManagementSer } - public getProviderNames(): string[] { - return []; - } - connectIfNotConnected(connection: IConnectionProfile, purpose?: 'dashboard' | 'insights' | 'connection', saveConnection: boolean = false): Promise { return undefined; } diff --git a/src/sql/workbench/contrib/connection/browser/connection.contribution.ts b/src/sql/workbench/contrib/connection/browser/connection.contribution.ts index 3c383623d4..caa72d545f 100644 --- a/src/sql/workbench/contrib/connection/browser/connection.contribution.ts +++ b/src/sql/workbench/contrib/connection/browser/connection.contribution.ts @@ -123,7 +123,7 @@ configurationRegistry.registerConfiguration({ }, 'sql.defaultEngine': { 'type': 'string', - 'description': localize('sql.defaultEngineDescription', "Default SQL Engine to use. This drives default language provider in .sql files and the default to use when creating a new connection. Valid option is currently MSSQL"), + 'description': localize('sql.defaultEngineDescription', "Default SQL Engine to use. This drives default language provider in .sql files and the default to use when creating a new connection."), 'default': 'MSSQL' }, 'connection.parseClipboardForConnectionString': { diff --git a/src/sql/workbench/contrib/query/browser/flavorStatus.ts b/src/sql/workbench/contrib/query/browser/flavorStatus.ts index f2e6978e94..f2d122e112 100644 --- a/src/sql/workbench/contrib/query/browser/flavorStatus.ts +++ b/src/sql/workbench/contrib/query/browser/flavorStatus.ts @@ -13,7 +13,6 @@ import * as nls from 'vs/nls'; import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement'; import * as WorkbenchUtils from 'sql/workbench/common/sqlWorkbenchUtils'; - import { DidChangeLanguageFlavorParams } from 'azdata'; import Severity from 'vs/base/common/severity'; import { INotificationService } from 'vs/platform/notification/common/notification'; @@ -21,7 +20,7 @@ import { EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { mssqlProviderName } from 'sql/platform/connection/common/constants'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { IStatusbarService, StatusbarAlignment, IStatusbarEntryAccessor } from 'vs/workbench/services/statusbar/common/statusbar'; +import { IStatusbarService, StatusbarAlignment, IStatusbarEntryAccessor, IStatusbarEntry } from 'vs/workbench/services/statusbar/common/statusbar'; export interface ISqlProviderEntry extends IQuickPickItem { providerId: string; @@ -76,6 +75,7 @@ export class SqlFlavorStatusbarItem extends Disposable implements IWorkbenchCont this.statusItem = this._register( this.statusbarService.addEntry({ text: nls.localize('changeProvider', "Change SQL language provider"), + command: 'sql.action.editor.changeProvider' }, SqlFlavorStatusbarItem.ID, @@ -150,14 +150,23 @@ export class SqlFlavorStatusbarItem extends Disposable implements IWorkbenchCont if (uri === currentUri) { let flavor: SqlProviderEntry = this._sqlStatusEditors[uri]; if (flavor) { - this.statusItem.update({ text: flavor.label }); + this.updateFlavorElement(flavor.label); } else { - this.statusItem.update({ text: SqlProviderEntry.getDefaultLabel() }); + this.updateFlavorElement(SqlProviderEntry.getDefaultLabel()); } this.show(); } } } + + private updateFlavorElement(text: string): void { + const props: IStatusbarEntry = { + text, + command: 'sql.action.editor.changeProvider' + }; + + this.statusItem.update(props); + } } export class ChangeFlavorAction extends Action { @@ -184,21 +193,21 @@ export class ChangeFlavorAction extends Action { return this._showMessage(Severity.Info, nls.localize('alreadyConnected', "A connection using engine {0} exists. To change please disconnect or change connection", currentProvider)); } - const editorWidget = getCodeEditor(activeEditor); + + const editorWidget = getCodeEditor(activeEditor.getControl()); if (!editorWidget) { return this._showMessage(Severity.Info, nls.localize('noEditor', "No text editor active at this time")); } // TODO #1334 use connectionManagementService.GetProviderNames here. The challenge is that the credentials provider is returned // so we need a way to filter this using a capabilities check, with isn't yet implemented - const ProviderOptions: ISqlProviderEntry[] = [ - new SqlProviderEntry(mssqlProviderName) - ]; - // TODO: select the current language flavor - return this._quickInputService.pick(ProviderOptions, { placeHolder: nls.localize('pickSqlProvider', "Select SQL Language Provider") }).then(provider => { + let providerNameToDisplayNameMap = this._connectionManagementService.providerNameToDisplayNameMap; + let providerOptions = Object.keys(this._connectionManagementService.getUniqueConnectionProvidersByNameMap(providerNameToDisplayNameMap)).map(p => new SqlProviderEntry(p)); + + return this._quickInputService.pick(providerOptions, { placeHolder: nls.localize('pickSqlProvider', "Select SQL Language Provider") }).then(provider => { if (provider) { - activeEditor = this._editorService.activeControl; + let activeEditor = this._editorService.activeControl.getControl(); const editorWidget = getCodeEditor(activeEditor); if (editorWidget) { if (currentUri) { @@ -218,3 +227,4 @@ export class ChangeFlavorAction extends Action { return Promise.resolve(undefined); } } + diff --git a/src/sql/workbench/contrib/query/browser/query.contribution.ts b/src/sql/workbench/contrib/query/browser/query.contribution.ts index 76d6dbf2ef..9e0025ef1b 100644 --- a/src/sql/workbench/contrib/query/browser/query.contribution.ts +++ b/src/sql/workbench/contrib/query/browser/query.contribution.ts @@ -7,7 +7,7 @@ import 'vs/css!sql/media/overwriteVsIcons'; import { Registry } from 'vs/platform/registry/common/platform'; import { EditorDescriptor, IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; +import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { IConfigurationRegistry, Extensions as ConfigExtensions, IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry'; import { SyncActionDescriptor, MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes'; @@ -31,7 +31,7 @@ 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 { SqlFlavorStatusbarItem, ChangeFlavorAction } 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'; @@ -76,7 +76,7 @@ Registry.as(EditorExtensions.Editors) Registry.as(EditorExtensions.Editors) .registerEditor(new EditorDescriptor(QueryEditor, QueryEditor.ID, localize('queryEditor.name', "Query Editor")), [new SyncDescriptor(FileQueryEditorInput), new SyncDescriptor(UntitledQueryEditorInput)]); -const actionRegistry = Registry.as(Extensions.WorkbenchActions); +const actionRegistry = Registry.as(ActionExtensions.WorkbenchActions); new NewQueryTask().registerTask(); @@ -205,6 +205,16 @@ actionRegistry.registerWorkbenchAction( ToggleQueryResultsKeyboardAction.LABEL ); +// Register Flavor Action +actionRegistry.registerWorkbenchAction( + SyncActionDescriptor.create( + ChangeFlavorAction, + ChangeFlavorAction.ID, + ChangeFlavorAction.LABEL + ), + 'Change Language Flavor' +); + KeybindingsRegistry.registerCommandAndKeybindingRule({ id: gridActions.GRID_COPY_ID, weight: KeybindingWeight.EditorContrib, diff --git a/src/sql/workbench/services/connection/browser/connectionDialogService.ts b/src/sql/workbench/services/connection/browser/connectionDialogService.ts index 6a9837839e..ee9ce2b489 100644 --- a/src/sql/workbench/services/connection/browser/connectionDialogService.ts +++ b/src/sql/workbench/services/connection/browser/connectionDialogService.ts @@ -308,10 +308,8 @@ export class ConnectionDialogService implements IConnectionDialogService { }); } if (!isProviderInParams) { - this._currentProviderType = find(Object.keys(this._providerNameToDisplayNameMap), (key) => - this._providerNameToDisplayNameMap[key] === input.selectedProviderDisplayName && - key !== Constants.cmsProviderName - ); + let uniqueProvidersMap = this._connectionManagementService.getUniqueConnectionProvidersByNameMap(this._providerNameToDisplayNameMap); + this._currentProviderType = find(Object.keys(uniqueProvidersMap), (key) => uniqueProvidersMap[key] === input.selectedProviderDisplayName); } } this._model.providerName = this._currentProviderType; diff --git a/src/sql/workbench/services/connection/browser/connectionDialogWidget.ts b/src/sql/workbench/services/connection/browser/connectionDialogWidget.ts index 7a1bdf9cec..64c810e5a9 100644 --- a/src/sql/workbench/services/connection/browser/connectionDialogWidget.ts +++ b/src/sql/workbench/services/connection/browser/connectionDialogWidget.ts @@ -37,6 +37,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry'; +import { entries } from 'sql/base/common/collections'; export interface OnShowUIResponse { selectedProviderDisplayName: string; @@ -116,21 +117,21 @@ export class ConnectionDialogWidget extends Modal { } public refresh(): void { - let filteredProviderDisplayNames = this.providerDisplayNameOptions; - + let filteredProviderMap = this.providerNameToDisplayNameMap; if (this._newConnectionParams && this._newConnectionParams.providers) { - const validProviderNames = Object.keys(this.providerNameToDisplayNameMap).filter(x => this.includeProvider(x, this._newConnectionParams)); - if (validProviderNames && validProviderNames.length > 0) { - filteredProviderDisplayNames = filteredProviderDisplayNames.filter(x => validProviderNames.some( - v => this.providerNameToDisplayNameMap[v] === x) !== undefined - ); + const validProviderMap = entries(this.providerNameToDisplayNameMap).filter(x => this.includeProvider(x[0], this._newConnectionParams)); + if (validProviderMap && validProviderMap.length > 0) { + let map: { [providerDisplayName: string]: string } = {}; + validProviderMap.forEach(v => { + map[v[0]] = v[1]; + }); + filteredProviderMap = map; } } - this._providerTypeSelectBox.setOptions(filteredProviderDisplayNames.filter((providerDisplayName, index) => - // Remove duplicate listings (CMS uses the same display name) - filteredProviderDisplayNames.indexOf(providerDisplayName) === index) - ); + // Remove duplicate listings (CMS uses the same display name) + let uniqueProvidersMap = this._connectionManagementService.getUniqueConnectionProvidersByNameMap(filteredProviderMap); + this._providerTypeSelectBox.setOptions(Object.keys(uniqueProvidersMap).map(k => uniqueProvidersMap[k])); } private includeProvider(providerName: string, params?: INewConnectionParams): Boolean { diff --git a/src/sql/workbench/services/connection/browser/connectionManagementService.ts b/src/sql/workbench/services/connection/browser/connectionManagementService.ts index dbff428293..6d7f302500 100644 --- a/src/sql/workbench/services/connection/browser/connectionManagementService.ts +++ b/src/sql/workbench/services/connection/browser/connectionManagementService.ts @@ -58,6 +58,7 @@ export class ConnectionManagementService extends Disposable implements IConnecti _serviceBrand: undefined; private _providers = new Map, properties: ConnectionProviderProperties }>(); + private _providerNameToDisplayNameMap: { [providerDisplayName: string]: string } = {}; private _iconProviders = new Map(); private _uriToProvider: { [uri: string]: string; } = Object.create(null); private _onAddConnectionProfile = new Emitter(); @@ -107,6 +108,8 @@ export class ConnectionManagementService extends Disposable implements IConnecti this._mementoObj = this._mementoContext.getMemento(StorageScope.GLOBAL); } + this.initializeConnectionProvidersMap(); + const registry = platform.Registry.as(ConnectionProviderExtensions.ConnectionProviderContributions); let providerRegistration = (p: { id: string, properties: ConnectionProviderProperties }) => { @@ -126,6 +129,30 @@ export class ConnectionManagementService extends Disposable implements IConnecti this._register(this._onDeleteConnectionProfile); } + /** + * Set the initial value for the connection provider map and listen to the provider change event + */ + private initializeConnectionProvidersMap() { + this.updateConnectionProvidersMap(); + if (this._capabilitiesService) { + this._capabilitiesService.onCapabilitiesRegistered(() => { + this.updateConnectionProvidersMap(); + }); + } + } + + /** + * Update the map using the values from capabilities service + */ + private updateConnectionProvidersMap() { + if (this._capabilitiesService) { + this._providerNameToDisplayNameMap = {}; + entries(this._capabilitiesService.providers).forEach(p => { + this._providerNameToDisplayNameMap[p[0]] = p[1].connection.displayName; + }); + } + } + public providerRegistered(providerId: string): boolean { return !!this._providers.get(providerId); } @@ -159,6 +186,10 @@ export class ConnectionManagementService extends Disposable implements IConnecti return this._onLanguageFlavorChanged.event; } + public get providerNameToDisplayNameMap(): { readonly [providerDisplayName: string]: string } { + return this._providerNameToDisplayNameMap; + } + // Connection Provider Registration public registerProvider(providerId: string, provider: azdata.ConnectionProvider): void { if (!this._providers.has(providerId)) { @@ -217,6 +248,20 @@ export class ConnectionManagementService extends Disposable implements IConnecti return providerId; } + /** + * Get the connection providers map and filter out CMS. + */ + public getUniqueConnectionProvidersByNameMap(providerNameToDisplayNameMap: { [providerDisplayName: string]: string }): { [providerDisplayName: string]: string } { + let uniqueProvidersMap = {}; + entries(providerNameToDisplayNameMap).forEach(p => { + if (p[0] !== Constants.cmsProviderName) { + uniqueProvidersMap[p[0]] = p[1]; + } + }); + + return uniqueProvidersMap; + } + /** * Loads the password and try to connect. If fails, shows the dialog so user can change the connection * @param Connection Profile @@ -672,6 +717,7 @@ export class ConnectionManagementService extends Disposable implements IConnecti */ public doChangeLanguageFlavor(uri: string, language: string, provider: string): void { if (this._providers.has(provider)) { + this._uriToProvider[uri] = provider; this._onLanguageFlavorChanged.fire({ uri: uri, language: language, @@ -689,9 +735,8 @@ export class ConnectionManagementService extends Disposable implements IConnecti public ensureDefaultLanguageFlavor(uri: string): void { if (!this.getProviderIdFromUri(uri)) { // Lookup the default settings and use this - let defaultProvider = WorkbenchUtils.getSqlConfigValue(this._configurationService, Constants.defaultEngine); - if (defaultProvider && this._providers.has(defaultProvider)) { - // Only set a default if it's in the list of registered providers + let defaultProvider = this.getDefaultProviderId(); + if (defaultProvider) { this.doChangeLanguageFlavor(uri, 'sql', defaultProvider); } } @@ -771,9 +816,6 @@ export class ConnectionManagementService extends Disposable implements IConnecti options: connection.options }); - // setup URI to provider ID map for connection - this._uriToProvider[uri] = connection.providerName; - return this._providers.get(connection.providerName).onReady.then((provider) => { provider.connect(uri, connectionInfo); this._onConnectRequestSent.fire(); diff --git a/src/sql/workbench/services/connection/test/browser/connectionManagementService.test.ts b/src/sql/workbench/services/connection/test/browser/connectionManagementService.test.ts index aa75956463..c9420d96b4 100644 --- a/src/sql/workbench/services/connection/test/browser/connectionManagementService.test.ts +++ b/src/sql/workbench/services/connection/test/browser/connectionManagementService.test.ts @@ -729,6 +729,21 @@ suite('SQL ConnectionManagementService tests', () => { } }); + test('getUniqueConnectionProvidersByNameMap should return non CMS providers', () => { + let nameToDisplayNameMap: { [providerDisplayName: string]: string } = { 'MSSQL': 'SQL Server', 'MSSQL-CMS': 'SQL Server' }; + let providerNames = Object.keys(connectionManagementService.getUniqueConnectionProvidersByNameMap(nameToDisplayNameMap)); + assert.equal(providerNames.length, 1); + assert.equal(providerNames[0], 'MSSQL'); + }); + + test('providerNameToDisplayNameMap should return all providers', () => { + let expectedNames = ['MSSQL', 'PGSQL']; + let providerNames = Object.keys(connectionManagementService.providerNameToDisplayNameMap); + assert.equal(providerNames.length, 2); + assert.equal(providerNames[0], expectedNames[0]); + assert.equal(providerNames[1], expectedNames[1]); + }); + test('ensureDefaultLanguageFlavor should not send event if uri is connected', done => { let uri: string = 'Editor Uri'; let options: IConnectionCompletionOptions = {