diff options
64 files changed, 999 insertions, 629 deletions
diff --git a/extensions/markdown-language-features/extension.webpack.config.js b/extensions/markdown-language-features/extension.webpack.config.js index 756d6735a60..de88398eca0 100644 --- a/extensions/markdown-language-features/extension.webpack.config.js +++ b/extensions/markdown-language-features/extension.webpack.config.js @@ -15,6 +15,6 @@ module.exports = withDefaults({ mainFields: ['module', 'main'] }, entry: { - extension: './src/extension.node.ts', + extension: './src/extension.ts', } }); diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index ec4b4d19e62..8cee84a8d74 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -10,7 +10,7 @@ "engines": { "vscode": "^1.20.0" }, - "main": "./out/extension.node", + "main": "./out/extension", "browser": "./dist/browser/extension", "categories": [ "Programming Languages" diff --git a/extensions/markdown-language-features/src/extension.node.ts b/extensions/markdown-language-features/src/extension.ts index 45dba90d3ca..45dba90d3ca 100644 --- a/extensions/markdown-language-features/src/extension.node.ts +++ b/extensions/markdown-language-features/src/extension.ts diff --git a/package.json b/package.json index d57d78b4625..b2590fdc25d 100644 --- a/package.json +++ b/package.json @@ -85,12 +85,12 @@ "vscode-proxy-agent": "^0.12.0", "vscode-regexpp": "^3.1.0", "vscode-textmate": "7.0.1", - "xterm": "4.20.0-beta.5", + "xterm": "4.20.0-beta.6", "xterm-addon-search": "0.10.0-beta.1", "xterm-addon-serialize": "0.8.0-beta.1", "xterm-addon-unicode11": "0.4.0-beta.3", - "xterm-addon-webgl": "0.13.0-beta.2", - "xterm-headless": "4.20.0-beta.5", + "xterm-addon-webgl": "0.13.0-beta.3", + "xterm-headless": "4.20.0-beta.6", "yauzl": "^2.9.2", "yazl": "^2.4.3" }, diff --git a/remote/package.json b/remote/package.json index 8da57bba60b..16d1d06eb07 100644 --- a/remote/package.json +++ b/remote/package.json @@ -24,12 +24,12 @@ "vscode-proxy-agent": "^0.12.0", "vscode-regexpp": "^3.1.0", "vscode-textmate": "7.0.1", - "xterm": "4.20.0-beta.5", + "xterm": "4.20.0-beta.6", "xterm-addon-search": "0.10.0-beta.1", "xterm-addon-serialize": "0.8.0-beta.1", "xterm-addon-unicode11": "0.4.0-beta.3", - "xterm-addon-webgl": "0.13.0-beta.2", - "xterm-headless": "4.20.0-beta.5", + "xterm-addon-webgl": "0.13.0-beta.3", + "xterm-headless": "4.20.0-beta.6", "yauzl": "^2.9.2", "yazl": "^2.4.3" }, diff --git a/remote/web/package.json b/remote/web/package.json index c3a32f4d4ec..9d6478ab4c7 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -11,9 +11,9 @@ "tas-client-umd": "0.1.6", "vscode-oniguruma": "1.6.1", "vscode-textmate": "7.0.1", - "xterm": "4.20.0-beta.5", + "xterm": "4.20.0-beta.6", "xterm-addon-search": "0.10.0-beta.1", "xterm-addon-unicode11": "0.4.0-beta.3", - "xterm-addon-webgl": "0.13.0-beta.2" + "xterm-addon-webgl": "0.13.0-beta.3" } } diff --git a/remote/web/yarn.lock b/remote/web/yarn.lock index 75a41e50c82..590ab4f8c93 100644 --- a/remote/web/yarn.lock +++ b/remote/web/yarn.lock @@ -78,12 +78,12 @@ xterm-addon-unicode11@0.4.0-beta.3: resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.4.0-beta.3.tgz#f350184155fafd5ad0d6fbf31d13e6ca7dea1efa" integrity sha512-FryZAVwbUjKTmwXnm1trch/2XO60F5JsDvOkZhzobV1hm10sFLVuZpFyHXiUx7TFeeFsvNP+S77LAtWoeT5z+Q== -xterm-addon-webgl@0.13.0-beta.2: - version "0.13.0-beta.2" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.13.0-beta.2.tgz#f58a7a3641ad7c8ac82dd24cfb0165656ed9ac1c" - integrity sha512-98tX0BkpD402RoCO6SyikUXpzCn9/OQhlXsRmM/kRFCxMWWofStWTXzCPhN0MjIx2IdGueDjCmnShhidwihErg== - -xterm@4.20.0-beta.5: - version "4.20.0-beta.5" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.20.0-beta.5.tgz#d707b0dcb477a554135fb767b24003fced079866" - integrity sha512-KBWfk9UPBKRy662DVGGTZEcW1becEjYvlyWbn2hLj9h2gy6Q4EEEEbggJh8I7SGwdFizl+apHQGhEOZmFCA70w== +xterm-addon-webgl@0.13.0-beta.3: + version "0.13.0-beta.3" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.13.0-beta.3.tgz#2b456c3105238e64b40a30787d6335f5f6f85abb" + integrity sha512-DFGcXAolA0VTsOLIKcORxUOp/FTJdD/YiRzKVLARjgOycwVRKvW2L5Tge8Z7ysZ16sKfnV2vCXyonXYfUWozXw== + +xterm@4.20.0-beta.6: + version "4.20.0-beta.6" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.20.0-beta.6.tgz#3ed87ba383a5cf44284098278f714df7113e3e3c" + integrity sha512-xJd6vyOuYo4Ht/hTY3DyXGIj0U6kHjr2vWQ1lRmearo3t7QKf7uqOAAfTLeWt/g1P8qe/r0DnsNTeag6vI9RVw== diff --git a/remote/yarn.lock b/remote/yarn.lock index bc084bb74dc..444fb613090 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -803,20 +803,20 @@ xterm-addon-unicode11@0.4.0-beta.3: resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.4.0-beta.3.tgz#f350184155fafd5ad0d6fbf31d13e6ca7dea1efa" integrity sha512-FryZAVwbUjKTmwXnm1trch/2XO60F5JsDvOkZhzobV1hm10sFLVuZpFyHXiUx7TFeeFsvNP+S77LAtWoeT5z+Q== -xterm-addon-webgl@0.13.0-beta.2: - version "0.13.0-beta.2" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.13.0-beta.2.tgz#f58a7a3641ad7c8ac82dd24cfb0165656ed9ac1c" - integrity sha512-98tX0BkpD402RoCO6SyikUXpzCn9/OQhlXsRmM/kRFCxMWWofStWTXzCPhN0MjIx2IdGueDjCmnShhidwihErg== - -xterm-headless@4.20.0-beta.5: - version "4.20.0-beta.5" - resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.20.0-beta.5.tgz#edcff27eb6437d158e6aea2ed7658e783bee5641" - integrity sha512-8SnVUsuNUrQ5P0XU/9Iau3uK7Tf8q/p0KHHwkwJXVxZDIlaDH9XKSs91U9BjJJE3sJgRxH4NSiDYR3vFLSFpxw== - -xterm@4.20.0-beta.5: - version "4.20.0-beta.5" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.20.0-beta.5.tgz#d707b0dcb477a554135fb767b24003fced079866" - integrity sha512-KBWfk9UPBKRy662DVGGTZEcW1becEjYvlyWbn2hLj9h2gy6Q4EEEEbggJh8I7SGwdFizl+apHQGhEOZmFCA70w== +xterm-addon-webgl@0.13.0-beta.3: + version "0.13.0-beta.3" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.13.0-beta.3.tgz#2b456c3105238e64b40a30787d6335f5f6f85abb" + integrity sha512-DFGcXAolA0VTsOLIKcORxUOp/FTJdD/YiRzKVLARjgOycwVRKvW2L5Tge8Z7ysZ16sKfnV2vCXyonXYfUWozXw== + +xterm-headless@4.20.0-beta.6: + version "4.20.0-beta.6" + resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.20.0-beta.6.tgz#bd016379e9fac47e5b8870d567cdf330cf6f49fc" + integrity sha512-EV0V7pxMKI0OEcOCD+6vdXq6rBARr7dSN3PovTsZnDWg5dmvUb2eEmz6BTejJj3UVd/JXNEmEXM+tCh97rDCDg== + +xterm@4.20.0-beta.6: + version "4.20.0-beta.6" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.20.0-beta.6.tgz#3ed87ba383a5cf44284098278f714df7113e3e3c" + integrity sha512-xJd6vyOuYo4Ht/hTY3DyXGIj0U6kHjr2vWQ1lRmearo3t7QKf7uqOAAfTLeWt/g1P8qe/r0DnsNTeag6vI9RVw== yallist@^4.0.0: version "4.0.0" diff --git a/src/vs/base/browser/ui/button/button.css b/src/vs/base/browser/ui/button/button.css index 5327fb0e1e3..66e61c3c398 100644 --- a/src/vs/base/browser/ui/button/button.css +++ b/src/vs/base/browser/ui/button/button.css @@ -42,8 +42,13 @@ outline-offset: -1px !important; } -.monaco-button-dropdown > .monaco-dropdown-button { - margin-left: 1px; +.monaco-button-dropdown .monaco-button-dropdown-separator { + padding: 4px 0; +} + +.monaco-button-dropdown .monaco-button-dropdown-separator > div { + height: 100%; + width: 1px; } .monaco-description-button { diff --git a/src/vs/base/browser/ui/button/button.ts b/src/vs/base/browser/ui/button/button.ts index 3187254843b..4ae8b9d6239 100644 --- a/src/vs/base/browser/ui/button/button.ts +++ b/src/vs/base/browser/ui/button/button.ts @@ -247,6 +247,8 @@ export class ButtonWithDropdown extends Disposable implements IButton { private readonly button: Button; private readonly action: Action; private readonly dropdownButton: Button; + private readonly separatorContainer: HTMLDivElement; + private readonly separator: HTMLDivElement; readonly element: HTMLElement; private readonly _onDidClick = this._register(new Emitter<Event | undefined>()); @@ -263,6 +265,13 @@ export class ButtonWithDropdown extends Disposable implements IButton { this._register(this.button.onDidClick(e => this._onDidClick.fire(e))); this.action = this._register(new Action('primaryAction', this.button.label, undefined, true, async () => this._onDidClick.fire(undefined))); + this.separatorContainer = document.createElement('div'); + this.separatorContainer.classList.add('monaco-button-dropdown-separator'); + + this.separator = document.createElement('div'); + this.separatorContainer.appendChild(this.separator); + this.element.appendChild(this.separatorContainer); + this.dropdownButton = this._register(new Button(this.element, { ...options, title: false, supportIcons: true })); this.dropdownButton.element.title = localize("button dropdown more actions", 'More Actions...'); this.dropdownButton.element.classList.add('monaco-dropdown-button'); @@ -299,6 +308,10 @@ export class ButtonWithDropdown extends Disposable implements IButton { style(styles: IButtonStyles): void { this.button.style(styles); this.dropdownButton.style(styles); + + // Separator + this.separatorContainer.style.backgroundColor = styles.buttonBackground?.toString() ?? ''; + this.separator.style.backgroundColor = styles.buttonForeground?.toString() ?? ''; } focus(): void { diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 9cf505e2470..7c128863922 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -232,7 +232,7 @@ class SharedProcessMain extends Disposable { fileService.registerProvider(Schemas.vscodeUserData, userDataFileSystemProvider); // User Data Profiles - const userDataProfilesService = this._register(new UserDataProfilesNativeService(this.configuration.profiles, mainProcessService, environmentService, fileService, logService)); + const userDataProfilesService = this._register(new UserDataProfilesNativeService(this.configuration.profiles, mainProcessService, environmentService)); services.set(IUserDataProfilesService, userDataProfilesService); // Configuration diff --git a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts index 66e250a2df1..0e19fa5b37b 100644 --- a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts +++ b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts @@ -18,12 +18,10 @@ import { ServerInstallOptions, ServerInstallVSIXOptions, ServerUninstallOptions, Metadata, ServerInstallExtensionEvent, ServerInstallExtensionResult, ServerUninstallExtensionEvent, ServerDidUninstallExtensionEvent } from 'vs/platform/extensionManagement/common/extensionManagement'; import { areSameExtensions, ExtensionKey, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, getMaliciousExtensionsSet } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; -import { IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService'; -import { ExtensionType, IExtensionManifest, TargetPlatform } from 'vs/platform/extensions/common/extensions'; +import { ExtensionType, IExtensionManifest, isApplicationScopedExtension, TargetPlatform } from 'vs/platform/extensions/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; import { IProductService } from 'vs/platform/product/common/productService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; export interface IInstallExtensionTask { @@ -66,13 +64,11 @@ export abstract class AbstractExtensionManagementService extends Disposable impl private readonly participants: IExtensionManagementParticipant[] = []; constructor( - @IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService, - @IUriIdentityService private readonly uriIdenityService: IUriIdentityService, @IExtensionGalleryService protected readonly galleryService: IExtensionGalleryService, - @IExtensionsProfileScannerService protected readonly extensionsProfileScannerService: IExtensionsProfileScannerService, @ITelemetryService protected readonly telemetryService: ITelemetryService, @ILogService protected readonly logService: ILogService, - @IProductService protected readonly productService: IProductService + @IProductService protected readonly productService: IProductService, + @IUserDataProfilesService protected readonly userDataProfilesService: IUserDataProfilesService, ) { super(); this._register(toDisposable(() => { @@ -120,7 +116,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl throw new Error(nls.localize('Not a Marketplace extension', "Only Marketplace Extensions can be reinstalled")); } - await this.createDefaultUninstallExtensionTask(extension, { remove: true, versionOnly: true }).run(); + await this.createUninstallExtensionTask(extension, { remove: true, versionOnly: true }).run(); await this.installFromGallery(galleryExtension); } @@ -140,13 +136,15 @@ export abstract class AbstractExtensionManagementService extends Disposable impl } protected async installExtension(manifest: IExtensionManifest, extension: URI | IGalleryExtension, options: ServerInstallOptions & ServerInstallVSIXOptions): Promise<ILocalExtension> { + + const getInstallExtensionTaskKey = (extension: IGalleryExtension) => `${ExtensionKey.create(extension).toString()}${options.profileLocation ? `-${options.profileLocation.toString()}` : ''}`; + // only cache gallery extensions tasks if (!URI.isUri(extension)) { - const installExtensionTask = this.installingExtensions.get(ExtensionKey.create(extension).toString()); + const installExtensionTask = this.installingExtensions.get(getInstallExtensionTaskKey(extension)); if (installExtensionTask) { this.logService.info('Extensions is already requested to install', extension.identifier.id); - const waitUntilTaskIsFinishedTask = this.createWaitUntilInstallExtensionTaskIsFinishedTask(installExtensionTask, options); - const { local } = await waitUntilTaskIsFinishedTask.waitUntilTaskIsFinished(); + const { local } = await installExtensionTask.waitUntilTaskIsFinished(); return local; } options = { ...options, installOnlyNewlyAddedFromExtensionPack: true /* always true for gallery extensions */ }; @@ -156,7 +154,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl const installResults: (ServerInstallExtensionResult & { local: ILocalExtension })[] = []; const installExtensionTask = this.createInstallExtensionTask(manifest, extension, options); if (!URI.isUri(extension)) { - this.installingExtensions.set(ExtensionKey.create(extension).toString(), installExtensionTask); + this.installingExtensions.set(getInstallExtensionTaskKey(extension), installExtensionTask); } this._onInstallExtension.fire({ identifier: installExtensionTask.identifier, source: extension, profileLocation: options.profileLocation }); this.logService.info('Installing extension:', installExtensionTask.identifier.id); @@ -171,7 +169,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl const allDepsAndPackExtensionsToInstall = await this.getAllDepsAndPackExtensionsToInstall(installExtensionTask.identifier, manifest, !!options.installOnlyNewlyAddedFromExtensionPack, !!options.installPreReleaseVersion, options.profileLocation); for (const { gallery, manifest } of allDepsAndPackExtensionsToInstall) { installExtensionHasDependents = installExtensionHasDependents || !!manifest.extensionDependencies?.some(id => areSameExtensions({ id }, installExtensionTask.identifier)); - const key = ExtensionKey.create(gallery).toString(); + const key = getInstallExtensionTaskKey(gallery); if (this.installingExtensions.has(key)) { this.logService.info('Extension is already requested to install', gallery.identifier.id); } else { @@ -260,7 +258,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl // rollback installed extensions if (installResults.length) { try { - const result = await Promise.allSettled(installResults.map(({ local }) => this.createUninstallExtensionTask(local, { versionOnly: true }, options.profileLocation).run())); + const result = await Promise.allSettled(installResults.map(({ local }) => this.createUninstallExtensionTask(local, { versionOnly: true, profileLocation: options.profileLocation }).run())); for (let index = 0; index < result.length; index++) { const r = result[index]; const { identifier } = installResults[index]; @@ -282,7 +280,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl /* Remove the gallery tasks from the cache */ for (const { task } of allInstallExtensionTasks) { if (!URI.isUri(task.source)) { - const key = ExtensionKey.create(task.source).toString(); + const key = getInstallExtensionTaskKey(task.source); if (!this.installingExtensions.delete(key)) { this.logService.warn('Installation task is not found in the cache', key); } @@ -434,7 +432,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl } const createUninstallExtensionTask = (extension: ILocalExtension, uninstallOptions: ServerUninstallOptions): IUninstallExtensionTask => { - const uninstallExtensionTask = this.createUninstallExtensionTask(extension, uninstallOptions, options.profileLocation); + const uninstallExtensionTask = this.createUninstallExtensionTask(extension, uninstallOptions); this.uninstallingExtensions.set(getUninstallExtensionTaskKey(uninstallExtensionTask.extension.identifier), uninstallExtensionTask); if (options.profileLocation) { this.logService.info('Uninstalling extension from the profile:', `${extension.identifier.id}@${extension.manifest.version}`, options.profileLocation.toString()); @@ -604,22 +602,17 @@ export abstract class AbstractExtensionManagementService extends Disposable impl } private createInstallExtensionTask(manifest: IExtensionManifest, extension: URI | IGalleryExtension, options: ServerInstallOptions & ServerInstallVSIXOptions): IInstallExtensionTask { - const installTask = this.createDefaultInstallExtensionTask(manifest, extension, options); - return options.profileLocation && this.userDataProfilesService.defaultProfile.extensionsResource ? new InstallExtensionInProfileTask(installTask, options.profileLocation, this.userDataProfilesService.defaultProfile.extensionsResource, this.extensionsProfileScannerService) : installTask; - } - - private createWaitUntilInstallExtensionTaskIsFinishedTask(installTask: IInstallExtensionTask, options: ServerInstallOptions & ServerInstallVSIXOptions): IInstallExtensionTask { - if (!options.profileLocation || !this.userDataProfilesService.defaultProfile.extensionsResource) { - return installTask; - } - if (installTask instanceof InstallExtensionInProfileTask && this.uriIdenityService.extUri.isEqual(installTask.profileLocation, options.profileLocation)) { - return installTask; + if (options.profileLocation && isApplicationScopedExtension(manifest)) { + options = { ...options, profileLocation: this.userDataProfilesService.defaultProfile.extensionsResource }; } - return new InstallExtensionInProfileTask(installTask, options.profileLocation, this.userDataProfilesService.defaultProfile.extensionsResource, this.extensionsProfileScannerService); + return this.doCreateInstallExtensionTask(manifest, extension, options); } - private createUninstallExtensionTask(extension: ILocalExtension, options: ServerUninstallOptions, profile?: URI): IUninstallExtensionTask { - return profile && this.userDataProfilesService.defaultProfile.extensionsResource ? new UninstallExtensionFromProfileTask(extension, profile, this.userDataProfilesService, this.extensionsProfileScannerService) : this.createDefaultUninstallExtensionTask(extension, options); + private createUninstallExtensionTask(extension: ILocalExtension, options: ServerUninstallOptions): IUninstallExtensionTask { + if (options.profileLocation && extension.isApplicationScoped) { + options = { ...options, profileLocation: this.userDataProfilesService.defaultProfile.extensionsResource }; + } + return this.doCreateUninstallExtensionTask(extension, options); } abstract getTargetPlatform(): Promise<TargetPlatform>; @@ -633,8 +626,8 @@ export abstract class AbstractExtensionManagementService extends Disposable impl abstract updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata): Promise<ILocalExtension>; abstract updateExtensionScope(local: ILocalExtension, isMachineScoped: boolean): Promise<ILocalExtension>; - protected abstract createDefaultInstallExtensionTask(manifest: IExtensionManifest, extension: URI | IGalleryExtension, options: ServerInstallOptions & ServerInstallVSIXOptions): IInstallExtensionTask; - protected abstract createDefaultUninstallExtensionTask(extension: ILocalExtension, options: ServerUninstallOptions): IUninstallExtensionTask; + protected abstract doCreateInstallExtensionTask(manifest: IExtensionManifest, extension: URI | IGalleryExtension, options: ServerInstallOptions & ServerInstallVSIXOptions): IInstallExtensionTask; + protected abstract doCreateUninstallExtensionTask(extension: ILocalExtension, options: ServerUninstallOptions): IUninstallExtensionTask; } export function joinErrors(errorOrErrors: (Error | string) | (Array<Error | string>)): Error { @@ -731,63 +724,3 @@ export abstract class AbstractExtensionTask<T> { protected abstract doRun(token: CancellationToken): Promise<T>; } - -class InstallExtensionInProfileTask implements IInstallExtensionTask { - - readonly identifier = this.task.identifier; - readonly source = this.task.source; - readonly operation = this.task.operation; - - private readonly promise: Promise<{ local: ILocalExtension; metadata: Metadata }>; - - constructor( - private readonly task: IInstallExtensionTask, - readonly profileLocation: URI, - private readonly defaultProfileLocation: URI, - private readonly extensionsProfileScannerService: IExtensionsProfileScannerService, - ) { - this.promise = this.waitAndAddExtensionToProfile(); - } - - private async waitAndAddExtensionToProfile(): Promise<{ local: ILocalExtension; metadata: Metadata }> { - const result = await this.task.waitUntilTaskIsFinished(); - const profileLocation = result.local.isApplicationScoped ? this.defaultProfileLocation : this.profileLocation; - await this.extensionsProfileScannerService.addExtensionsToProfile([[result.local, result.metadata]], profileLocation); - return result; - } - - async run(): Promise<{ local: ILocalExtension; metadata: Metadata }> { - await this.task.run(); - return this.promise; - } - - waitUntilTaskIsFinished(): Promise<{ local: ILocalExtension; metadata: Metadata }> { - return this.promise; - } - - cancel(): void { - return this.task.cancel(); - } -} - -class UninstallExtensionFromProfileTask extends AbstractExtensionTask<void> implements IUninstallExtensionTask { - - constructor( - readonly extension: ILocalExtension, - private readonly profileLocation: URI, - private readonly userDataProfilesService: IUserDataProfilesService, - private readonly extensionsProfileScannerService: IExtensionsProfileScannerService, - ) { - super(); - } - - protected async doRun(token: CancellationToken): Promise<void> { - const promises: Promise<any>[] = []; - promises.push(this.extensionsProfileScannerService.removeExtensionFromProfile(this.extension.identifier, this.profileLocation)); - if (this.extension.isApplicationScoped && this.userDataProfilesService.defaultProfile.extensionsResource) { - promises.push(this.extensionsProfileScannerService.removeExtensionFromProfile(this.extension.identifier, this.userDataProfilesService.defaultProfile.extensionsResource)); - } - await Promise.all(promises); - } - -} diff --git a/src/vs/platform/extensionManagement/common/extensionsScannerService.ts b/src/vs/platform/extensionManagement/common/extensionsScannerService.ts index 823a0be2541..194ff9965ae 100644 --- a/src/vs/platform/extensionManagement/common/extensionsScannerService.ts +++ b/src/vs/platform/extensionManagement/common/extensionsScannerService.ts @@ -27,13 +27,14 @@ import { areSameExtensions, computeTargetPlatform, ExtensionKey, getExtensionId, import { ExtensionType, ExtensionIdentifier, IExtensionManifest, TargetPlatform, IExtensionIdentifier, IRelaxedExtensionManifest, UNDEFINED_PUBLISHER, IExtensionDescription, BUILTIN_MANIFEST_CACHE_FILE, USER_MANIFEST_CACHE_FILE, MANIFEST_CACHE_FOLDER } from 'vs/platform/extensions/common/extensions'; import { validateExtensionManifest } from 'vs/platform/extensions/common/extensionValidator'; import { FileOperationResult, IFileService, toFileOperationResult } from 'vs/platform/files/common/files'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; import { IProductService } from 'vs/platform/product/common/productService'; import { Emitter, Event } from 'vs/base/common/event'; import { revive } from 'vs/base/common/marshalling'; import { IExtensionsProfileScannerService, IScannedProfileExtension } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService'; import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; export type IScannedExtensionManifest = IRelaxedExtensionManifest & { __metadata?: Metadata }; @@ -139,9 +140,9 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem readonly onDidChangeCache = this._onDidChangeCache.event; private readonly obsoleteFile = joinPath(this.userExtensionsLocation, '.obsolete'); - private readonly systemExtensionsCachedScanner = this._register(new CachedExtensionsScanner(joinPath(this.cacheLocation, BUILTIN_MANIFEST_CACHE_FILE), this.obsoleteFile, this.extensionsProfileScannerService, this.fileService, this.logService)); - private readonly userExtensionsCachedScanner = this._register(new CachedExtensionsScanner(joinPath(this.cacheLocation, USER_MANIFEST_CACHE_FILE), this.obsoleteFile, this.extensionsProfileScannerService, this.fileService, this.logService)); - private readonly extensionsScanner = this._register(new ExtensionsScanner(this.obsoleteFile, this.extensionsProfileScannerService, this.fileService, this.logService)); + private readonly systemExtensionsCachedScanner = this._register(this.instantiationService.createInstance(CachedExtensionsScanner, joinPath(this.cacheLocation, BUILTIN_MANIFEST_CACHE_FILE), this.obsoleteFile)); + private readonly userExtensionsCachedScanner = this._register(this.instantiationService.createInstance(CachedExtensionsScanner, joinPath(this.cacheLocation, USER_MANIFEST_CACHE_FILE), this.obsoleteFile)); + private readonly extensionsScanner = this._register(this.instantiationService.createInstance(ExtensionsScanner, this.obsoleteFile)); constructor( readonly systemExtensionsLocation: URI, @@ -154,6 +155,7 @@ export abstract class AbstractExtensionsScannerService extends Disposable implem @ILogService protected readonly logService: ILogService, @IEnvironmentService private readonly environmentService: IEnvironmentService, @IProductService private readonly productService: IProductService, + @IInstantiationService private readonly instantiationService: IInstantiationService, ) { super(); @@ -476,9 +478,10 @@ class ExtensionsScanner extends Disposable { constructor( private readonly obsoleteFile: URI, - protected readonly extensionsProfileScannerService: IExtensionsProfileScannerService, - protected readonly fileService: IFileService, - protected readonly logService: ILogService + @IExtensionsProfileScannerService protected readonly extensionsProfileScannerService: IExtensionsProfileScannerService, + @IUriIdentityService protected readonly uriIdentityService: IUriIdentityService, + @IFileService protected readonly fileService: IFileService, + @ILogService protected readonly logService: ILogService ) { super(); } @@ -518,15 +521,13 @@ class ExtensionsScanner extends Disposable { } private async scanExtensionsFromProfile(input: ExtensionScannerInput): Promise<IRelaxedScannedExtension[]> { - const profileExtensions = await this.scanExtensionsFromProfileResource(input.location, () => true, input); - const applicationExtensions = await this.scanApplicationExtensions(input); - return [...profileExtensions, ...applicationExtensions]; - } - - private async scanApplicationExtensions(input: ExtensionScannerInput): Promise<IRelaxedScannedExtension[]> { - return input.applicationExtensionslocation - ? this.scanExtensionsFromProfileResource(input.applicationExtensionslocation, (e) => !!e.metadata?.isApplicationScoped, input) - : []; + let profileExtensions = await this.scanExtensionsFromProfileResource(input.location, () => true, input); + if (input.applicationExtensionslocation && !this.uriIdentityService.extUri.isEqual(input.location, input.applicationExtensionslocation)) { + profileExtensions = profileExtensions.filter(e => !e.metadata?.isApplicationScoped); + const applicationExtensions = await this.scanExtensionsFromProfileResource(input.applicationExtensionslocation, (e) => !!e.metadata?.isApplicationScoped, input); + profileExtensions.push(...applicationExtensions); + } + return profileExtensions; } private async scanExtensionsFromProfileResource(profileResource: URI, filter: (extensionInfo: IScannedProfileExtension) => boolean, input: ExtensionScannerInput): Promise<IRelaxedScannedExtension[]> { @@ -845,11 +846,12 @@ class CachedExtensionsScanner extends ExtensionsScanner { constructor( private readonly cacheFile: URI, obsoleteFile: URI, - extensionsProfileScannerService: IExtensionsProfileScannerService, - fileService: IFileService, - logService: ILogService + @IExtensionsProfileScannerService extensionsProfileScannerService: IExtensionsProfileScannerService, + @IUriIdentityService uriIdentityService: IUriIdentityService, + @IFileService fileService: IFileService, + @ILogService logService: ILogService ) { - super(obsoleteFile, extensionsProfileScannerService, fileService, logService); + super(obsoleteFile, extensionsProfileScannerService, uriIdentityService, fileService, logService); } override async scanExtensions(input: ExtensionScannerInput): Promise<IRelaxedScannedExtension[]> { @@ -947,13 +949,14 @@ export class NativeExtensionsScannerService extends AbstractExtensionsScannerSer logService: ILogService, environmentService: IEnvironmentService, productService: IProductService, + instantiationService: IInstantiationService, ) { super( systemExtensionsLocation, userExtensionsLocation, joinPath(userHome, '.vscode-oss-dev', 'extensions', 'control.json'), joinPath(userDataPath, MANIFEST_CACHE_FOLDER), - userDataProfilesService, extensionsProfileScannerService, fileService, logService, environmentService, productService); + userDataProfilesService, extensionsProfileScannerService, fileService, logService, environmentService, productService, instantiationService); this.translationsPromise = (async () => { if (platform.translationsConfigFile) { try { diff --git a/src/vs/platform/extensionManagement/electron-sandbox/extensionsScannerService.ts b/src/vs/platform/extensionManagement/electron-sandbox/extensionsScannerService.ts index cfc0d5dacf3..9503799fef8 100644 --- a/src/vs/platform/extensionManagement/electron-sandbox/extensionsScannerService.ts +++ b/src/vs/platform/extensionManagement/electron-sandbox/extensionsScannerService.ts @@ -9,6 +9,7 @@ import { IExtensionsProfileScannerService } from 'vs/platform/extensionManagemen import { IExtensionsScannerService, NativeExtensionsScannerService, } from 'vs/platform/extensionManagement/common/extensionsScannerService'; import { IFileService } from 'vs/platform/files/common/files'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; import { IProductService } from 'vs/platform/product/common/productService'; import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; @@ -22,13 +23,14 @@ export class ExtensionsScannerService extends NativeExtensionsScannerService imp @ILogService logService: ILogService, @INativeEnvironmentService environmentService: INativeEnvironmentService, @IProductService productService: IProductService, + @IInstantiationService instantiationService: IInstantiationService, ) { super( URI.file(environmentService.builtinExtensionsPath), URI.file(environmentService.extensionsPath), environmentService.userHome, URI.file(environmentService.userDataPath), - userDataProfilesService, extensionsProfileScannerService, fileService, logService, environmentService, productService); + userDataProfilesService, extensionsProfileScannerService, fileService, logService, environmentService, productService, instantiationService); } } diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 56ea181d99e..a5f367b4124 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -65,13 +65,15 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi private readonly manifestCache: ExtensionsManifestCache; private readonly extensionsDownloader: ExtensionsDownloader; + private readonly installGalleryExtensionsTasks = new Map<string, InstallGalleryExtensionTask>(); + constructor( @IExtensionGalleryService galleryService: IExtensionGalleryService, @ITelemetryService telemetryService: ITelemetryService, @ILogService logService: ILogService, @INativeEnvironmentService private readonly environmentService: INativeEnvironmentService, @IExtensionsScannerService private readonly extensionsScannerService: IExtensionsScannerService, - @IExtensionsProfileScannerService extensionsProfileScannerService: IExtensionsProfileScannerService, + @IExtensionsProfileScannerService private readonly extensionsProfileScannerService: IExtensionsProfileScannerService, @IDownloadService private downloadService: IDownloadService, @IInstantiationService instantiationService: IInstantiationService, @IFileService private readonly fileService: IFileService, @@ -79,7 +81,7 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi @IUriIdentityService uriIdentityService: IUriIdentityService, @IUserDataProfilesService userDataProfilesService: IUserDataProfilesService, ) { - super(userDataProfilesService, uriIdentityService, galleryService, extensionsProfileScannerService, telemetryService, logService, productService); + super(galleryService, telemetryService, logService, productService, userDataProfilesService); const extensionLifecycle = this._register(instantiationService.createInstance(ExtensionsLifecycle)); this.extensionsScanner = this._register(instantiationService.createInstance(ExtensionsScanner, extension => extensionLifecycle.postUninstall(extension))); this.manifestCache = this._register(new ExtensionsManifestCache(environmentService, this)); @@ -176,11 +178,28 @@ export class ExtensionManagementService extends AbstractExtensionManagementServi return downloadedLocation; } - protected createDefaultInstallExtensionTask(manifest: IExtensionManifest, extension: URI | IGalleryExtension, options: ServerInstallOptions & ServerInstallVSIXOptions): IInstallExtensionTask { - return URI.isUri(extension) ? new InstallVSIXTask(manifest, extension, options, this.galleryService, this.extensionsScanner, this.logService) : new InstallGalleryExtensionTask(manifest, extension, options, this.extensionsDownloader, this.extensionsScanner, this.logService); + protected doCreateInstallExtensionTask(manifest: IExtensionManifest, extension: URI | IGalleryExtension, options: ServerInstallOptions & ServerInstallVSIXOptions): IInstallExtensionTask { + let installExtensionTask: IInstallExtensionTask | undefined; + if (URI.isUri(extension)) { + installExtensionTask = new InstallVSIXTask(manifest, extension, options, this.galleryService, this.extensionsScanner, this.logService); + } else { + const key = ExtensionKey.create(extension).toString(); + installExtensionTask = this.installGalleryExtensionsTasks.get(key); + if (!installExtensionTask) { + this.installGalleryExtensionsTasks.set(key, installExtensionTask = new InstallGalleryExtensionTask(manifest, extension, options, this.extensionsDownloader, this.extensionsScanner, this.logService)); + installExtensionTask.waitUntilTaskIsFinished().then(() => this.installGalleryExtensionsTasks.delete(key)); + } + } + if (options.profileLocation) { + return new InstallExtensionInProfileTask(installExtensionTask, options.profileLocation, this.extensionsProfileScannerService); + } + return installExtensionTask; } - protected createDefaultUninstallExtensionTask(extension: ILocalExtension, options: ServerUninstallOptions): IUninstallExtensionTask { + protected doCreateUninstallExtensionTask(extension: ILocalExtension, options: ServerUninstallOptions): IUninstallExtensionTask { + if (options.profileLocation) { + return new UninstallExtensionFromProfileTask(extension, options.profileLocation, this.extensionsProfileScannerService); + } return new UninstallExtensionTask(extension, options, this.extensionsScanner); } @@ -706,6 +725,42 @@ class InstallVSIXTask extends InstallExtensionTask { } } +class InstallExtensionInProfileTask implements IInstallExtensionTask { + + readonly identifier = this.task.identifier; + readonly source = this.task.source; + readonly operation = this.task.operation; + + private readonly promise: Promise<{ local: ILocalExtension; metadata: Metadata }>; + + constructor( + private readonly task: IInstallExtensionTask, + private readonly profileLocation: URI, + private readonly extensionsProfileScannerService: IExtensionsProfileScannerService, + ) { + this.promise = this.waitAndAddExtensionToProfile(); + } + + private async waitAndAddExtensionToProfile(): Promise<{ local: ILocalExtension; metadata: Metadata }> { + const result = await this.task.waitUntilTaskIsFinished(); + await this.extensionsProfileScannerService.addExtensionsToProfile([[result.local, result.metadata]], this.profileLocation); + return result; + } + + async run(): Promise<{ local: ILocalExtension; metadata: Metadata }> { + await this.task.run(); + return this.promise; + } + + waitUntilTaskIsFinished(): Promise<{ local: ILocalExtension; metadata: Metadata }> { + return this.promise; + } + + cancel(): void { + return this.task.cancel(); + } +} + class UninstallExtensionTask extends AbstractExtensionTask<void> implements IUninstallExtensionTask { constructor( @@ -745,3 +800,20 @@ class UninstallExtensionTask extends AbstractExtensionTask<void> implements IUni } } + +class UninstallExtensionFromProfileTask extends AbstractExtensionTask<void> implements IUninstallExtensionTask { + + constructor( + readonly extension: ILocalExtension, + private readonly profileLocation: URI, + private readonly extensionsProfileScannerService: IExtensionsProfileScannerService, + ) { + super(); + } + + protected async doRun(token: CancellationToken): Promise<void> { + await this.extensionsProfileScannerService.removeExtensionFromProfile(this.extension.identifier, this.profileLocation); + } + +} + diff --git a/src/vs/platform/extensionManagement/node/extensionsScannerService.ts b/src/vs/platform/extensionManagement/node/extensionsScannerService.ts index 9d90bf687d6..20a1fd0a61d 100644 --- a/src/vs/platform/extensionManagement/node/extensionsScannerService.ts +++ b/src/vs/platform/extensionManagement/node/extensionsScannerService.ts @@ -8,6 +8,7 @@ import { INativeEnvironmentService } from 'vs/platform/environment/common/enviro import { IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService'; import { IExtensionsScannerService, NativeExtensionsScannerService, } from 'vs/platform/extensionManagement/common/extensionsScannerService'; import { IFileService } from 'vs/platform/files/common/files'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; import { IProductService } from 'vs/platform/product/common/productService'; import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; @@ -21,13 +22,14 @@ export class ExtensionsScannerService extends NativeExtensionsScannerService imp @ILogService logService: ILogService, @INativeEnvironmentService environmentService: INativeEnvironmentService, @IProductService productService: IProductService, + @IInstantiationService instantiationService: IInstantiationService, ) { super( URI.file(environmentService.builtinExtensionsPath), URI.file(environmentService.extensionsPath), environmentService.userHome, URI.file(environmentService.userDataPath), - userDataProfilesService, extensionsProfileScannerService, fileService, logService, environmentService, productService); + userDataProfilesService, extensionsProfileScannerService, fileService, logService, environmentService, productService, instantiationService); } } diff --git a/src/vs/platform/extensionManagement/test/node/extensionsScannerService.test.ts b/src/vs/platform/extensionManagement/test/node/extensionsScannerService.test.ts index cdfaa38b842..b0a7ec8ad9a 100644 --- a/src/vs/platform/extensionManagement/test/node/extensionsScannerService.test.ts +++ b/src/vs/platform/extensionManagement/test/node/extensionsScannerService.test.ts @@ -14,9 +14,11 @@ import { ExtensionType, IExtensionManifest, MANIFEST_CACHE_FOLDER, TargetPlatfor import { IFileService } from 'vs/platform/files/common/files'; import { FileService } from 'vs/platform/files/common/fileService'; import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { ILogService, NullLogService } from 'vs/platform/log/common/log'; import { IProductService } from 'vs/platform/product/common/productService'; +import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; import { IUserDataProfilesService, UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; let translations: Translations = Object.create(null); @@ -31,13 +33,14 @@ class ExtensionsScannerService extends AbstractExtensionsScannerService implemen @ILogService logService: ILogService, @INativeEnvironmentService nativeEnvironmentService: INativeEnvironmentService, @IProductService productService: IProductService, + @IInstantiationService instantiationService: IInstantiationService, ) { super( URI.file(nativeEnvironmentService.builtinExtensionsPath), URI.file(nativeEnvironmentService.extensionsPath), joinPath(nativeEnvironmentService.userHome, '.vscode-oss-dev', 'extensions', 'control.json'), joinPath(ROOT, MANIFEST_CACHE_FOLDER), - userDataProfilesService, extensionsProfileScannerService, fileService, logService, nativeEnvironmentService, productService); + userDataProfilesService, extensionsProfileScannerService, fileService, logService, nativeEnvironmentService, productService, instantiationService); } protected async getTranslations(language: string): Promise<Translations> { @@ -70,7 +73,7 @@ suite('NativeExtensionsScanerService Test', () => { }); instantiationService.stub(IProductService, { version: '1.66.0' }); instantiationService.stub(IExtensionsProfileScannerService, new ExtensionsProfileScannerService(fileService, logService)); - instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, logService)); + instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, new UriIdentityService(fileService), logService)); await fileService.createFolder(systemExtensionsLocation); await fileService.createFolder(userExtensionsLocation); }); diff --git a/src/vs/platform/terminal/common/terminalEnvironment.ts b/src/vs/platform/terminal/common/terminalEnvironment.ts index 66b5ad31a3f..1d24a24f60d 100644 --- a/src/vs/platform/terminal/common/terminalEnvironment.ts +++ b/src/vs/platform/terminal/common/terminalEnvironment.ts @@ -12,3 +12,26 @@ export function escapeNonWindowsPath(path: string): string { newPath = newPath.replace(bannedChars, ''); return `'${newPath}'`; } + +/** + * Collapses the user's home directory into `~` if it exists within the path, this gives a shorter + * path that is more suitable within the context of a terminal. + */ +export function collapseTildePath(path: string | undefined, userHome: string | undefined, separator: string): string { + if (!path) { + return ''; + } + if (!userHome) { + return path; + } + // Trim the trailing separator from the end if it exists + if (userHome.match(/[\/\\]$/)) { + userHome = userHome.slice(0, userHome.length - 1); + } + const normalizedPath = path.replace(/\\/g, '/').toLowerCase(); + const normalizedUserHome = userHome.replace(/\\/g, '/').toLowerCase(); + if (!normalizedPath.includes(normalizedUserHome)) { + return path; + } + return `~${separator}${path.slice(userHome.length + 1)}`; +} diff --git a/src/vs/platform/terminal/test/common/terminalEnvironment.test.ts b/src/vs/platform/terminal/test/common/terminalEnvironment.test.ts new file mode 100644 index 00000000000..2c58f9ec1fd --- /dev/null +++ b/src/vs/platform/terminal/test/common/terminalEnvironment.test.ts @@ -0,0 +1,40 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { strictEqual } from 'assert'; +import { collapseTildePath } from 'vs/platform/terminal/common/terminalEnvironment'; + +suite('terminalEnvironment', () => { + suite('collapseTildePath', () => { + test('should return empty string for a falsy path', () => { + strictEqual(collapseTildePath('', '/foo', '/'), ''); + strictEqual(collapseTildePath(undefined, '/foo', '/'), ''); + }); + test('should return path for a falsy user home', () => { + strictEqual(collapseTildePath('/foo', '', '/'), '/foo'); + strictEqual(collapseTildePath('/foo', undefined, '/'), '/foo'); + }); + test('should not collapse when user home isn\'t present', () => { + strictEqual(collapseTildePath('/foo', '/bar', '/'), '/foo'); + strictEqual(collapseTildePath('C:\\foo', 'C:\\bar', '\\'), 'C:\\foo'); + }); + test('should collapse with Windows separators', () => { + strictEqual(collapseTildePath('C:\\foo\\bar', 'C:\\foo', '\\'), '~\\bar'); + strictEqual(collapseTildePath('C:\\foo\\bar', 'C:\\foo\\', '\\'), '~\\bar'); + strictEqual(collapseTildePath('C:\\foo\\bar\\baz', 'C:\\foo\\', '\\'), '~\\bar\\baz'); + strictEqual(collapseTildePath('C:\\foo\\bar\\baz', 'C:\\foo', '\\'), '~\\bar\\baz'); + }); + test('should collapse mixed case with Windows separators', () => { + strictEqual(collapseTildePath('c:\\foo\\bar', 'C:\\foo', '\\'), '~\\bar'); + strictEqual(collapseTildePath('C:\\foo\\bar\\baz', 'c:\\foo', '\\'), '~\\bar\\baz'); + }); + test('should collapse with Posix separators', () => { + strictEqual(collapseTildePath('/foo/bar', '/foo', '/'), '~/bar'); + strictEqual(collapseTildePath('/foo/bar', '/foo/', '/'), '~/bar'); + strictEqual(collapseTildePath('/foo/bar/baz', '/foo', '/'), '~/bar/baz'); + strictEqual(collapseTildePath('/foo/bar/baz', '/foo/', '/'), '~/bar/baz'); + }); + }); +}); diff --git a/src/vs/platform/userData/test/browser/fileUserDataProvider.test.ts b/src/vs/platform/userData/test/browser/fileUserDataProvider.test.ts index f6a9b0d6993..58fd53e21ec 100644 --- a/src/vs/platform/userData/test/browser/fileUserDataProvider.test.ts +++ b/src/vs/platform/userData/test/browser/fileUserDataProvider.test.ts @@ -19,6 +19,7 @@ import { AbstractNativeEnvironmentService } from 'vs/platform/environment/common import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import product from 'vs/platform/product/common/product'; import { IUserDataProfilesService, UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; const ROOT = URI.file('tests').with({ scheme: 'vscode-tests' }); @@ -52,7 +53,7 @@ suite('FileUserDataProvider', () => { await testObject.createFolder(backupWorkspaceHomeOnDisk); environmentService = new TestEnvironmentService(userDataHomeOnDisk); - userDataProfilesService = new UserDataProfilesService(environmentService, testObject, logService); + userDataProfilesService = new UserDataProfilesService(environmentService, testObject, new UriIdentityService(testObject), logService); fileUserDataProvider = new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, logService); disposables.add(fileUserDataProvider); diff --git a/src/vs/platform/userDataProfile/browser/userDataProfile.ts b/src/vs/platform/userDataProfile/browser/userDataProfile.ts new file mode 100644 index 00000000000..56edfc09480 --- /dev/null +++ b/src/vs/platform/userDataProfile/browser/userDataProfile.ts @@ -0,0 +1,66 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { revive } from 'vs/base/common/marshalling'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IFileService } from 'vs/platform/files/common/files'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; +import { IUserDataProfilesService, PROFILES_ENABLEMENT_CONFIG, StoredProfileAssociations, StoredUserDataProfile, UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; + +export class BrowserUserDataProfilesService extends UserDataProfilesService implements IUserDataProfilesService { + + protected override readonly defaultProfileShouldIncludeExtensionsResourceAlways: boolean = true; + + constructor( + @IEnvironmentService environmentService: IEnvironmentService, + @IFileService fileService: IFileService, + @IUriIdentityService uriIdentityService: IUriIdentityService, + @ILogService logService: ILogService, + ) { + super(environmentService, fileService, uriIdentityService, logService); + super.setEnablement(window.localStorage.getItem(PROFILES_ENABLEMENT_CONFIG) === 'true'); + } + + override setEnablement(enabled: boolean): void { + super.setEnablement(enabled); + window.localStorage.setItem(PROFILES_ENABLEMENT_CONFIG, enabled ? 'true' : 'false'); + } + + protected override getStoredProfiles(): StoredUserDataProfile[] { + try { + const value = window.localStorage.getItem(UserDataProfilesService.PROFILES_KEY); + if (value) { + return revive(JSON.parse(value)); + } + } catch (error) { + /* ignore */ + this.logService.error(error); + } + return []; + } + + protected override saveStoredProfiles(storedProfiles: StoredUserDataProfile[]): void { + window.localStorage.setItem(UserDataProfilesService.PROFILES_KEY, JSON.stringify(storedProfiles)); + } + + protected override getStoredProfileAssociations(): StoredProfileAssociations { + try { + const value = window.localStorage.getItem(UserDataProfilesService.PROFILE_ASSOCIATIONS_KEY); + if (value) { + return revive(JSON.parse(value)); + } + } catch (error) { + /* ignore */ + this.logService.error(error); + } + return {}; + } + + protected override saveStoredProfileAssociations(storedProfileAssociations: StoredProfileAssociations): void { + window.localStorage.setItem(UserDataProfilesService.PROFILE_ASSOCIATIONS_KEY, JSON.stringify(storedProfileAssociations)); + } + +} diff --git a/src/vs/platform/userDataProfile/common/userDataProfile.ts b/src/vs/platform/userDataProfile/common/userDataProfile.ts index 89a181cda49..53ab77f58ec 100644 --- a/src/vs/platform/userDataProfile/common/userDataProfile.ts +++ b/src/vs/platform/userDataProfile/common/userDataProfile.ts @@ -14,7 +14,11 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { IFileService } from 'vs/platform/files/common/files'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; -import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; +import { ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; +import { ResourceMap } from 'vs/base/common/map'; +import { IStringDictionary } from 'vs/base/common/collections'; +import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; +import { Promises } from 'vs/base/common/async'; /** * Flags to indicate whether to use the default profile or not. @@ -66,6 +70,16 @@ export type WorkspaceIdentifier = ISingleFolderWorkspaceIdentifier | IWorkspaceI export type DidChangeProfilesEvent = { readonly added: IUserDataProfile[]; readonly removed: IUserDataProfile[]; readonly all: IUserDataProfile[] }; +export type WillCreateProfileEvent = { + profile: IUserDataProfile; + join(promise: Promise<void>): void; +}; + +export type WillRemoveProfileEvent = { + profile: IUserDataProfile; + join(promise: Promise<void>): void; +}; + export const IUserDataProfilesService = createDecorator<IUserDataProfilesService>('IUserDataProfilesService'); export interface IUserDataProfilesService { readonly _serviceBrand: undefined; @@ -115,34 +129,249 @@ export function toUserDataProfile(name: string, location: URI, useDefaultFlags?: }; } +export type UserDataProfilesObject = { + profiles: IUserDataProfile[]; + workspaces: ResourceMap<IUserDataProfile>; + emptyWindow?: IUserDataProfile; +}; + +export type StoredUserDataProfile = { + name: string; + location: URI; + useDefaultFlags?: UseDefaultProfileFlags; +}; + +export type StoredProfileAssociations = { + workspaces?: IStringDictionary<string>; + emptyWindow?: string; +}; + export class UserDataProfilesService extends Disposable implements IUserDataProfilesService { + + protected static readonly PROFILES_KEY = 'userDataProfiles'; + protected static readonly PROFILE_ASSOCIATIONS_KEY = 'profileAssociations'; + readonly _serviceBrand: undefined; + private enabled: boolean = false; + protected readonly defaultProfileShouldIncludeExtensionsResourceAlways: boolean = false; readonly profilesHome: URI; get defaultProfile(): IUserDataProfile { return this.profiles[0]; } - protected _profiles: IUserDataProfile[] = [this.createDefaultUserDataProfile(false)]; - get profiles(): IUserDataProfile[] { return this._profiles; } + get profiles(): IUserDataProfile[] { return this.profilesObject.profiles; } protected readonly _onDidChangeProfiles = this._register(new Emitter<DidChangeProfilesEvent>()); readonly onDidChangeProfiles = this._onDidChangeProfiles.event; + protected readonly _onWillCreateProfile = this._register(new Emitter<WillCreateProfileEvent>()); + readonly onWillCreateProfile = this._onWillCreateProfile.event; + + protected readonly _onWillRemoveProfile = this._register(new Emitter<WillRemoveProfileEvent>()); + readonly onWillRemoveProfile = this._onWillRemoveProfile.event; + constructor( @IEnvironmentService protected readonly environmentService: IEnvironmentService, @IFileService protected readonly fileService: IFileService, + @IUriIdentityService protected readonly uriIdentityService: IUriIdentityService, @ILogService protected readonly logService: ILogService ) { super(); this.profilesHome = joinPath(this.environmentService.userRoamingDataHome, 'profiles'); } - protected createDefaultUserDataProfile(extensions: boolean): IUserDataProfile { - const profile = toUserDataProfile(localize('defaultProfile', "Default"), this.environmentService.userRoamingDataHome); - return { ...profile, isDefault: true, extensionsResource: extensions ? profile.extensionsResource : undefined }; + setEnablement(enabled: boolean): void { + if (this.enabled !== enabled) { + this._profilesObject = undefined; + this.enabled = enabled; + } + } + + protected _profilesObject: UserDataProfilesObject | undefined; + protected get profilesObject(): UserDataProfilesObject { + if (!this._profilesObject) { + const profiles = this.enabled ? this.getStoredProfiles().map<IUserDataProfile>(storedProfile => toUserDataProfile(storedProfile.name, storedProfile.location, storedProfile.useDefaultFlags)) : []; + let emptyWindow: IUserDataProfile | undefined; + const workspaces = new ResourceMap<IUserDataProfile>(); + if (profiles.length) { + const profileAssicaitions = this.getStoredProfileAssociations(); + if (profileAssicaitions.workspaces) { + for (const [workspacePath, profilePath] of Object.entries(profileAssicaitions.workspaces)) { + const workspace = URI.parse(workspacePath); + const profileLocation = URI.parse(profilePath); + const profile = profiles.find(p => this.uriIdentityService.extUri.isEqual(p.location, profileLocation)); + if (profile) { + workspaces.set(workspace, profile); + } + } + } + if (profileAssicaitions.emptyWindow) { + const emptyWindowProfileLocation = URI.parse(profileAssicaitions.emptyWindow); + emptyWindow = profiles.find(p => this.uriIdentityService.extUri.isEqual(p.location, emptyWindowProfileLocation)); + } + } + const profile = toUserDataProfile(localize('defaultProfile', "Default"), this.environmentService.userRoamingDataHome); + profiles.unshift({ ...profile, isDefault: true, extensionsResource: this.defaultProfileShouldIncludeExtensionsResourceAlways || profiles.length > 0 ? profile.extensionsResource : undefined }); + this._profilesObject = { profiles, workspaces, emptyWindow }; + } + return this._profilesObject; + } + + getProfile(workspaceIdentifier: WorkspaceIdentifier): IUserDataProfile { + const workspace = this.getWorkspace(workspaceIdentifier); + const profile = URI.isUri(workspace) ? this.profilesObject.workspaces.get(workspace) : this.profilesObject.emptyWindow; + return profile ?? this.defaultProfile; + } + + protected getWorkspace(workspaceIdentifier: WorkspaceIdentifier): URI | EmptyWindowWorkspaceIdentifier { + if (isSingleFolderWorkspaceIdentifier(workspaceIdentifier)) { + return workspaceIdentifier.uri; + } + if (isWorkspaceIdentifier(workspaceIdentifier)) { + return workspaceIdentifier.configPath; + } + return 'empty-window'; + } + + async createProfile(name: string, useDefaultFlags?: UseDefaultProfileFlags, workspaceIdentifier?: WorkspaceIdentifier): Promise<IUserDataProfile> { + if (!this.enabled) { + throw new Error(`Settings Profiles are disabled. Enable them via the '${PROFILES_ENABLEMENT_CONFIG}' setting.`); + } + if (this.getStoredProfiles().some(p => p.name === name)) { + throw new Error(`Profile with name ${name} already exists`); + } + + const profile = toUserDataProfile(name, joinPath(this.profilesHome, hash(name).toString(16)), useDefaultFlags); + await this.fileService.createFolder(profile.location); + + const joiners: Promise<void>[] = []; + this._onWillCreateProfile.fire({ + profile, + join(promise) { + joiners.push(promise); + } + }); + await Promises.settled(joiners); + + this.updateProfiles([profile], []); + + if (workspaceIdentifier) { + await this.setProfileForWorkspace(profile, workspaceIdentifier); + } + + return profile; + } + + async setProfileForWorkspace(profileToSet: IUserDataProfile, workspaceIdentifier: WorkspaceIdentifier): Promise<void> { + if (!this.enabled) { + throw new Error(`Settings Profiles are disabled. Enable them via the '${PROFILES_ENABLEMENT_CONFIG}' setting.`); + } + + const profile = this.profiles.find(p => p.id === profileToSet.id); + if (!profile) { + throw new Error(`Profile '${profileToSet.name}' does not exist`); + } + + this.updateWorkspaceAssociation(workspaceIdentifier, profile); + } + + async unsetWorkspace(workspaceIdentifier: WorkspaceIdentifier): Promise<void> { + if (!this.enabled) { + throw new Error(`Settings Profiles are disabled. Enable them via the '${PROFILES_ENABLEMENT_CONFIG}' setting.`); + } + this.updateWorkspaceAssociation(workspaceIdentifier); + } + + async removeProfile(profileToRemove: IUserDataProfile): Promise<void> { + if (!this.enabled) { + throw new Error(`Settings Profiles are disabled. Enable them via the '${PROFILES_ENABLEMENT_CONFIG}' setting.`); + } + if (profileToRemove.isDefault) { + throw new Error('Cannot remove default profile'); + } + const profile = this.profiles.find(p => p.id === profileToRemove.id); + if (!profile) { + throw new Error(`Profile '${profileToRemove.name}' does not exist`); + } + + const joiners: Promise<void>[] = []; + this._onWillRemoveProfile.fire({ + profile, + join(promise) { + joiners.push(promise); + } + }); + await Promises.settled(joiners); + + if (profile.id === this.profilesObject.emptyWindow?.id) { + this.profilesObject.emptyWindow = undefined; + } + for (const workspace of [...this.profilesObject.workspaces.keys()]) { + if (profile.id === this.profilesObject.workspaces.get(workspace)?.id) { + this.profilesObject.workspaces.delete(workspace); + } + } + this.updateStoredProfileAssociations(); + + this.updateProfiles([], [profile]); + + try { + if (this.profiles.length === 1) { + await this.fileService.del(this.profilesHome, { recursive: true }); + } else { + await this.fileService.del(profile.location, { recursive: true }); + } + } catch (error) { + this.logService.error(error); + } + } + + private updateProfiles(added: IUserDataProfile[], removed: IUserDataProfile[]) { + const storedProfiles: StoredUserDataProfile[] = []; + for (const profile of [...this.profilesObject.profiles, ...added]) { + if (profile.isDefault) { + continue; + } + if (removed.some(p => profile.id === p.id)) { + continue; + } + storedProfiles.push({ location: profile.location, name: profile.name, useDefaultFlags: profile.useDefaultFlags }); + } + this.saveStoredProfiles(storedProfiles); + this._profilesObject = undefined; + this._onDidChangeProfiles.fire({ added, removed, all: this.profiles }); + } + + private updateWorkspaceAssociation(workspaceIdentifier: WorkspaceIdentifier, newProfile?: IUserDataProfile) { + const workspace = this.getWorkspace(workspaceIdentifier); + + // Folder or Multiroot workspace + if (URI.isUri(workspace)) { + this.profilesObject.workspaces.delete(workspace); + if (newProfile && !newProfile.isDefault) { + this.profilesObject.workspaces.set(workspace, newProfile); + } + } + // Empty Window + else { + this.profilesObject.emptyWindow = !newProfile?.isDefault ? newProfile : undefined; + } + + this.updateStoredProfileAssociations(); + } + + private updateStoredProfileAssociations() { + const workspaces: IStringDictionary<string> = {}; + for (const [workspace, profile] of this.profilesObject.workspaces.entries()) { + workspaces[workspace.toString()] = profile.location.toString(); + } + const emptyWindow = this.profilesObject.emptyWindow?.location.toString(); + this.saveStoredProfileAssociations({ workspaces, emptyWindow }); + this._profilesObject = undefined; } - createProfile(name: string, useDefaultFlags?: UseDefaultProfileFlags, workspaceIdentifier?: WorkspaceIdentifier): Promise<IUserDataProfile> { throw new Error('Not implemented'); } - setProfileForWorkspace(profile: IUserDataProfile, workspaceIdentifier: WorkspaceIdentifier): Promise<void> { throw new Error('Not implemented'); } - getProfile(workspaceIdentifier: WorkspaceIdentifier): IUserDataProfile { throw new Error('Not implemented'); } - removeProfile(profile: IUserDataProfile): Promise<void> { throw new Error('Not implemented'); } + protected getStoredProfiles(): StoredUserDataProfile[] { return []; } + protected saveStoredProfiles(storedProfiles: StoredUserDataProfile[]): void { throw new Error('not implemented'); } + + protected getStoredProfileAssociations(): StoredProfileAssociations { return {}; } + protected saveStoredProfileAssociations(storedProfileAssociations: StoredProfileAssociations): void { throw new Error('not implemented'); } } diff --git a/src/vs/platform/userDataProfile/electron-main/userDataProfile.ts b/src/vs/platform/userDataProfile/electron-main/userDataProfile.ts index e52855d4867..0ac191f058a 100644 --- a/src/vs/platform/userDataProfile/electron-main/userDataProfile.ts +++ b/src/vs/platform/userDataProfile/electron-main/userDataProfile.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Emitter, Event } from 'vs/base/common/event'; +import { Event } from 'vs/base/common/event'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService } from 'vs/platform/files/common/files'; @@ -11,22 +11,9 @@ import { refineServiceDecorator } from 'vs/platform/instantiation/common/instant import { ILogService } from 'vs/platform/log/common/log'; import { IStateMainService } from 'vs/platform/state/electron-main/state'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; -import { IUserDataProfile, IUserDataProfilesService, PROFILES_ENABLEMENT_CONFIG, WorkspaceIdentifier, UseDefaultProfileFlags, toUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; -import { Promises } from 'vs/base/common/async'; -import { StoredProfileAssociations, StoredUserDataProfile, UserDataProfilesService } from 'vs/platform/userDataProfile/node/userDataProfile'; +import { IUserDataProfilesService, WorkspaceIdentifier, StoredUserDataProfile, StoredProfileAssociations, WillCreateProfileEvent, WillRemoveProfileEvent } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { UserDataProfilesService } from 'vs/platform/userDataProfile/node/userDataProfile'; import { IStringDictionary } from 'vs/base/common/collections'; -import { joinPath } from 'vs/base/common/resources'; -import { hash } from 'vs/base/common/hash'; - -export type WillCreateProfileEvent = { - profile: IUserDataProfile; - join(promise: Promise<void>): void; -}; - -export type WillRemoveProfileEvent = { - profile: IUserDataProfile; - join(promise: Promise<void>): void; -}; export const IUserDataProfilesMainService = refineServiceDecorator<IUserDataProfilesService, IUserDataProfilesMainService>(IUserDataProfilesService); export interface IUserDataProfilesMainService extends IUserDataProfilesService { @@ -37,12 +24,6 @@ export interface IUserDataProfilesMainService extends IUserDataProfilesService { export class UserDataProfilesMainService extends UserDataProfilesService implements IUserDataProfilesMainService { - private readonly _onWillCreateProfile = this._register(new Emitter<WillCreateProfileEvent>()); - readonly onWillCreateProfile = this._onWillCreateProfile.event; - - private readonly _onWillRemoveProfile = this._register(new Emitter<WillRemoveProfileEvent>()); - readonly onWillRemoveProfile = this._onWillRemoveProfile.event; - constructor( @IStateMainService private readonly stateMainService: IStateMainService, @IUriIdentityService uriIdentityService: IUriIdentityService, @@ -53,141 +34,12 @@ export class UserDataProfilesMainService extends UserDataProfilesService impleme super(stateMainService, uriIdentityService, environmentService, fileService, logService); } - override async createProfile(name: string, useDefaultFlags?: UseDefaultProfileFlags, workspaceIdentifier?: WorkspaceIdentifier): Promise<IUserDataProfile> { - if (!this.enabled) { - throw new Error(`Settings Profiles are disabled. Enable them via the '${PROFILES_ENABLEMENT_CONFIG}' setting.`); - } - if (this.getStoredProfiles().some(p => p.name === name)) { - throw new Error(`Profile with name ${name} already exists`); - } - - const profile = toUserDataProfile(name, joinPath(this.profilesHome, hash(name).toString(16)), useDefaultFlags); - await this.fileService.createFolder(profile.location); - - const joiners: Promise<void>[] = []; - this._onWillCreateProfile.fire({ - profile, - join(promise) { - joiners.push(promise); - } - }); - await Promises.settled(joiners); - - this.updateProfiles([profile], []); - - if (workspaceIdentifier) { - await this.setProfileForWorkspace(profile, workspaceIdentifier); - } - - return profile; - } - - override async setProfileForWorkspace(profileToSet: IUserDataProfile, workspaceIdentifier: WorkspaceIdentifier): Promise<void> { - if (!this.enabled) { - throw new Error(`Settings Profiles are disabled. Enable them via the '${PROFILES_ENABLEMENT_CONFIG}' setting.`); - } - - const profile = this.profiles.find(p => p.id === profileToSet.id); - if (!profile) { - throw new Error(`Profile '${profileToSet.name}' does not exist`); - } - - this.updateWorkspaceAssociation(workspaceIdentifier, profile); - } - - async unsetWorkspace(workspaceIdentifier: WorkspaceIdentifier): Promise<void> { - if (!this.enabled) { - throw new Error(`Settings Profiles are disabled. Enable them via the '${PROFILES_ENABLEMENT_CONFIG}' setting.`); - } - this.updateWorkspaceAssociation(workspaceIdentifier); - } - - override async removeProfile(profileToRemove: IUserDataProfile): Promise<void> { - if (!this.enabled) { - throw new Error(`Settings Profiles are disabled. Enable them via the '${PROFILES_ENABLEMENT_CONFIG}' setting.`); - } - if (profileToRemove.isDefault) { - throw new Error('Cannot remove default profile'); - } - const profile = this.profiles.find(p => p.id === profileToRemove.id); - if (!profile) { - throw new Error(`Profile '${profileToRemove.name}' does not exist`); - } - - const joiners: Promise<void>[] = []; - this._onWillRemoveProfile.fire({ - profile, - join(promise) { - joiners.push(promise); - } - }); - await Promises.settled(joiners); - - if (profile.id === this.profilesObject.emptyWindow?.id) { - this.profilesObject.emptyWindow = undefined; - } - for (const workspace of [...this.profilesObject.workspaces.keys()]) { - if (profile.id === this.profilesObject.workspaces.get(workspace)?.id) { - this.profilesObject.workspaces.delete(workspace); - } - } - this.saveStoredProfileAssociations(); - - this.updateProfiles([], [profile]); - - try { - if (this.profiles.length === 1) { - await this.fileService.del(this.profilesHome, { recursive: true }); - } else { - await this.fileService.del(profile.location, { recursive: true }); - } - } catch (error) { - this.logService.error(error); - } - } - - private updateProfiles(added: IUserDataProfile[], removed: IUserDataProfile[]) { - const storedProfiles: StoredUserDataProfile[] = []; - for (const profile of [...this.profilesObject.profiles, ...added]) { - if (profile.isDefault) { - continue; - } - if (removed.some(p => profile.id === p.id)) { - continue; - } - storedProfiles.push({ location: profile.location, name: profile.name, useDefaultFlags: profile.useDefaultFlags }); - } + protected override saveStoredProfiles(storedProfiles: StoredUserDataProfile[]): void { this.stateMainService.setItem(UserDataProfilesMainService.PROFILES_KEY, storedProfiles); - this._profilesObject = undefined; - this._onDidChangeProfiles.fire({ added, removed, all: this.profiles }); - } - - private updateWorkspaceAssociation(workspaceIdentifier: WorkspaceIdentifier, newProfile?: IUserDataProfile) { - const workspace = this.getWorkspace(workspaceIdentifier); - - // Folder or Multiroot workspace - if (URI.isUri(workspace)) { - this.profilesObject.workspaces.delete(workspace); - if (newProfile && !newProfile.isDefault) { - this.profilesObject.workspaces.set(workspace, newProfile); - } - } - // Empty Window - else { - this.profilesObject.emptyWindow = !newProfile?.isDefault ? newProfile : undefined; - } - - this.saveStoredProfileAssociations(); } - private saveStoredProfileAssociations() { - const workspaces: IStringDictionary<string> = {}; - for (const [workspace, profile] of this.profilesObject.workspaces.entries()) { - workspaces[workspace.toString()] = profile.location.toString(); - } - const emptyWindow = this.profilesObject.emptyWindow?.location.toString(); - this.stateMainService.setItem(UserDataProfilesMainService.PROFILE_ASSOCIATIONS_KEY, { workspaces, emptyWindow }); - this._profilesObject = undefined; + protected override saveStoredProfileAssociations(storedProfileAssociations: StoredProfileAssociations): void { + this.stateMainService.setItem(UserDataProfilesMainService.PROFILE_ASSOCIATIONS_KEY, storedProfileAssociations); } protected override getStoredProfileAssociations(): StoredProfileAssociations { diff --git a/src/vs/platform/userDataProfile/electron-sandbox/userDataProfile.ts b/src/vs/platform/userDataProfile/electron-sandbox/userDataProfile.ts index 1666539da3e..085e035b676 100644 --- a/src/vs/platform/userDataProfile/electron-sandbox/userDataProfile.ts +++ b/src/vs/platform/userDataProfile/electron-sandbox/userDataProfile.ts @@ -3,30 +3,40 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Emitter } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { joinPath } from 'vs/base/common/resources'; import { UriDto } from 'vs/base/common/types'; +import { URI } from 'vs/base/common/uri'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IFileService } from 'vs/platform/files/common/files'; import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services'; -import { ILogService } from 'vs/platform/log/common/log'; -import { DidChangeProfilesEvent, IUserDataProfile, IUserDataProfilesService, reviveProfile, UseDefaultProfileFlags, UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { DidChangeProfilesEvent, IUserDataProfile, IUserDataProfilesService, reviveProfile, UseDefaultProfileFlags, WorkspaceIdentifier } from 'vs/platform/userDataProfile/common/userDataProfile'; import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; -export class UserDataProfilesNativeService extends UserDataProfilesService implements IUserDataProfilesService { +export class UserDataProfilesNativeService extends Disposable implements IUserDataProfilesService { + + readonly _serviceBrand: undefined; private readonly channel: IChannel; - override get profiles(): IUserDataProfile[] { return this._profiles; } + readonly profilesHome: URI; + + get defaultProfile(): IUserDataProfile { return this.profiles[0]; } + private _profiles: IUserDataProfile[] = []; + get profiles(): IUserDataProfile[] { return this._profiles; } + + private readonly _onDidChangeProfiles = this._register(new Emitter<DidChangeProfilesEvent>()); + readonly onDidChangeProfiles = this._onDidChangeProfiles.event; constructor( profiles: UriDto<IUserDataProfile>[], @IMainProcessService mainProcessService: IMainProcessService, @IEnvironmentService environmentService: IEnvironmentService, - @IFileService fileService: IFileService, - @ILogService logService: ILogService, ) { - super(environmentService, fileService, logService); + super(); this.channel = mainProcessService.getChannel('userDataProfiles'); + this.profilesHome = joinPath(environmentService.userRoamingDataHome, 'profiles'); this._profiles = profiles.map(profile => reviveProfile(profile, this.profilesHome.scheme)); this._register(this.channel.listen<DidChangeProfilesEvent>('onDidChangeProfiles')(e => { const added = e.added.map(profile => reviveProfile(profile, this.profilesHome.scheme)); @@ -36,17 +46,19 @@ export class UserDataProfilesNativeService extends UserDataProfilesService imple })); } - override async createProfile(name: string, useDefaultFlags?: UseDefaultProfileFlags, workspaceIdentifier?: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): Promise<IUserDataProfile> { + async createProfile(name: string, useDefaultFlags?: UseDefaultProfileFlags, workspaceIdentifier?: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): Promise<IUserDataProfile> { const result = await this.channel.call<UriDto<IUserDataProfile>>('createProfile', [name, useDefaultFlags, workspaceIdentifier]); return reviveProfile(result, this.profilesHome.scheme); } - override async setProfileForWorkspace(profile: IUserDataProfile, workspaceIdentifier: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): Promise<void> { + async setProfileForWorkspace(profile: IUserDataProfile, workspaceIdentifier: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): Promise<void> { await this.channel.call<UriDto<IUserDataProfile>>('setProfileForWorkspace', [profile, workspaceIdentifier]); } - override removeProfile(profile: IUserDataProfile): Promise<void> { + removeProfile(profile: IUserDataProfile): Promise<void> { return this.channel.call('removeProfile', [profile]); } + + getProfile(workspaceIdentifier: WorkspaceIdentifier): IUserDataProfile { throw new Error('Not implemented'); } } diff --git a/src/vs/platform/userDataProfile/node/userDataProfile.ts b/src/vs/platform/userDataProfile/node/userDataProfile.ts index 34428dd9689..1405ca08e76 100644 --- a/src/vs/platform/userDataProfile/node/userDataProfile.ts +++ b/src/vs/platform/userDataProfile/node/userDataProfile.ts @@ -3,110 +3,32 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IStringDictionary } from 'vs/base/common/collections'; -import { ResourceMap } from 'vs/base/common/map'; import { revive } from 'vs/base/common/marshalling'; import { UriDto } from 'vs/base/common/types'; -import { URI } from 'vs/base/common/uri'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IFileService } from 'vs/platform/files/common/files'; import { ILogService } from 'vs/platform/log/common/log'; import { IStateService } from 'vs/platform/state/node/state'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; -import { UseDefaultProfileFlags, IUserDataProfile, IUserDataProfilesService, UserDataProfilesService as BaseUserDataProfilesService, toUserDataProfile, WorkspaceIdentifier, EmptyWindowWorkspaceIdentifier } from 'vs/platform/userDataProfile/common/userDataProfile'; -import { isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; - -export type UserDataProfilesObject = { - profiles: IUserDataProfile[]; - workspaces: ResourceMap<IUserDataProfile>; - emptyWindow?: IUserDataProfile; -}; - -export type StoredUserDataProfile = { - name: string; - location: URI; - useDefaultFlags?: UseDefaultProfileFlags; -}; - -export type StoredProfileAssociations = { - workspaces?: IStringDictionary<string>; - emptyWindow?: string; -}; +import { IUserDataProfilesService, UserDataProfilesService as BaseUserDataProfilesService, StoredUserDataProfile, StoredProfileAssociations } from 'vs/platform/userDataProfile/common/userDataProfile'; export class UserDataProfilesService extends BaseUserDataProfilesService implements IUserDataProfilesService { - protected static readonly PROFILES_KEY = 'userDataProfiles'; - protected static readonly PROFILE_ASSOCIATIONS_KEY = 'profileAssociations'; - - protected enabled: boolean = false; - constructor( @IStateService private readonly stateService: IStateService, - @IUriIdentityService protected readonly uriIdentityService: IUriIdentityService, + @IUriIdentityService uriIdentityService: IUriIdentityService, @IEnvironmentService environmentService: IEnvironmentService, @IFileService fileService: IFileService, @ILogService logService: ILogService, ) { - super(environmentService, fileService, logService); - } - - setEnablement(enabled: boolean): void { - this._profilesObject = undefined; - this.enabled = enabled; - } - - protected _profilesObject: UserDataProfilesObject | undefined; - protected get profilesObject(): UserDataProfilesObject { - if (!this._profilesObject) { - const profiles = this.enabled ? this.getStoredProfiles().map<IUserDataProfile>(storedProfile => toUserDataProfile(storedProfile.name, storedProfile.location, storedProfile.useDefaultFlags)) : []; - let emptyWindow: IUserDataProfile | undefined; - const workspaces = new ResourceMap<IUserDataProfile>(); - if (profiles.length) { - const profileAssicaitions = this.getStoredProfileAssociations(); - if (profileAssicaitions.workspaces) { - for (const [workspacePath, profilePath] of Object.entries(profileAssicaitions.workspaces)) { - const workspace = URI.parse(workspacePath); - const profileLocation = URI.parse(profilePath); - const profile = profiles.find(p => this.uriIdentityService.extUri.isEqual(p.location, profileLocation)); - if (profile) { - workspaces.set(workspace, profile); - } - } - } - if (profileAssicaitions.emptyWindow) { - const emptyWindowProfileLocation = URI.parse(profileAssicaitions.emptyWindow); - emptyWindow = profiles.find(p => this.uriIdentityService.extUri.isEqual(p.location, emptyWindowProfileLocation)); - } - } - profiles.unshift(this.createDefaultUserDataProfile(profiles.length > 0)); - this._profilesObject = { profiles, workspaces, emptyWindow }; - } - return this._profilesObject; - } - - override get profiles(): IUserDataProfile[] { return this.profilesObject.profiles; } - - override getProfile(workspaceIdentifier: WorkspaceIdentifier): IUserDataProfile { - const workspace = this.getWorkspace(workspaceIdentifier); - const profile = URI.isUri(workspace) ? this.profilesObject.workspaces.get(workspace) : this.profilesObject.emptyWindow; - return profile ?? this.defaultProfile; - } - - protected getWorkspace(workspaceIdentifier: WorkspaceIdentifier): URI | EmptyWindowWorkspaceIdentifier { - if (isSingleFolderWorkspaceIdentifier(workspaceIdentifier)) { - return workspaceIdentifier.uri; - } - if (isWorkspaceIdentifier(workspaceIdentifier)) { - return workspaceIdentifier.configPath; - } - return 'empty-window'; + super(environmentService, fileService, uriIdentityService, logService); } - protected getStoredProfiles(): StoredUserDataProfile[] { + protected override getStoredProfiles(): StoredUserDataProfile[] { return revive(this.stateService.getItem<UriDto<StoredUserDataProfile>[]>(UserDataProfilesService.PROFILES_KEY, [])); } - protected getStoredProfileAssociations(): StoredProfileAssociations { + protected override getStoredProfileAssociations(): StoredProfileAssociations { return revive(this.stateService.getItem<UriDto<StoredProfileAssociations>>(UserDataProfilesService.PROFILE_ASSOCIATIONS_KEY, {})); } diff --git a/src/vs/platform/userDataProfile/test/common/userDataProfileService.test.ts b/src/vs/platform/userDataProfile/test/common/userDataProfileService.test.ts index 549c686644c..662a70fe033 100644 --- a/src/vs/platform/userDataProfile/test/common/userDataProfileService.test.ts +++ b/src/vs/platform/userDataProfile/test/common/userDataProfileService.test.ts @@ -14,6 +14,7 @@ import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFil import { AbstractNativeEnvironmentService } from 'vs/platform/environment/common/environmentService'; import product from 'vs/platform/product/common/product'; import { UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; const ROOT = URI.file('tests').with({ scheme: 'vscode-tests' }); @@ -37,7 +38,7 @@ suite('UserDataProfileService (Common)', () => { disposables.add(fileService.registerProvider(ROOT.scheme, fileSystemProvider)); environmentService = new TestEnvironmentService(joinPath(ROOT, 'User')); - testObject = new UserDataProfilesService(environmentService, fileService, logService); + testObject = new UserDataProfilesService(environmentService, fileService, new UriIdentityService(fileService), logService); }); teardown(() => disposables.clear()); diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts index ec0a973bf60..6973141914f 100644 --- a/src/vs/platform/userDataSync/common/userDataSync.ts +++ b/src/vs/platform/userDataSync/common/userDataSync.ts @@ -184,7 +184,7 @@ export interface IUserDataSyncStoreClient { delete(resource: ServerResource, ref: string | null): Promise<void>; getAllRefs(resource: ServerResource): Promise<IResourceRefHandle[]>; - resolveContent(resource: ServerResource, ref: string): Promise<string | null>; + resolveContent(resource: ServerResource, ref: string, headers?: IHeaders): Promise<string | null>; } export const IUserDataSyncStoreService = createDecorator<IUserDataSyncStoreService>('IUserDataSyncStoreService'); diff --git a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts index 427babbd4f6..2b7bd79e2b8 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts @@ -244,13 +244,13 @@ export class UserDataSyncStoreClient extends Disposable implements IUserDataSync return result.map(({ url, created }) => ({ ref: relativePath(uri, uri.with({ path: url }))!, created: created * 1000 /* Server returns in seconds */ })); } - async resolveContent(resource: ServerResource, ref: string): Promise<string | null> { + async resolveContent(resource: ServerResource, ref: string, headers: IHeaders = {}): Promise<string | null> { if (!this.userDataSyncStoreUrl) { throw new Error('No settings sync store url configured.'); } const url = joinPath(this.userDataSyncStoreUrl, 'resource', resource, ref).toString(); - const headers: IHeaders = {}; + headers = { ...headers }; headers['Cache-Control'] = 'no-cache'; const context = await this.request(url, { type: 'GET', headers }, [], CancellationToken.None); diff --git a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts index 645181d8a3c..2252b873b35 100644 --- a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts +++ b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts @@ -83,14 +83,16 @@ export class UserDataSyncClient extends Disposable { fileService.registerProvider(Schemas.inMemory, new InMemoryFileSystemProvider()); this.instantiationService.stub(IFileService, fileService); - const userDataProfilesService = this.instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, logService)); + const uriIdentityService = this.instantiationService.createInstance(UriIdentityService); + this.instantiationService.stub(IUriIdentityService, uriIdentityService); + + const userDataProfilesService = this.instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService)); this.instantiationService.stub(IStorageService, this._register(new InMemoryStorageService())); const configurationService = this._register(new ConfigurationService(userDataProfilesService.defaultProfile.settingsResource, fileService, new NullPolicyService(), logService)); await configurationService.initialize(); this.instantiationService.stub(IConfigurationService, configurationService); - this.instantiationService.stub(IUriIdentityService, this.instantiationService.createInstance(UriIdentityService)); this.instantiationService.stub(IRequestService, this.testServer); diff --git a/src/vs/server/node/extensionsScannerService.ts b/src/vs/server/node/extensionsScannerService.ts index 5f2be934b4b..b780ab7d6e8 100644 --- a/src/vs/server/node/extensionsScannerService.ts +++ b/src/vs/server/node/extensionsScannerService.ts @@ -10,6 +10,7 @@ import { IExtensionsProfileScannerService } from 'vs/platform/extensionManagemen import { AbstractExtensionsScannerService, IExtensionsScannerService, Translations } from 'vs/platform/extensionManagement/common/extensionsScannerService'; import { MANIFEST_CACHE_FOLDER } from 'vs/platform/extensions/common/extensions'; import { IFileService } from 'vs/platform/files/common/files'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; import { IProductService } from 'vs/platform/product/common/productService'; import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; @@ -24,13 +25,14 @@ export class ExtensionsScannerService extends AbstractExtensionsScannerService i @ILogService logService: ILogService, @INativeEnvironmentService private readonly nativeEnvironmentService: INativeEnvironmentService, @IProductService productService: IProductService, + @IInstantiationService instantiationService: IInstantiationService, ) { super( URI.file(nativeEnvironmentService.builtinExtensionsPath), URI.file(nativeEnvironmentService.extensionsPath), joinPath(nativeEnvironmentService.userHome, '.vscode-oss-dev', 'extensions', 'control.json'), joinPath(URI.file(nativeEnvironmentService.userDataPath), MANIFEST_CACHE_FOLDER), - userDataProfilesService, extensionsProfileScannerService, fileService, logService, nativeEnvironmentService, productService); + userDataProfilesService, extensionsProfileScannerService, fileService, logService, nativeEnvironmentService, productService, instantiationService); } protected async getTranslations(language: string): Promise<Translations> { diff --git a/src/vs/server/node/remoteExtensionHostAgentCli.ts b/src/vs/server/node/remoteExtensionHostAgentCli.ts index 653748724e0..52647624f79 100644 --- a/src/vs/server/node/remoteExtensionHostAgentCli.ts +++ b/src/vs/server/node/remoteExtensionHostAgentCli.ts @@ -93,8 +93,11 @@ class CliMain extends Disposable { services.set(IFileService, fileService); fileService.registerProvider(Schemas.file, this._register(new DiskFileSystemProvider(logService))); + const uriIdentityService = new UriIdentityService(fileService); + services.set(IUriIdentityService, uriIdentityService); + // User Data Profiles - const userDataProfilesService = this._register(new UserDataProfilesService(environmentService, fileService, logService)); + const userDataProfilesService = this._register(new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService)); services.set(IUserDataProfilesService, userDataProfilesService); // Configuration @@ -102,7 +105,6 @@ class CliMain extends Disposable { await configurationService.initialize(); services.set(IConfigurationService, configurationService); - services.set(IUriIdentityService, new UriIdentityService(fileService)); services.set(IRequestService, new SyncDescriptor(RequestService)); services.set(IDownloadService, new SyncDescriptor(DownloadService)); services.set(ITelemetryService, NullTelemetryService); diff --git a/src/vs/server/node/serverServices.ts b/src/vs/server/node/serverServices.ts index c20bd776c55..ab5f5cb7d57 100644 --- a/src/vs/server/node/serverServices.ts +++ b/src/vs/server/node/serverServices.ts @@ -110,8 +110,12 @@ export async function setupServerServices(connectionToken: ServerConnectionToken services.set(IFileService, fileService); fileService.registerProvider(Schemas.file, disposables.add(new DiskFileSystemProvider(logService))); + // URI Identity + const uriIdentityService = new UriIdentityService(fileService); + services.set(IUriIdentityService, uriIdentityService); + // User Data Profiles - const userDataProfilesService = new UserDataProfilesService(environmentService, fileService, logService); + const userDataProfilesService = new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService); services.set(IUserDataProfilesService, userDataProfilesService); // Configuration @@ -122,9 +126,6 @@ export async function setupServerServices(connectionToken: ServerConnectionToken const extensionHostStatusService = new ExtensionHostStatusService(); services.set(IExtensionHostStatusService, extensionHostStatusService); - // URI Identity - services.set(IUriIdentityService, new UriIdentityService(fileService)); - // Request services.set(IRequestService, new SyncDescriptor(RequestService)); diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index e9b7f4fc8b0..d0791572ccf 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -22,7 +22,7 @@ import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteA import { IWorkbenchFileService } from 'vs/workbench/services/files/common/files'; import { FileService } from 'vs/platform/files/common/fileService'; import { Schemas, connectionTokenCookieName } from 'vs/base/common/network'; -import { IAnyWorkspaceIdentifier, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IAnyWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; import { onUnexpectedError } from 'vs/base/common/errors'; import { setFullscreen } from 'vs/base/browser/browser'; @@ -73,13 +73,14 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IProgressService } from 'vs/platform/progress/common/progress'; import { DelayedLogChannel } from 'vs/workbench/services/output/common/delayedLogChannel'; import { dirname, joinPath } from 'vs/base/common/resources'; -import { IUserDataProfilesService, UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IUserDataProfilesService, PROFILES_ENABLEMENT_CONFIG } from 'vs/platform/userDataProfile/common/userDataProfile'; import { NullPolicyService } from 'vs/platform/policy/common/policy'; import { IRemoteExplorerService, TunnelSource } from 'vs/workbench/services/remote/common/remoteExplorerService'; import { DisposableTunnel } from 'vs/platform/tunnel/common/tunnel'; import { ILabelService } from 'vs/platform/label/common/label'; import { UserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfileService'; import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { BrowserUserDataProfilesService } from 'vs/platform/userDataProfile/browser/userDataProfile'; export class BrowserMain extends Disposable { @@ -259,17 +260,16 @@ export class BrowserMain extends Disposable { serviceCollection.set(IWorkbenchFileService, fileService); await this.registerFileSystemProviders(environmentService, fileService, remoteAgentService, logService, logsPath); - // User Data Profiles - const userDataProfilesService = new UserDataProfilesService(environmentService, fileService, logService); - serviceCollection.set(IUserDataProfilesService, userDataProfilesService); - - const userDataProfileService = new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService); - serviceCollection.set(IUserDataProfileService, userDataProfileService); - // URI Identity const uriIdentityService = new UriIdentityService(fileService); serviceCollection.set(IUriIdentityService, uriIdentityService); + // User Data Profiles + const userDataProfilesService = new BrowserUserDataProfilesService(environmentService, fileService, uriIdentityService, logService); + serviceCollection.set(IUserDataProfilesService, userDataProfilesService); + const userDataProfileService = new UserDataProfileService(userDataProfilesService.getProfile(isWorkspaceIdentifier(payload) || isSingleFolderWorkspaceIdentifier(payload) ? payload : 'empty-window'), userDataProfilesService); + serviceCollection.set(IUserDataProfileService, userDataProfileService); + // Long running services (workspace, config, storage) const [configurationService, storageService] = await Promise.all([ this.createWorkspaceService(payload, environmentService, userDataProfileService, userDataProfilesService, fileService, remoteAgentService, uriIdentityService, logService).then(service => { @@ -292,6 +292,7 @@ export class BrowserMain extends Disposable { }) ]); + userDataProfilesService.setEnablement(!!configurationService.getValue(PROFILES_ENABLEMENT_CONFIG)); // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // diff --git a/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts b/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts index deb0cb486bc..12f620df779 100644 --- a/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts +++ b/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts @@ -85,7 +85,7 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo super(); if (this.environmentService.editSessionId !== undefined) { - void this.applyEditSession(this.environmentService.editSessionId).finally(() => this.environmentService.editSessionId = undefined); + void this.resumeEditSession(this.environmentService.editSessionId).finally(() => this.environmentService.editSessionId = undefined); } this.configurationService.onDidChangeConfiguration((e) => { @@ -132,7 +132,7 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo this.registerContinueEditSessionAction(); - this.registerApplyLatestEditSessionAction(); + this.registerResumeLatestEditSessionAction(); this.registerStoreLatestEditSessionAction(); this.registerContinueInLocalFolderAction(); @@ -171,9 +171,9 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo })); } - private registerApplyLatestEditSessionAction(): void { + private registerResumeLatestEditSessionAction(): void { const that = this; - this._register(registerAction2(class ApplyLatestEditSessionAction extends Action2 { + this._register(registerAction2(class ResumeLatestEditSessionAction extends Action2 { constructor() { super({ id: 'workbench.experimental.editSessions.actions.resumeLatest', @@ -186,8 +186,8 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo async run(accessor: ServicesAccessor): Promise<void> { await that.progressService.withProgress({ location: ProgressLocation.Notification, - title: localize('applying edit session', 'Applying edit session...') - }, async () => await that.applyEditSession()); + title: localize('resuming edit session', 'Resuming edit session...') + }, async () => await that.resumeEditSession()); } })); } @@ -213,26 +213,24 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo })); } - async applyEditSession(ref?: string): Promise<void> { - if (ref !== undefined) { - this.logService.info(`Applying edit session with ref ${ref}.`); - } + async resumeEditSession(ref?: string): Promise<void> { + this.logService.info(ref !== undefined ? `Resuming edit session with ref ${ref}...` : 'Resuming edit session...'); const data = await this.editSessionsWorkbenchService.read(ref); if (!data) { if (ref === undefined) { - this.notificationService.info(localize('no edit session', 'There are no edit sessions to apply.')); + this.notificationService.info(localize('no edit session', 'There are no edit sessions to resume.')); } else { - this.notificationService.warn(localize('no edit session content for ref', 'Could not apply edit session contents for ID {0}.', ref)); + this.notificationService.warn(localize('no edit session content for ref', 'Could not resume edit session contents for ID {0}.', ref)); } - this.logService.info(`Aborting applying edit session as no edit session content is available to be applied from ref ${ref}.`); + this.logService.info(`Aborting resuming edit session as no edit session content is available to be applied from ref ${ref}.`); return; } const editSession = data.editSession; ref = data.ref; if (editSession.version > EditSessionSchemaVersion) { - this.notificationService.error(localize('client too old', "Please upgrade to a newer version of {0} to apply this edit session.", this.productService.nameLong)); + this.notificationService.error(localize('client too old', "Please upgrade to a newer version of {0} to resume this edit session.", this.productService.nameLong)); return; } @@ -266,7 +264,7 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo if (hasLocalUncommittedChanges) { // TODO@joyceerhl Provide the option to diff files which would be overwritten by edit session contents const result = await this.dialogService.confirm({ - message: localize('apply edit session warning', 'Applying your edit session may overwrite your existing uncommitted changes. Do you want to proceed?'), + message: localize('resume edit session warning', 'Resuming your edit session may overwrite your existing uncommitted changes. Do you want to proceed?'), type: 'warning', title: EDIT_SESSION_SYNC_CATEGORY.value }); @@ -287,8 +285,8 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo await this.editSessionsWorkbenchService.delete(ref); this.logService.info(`Deleted edit session with ref ${ref}.`); } catch (ex) { - this.logService.error('Failed to apply edit session, reason: ', (ex as Error).toString()); - this.notificationService.error(localize('apply failed', "Failed to apply your edit session.")); + this.logService.error('Failed to resume edit session, reason: ', (ex as Error).toString()); + this.notificationService.error(localize('resume failed', "Failed to resume your edit session.")); } } diff --git a/src/vs/workbench/contrib/editSessions/browser/editSessionsWorkbenchService.ts b/src/vs/workbench/contrib/editSessions/browser/editSessionsWorkbenchService.ts index af25b5ee15a..b63b550ab35 100644 --- a/src/vs/workbench/contrib/editSessions/browser/editSessionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/editSessions/browser/editSessionsWorkbenchService.ts @@ -14,11 +14,13 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { IRequestService } from 'vs/platform/request/common/request'; import { IStorageService, IStorageValueChangeEvent, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; -import { IAuthenticationProvider } from 'vs/platform/userDataSync/common/userDataSync'; +import { createSyncHeaders, IAuthenticationProvider } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataSyncStoreClient } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; import { AuthenticationSession, AuthenticationSessionsChangeEvent, IAuthenticationService } from 'vs/workbench/services/authentication/common/authentication'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { EDIT_SESSIONS_SIGNED_IN, EditSession, EDIT_SESSION_SYNC_CATEGORY, IEditSessionsWorkbenchService, EDIT_SESSIONS_SIGNED_IN_KEY, IEditSessionsLogService } from 'vs/workbench/contrib/editSessions/common/editSessions'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { generateUuid } from 'vs/base/common/uuid'; type ExistingSession = IQuickPickItem & { session: AuthenticationSession & { providerId: string } }; type AuthenticationProviderOption = IQuickPickItem & { provider: IAuthenticationProvider }; @@ -47,6 +49,7 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes @IProductService private readonly productService: IProductService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IRequestService private readonly requestService: IRequestService, + @IDialogService private readonly dialogService: IDialogService, ) { super(); @@ -73,7 +76,7 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes throw new Error('Please sign in to store your edit session.'); } - return this.storeClient!.write('editSessions', JSON.stringify(editSession), null); + return this.storeClient!.write('editSessions', JSON.stringify(editSession), null, createSyncHeaders(generateUuid())); } /** @@ -89,11 +92,12 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes } let content: string | undefined | null; + const headers = createSyncHeaders(generateUuid()); try { if (ref !== undefined) { - content = await this.storeClient?.resolveContent('editSessions', ref); + content = await this.storeClient?.resolveContent('editSessions', ref, headers); } else { - const result = await this.storeClient?.read('editSessions', null); + const result = await this.storeClient?.read('editSessions', null, headers); content = result?.content; ref = result?.ref; } @@ -160,7 +164,7 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes const existing = await this.getExistingSession(); if (existing !== undefined) { this.logService.trace(`Found existing authentication session with ID ${existingSessionId}`); - this.#authenticationInfo = { sessionId: existing.session.id, token: existing.session.accessToken, providerId: existing.session.providerId }; + this.#authenticationInfo = { sessionId: existing.session.id, token: existing.session.idToken ?? existing.session.accessToken, providerId: existing.session.providerId }; this.storeClient.setAuthToken(this.#authenticationInfo.token, this.#authenticationInfo.providerId); return true; } @@ -169,7 +173,7 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes // Ask the user to pick a preferred account const session = await this.getAccountPreference(); if (session !== undefined) { - this.#authenticationInfo = { sessionId: session.id, token: session.accessToken, providerId: session.providerId }; + this.#authenticationInfo = { sessionId: session.id, token: session.idToken ?? session.accessToken, providerId: session.providerId }; this.storeClient.setAuthToken(this.#authenticationInfo.token, this.#authenticationInfo.providerId); this.existingSessionId = session.id; this.logService.trace(`Saving authentication session preference for ID ${session.id}.`); @@ -350,8 +354,19 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes }); } - run() { - that.clearAuthenticationPreference(); + async run() { + const result = await that.dialogService.confirm({ + type: 'info', + message: localize('sign out of edit sessions clear data prompt', 'Do you want to sign out of edit sessions?'), + checkbox: { label: localize('delete all edit sessions', 'Delete all stored edit sessions from the cloud.') }, + primaryButton: localize('clear data confirm', 'Yes'), + }); + if (result.confirmed) { + if (result.checkboxChecked) { + that.storeClient?.delete('editSessions', null); + } + that.clearAuthenticationPreference(); + } } })); } diff --git a/src/vs/workbench/contrib/editSessions/test/browser/editSessions.test.ts b/src/vs/workbench/contrib/editSessions/test/browser/editSessions.test.ts index b7caca6f077..07917f285e5 100644 --- a/src/vs/workbench/contrib/editSessions/test/browser/editSessions.test.ts +++ b/src/vs/workbench/contrib/editSessions/test/browser/editSessions.test.ts @@ -112,8 +112,8 @@ suite('Edit session sync', () => { // Create root folder await fileService.createFolder(folderUri); - // Apply edit session - await editSessionsContribution.applyEditSession(); + // Resume edit session + await editSessionsContribution.resumeEditSession(); // Verify edit session was correctly applied assert.equal((await fileService.readFile(fileUri)).value.toString(), fileContents); diff --git a/src/vs/workbench/contrib/markers/browser/markersTable.ts b/src/vs/workbench/contrib/markers/browser/markersTable.ts index a4938d08d58..4d39773daed 100644 --- a/src/vs/workbench/contrib/markers/browser/markersTable.ts +++ b/src/vs/workbench/contrib/markers/browser/markersTable.ts @@ -135,15 +135,17 @@ class MarkerCodeColumnRenderer implements ITableRenderer<MarkerTableItem, IMarke } renderElement(element: MarkerTableItem, index: number, templateData: IMarkerCodeColumnTemplateData, height: number | undefined): void { - if (element.marker.source && element.marker.code) { - templateData.codeColumn.classList.toggle('code-link', typeof element.marker.code !== 'string'); - DOM.show(templateData.codeLabel.element); + templateData.codeColumn.classList.remove('code-label'); + templateData.codeColumn.classList.remove('code-link'); + if (element.marker.source && element.marker.code) { if (typeof element.marker.code === 'string') { + templateData.codeColumn.classList.add('code-label'); templateData.codeColumn.title = `${element.marker.source} (${element.marker.code})`; templateData.sourceLabel.set(element.marker.source, element.sourceMatches); templateData.codeLabel.set(element.marker.code, element.codeMatches); } else { + templateData.codeColumn.classList.add('code-link'); templateData.codeColumn.title = `${element.marker.source} (${element.marker.code.value})`; templateData.sourceLabel.set(element.marker.source, element.sourceMatches); @@ -159,7 +161,6 @@ class MarkerCodeColumnRenderer implements ITableRenderer<MarkerTableItem, IMarke } else { templateData.codeColumn.title = ''; templateData.sourceLabel.set('-'); - DOM.hide(templateData.codeLabel.element); } } diff --git a/src/vs/workbench/contrib/markers/browser/media/markers.css b/src/vs/workbench/contrib/markers/browser/media/markers.css index 4ce704bb880..4a47972350e 100644 --- a/src/vs/workbench/contrib/markers/browser/media/markers.css +++ b/src/vs/workbench/contrib/markers/browser/media/markers.css @@ -279,17 +279,18 @@ content: ')'; } -.markers-panel .markers-table-container .monaco-table .monaco-list-row .monaco-table-tr > .monaco-table-td > .code.code-link > .code-label { +.markers-panel .markers-table-container .monaco-table .monaco-list-row .monaco-table-tr > .monaco-table-td > .code > .code-label, +.markers-panel .markers-table-container .monaco-table .monaco-list-row .monaco-table-tr > .monaco-table-td > .code > .monaco-link { display: none; } -.markers-panel .markers-table-container .monaco-table .monaco-list-row .monaco-table-tr > .monaco-table-td > .code.code-link > .monaco-link { +.markers-panel .markers-table-container .monaco-table .monaco-list-row .monaco-table-tr > .monaco-table-td > .code.code-label > .code-label { display: inline; - text-decoration: underline; } -.markers-panel .markers-table-container .monaco-table .monaco-list-row .monaco-table-tr > .monaco-table-td > .code > .monaco-link { - display: none; +.markers-panel .markers-table-container .monaco-table .monaco-list-row .monaco-table-tr > .monaco-table-td > .code.code-link > .monaco-link { + display: inline; + text-decoration: underline; } .markers-panel .markers-table-container .monaco-table .monaco-list-row .monaco-table-tr > .monaco-table-td > .file > .file-position { diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts index ed23a5f2632..a75b4184bd2 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts @@ -41,6 +41,8 @@ import { Disposable } from 'vs/base/common/lifecycle'; const OpenInEditorCommandId = 'search.action.openInEditor'; const OpenNewEditorToSideCommandId = 'search.action.openNewEditorToSide'; const FocusQueryEditorWidgetCommandId = 'search.action.focusQueryEditorWidget'; +const FocusQueryEditorFilesToIncludeCommandId = 'search.action.focusFilesToInclude'; +const FocusQueryEditorFilesToExcludeCommandId = 'search.action.focusFilesToExclude'; const ToggleSearchEditorCaseSensitiveCommandId = 'toggleSearchEditorCaseSensitive'; const ToggleSearchEditorWholeWordCommandId = 'toggleSearchEditorWholeWord'; @@ -377,6 +379,44 @@ registerAction2(class extends Action2 { registerAction2(class extends Action2 { constructor() { super({ + id: FocusQueryEditorFilesToIncludeCommandId, + title: { value: localize('search.action.focusFilesToInclude', "Focus Search Editor Files to Include"), original: 'Focus Search Editor Files to Include' }, + category, + f1: true, + precondition: SearchEditorConstants.InSearchEditor, + }); + } + async run(accessor: ServicesAccessor) { + const editorService = accessor.get(IEditorService); + const input = editorService.activeEditor; + if (input instanceof SearchEditorInput) { + (editorService.activeEditorPane as SearchEditor).focusFilesToIncludeInput(); + } + } +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: FocusQueryEditorFilesToExcludeCommandId, + title: { value: localize('search.action.focusFilesToExclude', "Focus Search Editor Files to Exclude"), original: 'Focus Search Editor Files to Exclude' }, + category, + f1: true, + precondition: SearchEditorConstants.InSearchEditor, + }); + } + async run(accessor: ServicesAccessor) { + const editorService = accessor.get(IEditorService); + const input = editorService.activeEditor; + if (input instanceof SearchEditorInput) { + (editorService.activeEditorPane as SearchEditor).focusFilesToExcludeInput(); + } + } +}); + +registerAction2(class extends Action2 { + constructor() { + super({ id: ToggleSearchEditorCaseSensitiveCommandId, title: { value: localize('searchEditor.action.toggleSearchEditorCaseSensitive', "Toggle Match Case"), original: 'Toggle Match Case' }, category, diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts index 380f8dbcc8f..54be4156b6e 100644 --- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts +++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts @@ -275,6 +275,20 @@ export class SearchEditor extends AbstractTextCodeEditor<SearchEditorViewState> this.queryEditorWidget.searchInput.focus(); } + focusFilesToIncludeInput() { + if (!this.showingIncludesExcludes) { + this.toggleIncludesExcludes(true); + } + this.inputPatternIncludes.focus(); + } + + focusFilesToExcludeInput() { + if (!this.showingIncludesExcludes) { + this.toggleIncludesExcludes(true); + } + this.inputPatternExcludes.focus(); + } + focusNextInput() { if (this.queryEditorWidget.searchInputHasFocus()) { if (this.showingIncludesExcludes) { diff --git a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh index 88498fbfb9a..a2f4afa1400 100755 --- a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh +++ b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh @@ -110,23 +110,22 @@ __vsc_precmd() { } __vsc_preexec() { - if [ "$__vsc_in_command_execution" = "0" ]; then - __vsc_initialized=1 - __vsc_in_command_execution="1" - if [[ ! "$BASH_COMMAND" =~ ^__vsc_prompt* ]]; then - __vsc_current_command=$BASH_COMMAND - else - __vsc_current_command="" - fi - __vsc_command_output_start + __vsc_initialized=1 + if [[ ! "$BASH_COMMAND" =~ ^__vsc_prompt* ]]; then + __vsc_current_command=$BASH_COMMAND + else + __vsc_current_command="" fi + __vsc_command_output_start } # Debug trapping/preexec inspired by starship (ISC) if [[ -n "${bash_preexec_imported:-}" ]]; then __vsc_preexec_only() { - __vsc_status="$?" - __vsc_preexec + if [ "$__vsc_in_command_execution" = "0" ]; then + __vsc_in_command_execution="1" + __vsc_preexec + fi } precmd_functions+=(__vsc_prompt_cmd) preexec_functions+=(__vsc_preexec_only) @@ -134,15 +133,19 @@ else __vsc_dbg_trap="$(trap -p DEBUG | cut -d' ' -f3 | tr -d \')" if [[ -z "$__vsc_dbg_trap" ]]; then __vsc_preexec_only() { - __vsc_status="$?" - __vsc_preexec + if [ "$__vsc_in_command_execution" = "0" ]; then + __vsc_in_command_execution="1" + __vsc_preexec + fi } trap '__vsc_preexec_only "$_"' DEBUG elif [[ "$__vsc_dbg_trap" != '__vsc_preexec "$_"' && "$__vsc_dbg_trap" != '__vsc_preexec_all "$_"' ]]; then __vsc_preexec_all() { - __vsc_status="$?" - builtin eval ${__vsc_dbg_trap} - __vsc_preexec + if [ "$__vsc_in_command_execution" = "0" ]; then + __vsc_in_command_execution="1" + builtin eval ${__vsc_dbg_trap} + __vsc_preexec + fi } trap '__vsc_preexec_all "$_"' DEBUG fi @@ -151,6 +154,7 @@ fi __vsc_update_prompt __vsc_prompt_cmd_original() { + __vsc_status="$?" if [[ ${IFS+set} ]]; then __vsc_original_ifs="$IFS" fi diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index e4187f6ab3c..b791e1e8472 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -47,7 +47,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ITerminalCommand, TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities'; import { TerminalCapabilityStoreMultiplexer } from 'vs/platform/terminal/common/capabilities/terminalCapabilityStore'; import { IProcessDataEvent, IProcessPropertyMap, IShellLaunchConfig, ITerminalDimensionsOverride, ITerminalLaunchError, PosixShellType, ProcessPropertyType, TerminalIcon, TerminalLocation, TerminalSettingId, TerminalShellType, TitleEventSource, WindowsShellType } from 'vs/platform/terminal/common/terminal'; -import { escapeNonWindowsPath } from 'vs/platform/terminal/common/terminalEnvironment'; +import { escapeNonWindowsPath, collapseTildePath } from 'vs/platform/terminal/common/terminalEnvironment'; import { activeContrastBorder, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground } from 'vs/platform/theme/common/colorRegistry'; import { IColorTheme, ICssStyleCollector, IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; @@ -355,6 +355,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { private readonly _terminalHasFixedWidth: IContextKey<boolean>, private readonly _terminalShellTypeContextKey: IContextKey<string>, private readonly _terminalAltBufferActiveContextKey: IContextKey<boolean>, + private readonly _terminalInRunCommandPicker: IContextKey<boolean>, private readonly _configHelper: TerminalConfigHelper, private _shellLaunchConfig: IShellLaunchConfig, resource: URI | undefined, @@ -851,7 +852,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { if (label.length === 0 || commandMap.has(label)) { continue; } - let description = `${entry.cwd}`; + let description = collapseTildePath(entry.cwd, this._userHome, this._processManager?.os === OperatingSystem.Windows ? '\\' : '/'); if (entry.exitCode) { // Since you cannot get the last command's exit code on pwsh, just whether it failed // or not, -1 is treated specially as simply failed @@ -1024,7 +1025,11 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } return new Promise<void>(r => { quickPick.show(); - quickPick.onDidHide(() => r()); + this._terminalInRunCommandPicker.set(true); + quickPick.onDidHide(() => { + this._terminalInRunCommandPicker.set(false); + r(); + }); }); } @@ -1455,9 +1460,15 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } async sendText(text: string, addNewLine: boolean): Promise<void> { + // Apply bracketed paste sequences if the terminal has the mode enabled, this will prevent + // the text from triggering keybindings https://github.com/microsoft/vscode/issues/153592 + if (this.xterm?.raw.modes.bracketedPasteMode) { + text = `\x1b[200~${text}\x1b[201~`; + } + // Normalize line endings to 'enter' press. text = text.replace(/\r?\n/g, '\r'); - if (addNewLine && text.substr(text.length - 1) !== '\r') { + if (addNewLine && text[text.length - 1] !== '\r') { text += '\r'; } @@ -1698,7 +1709,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { const parsedExitResult = parseExitResult(exitCodeOrError, this.shellLaunchConfig, this._processManager.processState, this._initialCwd); - if (this._usedShellIntegrationInjection && (this._processManager.processState === ProcessState.KilledDuringLaunch || this._processManager.processState === ProcessState.KilledByProcess)) { + if (this._usedShellIntegrationInjection && this._processManager.processState === ProcessState.KilledDuringLaunch && parsedExitResult?.code !== 0) { this._relaunchWithShellIntegrationDisabled(parsedExitResult?.message); this._onExit.fire(exitCodeOrError); return; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts index c9da9557b27..a7732ab7c62 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts @@ -23,6 +23,7 @@ export class TerminalInstanceService extends Disposable implements ITerminalInst private _terminalHasFixedWidth: IContextKey<boolean>; private _terminalShellTypeContextKey: IContextKey<string>; private _terminalAltBufferActiveContextKey: IContextKey<boolean>; + private _terminalInRunCommandPicker: IContextKey<boolean>; private _configHelper: TerminalConfigHelper; private readonly _onDidCreateInstance = new Emitter<ITerminalInstance>(); @@ -37,6 +38,7 @@ export class TerminalInstanceService extends Disposable implements ITerminalInst this._terminalHasFixedWidth = TerminalContextKeys.terminalHasFixedWidth.bindTo(this._contextKeyService); this._terminalShellTypeContextKey = TerminalContextKeys.shellType.bindTo(this._contextKeyService); this._terminalAltBufferActiveContextKey = TerminalContextKeys.altBufferActive.bindTo(this._contextKeyService); + this._terminalInRunCommandPicker = TerminalContextKeys.inTerminalRunCommandPicker.bindTo(this._contextKeyService); this._configHelper = _instantiationService.createInstance(TerminalConfigHelper); } @@ -49,6 +51,7 @@ export class TerminalInstanceService extends Disposable implements ITerminalInst this._terminalHasFixedWidth, this._terminalShellTypeContextKey, this._terminalAltBufferActiveContextKey, + this._terminalInRunCommandPicker, this._configHelper, shellLaunchConfig, resource diff --git a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts index 72f1792fc71..1d292d45536 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts @@ -31,6 +31,7 @@ export const enum TerminalContextKeyStrings { TabsSingularSelection = 'terminalTabsSingularSelection', SplitTerminal = 'terminalSplitTerminal', ShellType = 'terminalShellType', + InTerminalRunCommandPicker = 'inTerminalRunCommandPicker', } export namespace TerminalContextKeys { @@ -119,4 +120,7 @@ export namespace TerminalContextKeys { /** Whether the focused tab's terminal is a split terminal. */ export const splitTerminal = new RawContextKey<boolean>(TerminalContextKeyStrings.SplitTerminal, false, localize('isSplitTerminalContextKey', "Whether the focused tab's terminal is a split terminal.")); + + /** Whether the terminal run command picker is currently open. */ + export const inTerminalRunCommandPicker = new RawContextKey<boolean>(TerminalContextKeyStrings.InTerminalRunCommandPicker, false, localize('inTerminalRunCommandPickerContextKey', "Whether the terminal run command picker is currently open.")); } diff --git a/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts b/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts index 52d57770b21..e9d136fdc9f 100644 --- a/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts +++ b/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts @@ -35,7 +35,7 @@ const testFilterDescriptions: { [K in TestFilterTerm]: string } = { export class TestingExplorerFilter extends BaseActionViewItem { private input!: SuggestEnabledInputWithHistory; private wrapper!: HTMLDivElement; - private readonly history: StoredValue<string[]> = this.instantiationService.createInstance(StoredValue, { + private readonly history: StoredValue<{ values: string[]; lastValue: string } | string[]> = this.instantiationService.createInstance(StoredValue, { key: 'testing.filterHistory2', scope: StorageScope.WORKSPACE, target: StorageTarget.USER @@ -65,9 +65,12 @@ export class TestingExplorerFilter extends BaseActionViewItem { const wrapper = this.wrapper = dom.$('.testing-filter-wrapper'); container.appendChild(wrapper); - const history = this.history.get([]); - if (history.length) { - this.state.setText(history[history.length - 1]); + let history = this.history.get({ lastValue: '', values: [] }); + if (history instanceof Array) { + history = { lastValue: '', values: history }; + } + if (history.lastValue) { + this.state.setText(history.lastValue); } const input = this.input = this._register(this.instantiationService.createInstance(ContextScopedSuggestEnabledInputWithHistory, { @@ -94,7 +97,7 @@ export class TestingExplorerFilter extends BaseActionViewItem { value: this.state.text.value, placeholderText: localize('testExplorerFilter', "Filter (e.g. text, !exclude, @tag)"), }, - history + history: history.values })); this._register(attachSuggestEnabledInputBoxStyler(input, this.themeService)); @@ -145,12 +148,7 @@ export class TestingExplorerFilter extends BaseActionViewItem { * Persists changes to the input history. */ public saveState() { - const history = this.input.getHistory(); - if (history.length) { - this.history.store(history); - } else { - this.history.delete(); - } + this.history.store({ lastValue: this.input.getValue(), values: this.input.getHistory() }); } /** diff --git a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts index 44aa262be18..13f2e154c6d 100644 --- a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts +++ b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts @@ -6,7 +6,6 @@ import { Codicon } from 'vs/base/common/codicons'; import { Event } from 'vs/base/common/event'; import { Disposable, DisposableStore, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; -import { isWeb } from 'vs/base/common/platform'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { localize } from 'vs/nls'; import { Action2, ISubmenuItem, MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; @@ -15,6 +14,7 @@ import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from ' import { IProductService } from 'vs/platform/product/common/productService'; import { Registry } from 'vs/platform/registry/common/platform'; import { registerColor } from 'vs/platform/theme/common/colorRegistry'; +import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { themeColorFromId } from 'vs/platform/theme/common/themeService'; import { IUserDataProfile, IUserDataProfilesService, PROFILES_ENABLEMENT_CONFIG } from 'vs/platform/userDataProfile/common/userDataProfile'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; @@ -25,6 +25,8 @@ import { IUserDataProfileManagementService, IUserDataProfileService, ManageProfi const CONTEXT_CURRENT_PROFILE = new RawContextKey<string>('currentUserDataProfile', ''); +export const userDataProfilesIcon = registerIcon('settingsProfiles-icon', Codicon.settings, localize('settingsProfilesIcon', 'Icon for Settings Profiles.')); + export class UserDataProfilesWorkbenchContribution extends Disposable implements IWorkbenchContribution { private readonly currentProfileContext: IContextKey<string>; @@ -53,7 +55,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements } private registerConfiguration(): void { - if (!isWeb && this.productService.quality !== 'stable') { + if (this.productService.quality !== 'stable') { Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).registerConfiguration({ ...workbenchConfigurationNodeBase, 'properties': { @@ -115,7 +117,6 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements id: `workbench.profiles.actions.profileEntry.${profile.id}`, title: profile.name, toggled: ContextKeyExpr.equals(CONTEXT_CURRENT_PROFILE.key, profile.id), - precondition: ContextKeyExpr.notEquals(CONTEXT_CURRENT_PROFILE.key, profile.id), menu: [ { id: ManageProfilesSubMenu, @@ -126,7 +127,9 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements }); } async run(accessor: ServicesAccessor) { - return that.userDataProfileManagementService.switchProfile(profile); + if (that.userDataProfileService.currentProfile.id !== profile.id) { + return that.userDataProfileManagementService.switchProfile(profile); + } } }); } @@ -138,7 +141,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements name: PROFILES_CATEGORY, command: 'workbench.profiles.actions.switchProfile', ariaLabel: localize('currentProfile', "Current Settings Profile is {0}", this.userDataProfileService.currentProfile.name), - text: `$(${Codicon.multipleWindows.id}) ${this.userDataProfileService.currentProfile.name!}`, + text: `$(${userDataProfilesIcon.id}) ${this.userDataProfileService.currentProfile.name!}`, tooltip: localize('profileTooltip', "{0}: {1}", PROFILES_CATEGORY, this.userDataProfileService.currentProfile.name), color: themeColorFromId(STATUS_BAR_SETTINGS_PROFILE_FOREGROUND), backgroundColor: themeColorFromId(STATUS_BAR_SETTINGS_PROFILE_BACKGROUND) diff --git a/src/vs/workbench/electron-sandbox/desktop.main.ts b/src/vs/workbench/electron-sandbox/desktop.main.ts index f5878699fdf..4986c87d42a 100644 --- a/src/vs/workbench/electron-sandbox/desktop.main.ts +++ b/src/vs/workbench/electron-sandbox/desktop.main.ts @@ -239,7 +239,7 @@ export class DesktopMain extends Disposable { serviceCollection.set(IUriIdentityService, uriIdentityService); // User Data Profiles - const userDataProfilesService = new UserDataProfilesNativeService(this.configuration.profiles.all, mainProcessService, environmentService, fileService, logService); + const userDataProfilesService = new UserDataProfilesNativeService(this.configuration.profiles.all, mainProcessService, environmentService); serviceCollection.set(IUserDataProfilesService, userDataProfilesService); const userDataProfileService = new UserDataProfileService(reviveProfile(this.configuration.profiles.current, userDataProfilesService.profilesHome.scheme), userDataProfilesService); serviceCollection.set(IUserDataProfileService, userDataProfileService); diff --git a/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts index 6412bd0e4c3..867ff2532bf 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationEditingService.test.ts @@ -110,13 +110,14 @@ suite('ConfigurationEditingService', () => { environmentService = TestEnvironmentService; environmentService.policyFile = joinPath(workspaceFolder, 'policies.json'); instantiationService.stub(IEnvironmentService, environmentService); - const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, logService)); + const uriIdentityService = new UriIdentityService(fileService); + const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService)); userDataProfileService = new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService); const remoteAgentService = disposables.add(instantiationService.createInstance(RemoteAgentService, null)); disposables.add(fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, logService)))); instantiationService.stub(IFileService, fileService); instantiationService.stub(IRemoteAgentService, remoteAgentService); - workspaceService = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService(), new FilePolicyService(environmentService.policyFile, fileService, logService))); + workspaceService = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, remoteAgentService, uriIdentityService, new NullLogService(), new FilePolicyService(environmentService.policyFile, fileService, logService))); await workspaceService.initialize({ id: hash(workspaceFolder.toString()).toString(16), uri: workspaceFolder diff --git a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts index 41573109ed5..fb05bed96f1 100644 --- a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts @@ -86,8 +86,9 @@ suite('WorkspaceContextService - Folder', () => { const environmentService = TestEnvironmentService; fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); - const userDataProfilesService = new UserDataProfilesService(environmentService, fileService, logService); - testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService), userDataProfilesService, fileService, new RemoteAgentService(null, environmentService, TestProductService, new RemoteAuthorityResolverService(TestProductService, undefined, undefined), new SignService(undefined), new NullLogService()), new UriIdentityService(fileService), new NullLogService(), new NullPolicyService())); + const uriIdentityService = new UriIdentityService(fileService); + const userDataProfilesService = new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService); + testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService), userDataProfilesService, fileService, new RemoteAgentService(null, environmentService, TestProductService, new RemoteAuthorityResolverService(TestProductService, undefined, undefined), new SignService(undefined), new NullLogService()), uriIdentityService, new NullLogService(), new NullPolicyService())); await (<WorkspaceService>testObject).initialize(convertToWorkspacePayload(folder)); }); @@ -127,8 +128,9 @@ suite('WorkspaceContextService - Folder', () => { const environmentService = TestEnvironmentService; fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); - const userDataProfilesService = new UserDataProfilesService(environmentService, fileService, logService); - const testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService), userDataProfilesService, fileService, new RemoteAgentService(null, environmentService, TestProductService, new RemoteAuthorityResolverService(TestProductService, undefined, undefined), new SignService(undefined), new NullLogService()), new UriIdentityService(fileService), new NullLogService(), new NullPolicyService())); + const uriIdentityService = new UriIdentityService(fileService); + const userDataProfilesService = new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService); + const testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService), userDataProfilesService, fileService, new RemoteAgentService(null, environmentService, TestProductService, new RemoteAuthorityResolverService(TestProductService, undefined, undefined), new SignService(undefined), new NullLogService()), uriIdentityService, new NullLogService(), new NullPolicyService())); await (<WorkspaceService>testObject).initialize(convertToWorkspacePayload(folder)); const actual = testObject.getWorkspaceFolder(joinPath(folder, 'a')); @@ -148,8 +150,9 @@ suite('WorkspaceContextService - Folder', () => { const environmentService = TestEnvironmentService; fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); - const userDataProfilesService = new UserDataProfilesService(environmentService, fileService, logService); - const testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService), userDataProfilesService, fileService, new RemoteAgentService(null, environmentService, TestProductService, new RemoteAuthorityResolverService(TestProductService, undefined, undefined), new SignService(undefined), new NullLogService()), new UriIdentityService(fileService), new NullLogService(), new NullPolicyService())); + const uriIdentityService = new UriIdentityService(fileService); + const userDataProfilesService = new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService); + const testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService), userDataProfilesService, fileService, new RemoteAgentService(null, environmentService, TestProductService, new RemoteAuthorityResolverService(TestProductService, undefined, undefined), new SignService(undefined), new NullLogService()), uriIdentityService, new NullLogService(), new NullPolicyService())); await (<WorkspaceService>testObject).initialize(convertToWorkspacePayload(folder)); @@ -196,8 +199,9 @@ suite('WorkspaceContextService - Workspace', () => { const remoteAgentService = disposables.add(instantiationService.createInstance(RemoteAgentService, null)); instantiationService.stub(IRemoteAgentService, remoteAgentService); fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); - const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, logService)); - testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService), userDataProfilesService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService(), new NullPolicyService())); + const uriIdentityService = new UriIdentityService(fileService); + const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService)); + testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService), userDataProfilesService, fileService, remoteAgentService, uriIdentityService, new NullLogService(), new NullPolicyService())); instantiationService.stub(IWorkspaceContextService, testObject); instantiationService.stub(IConfigurationService, testObject); @@ -255,8 +259,9 @@ suite('WorkspaceContextService - Workspace Editing', () => { const remoteAgentService = instantiationService.createInstance(RemoteAgentService, null); instantiationService.stub(IRemoteAgentService, remoteAgentService); fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); - const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, logService)); - testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService), userDataProfilesService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService(), new NullPolicyService())); + const uriIdentityService = new UriIdentityService(fileService); + const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService)); + testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService), userDataProfilesService, fileService, remoteAgentService, uriIdentityService, new NullLogService(), new NullPolicyService())); instantiationService.stub(IFileService, fileService); instantiationService.stub(IWorkspaceContextService, testObject); @@ -499,9 +504,10 @@ suite('WorkspaceService - Initialization', () => { const remoteAgentService = instantiationService.createInstance(RemoteAgentService, null); instantiationService.stub(IRemoteAgentService, remoteAgentService); fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); - const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, logService)); + const uriIdentityService = new UriIdentityService(fileService); + const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService)); userDataProfileService = instantiationService.stub(IUserDataProfileService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService)); - testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService(), new NullPolicyService())); + testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, remoteAgentService, uriIdentityService, new NullLogService(), new NullPolicyService())); instantiationService.stub(IFileService, fileService); instantiationService.stub(IWorkspaceContextService, testObject); instantiationService.stub(IConfigurationService, testObject); @@ -759,9 +765,10 @@ suite('WorkspaceConfigurationService - Folder', () => { const remoteAgentService = instantiationService.createInstance(RemoteAgentService, null); instantiationService.stub(IRemoteAgentService, remoteAgentService); fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); - const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, logService)); + const uriIdentityService = new UriIdentityService(fileService); + const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService)); userDataProfileService = instantiationService.stub(IUserDataProfileService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService)); - workspaceService = testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService(), new FilePolicyService(environmentService.policyFile, fileService, logService))); + workspaceService = testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, remoteAgentService, uriIdentityService, new NullLogService(), new FilePolicyService(environmentService.policyFile, fileService, logService))); instantiationService.stub(IFileService, fileService); instantiationService.stub(IWorkspaceContextService, testObject); instantiationService.stub(IConfigurationService, testObject); @@ -1425,9 +1432,10 @@ suite('WorkspaceConfigurationService - Profiles', () => { const remoteAgentService = instantiationService.createInstance(RemoteAgentService, null); instantiationService.stub(IRemoteAgentService, remoteAgentService); fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); - const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, logService)); + const uriIdentityService = new UriIdentityService(fileService); + const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService)); userDataProfileService = instantiationService.stub(IUserDataProfileService, new UserDataProfileService(toUserDataProfile('custom', joinPath(environmentService.userRoamingDataHome, 'profiles', 'temp')), userDataProfilesService)); - workspaceService = testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService(), new FilePolicyService(environmentService.policyFile, fileService, logService))); + workspaceService = testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, remoteAgentService, uriIdentityService, new NullLogService(), new FilePolicyService(environmentService.policyFile, fileService, logService))); instantiationService.stub(IFileService, fileService); instantiationService.stub(IWorkspaceContextService, testObject); instantiationService.stub(IConfigurationService, testObject); @@ -1613,9 +1621,10 @@ suite('WorkspaceConfigurationService-Multiroot', () => { const remoteAgentService = instantiationService.createInstance(RemoteAgentService, null); instantiationService.stub(IRemoteAgentService, remoteAgentService); fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); - const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, logService)); + const uriIdentityService = new UriIdentityService(fileService); + const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService)); userDataProfileService = instantiationService.stub(IUserDataProfileService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService)); - const workspaceService = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService(), new NullPolicyService())); + const workspaceService = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, remoteAgentService, uriIdentityService, new NullLogService(), new NullPolicyService())); instantiationService.stub(IFileService, fileService); instantiationService.stub(IWorkspaceContextService, workspaceService); @@ -2276,9 +2285,10 @@ suite('WorkspaceConfigurationService - Remote Folder', () => { const remoteAgentService = instantiationService.stub(IRemoteAgentService, <Partial<IRemoteAgentService>>{ getEnvironment: () => remoteEnvironmentPromise }); fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService()))); const configurationCache: IConfigurationCache = { read: () => Promise.resolve(''), write: () => Promise.resolve(), remove: () => Promise.resolve(), needsCaching: () => false }; - const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, logService)); + const uriIdentityService = new UriIdentityService(fileService); + const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService)); userDataProfileService = instantiationService.stub(IUserDataProfileService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService)); - testObject = disposables.add(new WorkspaceService({ configurationCache, remoteAuthority }, environmentService, userDataProfileService, userDataProfilesService, fileService, remoteAgentService, new UriIdentityService(fileService), new NullLogService(), new NullPolicyService())); + testObject = disposables.add(new WorkspaceService({ configurationCache, remoteAuthority }, environmentService, userDataProfileService, userDataProfilesService, fileService, remoteAgentService, uriIdentityService, new NullLogService(), new NullPolicyService())); instantiationService.stub(IWorkspaceContextService, testObject); instantiationService.stub(IConfigurationService, testObject); instantiationService.stub(IEnvironmentService, environmentService); diff --git a/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts b/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts index fb5c4babf4a..bc5e140d42a 100644 --- a/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts +++ b/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts @@ -40,6 +40,9 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { validateExtensionManifest } from 'vs/platform/extensions/common/extensionValidator'; import Severity from 'vs/base/common/severity'; import { IStringDictionary } from 'vs/base/common/collections'; +import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; type GalleryExtensionInfo = { readonly id: string; preRelease?: boolean; migrateStorageFrom?: string }; type ExtensionInfo = { readonly id: string; preRelease: boolean }; @@ -83,7 +86,6 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten private readonly systemExtensionsCacheResource: URI | undefined = undefined; private readonly customBuiltinExtensionsCacheResource: URI | undefined = undefined; - private readonly installedExtensionsResource: URI | undefined = undefined; private readonly resourcesAccessQueueMap = new ResourceMap<Queue<IWebExtension[]>>(); constructor( @@ -97,11 +99,12 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten @IExtensionStorageService private readonly extensionStorageService: IExtensionStorageService, @IStorageService private readonly storageService: IStorageService, @IProductService private readonly productService: IProductService, + @IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService, + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, @ILifecycleService lifecycleService: ILifecycleService, ) { super(); if (isWeb) { - this.installedExtensionsResource = joinPath(environmentService.userRoamingDataHome, 'extensions.json'); this.systemExtensionsCacheResource = joinPath(environmentService.userRoamingDataHome, 'systemExtensionsCache.json'); this.customBuiltinExtensionsCacheResource = joinPath(environmentService.userRoamingDataHome, 'customBuiltinExtensionsCache.json'); this.registerActions(); @@ -369,7 +372,7 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten return this.readSystemExtensions(); } - async scanUserExtensions(scanOptions?: ScanOptions): Promise<IScannedExtension[]> { + async scanUserExtensions(profileLocation?: URI, scanOptions?: ScanOptions): Promise<IScannedExtension[]> { const extensions = new Map<string, IScannedExtension>(); // Custom builtin extensions defined through `additionalBuiltinExtensions` API @@ -379,7 +382,7 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten } // User Installed extensions - const installedExtensions = await this.scanInstalledExtensions(scanOptions); + const installedExtensions = await this.scanInstalledExtensions(profileLocation, scanOptions); for (const extension of installedExtensions) { extensions.set(extension.identifier.id.toLowerCase(), extension); } @@ -408,17 +411,17 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten return result; } - async scanExistingExtension(extensionLocation: URI, extensionType: ExtensionType): Promise<IScannedExtension | null> { + async scanExistingExtension(extensionLocation: URI, extensionType: ExtensionType, profileLocation?: URI): Promise<IScannedExtension | null> { if (extensionType === ExtensionType.System) { const systemExtensions = await this.scanSystemExtensions(); return systemExtensions.find(e => e.location.toString() === extensionLocation.toString()) || null; } - const userExtensions = await this.scanUserExtensions(); + const userExtensions = await this.scanUserExtensions(profileLocation); return userExtensions.find(e => e.location.toString() === extensionLocation.toString()) || null; } - async scanMetadata(extensionLocation: URI): Promise<Metadata | undefined> { - const extension = await this.scanExistingExtension(extensionLocation, ExtensionType.User); + async scanMetadata(extensionLocation: URI, profileLocation?: URI): Promise<Metadata | undefined> { + const extension = await this.scanExistingExtension(extensionLocation, ExtensionType.User, profileLocation); return extension?.metadata; } @@ -435,21 +438,35 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten return null; } - async addExtensionFromGallery(galleryExtension: IGalleryExtension, metadata?: Metadata): Promise<IExtension> { + async addExtensionFromGallery(galleryExtension: IGalleryExtension, metadata: Metadata, profileLocation?: URI): Promise<IScannedExtension> { const webExtension = await this.toWebExtensionFromGallery(galleryExtension, metadata); - return this.addWebExtension(webExtension); + return this.addWebExtension(webExtension, profileLocation); } - async addExtension(location: URI, metadata?: Metadata): Promise<IExtension> { + async addExtension(location: URI, metadata: Metadata, profileLocation?: URI): Promise<IScannedExtension> { const webExtension = await this.toWebExtension(location, undefined, undefined, undefined, undefined, undefined, metadata); - return this.addWebExtension(webExtension); + return this.addWebExtension(webExtension, profileLocation); } - async removeExtension(identifier: IExtensionIdentifier, version?: string): Promise<void> { - await this.writeInstalledExtensions(installedExtensions => installedExtensions.filter(extension => !(areSameExtensions(extension.identifier, identifier) && (version ? extension.version === version : true)))); + async removeExtension(extension: IScannedExtension, profileLocation?: URI): Promise<void> { + await this.writeInstalledExtensions(profileLocation, installedExtensions => installedExtensions.filter(installedExtension => !areSameExtensions(installedExtension.identifier, extension.identifier))); } - private async addWebExtension(webExtension: IWebExtension): Promise<IScannedExtension> { + async copyExtensions(fromProfileLocation: URI, toProfileLocation: URI, filter: (extension: IScannedExtension) => boolean): Promise<void> { + const extensionsToCopy: IWebExtension[] = []; + const fromWebExtensions = await this.readInstalledExtensions(fromProfileLocation); + await Promise.all(fromWebExtensions.map(async webExtension => { + const scannedExtension = await this.toScannedExtension(webExtension, false); + if (filter(scannedExtension)) { + extensionsToCopy.push(webExtension); + } + })); + if (extensionsToCopy.length) { + await this.addToInstalledExtensions(extensionsToCopy, toProfileLocation); + } + } + + private async addWebExtension(webExtension: IWebExtension, profileLocation?: URI): Promise<IScannedExtension> { const isSystem = !!(await this.scanSystemExtensions()).find(e => areSameExtensions(e.identifier, webExtension.identifier)); const isBuiltin = !!webExtension.metadata?.isBuiltin; const extension = await this.toScannedExtension(webExtension, isBuiltin); @@ -473,30 +490,40 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten return customBuiltinExtensions; }); - const installedExtensions = await this.readInstalledExtensions(); + const installedExtensions = await this.readInstalledExtensions(profileLocation); // Also add to installed extensions if it is installed to update its version if (installedExtensions.some(e => areSameExtensions(e.identifier, webExtension.identifier))) { - await this.addToInstalledExtensions(webExtension); + await this.addToInstalledExtensions([webExtension], profileLocation); } return extension; } // Add to installed extensions - await this.addToInstalledExtensions(webExtension); + await this.addToInstalledExtensions([webExtension], profileLocation); return extension; } - private async addToInstalledExtensions(webExtension: IWebExtension): Promise<void> { - await this.writeInstalledExtensions(installedExtensions => { + private async addToInstalledExtensions(webExtensions: IWebExtension[], profileLocation?: URI): Promise<void> { + await this.writeInstalledExtensions(profileLocation, installedExtensions => { // Remove the existing extension to avoid duplicates - installedExtensions = installedExtensions.filter(e => !areSameExtensions(e.identifier, webExtension.identifier)); - installedExtensions.push(webExtension); + installedExtensions = installedExtensions.filter(installedExtension => webExtensions.some(extension => !areSameExtensions(installedExtension.identifier, extension.identifier))); + installedExtensions.push(...webExtensions); return installedExtensions; }); } - private async scanInstalledExtensions(scanOptions?: ScanOptions): Promise<IScannedExtension[]> { - const installedExtensions = await this.readInstalledExtensions(); + private async scanInstalledExtensions(profileLocation?: URI, scanOptions?: ScanOptions): Promise<IScannedExtension[]> { + let installedExtensions = await this.readInstalledExtensions(profileLocation); + + // If current profile is not a default profile, then add the application extensions to the list + if (this.userDataProfilesService.defaultProfile.extensionsResource && !this.uriIdentityService.extUri.isEqual(profileLocation, this.userDataProfilesService.defaultProfile.extensionsResource)) { + // Remove application extensions from the non default profile + installedExtensions = installedExtensions.filter(i => !i.metadata?.isApplicationScoped); + // Add application extensions from the default profile to the list + const defaultProfileExtensions = await this.readInstalledExtensions(this.userDataProfilesService.defaultProfile.extensionsResource); + installedExtensions.push(...defaultProfileExtensions.filter(i => i.metadata?.isApplicationScoped)); + } + installedExtensions.sort((a, b) => a.identifier.id < b.identifier.id ? -1 : a.identifier.id > b.identifier.id ? 1 : semver.rcompare(a.version, b.version)); const result = new Map<string, IScannedExtension>(); for (const webExtension of installedExtensions) { @@ -670,17 +697,12 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten return manifest; } - private async readInstalledExtensions(): Promise<IWebExtension[]> { - await this.migratePackageNLSUris(); - return this.withWebExtensions(this.installedExtensionsResource); - } - // TODO: @TylerLeonhardt/@Sandy081: Delete after 6 months private _migratePackageNLSUrisPromise: Promise<void> | undefined; private migratePackageNLSUris(): Promise<void> { if (!this._migratePackageNLSUrisPromise) { this._migratePackageNLSUrisPromise = (async () => { - const webExtensions = await this.withWebExtensions(this.installedExtensionsResource); + const webExtensions = await this.withWebExtensions(this.userDataProfilesService.defaultProfile.extensionsResource); if (webExtensions.some(e => !e.packageNLSUris && e.packageNLSUri)) { const migratedExtensions = await Promise.all(webExtensions.map(async e => { if (!e.packageNLSUris && e.packageNLSUri) { @@ -691,15 +713,22 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten } return e; })); - await this.withWebExtensions(this.installedExtensionsResource, () => migratedExtensions); + await this.withWebExtensions(this.userDataProfilesService.defaultProfile.extensionsResource, () => migratedExtensions); } })(); } return this._migratePackageNLSUrisPromise; } - private writeInstalledExtensions(updateFn: (extensions: IWebExtension[]) => IWebExtension[]): Promise<IWebExtension[]> { - return this.withWebExtensions(this.installedExtensionsResource, updateFn); + private async readInstalledExtensions(profileLocation?: URI): Promise<IWebExtension[]> { + if (this.uriIdentityService.extUri.isEqual(profileLocation, this.userDataProfilesService.defaultProfile.extensionsResource)) { + await this.migratePackageNLSUris(); + } + return this.withWebExtensions(profileLocation); + } + + private writeInstalledExtensions(profileLocation: URI | undefined, updateFn: (extensions: IWebExtension[]) => IWebExtension[]): Promise<IWebExtension[]> { + return this.withWebExtensions(profileLocation, updateFn); } private readCustomBuiltinExtensionsCache(): Promise<IWebExtension[]> { @@ -797,7 +826,6 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten } private registerActions(): void { - const that = this; this._register(registerAction2(class extends Action2 { constructor() { super({ @@ -809,7 +837,9 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten }); } run(serviceAccessor: ServicesAccessor): void { - serviceAccessor.get(IEditorService).openEditor({ resource: that.installedExtensionsResource }); + const editorService = serviceAccessor.get(IEditorService); + const userDataProfileService = serviceAccessor.get(IUserDataProfileService); + editorService.openEditor({ resource: userDataProfileService.currentProfile.extensionsResource }); } })); } diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagement.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagement.ts index 9c9683c1c3b..1cad71d1c3a 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagement.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagement.ts @@ -6,7 +6,7 @@ import { Event } from 'vs/base/common/event'; import { createDecorator, refineServiceDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IExtension, ExtensionType, IExtensionManifest } from 'vs/platform/extensions/common/extensions'; -import { IExtensionManagementService, IGalleryExtension, IExtensionIdentifier, ILocalExtension, InstallOptions, InstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionResult, Metadata, InstallVSIXOptions, UninstallExtensionEvent } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionManagementService, IGalleryExtension, ILocalExtension, InstallOptions, InstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionResult, Metadata, InstallVSIXOptions, UninstallExtensionEvent } from 'vs/platform/extensionManagement/common/extensionManagement'; import { URI } from 'vs/base/common/uri'; import { FileAccess } from 'vs/base/common/network'; @@ -158,14 +158,15 @@ export interface IWebExtensionsScannerService { readonly _serviceBrand: undefined; scanSystemExtensions(): Promise<IExtension[]>; - scanUserExtensions(options?: ScanOptions): Promise<IScannedExtension[]>; + scanUserExtensions(profileLocation: URI | undefined, options?: ScanOptions): Promise<IScannedExtension[]>; scanExtensionsUnderDevelopment(): Promise<IExtension[]>; - scanExistingExtension(extensionLocation: URI, extensionType: ExtensionType): Promise<IScannedExtension | null>; + scanExistingExtension(extensionLocation: URI, extensionType: ExtensionType, profileLocation: URI | undefined): Promise<IScannedExtension | null>; - addExtension(location: URI, metadata?: Metadata): Promise<IExtension>; - addExtensionFromGallery(galleryExtension: IGalleryExtension, metadata?: Metadata): Promise<IExtension>; - removeExtension(identifier: IExtensionIdentifier, version?: string): Promise<void>; + addExtension(location: URI, metadata: Metadata, profileLocation: URI | undefined): Promise<IScannedExtension>; + addExtensionFromGallery(galleryExtension: IGalleryExtension, metadata: Metadata, profileLocation: URI | undefined): Promise<IScannedExtension>; + removeExtension(extension: IScannedExtension, profileLocation: URI | undefined): Promise<void>; + copyExtensions(fromProfileLocation: URI, toProfileLocation: URI, filter: (extension: IScannedExtension) => boolean): Promise<void>; - scanMetadata(extensionLocation: URI): Promise<Metadata | undefined>; + scanMetadata(extensionLocation: URI, profileLocation: URI | undefined): Promise<Metadata | undefined>; scanExtensionManifest(extensionLocation: URI): Promise<IExtensionManifest | null>; } diff --git a/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts index 2c371645082..23413b1b130 100644 --- a/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts @@ -3,10 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ExtensionType, IExtension, IExtensionIdentifier, IExtensionManifest, TargetPlatform } from 'vs/platform/extensions/common/extensions'; -import { ILocalExtension, IGalleryExtension, IGalleryMetadata, InstallOperation, IExtensionGalleryService, InstallOptions, Metadata, UninstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { ExtensionIdentifier, ExtensionType, IExtension, IExtensionIdentifier, IExtensionManifest, TargetPlatform } from 'vs/platform/extensions/common/extensions'; +import { ILocalExtension, IGalleryExtension, IGalleryMetadata, InstallOperation, IExtensionGalleryService, Metadata, ServerInstallOptions, ServerUninstallOptions, IServerExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { URI } from 'vs/base/common/uri'; -import { Event } from 'vs/base/common/event'; +import { Emitter } from 'vs/base/common/event'; import { areSameExtensions, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IProfileAwareExtensionManagementService, IScannedExtension, IWebExtensionsScannerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { ILogService } from 'vs/platform/log/common/log'; @@ -16,15 +16,17 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; import { IProductService } from 'vs/platform/product/common/productService'; import { isBoolean, isUndefined } from 'vs/base/common/types'; -import { IExtensionsProfileScannerService } from 'vs/platform/extensionManagement/common/extensionsProfileScannerService'; +import { DidChangeUserDataProfileEvent, IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { delta } from 'vs/base/common/arrays'; +import { compare } from 'vs/base/common/strings'; import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; -import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; -export class WebExtensionManagementService extends AbstractExtensionManagementService implements IProfileAwareExtensionManagementService { +export class WebExtensionManagementService extends AbstractExtensionManagementService implements IProfileAwareExtensionManagementService, IServerExtensionManagementService { declare readonly _serviceBrand: undefined; - readonly onDidChangeProfileExtensions = Event.None; + private readonly _onDidChangeProfileExtensions = this._register(new Emitter<{ readonly added: ILocalExtension[]; readonly removed: ILocalExtension[] }>()); + readonly onDidChangeProfileExtensions = this._onDidChangeProfileExtensions.event; constructor( @IExtensionGalleryService extensionGalleryService: IExtensionGalleryService, @@ -32,12 +34,12 @@ export class WebExtensionManagementService extends AbstractExtensionManagementSe @ILogService logService: ILogService, @IWebExtensionsScannerService private readonly webExtensionsScannerService: IWebExtensionsScannerService, @IExtensionManifestPropertiesService private readonly extensionManifestPropertiesService: IExtensionManifestPropertiesService, - @IExtensionsProfileScannerService extensionsProfileScannerService: IExtensionsProfileScannerService, + @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, @IProductService productService: IProductService, @IUserDataProfilesService userDataProfilesService: IUserDataProfilesService, - @IUriIdentityService uriIdentityService: IUriIdentityService, ) { - super(userDataProfilesService, uriIdentityService, extensionGalleryService, extensionsProfileScannerService, telemetryService, logService, productService); + super(extensionGalleryService, telemetryService, logService, productService, userDataProfilesService); + this._register(userDataProfileService.onDidChangeCurrentProfile(e => e.join(this.whenProfileChanged(e)))); } async getTargetPlatform(): Promise<TargetPlatform> { @@ -61,13 +63,13 @@ export class WebExtensionManagementService extends AbstractExtensionManagementSe extensions.push(...systemExtensions); } if (type === undefined || type === ExtensionType.User) { - const userExtensions = await this.webExtensionsScannerService.scanUserExtensions(); + const userExtensions = await this.webExtensionsScannerService.scanUserExtensions(this.userDataProfileService.currentProfile.extensionsResource); extensions.push(...userExtensions); } return Promise.all(extensions.map(e => toLocalExtension(e))); } - async install(location: URI, options: InstallOptions = {}): Promise<ILocalExtension> { + async install(location: URI, options: ServerInstallOptions = {}): Promise<ILocalExtension> { this.logService.trace('ExtensionManagementService#install', location.toString()); const manifest = await this.webExtensionsScannerService.scanExtensionManifest(location); if (!manifest) { @@ -77,7 +79,7 @@ export class WebExtensionManagementService extends AbstractExtensionManagementSe } getMetadata(extension: ILocalExtension): Promise<Metadata | undefined> { - return this.webExtensionsScannerService.scanMetadata(extension.location); + return this.webExtensionsScannerService.scanMetadata(extension.location, this.userDataProfileService.currentProfile.extensionsResource); } protected override async getCompatibleVersion(extension: IGalleryExtension, sameVersion: boolean, includePreRelease: boolean): Promise<IGalleryExtension | null> { @@ -100,11 +102,11 @@ export class WebExtensionManagementService extends AbstractExtensionManagementSe return local; } - protected createDefaultInstallExtensionTask(manifest: IExtensionManifest, extension: URI | IGalleryExtension, options: InstallOptions): IInstallExtensionTask { + protected doCreateInstallExtensionTask(manifest: IExtensionManifest, extension: URI | IGalleryExtension, options: ServerInstallOptions): IInstallExtensionTask { return new InstallExtensionTask(manifest, extension, options, this.webExtensionsScannerService); } - protected createDefaultUninstallExtensionTask(extension: ILocalExtension, options: UninstallOptions): IUninstallExtensionTask { + protected doCreateUninstallExtensionTask(extension: ILocalExtension, options: ServerUninstallOptions): IUninstallExtensionTask { return new UninstallExtensionTask(extension, options, this.webExtensionsScannerService); } @@ -112,6 +114,24 @@ export class WebExtensionManagementService extends AbstractExtensionManagementSe unzip(zipLocation: URI): Promise<IExtensionIdentifier> { throw new Error('unsupported'); } getManifest(vsix: URI): Promise<IExtensionManifest> { throw new Error('unsupported'); } updateExtensionScope(): Promise<ILocalExtension> { throw new Error('unsupported'); } + + private async whenProfileChanged(e: DidChangeUserDataProfileEvent): Promise<void> { + const previousProfileLocation = e.previous.extensionsResource; + const currentProfileLocation = e.profile.extensionsResource; + if (!previousProfileLocation || !currentProfileLocation) { + throw new Error('This should not happen'); + } + if (e.preserveData) { + await this.webExtensionsScannerService.copyExtensions(previousProfileLocation, currentProfileLocation, e => !e.metadata?.isApplicationScoped); + } else { + const oldExtensions = await this.webExtensionsScannerService.scanUserExtensions(previousProfileLocation); + const newExtensions = await this.webExtensionsScannerService.scanUserExtensions(currentProfileLocation); + const { added, removed } = delta(oldExtensions, newExtensions, (a, b) => compare(`${ExtensionIdentifier.toKey(a.identifier.id)}@${a.manifest.version}`, `${ExtensionIdentifier.toKey(b.identifier.id)}@${b.manifest.version}`)); + if (added.length || removed.length) { + this._onDidChangeProfileExtensions.fire({ added: added.map(e => toLocalExtension(e)), removed: removed.map(e => toLocalExtension(e)) }); + } + } + } } function toLocalExtension(extension: IExtension): ILocalExtension { @@ -131,7 +151,7 @@ function toLocalExtension(extension: IExtension): ILocalExtension { }; } -function getMetadata(options?: InstallOptions, existingExtension?: IExtension): Metadata { +function getMetadata(options?: ServerInstallOptions, existingExtension?: IExtension): Metadata { const metadata: Metadata = { ...((<IScannedExtension>existingExtension)?.metadata || {}) }; metadata.isMachineScoped = options?.isMachineScoped || metadata.isMachineScoped; return metadata; @@ -148,7 +168,7 @@ class InstallExtensionTask extends AbstractExtensionTask<{ local: ILocalExtensio constructor( manifest: IExtensionManifest, private readonly extension: URI | IGalleryExtension, - private readonly options: InstallOptions, + private readonly options: ServerInstallOptions, private readonly webExtensionsScannerService: IWebExtensionsScannerService, ) { super(); @@ -157,7 +177,7 @@ class InstallExtensionTask extends AbstractExtensionTask<{ local: ILocalExtensio } protected async doRun(token: CancellationToken): Promise<{ local: ILocalExtension; metadata: Metadata }> { - const userExtensions = await this.webExtensionsScannerService.scanUserExtensions(); + const userExtensions = await this.webExtensionsScannerService.scanUserExtensions(this.options.profileLocation); const existingExtension = userExtensions.find(e => areSameExtensions(e.identifier, this.identifier)); if (existingExtension) { this._operation = InstallOperation.Update; @@ -179,8 +199,8 @@ class InstallExtensionTask extends AbstractExtensionTask<{ local: ILocalExtensio : metadata?.preRelease /* Respect the existing pre-release flag if it was set */); } - const scannedExtension = URI.isUri(this.extension) ? await this.webExtensionsScannerService.addExtension(this.extension, metadata) - : await this.webExtensionsScannerService.addExtensionFromGallery(this.extension, metadata); + const scannedExtension = URI.isUri(this.extension) ? await this.webExtensionsScannerService.addExtension(this.extension, metadata, this.options.profileLocation) + : await this.webExtensionsScannerService.addExtensionFromGallery(this.extension, metadata, this.options.profileLocation); return { local: toLocalExtension(scannedExtension), metadata }; } } @@ -189,13 +209,13 @@ class UninstallExtensionTask extends AbstractExtensionTask<void> implements IUni constructor( readonly extension: ILocalExtension, - options: UninstallOptions, + private readonly options: ServerUninstallOptions, private readonly webExtensionsScannerService: IWebExtensionsScannerService, ) { super(); } protected doRun(token: CancellationToken): Promise<void> { - return this.webExtensionsScannerService.removeExtension(this.extension.identifier); + return this.webExtensionsScannerService.removeExtension(this.extension, this.options.profileLocation); } } diff --git a/src/vs/workbench/services/extensions/browser/extensionService.ts b/src/vs/workbench/services/extensions/browser/extensionService.ts index acdf6ca2308..8c48d48d131 100644 --- a/src/vs/workbench/services/extensions/browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/browser/extensionService.ts @@ -30,6 +30,7 @@ import { IExtensionManifestPropertiesService } from 'vs/workbench/services/exten import { IUserDataInitializationService } from 'vs/workbench/services/userData/browser/userDataInit'; import { IAutomatedWindow } from 'vs/platform/log/browser/log'; import { ILogService } from 'vs/platform/log/common/log'; +import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; export class ExtensionService extends AbstractExtensionService implements IExtensionService { @@ -54,6 +55,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten @ILifecycleService lifecycleService: ILifecycleService, @IRemoteAuthorityResolverService private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService, @IUserDataInitializationService private readonly _userDataInitializationService: IUserDataInitializationService, + @IUserDataProfileService userDataProfileService: IUserDataProfileService, ) { super( instantiationService, @@ -70,7 +72,8 @@ export class ExtensionService extends AbstractExtensionService implements IExten webExtensionsScannerService, logService, remoteAgentService, - lifecycleService + lifecycleService, + userDataProfileService ); // Initialize installed extensions first and do it only after workbench is ready @@ -92,7 +95,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten return this._remoteAgentService.scanSingleExtension(extension.location, extension.type === ExtensionType.System); } - const scannedExtension = await this._webExtensionsScannerService.scanExistingExtension(extension.location, extension.type); + const scannedExtension = await this._webExtensionsScannerService.scanExistingExtension(extension.location, extension.type, this._userDataProfileService.currentProfile.extensionsResource); if (scannedExtension) { return toExtensionDescription(scannedExtension); } diff --git a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts index 76589d23182..057e5467ee9 100644 --- a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts +++ b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts @@ -37,6 +37,7 @@ import { ApiProposalName, allApiProposals } from 'vs/workbench/services/extensio import { ILogService } from 'vs/platform/log/common/log'; import { IExtensionHostExitInfo, IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; const hasOwnProperty = Object.hasOwnProperty; const NO_OP_VOID_PROMISE = Promise.resolve<void>(undefined); @@ -189,6 +190,7 @@ export abstract class AbstractExtensionService extends Disposable implements IEx @ILogService protected readonly _logService: ILogService, @IRemoteAgentService protected readonly _remoteAgentService: IRemoteAgentService, @ILifecycleService private readonly _lifecycleService: ILifecycleService, + @IUserDataProfileService protected readonly _userDataProfileService: IUserDataProfileService, ) { super(); @@ -1331,7 +1333,7 @@ export abstract class AbstractExtensionService extends Disposable implements IEx try { await Promise.all([ this._webExtensionsScannerService.scanSystemExtensions().then(extensions => system.push(...extensions.map(e => toExtensionDescription(e)))), - this._webExtensionsScannerService.scanUserExtensions({ skipInvalidExtensions: true }).then(extensions => user.push(...extensions.map(e => toExtensionDescription(e)))), + this._webExtensionsScannerService.scanUserExtensions(this._userDataProfileService.currentProfile.extensionsResource, { skipInvalidExtensions: true }).then(extensions => user.push(...extensions.map(e => toExtensionDescription(e)))), this._webExtensionsScannerService.scanExtensionsUnderDevelopment().then(extensions => development.push(...extensions.map(e => toExtensionDescription(e, true)))) ]); } catch (error) { diff --git a/src/vs/workbench/services/extensions/electron-sandbox/electronExtensionService.ts b/src/vs/workbench/services/extensions/electron-sandbox/electronExtensionService.ts index 3c267c40962..87e9f45b545 100644 --- a/src/vs/workbench/services/extensions/electron-sandbox/electronExtensionService.ts +++ b/src/vs/workbench/services/extensions/electron-sandbox/electronExtensionService.ts @@ -48,6 +48,7 @@ import { isCI } from 'vs/base/common/platform'; import { IResolveAuthorityErrorResult } from 'vs/workbench/services/extensions/common/extensionHostProxy'; import { URI } from 'vs/base/common/uri'; import { ILocalProcessExtensionHostDataProvider, ILocalProcessExtensionHostInitData, SandboxLocalProcessExtensionHost } from 'vs/workbench/services/extensions/electron-sandbox/localProcessExtensionHost'; +import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; export abstract class ElectronExtensionService extends AbstractExtensionService implements IExtensionService { @@ -80,6 +81,7 @@ export abstract class ElectronExtensionService extends AbstractExtensionService @IRemoteExplorerService private readonly _remoteExplorerService: IRemoteExplorerService, @IExtensionGalleryService private readonly _extensionGalleryService: IExtensionGalleryService, @IWorkspaceTrustManagementService private readonly _workspaceTrustManagementService: IWorkspaceTrustManagementService, + @IUserDataProfileService userDataProfileService: IUserDataProfileService, ) { super( instantiationService, @@ -96,7 +98,8 @@ export abstract class ElectronExtensionService extends AbstractExtensionService webExtensionsScannerService, logService, remoteAgentService, - lifecycleService + lifecycleService, + userDataProfileService ); [this._enableLocalWebWorker, this._lazyLocalWebWorker] = this._isLocalWebWorkerEnabled(); diff --git a/src/vs/workbench/services/extensions/test/browser/extensionService.test.ts b/src/vs/workbench/services/extensions/test/browser/extensionService.test.ts index 18d684cacaa..eaa0069f098 100644 --- a/src/vs/workbench/services/extensions/test/browser/extensionService.test.ts +++ b/src/vs/workbench/services/extensions/test/browser/extensionService.test.ts @@ -33,6 +33,11 @@ import { TestEnvironmentService, TestFileService, TestLifecycleService, TestRemo import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; import { mock } from 'vs/base/test/common/mock'; import { IExtensionHostManager } from 'vs/workbench/services/extensions/common/extensionHostManager'; +import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { UserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfileService'; +import { IUserDataProfilesService, UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; +import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; suite('BrowserExtensionService', () => { test('pickRunningLocation', () => { @@ -175,7 +180,10 @@ suite('ExtensionService', () => { [IWorkbenchExtensionEnablementService, TestWorkbenchExtensionEnablementService], [ITelemetryService, NullTelemetryService], [IEnvironmentService, TestEnvironmentService], - [IWorkspaceTrustEnablementService, WorkspaceTrustEnablementService] + [IWorkspaceTrustEnablementService, WorkspaceTrustEnablementService], + [IUserDataProfilesService, UserDataProfilesService], + [IUserDataProfileService, UserDataProfileService], + [IUriIdentityService, UriIdentityService], ]); extService = <MyTestExtensionService>instantiationService.get(IExtensionService); }); diff --git a/src/vs/workbench/services/extensions/test/browser/extensionStorageMigration.test.ts b/src/vs/workbench/services/extensions/test/browser/extensionStorageMigration.test.ts index d6843185c30..a02b86def5c 100644 --- a/src/vs/workbench/services/extensions/test/browser/extensionStorageMigration.test.ts +++ b/src/vs/workbench/services/extensions/test/browser/extensionStorageMigration.test.ts @@ -22,6 +22,7 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import { IUserDataProfilesService, UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { UserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfileService'; import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; suite('ExtensionStorageMigration', () => { @@ -38,7 +39,7 @@ suite('ExtensionStorageMigration', () => { fileService.registerProvider(ROOT.scheme, disposables.add(new InMemoryFileSystemProvider())); instantiationService.stub(IFileService, fileService); const environmentService = instantiationService.stub(IEnvironmentService, <Partial<IEnvironmentService>>{ userRoamingDataHome: ROOT, workspaceStorageHome }); - const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, new NullLogService())); + const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, new UriIdentityService(fileService), new NullLogService())); instantiationService.stub(IUserDataProfileService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService)); instantiationService.stub(IExtensionStorageService, instantiationService.createInstance(ExtensionStorageService)); diff --git a/src/vs/workbench/services/keybinding/test/browser/keybindingEditing.test.ts b/src/vs/workbench/services/keybinding/test/browser/keybindingEditing.test.ts index a20bd496d4b..40e00516362 100644 --- a/src/vs/workbench/services/keybinding/test/browser/keybindingEditing.test.ts +++ b/src/vs/workbench/services/keybinding/test/browser/keybindingEditing.test.ts @@ -31,6 +31,7 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { UserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfileService'; import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; interface Modifiers { metaKey?: boolean; @@ -66,7 +67,7 @@ suite('KeybindingsEditing', () => { const configService = new TestConfigurationService(); configService.setUserConfiguration('files', { 'eol': '\n' }); - const userDataProfilesService = new UserDataProfilesService(environmentService, fileService, logService); + const userDataProfilesService = new UserDataProfilesService(environmentService, fileService, new UriIdentityService(fileService), logService); userDataProfileService = new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService); instantiationService = workbenchInstantiationService({ diff --git a/src/vs/workbench/services/storage/test/browser/storageService.test.ts b/src/vs/workbench/services/storage/test/browser/storageService.test.ts index 4205c92d7a8..8491d3f9301 100644 --- a/src/vs/workbench/services/storage/test/browser/storageService.test.ts +++ b/src/vs/workbench/services/storage/test/browser/storageService.test.ts @@ -16,6 +16,7 @@ import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFil import { NullLogService } from 'vs/platform/log/common/log'; import { StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { createSuite } from 'vs/platform/storage/test/common/storageService.test'; +import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; import { IUserDataProfile, UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { BrowserStorageService, IndexedDBStorageDatabase } from 'vs/workbench/services/storage/browser/storageService'; import { UserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfileService'; @@ -46,7 +47,7 @@ async function createStorageService(): Promise<[DisposableStore, BrowserStorageS extensionsResource: joinPath(inMemoryExtraProfileRoot, 'extensionsResource') }; - const storageService = disposables.add(new BrowserStorageService({ id: 'workspace-storage-test' }, new UserDataProfileService(inMemoryExtraProfile, new UserDataProfilesService(TestEnvironmentService, fileService, logService)), logService)); + const storageService = disposables.add(new BrowserStorageService({ id: 'workspace-storage-test' }, new UserDataProfileService(inMemoryExtraProfile, new UserDataProfilesService(TestEnvironmentService, fileService, new UriIdentityService(fileService), logService)), logService)); await storageService.initialize(); diff --git a/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts b/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts index bed76364445..6e09fd1d153 100644 --- a/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts +++ b/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts @@ -10,7 +10,7 @@ import { MenuId } from 'vs/platform/actions/common/actions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IUserDataProfile, PROFILES_ENABLEMENT_CONFIG, UseDefaultProfileFlags } from 'vs/platform/userDataProfile/common/userDataProfile'; import { ContextKeyDefinedExpr, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { IsWebContext, ProductQualityContext } from 'vs/platform/contextkey/common/contextkeys'; +import { ProductQualityContext } from 'vs/platform/contextkey/common/contextkeys'; export interface DidChangeUserDataProfileEvent { readonly preserveData: boolean; @@ -73,4 +73,4 @@ export const PROFILES_TTILE = { value: localize('settings profiles', "Settings P export const PROFILES_CATEGORY = PROFILES_TTILE.value; export const PROFILE_EXTENSION = 'code-profile'; export const PROFILE_FILTER = [{ name: localize('profile', "Settings Profile"), extensions: [PROFILE_EXTENSION] }]; -export const PROFILES_ENABLEMENT_CONTEXT = ContextKeyExpr.and(ProductQualityContext.notEqualsTo('stable'), IsWebContext.negate(), ContextKeyDefinedExpr.create(`config.${PROFILES_ENABLEMENT_CONFIG}`)); +export const PROFILES_ENABLEMENT_CONTEXT = ContextKeyExpr.and(ProductQualityContext.notEqualsTo('stable'), ContextKeyDefinedExpr.create(`config.${PROFILES_ENABLEMENT_CONFIG}`)); diff --git a/src/vs/workbench/services/userDataProfile/common/userDataProfileService.ts b/src/vs/workbench/services/userDataProfile/common/userDataProfileService.ts index 57c6e4161a2..e03387a0b29 100644 --- a/src/vs/workbench/services/userDataProfile/common/userDataProfileService.ts +++ b/src/vs/workbench/services/userDataProfile/common/userDataProfileService.ts @@ -19,7 +19,10 @@ export class UserDataProfileService extends Disposable implements IUserDataProfi private _currentProfile: IUserDataProfile; get currentProfile(): IUserDataProfile { return this._currentProfile; } - constructor(currentProfile: IUserDataProfile, userDataProfilesService: IUserDataProfilesService) { + constructor( + currentProfile: IUserDataProfile, + @IUserDataProfilesService userDataProfilesService: IUserDataProfilesService + ) { super(); this._currentProfile = currentProfile; this._register(userDataProfilesService.onDidChangeProfiles(() => { diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 1a2209c0e43..9b0dc8d9c43 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -163,7 +163,7 @@ import { ILayoutOffsetInfo } from 'vs/platform/layout/browser/layoutService'; import { IUserDataProfilesService, UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { UserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfileService'; import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; -import { EnablementState, IExtensionManagementServer, IScannedExtension, IWebExtensionsScannerService, IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService, ScanOptions } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { EnablementState, IExtensionManagementServer, IScannedExtension, IWebExtensionsScannerService, IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { InstallVSIXOptions, ILocalExtension, IGalleryExtension, InstallOptions, IExtensionIdentifier, UninstallOptions, IExtensionsControlManifest, IGalleryMetadata, IExtensionManagementParticipant } from 'vs/platform/extensionManagement/common/extensionManagement'; import { Codicon } from 'vs/base/common/codicons'; @@ -286,9 +286,10 @@ export function workbenchInstantiationService( instantiationService.stub(IModelService, disposables.add(instantiationService.createInstance(ModelService))); const fileService = overrides?.fileService ? overrides.fileService(instantiationService) : new TestFileService(); instantiationService.stub(IFileService, fileService); - const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, new NullLogService())); + const uriIdentityService = new UriIdentityService(fileService); + instantiationService.stub(IUriIdentityService, uriIdentityService); + const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, uriIdentityService, new NullLogService())); instantiationService.stub(IUserDataProfileService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService)); - instantiationService.stub(IUriIdentityService, new UriIdentityService(fileService)); instantiationService.stub(IWorkingCopyBackupService, new TestWorkingCopyBackupService()); instantiationService.stub(ITelemetryService, NullTelemetryService); instantiationService.stub(INotificationService, new TestNotificationService()); @@ -2003,9 +2004,13 @@ export class TestWorkbenchExtensionManagementService implements IWorkbenchExtens export class TestWebExtensionsScannerService implements IWebExtensionsScannerService { _serviceBrand: undefined; + onDidChangeProfileExtensions = Event.None; async scanSystemExtensions(): Promise<IExtension[]> { return []; } - async scanUserExtensions(options?: ScanOptions | undefined): Promise<IScannedExtension[]> { return []; } + async scanUserExtensions(): Promise<IScannedExtension[]> { return []; } async scanExtensionsUnderDevelopment(): Promise<IExtension[]> { return []; } + async copyExtensions(): Promise<void> { + throw new Error('Method not implemented.'); + } scanExistingExtension(extensionLocation: URI, extensionType: ExtensionType): Promise<IScannedExtension | null> { throw new Error('Method not implemented.'); } @@ -2015,7 +2020,7 @@ export class TestWebExtensionsScannerService implements IWebExtensionsScannerSer addExtensionFromGallery(galleryExtension: IGalleryExtension, metadata?: Partial<IGalleryMetadata & { isApplicationScoped: boolean; isMachineScoped: boolean; isBuiltin: boolean; isSystem: boolean; updated: boolean; preRelease: boolean; installedTimestamp: number }> | undefined): Promise<IExtension> { throw new Error('Method not implemented.'); } - removeExtension(identifier: IExtensionIdentifier, version?: string | undefined): Promise<void> { + removeExtension(): Promise<void> { throw new Error('Method not implemented.'); } scanMetadata(extensionLocation: URI): Promise<Partial<IGalleryMetadata & { isApplicationScoped: boolean; isMachineScoped: boolean; isBuiltin: boolean; isSystem: boolean; updated: boolean; preRelease: boolean; installedTimestamp: number }> | undefined> { diff --git a/src/vs/workbench/test/electron-browser/workbenchTestServices.ts b/src/vs/workbench/test/electron-browser/workbenchTestServices.ts index 1880f3377d6..3a35d0f4018 100644 --- a/src/vs/workbench/test/electron-browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/electron-browser/workbenchTestServices.ts @@ -53,6 +53,7 @@ import { FileService } from 'vs/platform/files/common/fileService'; import { joinPath } from 'vs/base/common/resources'; import { UserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfileService'; import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService'; const args = parseArgs(process.argv, OPTIONS); @@ -289,7 +290,8 @@ export function workbenchInstantiationService(disposables = new DisposableStore( instantiationService.stub(INativeEnvironmentService, TestEnvironmentService); instantiationService.stub(IWorkbenchEnvironmentService, TestEnvironmentService); instantiationService.stub(INativeWorkbenchEnvironmentService, TestEnvironmentService); - const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(TestEnvironmentService, new FileService(new NullLogService()), new NullLogService())); + const fileService = new FileService(new NullLogService()); + const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(TestEnvironmentService, fileService, new UriIdentityService(fileService), new NullLogService())); instantiationService.stub(IUserDataProfileService, new UserDataProfileService(userDataProfilesService.defaultProfile, userDataProfilesService)); return instantiationService; diff --git a/yarn.lock b/yarn.lock index b0e565c38b0..68918b4a06a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12096,20 +12096,20 @@ xterm-addon-unicode11@0.4.0-beta.3: resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.4.0-beta.3.tgz#f350184155fafd5ad0d6fbf31d13e6ca7dea1efa" integrity sha512-FryZAVwbUjKTmwXnm1trch/2XO60F5JsDvOkZhzobV1hm10sFLVuZpFyHXiUx7TFeeFsvNP+S77LAtWoeT5z+Q== -xterm-addon-webgl@0.13.0-beta.2: - version "0.13.0-beta.2" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.13.0-beta.2.tgz#f58a7a3641ad7c8ac82dd24cfb0165656ed9ac1c" - integrity sha512-98tX0BkpD402RoCO6SyikUXpzCn9/OQhlXsRmM/kRFCxMWWofStWTXzCPhN0MjIx2IdGueDjCmnShhidwihErg== - -xterm-headless@4.20.0-beta.5: - version "4.20.0-beta.5" - resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.20.0-beta.5.tgz#edcff27eb6437d158e6aea2ed7658e783bee5641" - integrity sha512-8SnVUsuNUrQ5P0XU/9Iau3uK7Tf8q/p0KHHwkwJXVxZDIlaDH9XKSs91U9BjJJE3sJgRxH4NSiDYR3vFLSFpxw== - -xterm@4.20.0-beta.5: - version "4.20.0-beta.5" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.20.0-beta.5.tgz#d707b0dcb477a554135fb767b24003fced079866" - integrity sha512-KBWfk9UPBKRy662DVGGTZEcW1becEjYvlyWbn2hLj9h2gy6Q4EEEEbggJh8I7SGwdFizl+apHQGhEOZmFCA70w== +xterm-addon-webgl@0.13.0-beta.3: + version "0.13.0-beta.3" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.13.0-beta.3.tgz#2b456c3105238e64b40a30787d6335f5f6f85abb" + integrity sha512-DFGcXAolA0VTsOLIKcORxUOp/FTJdD/YiRzKVLARjgOycwVRKvW2L5Tge8Z7ysZ16sKfnV2vCXyonXYfUWozXw== + +xterm-headless@4.20.0-beta.6: + version "4.20.0-beta.6" + resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.20.0-beta.6.tgz#bd016379e9fac47e5b8870d567cdf330cf6f49fc" + integrity sha512-EV0V7pxMKI0OEcOCD+6vdXq6rBARr7dSN3PovTsZnDWg5dmvUb2eEmz6BTejJj3UVd/JXNEmEXM+tCh97rDCDg== + +xterm@4.20.0-beta.6: + version "4.20.0-beta.6" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.20.0-beta.6.tgz#3ed87ba383a5cf44284098278f714df7113e3e3c" + integrity sha512-xJd6vyOuYo4Ht/hTY3DyXGIj0U6kHjr2vWQ1lRmearo3t7QKf7uqOAAfTLeWt/g1P8qe/r0DnsNTeag6vI9RVw== y18n@^3.2.1: version "3.2.2" |