From 288445a5507e121094ac1ae95e9d5fb0f643ddae Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 7 Apr 2020 15:26:38 +0200 Subject: [PATCH] #94599 Move exe based tips to new service in shared process --- .../sharedProcess/sharedProcessMain.ts | 10 +- .../common/extensionManagement.ts | 13 +++ .../common/extensionManagementIpc.ts | 21 ++++- .../node/extensionTipsService.ts | 93 +++++++++++++++++++ .../extensionRecommendationsService.ts | 91 +++++------------- .../common/extensionTipsService.ts | 26 ++++++ .../electron-browser/extensionTipsService.ts | 33 +++++++ src/vs/workbench/workbench.desktop.main.ts | 1 + src/vs/workbench/workbench.web.main.ts | 1 + 9 files changed, 216 insertions(+), 73 deletions(-) create mode 100644 src/vs/platform/extensionManagement/node/extensionTipsService.ts create mode 100644 src/vs/workbench/services/extensionManagement/common/extensionTipsService.ts create mode 100644 src/vs/workbench/services/extensionManagement/electron-browser/extensionTipsService.ts diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index ac597749c02..47f26569b0b 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -13,8 +13,8 @@ import { InstantiationService } from 'vs/platform/instantiation/common/instantia import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ParsedArgs } from 'vs/platform/environment/node/argv'; import { EnvironmentService } from 'vs/platform/environment/node/environmentService'; -import { ExtensionManagementChannel } from 'vs/platform/extensionManagement/common/extensionManagementIpc'; -import { IExtensionManagementService, IExtensionGalleryService, IGlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { ExtensionManagementChannel, ExtensionTipsChannel } from 'vs/platform/extensionManagement/common/extensionManagementIpc'; +import { IExtensionManagementService, IExtensionGalleryService, IGlobalExtensionEnablementService, IExtensionTipsService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -69,6 +69,7 @@ import { IAuthenticationTokenService, AuthenticationTokenService } from 'vs/plat import { AuthenticationTokenServiceChannel } from 'vs/platform/authentication/common/authenticationIpc'; import { UserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSyncBackupStoreService'; import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys'; +import { ExtensionTipsService } from 'vs/platform/extensionManagement/node/extensionTipsService'; export interface ISharedProcessConfiguration { readonly machineId: string; @@ -190,6 +191,7 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryService)); services.set(ILocalizationsService, new SyncDescriptor(LocalizationsService)); services.set(IDiagnosticsService, new SyncDescriptor(DiagnosticsService)); + services.set(IExtensionTipsService, new SyncDescriptor(ExtensionTipsService)); services.set(ICredentialsService, new SyncDescriptor(KeytarCredentialsService)); services.set(IAuthenticationTokenService, new SyncDescriptor(AuthenticationTokenService)); @@ -218,6 +220,10 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat const diagnosticsChannel = new DiagnosticsChannel(diagnosticsService); server.registerChannel('diagnostics', diagnosticsChannel); + const extensionTipsService = accessor.get(IExtensionTipsService); + const extensionTipsChannel = new ExtensionTipsChannel(extensionTipsService); + server.registerChannel('extensionTipsService', extensionTipsChannel); + const authTokenService = accessor.get(IAuthenticationTokenService); const authTokenChannel = new AuthenticationTokenServiceChannel(authTokenService); server.registerChannel('authToken', authTokenChannel); diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index 293d0a90056..64bf05f0b24 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -10,6 +10,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { URI } from 'vs/base/common/uri'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IExtensionManifest, IExtension, ExtensionType } from 'vs/platform/extensions/common/extensions'; +import { IExeBasedExtensionTip } from 'vs/platform/product/common/productService'; export const EXTENSION_IDENTIFIER_PATTERN = '^([a-z0-9A-Z][a-z0-9\-A-Z]*)\\.([a-z0-9A-Z][a-z0-9\-A-Z]*)$'; export const EXTENSION_IDENTIFIER_REGEX = new RegExp(EXTENSION_IDENTIFIER_PATTERN); @@ -221,6 +222,18 @@ export interface IGlobalExtensionEnablementService { } +export type IExecutableBasedExtensionTip = { extensionId: string } & Omit, 'important'>; + +export const IExtensionTipsService = createDecorator('IExtensionTipsService'); +export interface IExtensionTipsService { + _serviceBrand: undefined; + + getImportantExecutableBasedTips(): Promise; + getOtherExecutableBasedTips(): Promise; +} + + + export const ExtensionsLabel = localize('extensions', "Extensions"); export const ExtensionsChannelId = 'extensions'; export const PreferencesLabel = localize('preferences', "Preferences"); diff --git a/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts b/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts index 035559f7ec6..5f93984ece7 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagementIpc.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; -import { IExtensionManagementService, ILocalExtension, InstallExtensionEvent, DidInstallExtensionEvent, IGalleryExtension, DidUninstallExtensionEvent, IExtensionIdentifier, IGalleryMetadata, IReportedExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionManagementService, ILocalExtension, InstallExtensionEvent, DidInstallExtensionEvent, IGalleryExtension, DidUninstallExtensionEvent, IExtensionIdentifier, IGalleryMetadata, IReportedExtension, IExtensionTipsService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { Event } from 'vs/base/common/event'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IURITransformer, DefaultURITransformer, transformAndReviveIncomingURIs } from 'vs/base/common/uriIpc'; @@ -130,3 +130,22 @@ export class ExtensionManagementChannelClient implements IExtensionManagementSer return Promise.resolve(this.channel.call('getExtensionsReport')); } } + +export class ExtensionTipsChannel implements IServerChannel { + + constructor(private service: IExtensionTipsService) { + } + + listen(context: any, event: string): Event { + throw new Error('Invalid listen'); + } + + call(context: any, command: string, args?: any): Promise { + switch (command) { + case 'getImportantExecutableBasedTips': return this.service.getImportantExecutableBasedTips(); + case 'getOtherExecutableBasedTips': return this.service.getOtherExecutableBasedTips(); + } + + throw new Error('Invalid call'); + } +} diff --git a/src/vs/platform/extensionManagement/node/extensionTipsService.ts b/src/vs/platform/extensionManagement/node/extensionTipsService.ts new file mode 100644 index 00000000000..46aa52eb17b --- /dev/null +++ b/src/vs/platform/extensionManagement/node/extensionTipsService.ts @@ -0,0 +1,93 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { URI } from 'vs/base/common/uri'; +import { join, } from 'vs/base/common/path'; +import { IProductService, IExeBasedExtensionTip } from 'vs/platform/product/common/productService'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { env as processEnv } from 'vs/base/common/process'; +import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; +import { IFileService } from 'vs/platform/files/common/files'; +import { isWindows } from 'vs/base/common/platform'; +import { isNonEmptyArray } from 'vs/base/common/arrays'; +import { IExtensionTipsService, IExecutableBasedExtensionTip } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IStringDictionary, forEach } from 'vs/base/common/collections'; + +export class ExtensionTipsService implements IExtensionTipsService { + + _serviceBrand: any; + + private readonly allImportantExecutableTips: IStringDictionary = {}; + private readonly allOtherExecutableTips: IStringDictionary = {}; + + constructor( + @IFileService private readonly fileService: IFileService, + @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IProductService private readonly productService: IProductService, + ) { + if (this.productService.exeBasedExtensionTips) { + forEach(this.productService.exeBasedExtensionTips, ({ key, value }) => { + if (value.important) { + this.allImportantExecutableTips[key] = value; + } else { + this.allOtherExecutableTips[key] = value; + } + }); + } + } + + getImportantExecutableBasedTips(): Promise { + return this.getValidExecutableBasedExtensionTips(this.allImportantExecutableTips); + } + + getOtherExecutableBasedTips(): Promise { + return this.getValidExecutableBasedExtensionTips(this.allOtherExecutableTips); + } + + private async getValidExecutableBasedExtensionTips(executableTips: IStringDictionary): Promise { + const result: IExecutableBasedExtensionTip[] = []; + + const checkedExecutables: Map = new Map(); + for (const exeName of Object.keys(executableTips)) { + const extensionTip = executableTips[exeName]; + if (!isNonEmptyArray(extensionTip?.recommendations)) { + continue; + } + + const exePaths: string[] = []; + if (isWindows) { + if (extensionTip.windowsPath) { + exePaths.push(extensionTip.windowsPath.replace('%USERPROFILE%', processEnv['USERPROFILE']!) + .replace('%ProgramFiles(x86)%', processEnv['ProgramFiles(x86)']!) + .replace('%ProgramFiles%', processEnv['ProgramFiles']!) + .replace('%APPDATA%', processEnv['APPDATA']!) + .replace('%WINDIR%', processEnv['WINDIR']!)); + } + } else { + exePaths.push(join('/usr/local/bin', exeName)); + exePaths.push(join((this.environmentService as INativeEnvironmentService).userHome.fsPath, exeName)); + } + + for (const exePath of exePaths) { + let exists = checkedExecutables.get(exePath); + if (exists === undefined) { + exists = await this.fileService.exists(URI.file(exePath)); + checkedExecutables.set(exePath, exists); + } + if (exists) { + extensionTip.recommendations.forEach(recommendation => result.push({ + extensionId: recommendation, + friendlyName: extensionTip.friendlyName, + exeFriendlyName: extensionTip.exeFriendlyName, + windowsPath: extensionTip.windowsPath, + })); + } + } + } + + return result; + } + +} diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionRecommendationsService.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionRecommendationsService.ts index 1c8dd9062b8..97c7e08319e 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensionRecommendationsService.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionRecommendationsService.ts @@ -5,11 +5,10 @@ import { ExtensionRecommendationsService, milliSecondsInADay, choiceNever } from 'vs/workbench/contrib/extensions/browser/extensionRecommendationsService'; import { IExtensionRecommendationsService, IWorkbenchExtensionEnablementService, ExtensionRecommendationReason, IExtensionRecommendation, ExtensionRecommendationSource } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; -import { URI } from 'vs/base/common/uri'; -import { join, basename } from 'vs/base/common/path'; +import { basename } from 'vs/base/common/path'; import { distinct, shuffle } from 'vs/base/common/arrays'; -import { IExeBasedExtensionTip, IProductService } from 'vs/platform/product/common/productService'; -import { IExtensionGalleryService, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { IExtensionGalleryService, IExtensionManagementService, IExtensionTipsService, IExecutableBasedExtensionTip } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -31,10 +30,8 @@ import { ExtensionType } from 'vs/platform/extensions/common/extensions'; import { localize } from 'vs/nls'; import Severity from 'vs/base/common/severity'; import { InstallRecommendedExtensionAction, ShowRecommendedExtensionsAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; -import { forEach } from 'vs/base/common/collections'; -import { platform, env as processEnv } from 'vs/base/common/process'; +import { forEach, IStringDictionary } from 'vs/base/common/collections'; import { isNumber } from 'vs/base/common/types'; -import { INativeEnvironmentService } from 'vs/platform/environment/node/environmentService'; interface IDynamicWorkspaceRecommendations { remoteSet: string[]; @@ -43,8 +40,7 @@ interface IDynamicWorkspaceRecommendations { export class NativeExtensionRecommendationsService extends ExtensionRecommendationsService implements IExtensionRecommendationsService { - private _exeBasedRecommendations: { [id: string]: IExeBasedExtensionTip; } = Object.create(null); - private _importantExeBasedRecommendations: { [id: string]: IExeBasedExtensionTip; } = Object.create(null); + private _exeBasedRecommendations: { [id: string]: IExecutableBasedExtensionTip; } = Object.create(null); private proactiveRecommendationsFetched: boolean = false; private _extensionsRecommendationsUrl: string | undefined; private _dynamicWorkspaceRecommendations: string[] = []; @@ -71,6 +67,7 @@ export class NativeExtensionRecommendationsService extends ExtensionRecommendati @IProductService productService: IProductService, @IRequestService private readonly requestService: IRequestService, @IWorkspaceTagsService private readonly workspaceTagsService: IWorkspaceTagsService, + @IExtensionTipsService private readonly extensionTipsService: IExtensionTipsService, ) { super(galleryService, modelService, storageService, extensionsService, extensionEnablementService, instantiationService, fileService, contextService, configurationService, telemetryService, environmentService, extensionService, viewletService, notificationService, extensionManagementService, extensionWorkbenchService, @@ -111,14 +108,15 @@ export class NativeExtensionRecommendationsService extends ExtensionRecommendati if (!this.proactiveRecommendationsFetched) { this.proactiveRecommendationsFetched = true; // Executable based recommendations carry out a lot of file stats, delay the resolution so that the startup is not affected - fetchPromise = timeout(10000).then(() => Promise.all([this.fetchDynamicWorkspaceRecommendations(), this.fetchExecutableRecommendations(false)])); + fetchPromise = timeout(10000).then(() => Promise.all([this.fetchDynamicWorkspaceRecommendations(), this.fetchOtherExeBasedRecommendations()])); } return fetchPromise; } private async fetchImportantExeBasedRecommendation(): Promise { - await this.fetchExecutableRecommendations(true); - await this.promptForImportantExeBasedExtension(); + const importantExectuableBasedTips = await this.extensionTipsService.getImportantExecutableBasedTips(); + importantExectuableBasedTips.forEach(tip => this._exeBasedRecommendations[tip.extensionId.toLowerCase()] = tip); + await this.promptForImportantExeBasedExtension(importantExectuableBasedTips); } /** @@ -209,13 +207,16 @@ export class NativeExtensionRecommendationsService extends ExtensionRecommendati } } - private async promptForImportantExeBasedExtension(): Promise { + private async promptForImportantExeBasedExtension(importantExectuableBasedTips: IExecutableBasedExtensionTip[]): Promise { - let recommendationsToSuggest = Object.keys(this._importantExeBasedRecommendations); + const importantExeBasedRecommendations: IStringDictionary = {}; + importantExectuableBasedTips.forEach(tip => importantExeBasedRecommendations[tip.extensionId.toLowerCase()] = tip); + + let recommendationsToSuggest = Object.keys(importantExeBasedRecommendations); const installed = await this.extensionManagementService.getInstalled(ExtensionType.User); recommendationsToSuggest = this.filterInstalled(recommendationsToSuggest, installed, (extensionId) => { - const tip = this._importantExeBasedRecommendations[extensionId]; + const tip = importantExeBasedRecommendations[extensionId]; /* __GDPR__ "exeExtensionRecommendations:alreadyInstalled" : { @@ -232,7 +233,7 @@ export class NativeExtensionRecommendationsService extends ExtensionRecommendati } for (const extensionId of recommendationsToSuggest) { - const tip = this._importantExeBasedRecommendations[extensionId]; + const tip = importantExeBasedRecommendations[extensionId]; /* __GDPR__ "exeExtensionRecommendations:notInstalled" : { @@ -259,7 +260,7 @@ export class NativeExtensionRecommendationsService extends ExtensionRecommendati } const extensionId = recommendationsToSuggest[0]; - const tip = this._importantExeBasedRecommendations[extensionId]; + const tip = importantExeBasedRecommendations[extensionId]; const message = localize('exeRecommended', "The '{0}' extension is recommended as you have {1} installed on your system.", tip.friendlyName!, tip.exeFriendlyName || basename(tip.windowsPath!)); this.notificationService.prompt(Severity.Info, message, @@ -332,59 +333,9 @@ export class NativeExtensionRecommendationsService extends ExtensionRecommendati return true; } - /** - * If user has any of the tools listed in this.productService.exeBasedExtensionTips, fetch corresponding recommendations - */ - private async fetchExecutableRecommendations(important: boolean): Promise { - if (!this.productService.exeBasedExtensionTips) { - return; - } - - const foundExecutables: Set = new Set(); - const findExecutable = (exeName: string, tip: IExeBasedExtensionTip, path: string) => { - return this.fileService.exists(URI.file(path)).then(exists => { - if (exists && !foundExecutables.has(exeName)) { - foundExecutables.add(exeName); - (tip['recommendations'] || []).forEach(extensionId => { - if (tip.friendlyName) { - if (important) { - this._importantExeBasedRecommendations[extensionId.toLowerCase()] = tip; - } - this._exeBasedRecommendations[extensionId.toLowerCase()] = tip; - } - }); - } - }); - }; - - const promises: Promise[] = []; - // Loop through recommended extensions - forEach(this.productService.exeBasedExtensionTips, entry => { - if (typeof entry.value !== 'object' || !Array.isArray(entry.value['recommendations'])) { - return; - } - if (important !== !!entry.value.important) { - return; - } - const exeName = entry.key; - if (platform === 'win32') { - let windowsPath = entry.value['windowsPath']; - if (!windowsPath || typeof windowsPath !== 'string') { - return; - } - windowsPath = windowsPath.replace('%USERPROFILE%', processEnv['USERPROFILE']!) - .replace('%ProgramFiles(x86)%', processEnv['ProgramFiles(x86)']!) - .replace('%ProgramFiles%', processEnv['ProgramFiles']!) - .replace('%APPDATA%', processEnv['APPDATA']!) - .replace('%WINDIR%', processEnv['WINDIR']!); - promises.push(findExecutable(exeName, entry.value, windowsPath)); - } else { - promises.push(findExecutable(exeName, entry.value, join('/usr/local/bin', exeName))); - promises.push(findExecutable(exeName, entry.value, join((this.environmentService as INativeEnvironmentService).userHome.fsPath, exeName))); - } - }); - - await Promise.all(promises); + private async fetchOtherExeBasedRecommendations(): Promise { + const otherExectuableBasedTips = await this.extensionTipsService.getOtherExecutableBasedTips(); + otherExectuableBasedTips.forEach(tip => this._exeBasedRecommendations[tip.extensionId.toLowerCase()] = tip); } getAllRecommendationsWithReason(): { [id: string]: { reasonId: ExtensionRecommendationReason, reasonText: string }; } { diff --git a/src/vs/workbench/services/extensionManagement/common/extensionTipsService.ts b/src/vs/workbench/services/extensionManagement/common/extensionTipsService.ts new file mode 100644 index 00000000000..453fe231d1b --- /dev/null +++ b/src/vs/workbench/services/extensionManagement/common/extensionTipsService.ts @@ -0,0 +1,26 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IExtensionTipsService, IExecutableBasedExtensionTip } from 'vs/platform/extensionManagement/common/extensionManagement'; + +class WebExtensionTipsService implements IExtensionTipsService { + + _serviceBrand: any; + + constructor() { + } + + async getImportantExecutableBasedTips(): Promise { + return []; + } + + async getOtherExecutableBasedTips(): Promise { + return []; + } + +} + +registerSingleton(IExtensionTipsService, WebExtensionTipsService); diff --git a/src/vs/workbench/services/extensionManagement/electron-browser/extensionTipsService.ts b/src/vs/workbench/services/extensionManagement/electron-browser/extensionTipsService.ts new file mode 100644 index 00000000000..ff383c3175d --- /dev/null +++ b/src/vs/workbench/services/extensionManagement/electron-browser/extensionTipsService.ts @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; +import { IChannel } from 'vs/base/parts/ipc/common/ipc'; +import { IExtensionTipsService, IExecutableBasedExtensionTip } from 'vs/platform/extensionManagement/common/extensionManagement'; + +class NativeExtensionTipsService implements IExtensionTipsService { + + _serviceBrand: any; + + private readonly channel: IChannel; + + constructor( + @ISharedProcessService sharedProcessService: ISharedProcessService + ) { + this.channel = sharedProcessService.getChannel('extensionTipsService'); + } + + getImportantExecutableBasedTips(): Promise { + return this.channel.call('getImportantExecutableBasedTips'); + } + + getOtherExecutableBasedTips(): Promise { + return this.channel.call('getOtherExecutableBasedTips'); + } + +} + +registerSingleton(IExtensionTipsService, NativeExtensionTipsService); diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index 09996f1ca55..cdd9d93c1ea 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -39,6 +39,7 @@ import 'vs/workbench/services/keybinding/electron-browser/keybinding.contributio import 'vs/workbench/services/extensions/electron-browser/extensionService'; import 'vs/workbench/services/contextmenu/electron-browser/contextmenuService'; import 'vs/workbench/services/extensionManagement/electron-browser/extensionManagementServerService'; +import 'vs/workbench/services/extensionManagement/electron-browser/extensionTipsService'; import 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl'; import 'vs/workbench/services/telemetry/electron-browser/telemetryService'; import 'vs/workbench/services/configurationResolver/electron-browser/configurationResolverService'; diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts index e59fb0de38a..27940667bc7 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -34,6 +34,7 @@ import 'vs/workbench/services/textfile/browser/browserTextFileService'; import 'vs/workbench/services/keybinding/browser/keymapService'; import 'vs/workbench/services/extensions/browser/extensionService'; import 'vs/workbench/services/extensionManagement/common/extensionManagementServerService'; +import 'vs/workbench/services/extensionManagement/common/extensionTipsService'; import 'vs/workbench/services/telemetry/browser/telemetryService'; import 'vs/workbench/services/configurationResolver/browser/configurationResolverService'; import 'vs/workbench/services/credentials/browser/credentialsService'; -- GitLab