diff --git a/src/service-override/environment.ts b/src/service-override/environment.ts index 80e34d8d..4b5f8a37 100644 --- a/src/service-override/environment.ts +++ b/src/service-override/environment.ts @@ -6,6 +6,18 @@ import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/envir import { IProductService } from 'vs/platform/product/common/productService.service' import type { IWorkbenchConstructionOptions } from 'vs/workbench/browser/web.api' import { getWorkbenchConstructionOptions, getWorkspaceIdentifier, logsPath } from '../workbench' +import type { URI } from 'vs/base/common/uri' + +export interface WorkbenchEnvironmentServiceOverrides { + windowLogsPath?: URI + logFile?: URI + userRoamingDataHome?: URI + argvResource?: URI + cacheHome?: URI + workspaceStorageHome?: URI + localHistoryHome?: URI + stateResource?: URI +} class InjectedBrowserWorkbenchEnvironmentService extends BrowserWorkbenchEnvironmentService @@ -13,24 +25,45 @@ class InjectedBrowserWorkbenchEnvironmentService { constructor( workspaceId: string = getWorkspaceIdentifier().id, - options: IWorkbenchConstructionOptions = getWorkbenchConstructionOptions(), + private overrides: WorkbenchEnvironmentServiceOverrides = {}, @IProductService productService: IProductService ) { - super(workspaceId, logsPath, options, productService) + super(workspaceId, logsPath, getWorkbenchConstructionOptions(), productService) } -} -/** - * @deprecated Provide construction option via the services `initialize` function `configuration` parameter - */ -function getServiceOverride(options: IWorkbenchConstructionOptions): IEditorOverrideServices -function getServiceOverride(): IEditorOverrideServices + override get windowLogsPath() { + return this.overrides.windowLogsPath ?? super.windowLogsPath + } + override get logFile() { + return this.overrides.logFile ?? super.logFile + } + override get userRoamingDataHome() { + return this.overrides.userRoamingDataHome ?? super.userRoamingDataHome + } + override get argvResource() { + return this.overrides.argvResource ?? super.argvResource + } + override get cacheHome() { + return this.overrides.cacheHome ?? super.cacheHome + } + override get workspaceStorageHome() { + return this.overrides.workspaceStorageHome ?? super.workspaceStorageHome + } + override get localHistoryHome() { + return this.overrides.localHistoryHome ?? super.localHistoryHome + } + override get stateResource() { + return this.overrides.stateResource ?? super.stateResource + } +} -function getServiceOverride(options?: IWorkbenchConstructionOptions): IEditorOverrideServices { +function getServiceOverride( + overrides?: WorkbenchEnvironmentServiceOverrides +): IEditorOverrideServices { return { [IEnvironmentService.toString()]: new SyncDescriptor( InjectedBrowserWorkbenchEnvironmentService, - [undefined, options], + [undefined, overrides], true ) } diff --git a/src/service-override/files.ts b/src/service-override/files.ts index 680d3329..4133f022 100644 --- a/src/service-override/files.ts +++ b/src/service-override/files.ts @@ -759,7 +759,8 @@ class OverlayFileSystemProvider capabilities = FileSystemProviderCapabilities.FileReadWrite | FileSystemProviderCapabilities.PathCaseSensitive | - FileSystemProviderCapabilities.FileReadStream + FileSystemProviderCapabilities.FileReadStream | + FileSystemProviderCapabilities.FileAppend private async readFromDelegates( caller: (delegate: IFileSystemProviderWithFileReadWriteCapability) => Promise, @@ -768,7 +769,7 @@ class OverlayFileSystemProvider if (this.delegates.length === 0) { throw createFileSystemProviderError('No delegate', FileSystemProviderErrorCode.Unavailable) } - let firstError: unknown | undefined + const errors: unknown[] = [] for (const delegate of this.delegates) { if (token != null && token.isCancellationRequested) { throw new Error('Cancelled') @@ -776,21 +777,28 @@ class OverlayFileSystemProvider try { return await caller(delegate) } catch (err) { - firstError ??= err - if ( - err instanceof FileSystemProviderError && - [ - FileSystemProviderErrorCode.NoPermissions, - FileSystemProviderErrorCode.FileNotFound, - FileSystemProviderErrorCode.Unavailable - ].includes(err.code) - ) { - continue + errors.push(err) + if (err instanceof FileSystemProviderError) { + if ( + [ + FileSystemProviderErrorCode.NoPermissions, + FileSystemProviderErrorCode.FileNotFound, + FileSystemProviderErrorCode.Unavailable + ].includes(err.code) + ) { + continue + } } throw err } } - throw firstError + + const fileSystemErrors = errors?.filter((err) => err instanceof FileSystemProviderError) + const mostRelevantErrors = + fileSystemErrors.find((err) => err.code === FileSystemProviderErrorCode.FileNotFound) ?? + fileSystemErrors[0] ?? + errors[0] + throw mostRelevantErrors } private async writeToDelegates( @@ -951,7 +959,7 @@ class OverlayFileSystemProvider } } -class DelegateFileSystemProvider implements IFileSystemProvider { +class DelegateFileSystemProvider implements IFileSystemProviderWithFileReadWriteCapability { constructor( private options: { delegate: IFileSystemProvider @@ -977,17 +985,32 @@ class DelegateFileSystemProvider implements IFileSystemProvider { ? (resource: URI): Promise => { return this.options.delegate.readFile!(this.options.toDelegate(resource)) } - : undefined + : () => { + throw createFileSystemProviderError( + 'No delegate', + FileSystemProviderErrorCode.Unavailable + ) + } writeFile = this.options.delegate.writeFile != null ? (resource: URI, content: Uint8Array, opts: IFileWriteOptions): Promise => { return this.options.delegate.writeFile!(this.options.toDelegate(resource), content, opts) } - : undefined + : () => { + throw createFileSystemProviderError( + 'No delegate', + FileSystemProviderErrorCode.Unavailable + ) + } watch(resource: URI, opts: IWatchOptions): IDisposable { - return this.options.delegate.watch(this.options.toDelegate(resource), opts) + try { + return this.options.delegate.watch(this.options.toDelegate(resource), opts) + } catch { + // ignore watch error + } + return Disposable.None } stat(resource: URI): Promise { diff --git a/src/service-override/storage.ts b/src/service-override/storage.ts index 5d8f360b..b22f0939 100644 --- a/src/service-override/storage.ts +++ b/src/service-override/storage.ts @@ -1,214 +1,79 @@ +import { VSBuffer } from 'vs/base/common/buffer' import { Event } from 'vs/base/common/event' -import type { IDisposable } from 'vs/base/common/lifecycle' +import type { DisposableStore } from 'vs/base/common/lifecycle' +import type { URI } from 'vs/base/common/uri' import { - type IStorage, type IStorageDatabase, type IStorageItemsChangeEvent, type IUpdateRequest, Storage } from 'vs/base/parts/storage/common/storage' -import type { IEditorOverrideServices } from 'vs/editor/standalone/browser/standaloneServices' +import type { ServicesAccessor } from 'vs/editor/browser/editorExtensions' +import { type IEditorOverrideServices } from 'vs/editor/standalone/browser/standaloneServices' +import { ExtensionStorageService } from 'vs/platform/extensionManagement/common/extensionStorage' +import { IExtensionStorageService } from 'vs/platform/extensionManagement/common/extensionStorage.service' +import { IFileService } from 'vs/platform/files/common/files.service' import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors' -import { - AbstractStorageService, - StorageScope as VSStorageScope -} from 'vs/platform/storage/common/storage' +import { ILogService } from 'vs/platform/log/common/log.service' +import { AbstractStorageService, StorageScope } from 'vs/platform/storage/common/storage' import { IStorageService } from 'vs/platform/storage/common/storage.service' +import type { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile' +import type { IAnyWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace' +import { IHostService } from 'vs/workbench/services/host/browser/host.service' import { BrowserStorageService } from 'vs/workbench/services/storage/browser/storageService' -import { ILogService } from 'vs/platform/log/common/log.service' import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile.service' -import { IHostService } from 'vs/workbench/services/host/browser/host.service' -import { ExtensionStorageService } from 'vs/platform/extensionManagement/common/extensionStorage' -import { IExtensionStorageService } from 'vs/platform/extensionManagement/common/extensionStorage.service' import { registerServiceInitializeParticipant, registerServiceInitializePreParticipant } from '../lifecycle' +import { IInstantiationService } from '../services' import { getWorkspaceIdentifier } from '../workbench' -export enum StorageScope { - APPLICATION = VSStorageScope.APPLICATION, - PROFILE = VSStorageScope.PROFILE, - WORKSPACE = VSStorageScope.WORKSPACE -} - -export interface IStorageProvider { - read(scope: StorageScope): Map | undefined - write(scope: StorageScope, data: Map): Promise - close?(scope: StorageScope): Promise - onDidChange?(listener: (event: IStorageItemsChangeEvent) => void): IDisposable - optimize?(scope: StorageScope): Promise -} - -class ExternalStorage extends Storage { - constructor(scope: StorageScope, provider: IStorageProvider) { - const items = provider.read(scope) +export class JsonFileStorageDatabase implements IStorageDatabase { + onDidChangeItemsExternal = Event.None - super(new ExternalStorageDatabase(scope, provider, items)) + private cache: Map | undefined = undefined - if (items != null) { - for (const [key, value] of items) { - this.items.set(key, value) + constructor( + private fileUri: URI, + @IFileService private fileService: IFileService + ) {} + + private async init() { + if (this.cache == null) { + try { + const content = await this.fileService.readFile(this.fileUri) + + const data = JSON.parse(content.value.toString()) as Record + this.cache = new Map(Object.entries(data)) + } catch { + this.cache = new Map() } } } -} - -class ExternalStorageDatabase implements IStorageDatabase { - readonly onDidChangeItemsExternal: Event - - constructor( - private readonly scope: StorageScope, - private readonly provider: IStorageProvider, - private readonly items = new Map() - ) { - this.onDidChangeItemsExternal = this.provider.onDidChange ?? Event.None - } async getItems(): Promise> { - return this.items + await this.init() + return this.cache! } async updateItems(request: IUpdateRequest): Promise { - request.insert?.forEach((value, key) => this.items.set(key, value)) - - request.delete?.forEach((key) => this.items.delete(key)) - - await this.provider.write(this.scope, this.items) - } - - async close() { - return await this.provider.close?.(this.scope) - } - - async optimize(): Promise { - return await this.provider.optimize?.(this.scope) - } -} - -class ExternalStorageService extends AbstractStorageService { - private readonly applicationStorage = this._register( - new ExternalStorage(StorageScope.APPLICATION, this.provider) - ) - - private readonly profileStorage = this._register( - new ExternalStorage(StorageScope.PROFILE, this.provider) - ) - - private readonly workspaceStorage = this._register( - new ExternalStorage(StorageScope.WORKSPACE, this.provider) - ) - - constructor( - protected readonly provider: IStorageProvider, - private fallbackOverride?: Record - ) { - super({ - flushInterval: 5000 - }) - - this._register( - this.workspaceStorage.onDidChangeStorage((key) => - this.emitDidChangeValue(VSStorageScope.WORKSPACE, key) - ) - ) - this._register( - this.profileStorage.onDidChangeStorage((key) => - this.emitDidChangeValue(VSStorageScope.PROFILE, key) - ) - ) - this._register( - this.applicationStorage.onDidChangeStorage((key) => - this.emitDidChangeValue(VSStorageScope.APPLICATION, key) - ) - ) - } - - protected getStorage(scope: VSStorageScope): IStorage { - switch (scope) { - case VSStorageScope.APPLICATION: - return this.applicationStorage - case VSStorageScope.PROFILE: - return this.profileStorage - default: - return this.workspaceStorage + await this.init() + for (const [key, value] of request.insert ?? []) { + this.cache?.set(key, value) } - } - - protected getLogDetails(scope: VSStorageScope): string | undefined { - switch (scope) { - case VSStorageScope.APPLICATION: - return 'External (application)' - case VSStorageScope.PROFILE: - return 'External (profile)' - default: - return 'External (workspace)' + for (const key of request.delete ?? []) { + this.cache?.delete(key) } - } - - protected async doInitialize(): Promise { - // no-op - } - - protected async switchToProfile(): Promise { - // no-op - } - - protected async switchToWorkspace(): Promise { - // no-op - } - - hasScope(): boolean { - return false - } - - override get(key: string, scope: VSStorageScope, fallbackValue: string): string - override get(key: string, scope: VSStorageScope): string | undefined - override get(key: string, scope: VSStorageScope, fallbackValue?: string): string | undefined { - return this.getStorage(scope).get( - key, - (this.fallbackOverride?.[key] as string | undefined) ?? fallbackValue - ) - } - - override getBoolean(key: string, scope: VSStorageScope, fallbackValue: boolean): boolean - override getBoolean(key: string, scope: VSStorageScope): boolean | undefined - override getBoolean( - key: string, - scope: VSStorageScope, - fallbackValue?: boolean - ): boolean | undefined { - return this.getStorage(scope).getBoolean( - key, - (this.fallbackOverride?.[key] as boolean | undefined) ?? fallbackValue - ) - } - - override getNumber(key: string, scope: VSStorageScope, fallbackValue: number): number - override getNumber(key: string, scope: VSStorageScope): number | undefined - override getNumber( - key: string, - scope: VSStorageScope, - fallbackValue?: number - ): number | undefined { - return this.getStorage(scope).getNumber( - key, - (this.fallbackOverride?.[key] as number | undefined) ?? fallbackValue + await this.fileService!.writeFile( + this.fileUri, + VSBuffer.fromString(JSON.stringify(Object.fromEntries(this.cache!), null, 2)) ) } - - override getObject(key: string, scope: VSStorageScope, fallbackValue: object): object - override getObject(key: string, scope: VSStorageScope): object | undefined - override getObject( - key: string, - scope: VSStorageScope, - fallbackValue?: object - ): object | undefined { - return this.getStorage(scope).getObject( - key, - (this.fallbackOverride?.[key] as object | undefined) ?? fallbackValue - ) + async optimize(): Promise { + // nothing to do } + async close(): Promise {} } registerServiceInitializePreParticipant(async (accessor) => { @@ -228,29 +93,48 @@ registerServiceInitializeParticipant(async (accessor) => { }) }) +export interface DatabaseFactories { + [StorageScope.APPLICATION]?: ( + accessor: ServicesAccessor, + disposableStore: DisposableStore + ) => IStorageDatabase + [StorageScope.PROFILE]?: ( + accessor: ServicesAccessor, + profile: IUserDataProfile, + disposableStore: DisposableStore + ) => IStorageDatabase + [StorageScope.WORKSPACE]?: ( + accessor: ServicesAccessor, + workspace: IAnyWorkspaceIdentifier, + disposableStore: DisposableStore + ) => IStorageDatabase +} + class InjectedBrowserStorageService extends BrowserStorageService { constructor( private fallbackOverride: Record | undefined, + private databaseFactories: DatabaseFactories = {}, @IUserDataProfileService userDataProfileService: IUserDataProfileService, - @ILogService logService: ILogService + @ILogService logService: ILogService, + @IInstantiationService private instantiationService: IInstantiationService ) { super(getWorkspaceIdentifier(), userDataProfileService, logService) } - override get(key: string, scope: VSStorageScope, fallbackValue: string): string - override get(key: string, scope: VSStorageScope): string | undefined - override get(key: string, scope: VSStorageScope, fallbackValue?: string): string | undefined { + override get(key: string, scope: StorageScope, fallbackValue: string): string + override get(key: string, scope: StorageScope): string | undefined + override get(key: string, scope: StorageScope, fallbackValue?: string): string | undefined { return this.getStorage(scope)?.get( key, (this.fallbackOverride?.[key] as string | undefined) ?? fallbackValue ) } - override getBoolean(key: string, scope: VSStorageScope, fallbackValue: boolean): boolean - override getBoolean(key: string, scope: VSStorageScope): boolean | undefined + override getBoolean(key: string, scope: StorageScope, fallbackValue: boolean): boolean + override getBoolean(key: string, scope: StorageScope): boolean | undefined override getBoolean( key: string, - scope: VSStorageScope, + scope: StorageScope, fallbackValue?: boolean ): boolean | undefined { return this.getStorage(scope)?.getBoolean( @@ -259,65 +143,80 @@ class InjectedBrowserStorageService extends BrowserStorageService { ) } - override getNumber(key: string, scope: VSStorageScope, fallbackValue: number): number - override getNumber(key: string, scope: VSStorageScope): number | undefined - override getNumber( - key: string, - scope: VSStorageScope, - fallbackValue?: number - ): number | undefined { + override getNumber(key: string, scope: StorageScope, fallbackValue: number): number + override getNumber(key: string, scope: StorageScope): number | undefined + override getNumber(key: string, scope: StorageScope, fallbackValue?: number): number | undefined { return this.getStorage(scope)?.getNumber( key, (this.fallbackOverride?.[key] as number | undefined) ?? fallbackValue ) } - override getObject(key: string, scope: VSStorageScope, fallbackValue: object): object - override getObject(key: string, scope: VSStorageScope): object | undefined - override getObject( - key: string, - scope: VSStorageScope, - fallbackValue?: object - ): object | undefined { + override getObject(key: string, scope: StorageScope, fallbackValue: object): object + override getObject(key: string, scope: StorageScope): object | undefined + override getObject(key: string, scope: StorageScope, fallbackValue?: object): object | undefined { return this.getStorage(scope)?.getObject( key, (this.fallbackOverride?.[key] as object | undefined) ?? fallbackValue ) } + + protected override async createWorkspaceStorageDatabase( + workspace: IAnyWorkspaceIdentifier, + disposableStore: DisposableStore + ) { + const factory = this.databaseFactories[StorageScope.WORKSPACE] + if (factory != null) { + return this.instantiationService.invokeFunction(factory, workspace, disposableStore) + } + return await super.createWorkspaceStorageDatabase(workspace, disposableStore) + } + + protected override async createApplicationStorageDatabase(disposableStore: DisposableStore) { + const factory = this.databaseFactories[StorageScope.APPLICATION] + if (factory != null) { + return this.instantiationService.invokeFunction(factory, disposableStore) + } + return await super.createApplicationStorageDatabase(disposableStore) + } + + protected override async createProfileStorageDatabase( + profile: IUserDataProfile, + disposableStore: DisposableStore + ) { + const factory = this.databaseFactories[StorageScope.PROFILE] + if (factory != null) { + return this.instantiationService.invokeFunction(factory, profile, disposableStore) + } + return await super.createProfileStorageDatabase(profile, disposableStore) + } } interface StorageServiceParameters { - customProvider?: IStorageProvider /** * Allows to override the storage key default values */ fallbackOverride?: Record + + /** + * Allow to override the storage database for a specific scope (application, profile, workspace) + */ + databaseFactories?: DatabaseFactories } export default function getStorageServiceOverride({ - customProvider, - fallbackOverride + fallbackOverride, + databaseFactories }: StorageServiceParameters = {}): IEditorOverrideServices { - if (customProvider != null) { - return { - [IStorageService.toString()]: new SyncDescriptor( - ExternalStorageService, - [customProvider, fallbackOverride], - true - ), - [IExtensionStorageService.toString()]: new SyncDescriptor(ExtensionStorageService, [], true) - } - } else { - return { - [IStorageService.toString()]: new SyncDescriptor( - InjectedBrowserStorageService, - [fallbackOverride], - true - ), - [IExtensionStorageService.toString()]: new SyncDescriptor(ExtensionStorageService, [], true) - } + return { + [IStorageService.toString()]: new SyncDescriptor( + InjectedBrowserStorageService, + [fallbackOverride, databaseFactories], + true + ), + [IExtensionStorageService.toString()]: new SyncDescriptor(ExtensionStorageService, [], true) } } -export { ExternalStorageService, InjectedBrowserStorageService as BrowserStorageService } +export { InjectedBrowserStorageService as BrowserStorageService, Storage, StorageScope } export type { IStorageItemsChangeEvent } diff --git a/src/workbench.ts b/src/workbench.ts index de4ea56c..4757a485 100644 --- a/src/workbench.ts +++ b/src/workbench.ts @@ -47,12 +47,12 @@ function resolveWorkspace(configuration: IWorkbenchConstructionOptions): IAnyWor // Multi-root workspace if (workspace != null && isWorkspaceToOpen(workspace)) { - return getWorkspaceIdentifierFromUri(workspace.workspaceUri) + return getWorkspaceIdentifierFromUri(workspace.workspaceUri, workspace.id) } // Single-folder workspace if (workspace != null && isFolderToOpen(workspace)) { - return getSingleFolderWorkspaceIdentifier(workspace.folderUri) + return getSingleFolderWorkspaceIdentifier(workspace.folderUri, workspace.id) } // Empty window workspace diff --git a/vscode-patches/0091-refactor-extract-storage-creation-method.patch b/vscode-patches/0091-refactor-extract-storage-creation-method.patch new file mode 100644 index 00000000..eaf0d104 --- /dev/null +++ b/vscode-patches/0091-refactor-extract-storage-creation-method.patch @@ -0,0 +1,165 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Lo=C3=AFc=20Mangeonjean?= +Date: Wed, 18 Feb 2026 15:29:49 +0100 +Subject: [PATCH] refactor: extract storage creation method + +--- + .../storage/browser/storageService.ts | 69 ++++++++++++------- + 1 file changed, 45 insertions(+), 24 deletions(-) + +diff --git a/src/vs/workbench/services/storage/browser/storageService.ts b/src/vs/workbench/services/storage/browser/storageService.ts +index db1abe1dec4..0d566e2f71a 100644 +--- a/src/vs/workbench/services/storage/browser/storageService.ts ++++ b/src/vs/workbench/services/storage/browser/storageService.ts +@@ -19,28 +19,37 @@ import { isUserDataProfile, IUserDataProfile } from '../../../../platform/userDa + import { IAnyWorkspaceIdentifier } from '../../../../platform/workspace/common/workspace.js'; + import { IUserDataProfileService } from '../../userDataProfile/common/userDataProfile.js'; + ++function isIIndexedDBStorageDatabase(thing: unknown): thing is IIndexedDBStorageDatabase { ++ return thing instanceof IndexedDBStorageDatabase || thing instanceof InMemoryIndexedDBStorageDatabase; ++} ++ ++function getIIndexedDBStorageDatabase(database: IStorageDatabase | undefined): IIndexedDBStorageDatabase | undefined { ++ return isIIndexedDBStorageDatabase(database) ? database : undefined; ++} ++ + export class BrowserStorageService extends AbstractStorageService { + + private static BROWSER_DEFAULT_FLUSH_INTERVAL = 5 * 1000; // every 5s because async operations are not permitted on shutdown + + private applicationStorage: IStorage | undefined; +- private applicationStorageDatabase: IIndexedDBStorageDatabase | undefined; +- private readonly applicationStoragePromise = new DeferredPromise<{ indexedDb: IIndexedDBStorageDatabase; storage: IStorage }>(); ++ private applicationStorageDatabase: IStorageDatabase | undefined; ++ private readonly applicationStoragePromise = new DeferredPromise<{ database: IStorageDatabase; storage: IStorage }>(); ++ private readonly applicationStorageDisposables = this._register(new DisposableStore()); + + private profileStorage: IStorage | undefined; +- private profileStorageDatabase: IIndexedDBStorageDatabase | undefined; ++ private profileStorageDatabase: IStorageDatabase | undefined; + private profileStorageProfile: IUserDataProfile; + private readonly profileStorageDisposables = this._register(new DisposableStore()); + + private workspaceStorage: IStorage | undefined; +- private workspaceStorageDatabase: IIndexedDBStorageDatabase | undefined; ++ private workspaceStorageDatabase: IStorageDatabase | undefined; + private readonly workspaceStorageDisposables = this._register(new DisposableStore()); + + get hasPendingUpdate(): boolean { + return Boolean( +- this.applicationStorageDatabase?.hasPendingUpdate || +- this.profileStorageDatabase?.hasPendingUpdate || +- this.workspaceStorageDatabase?.hasPendingUpdate ++ getIIndexedDBStorageDatabase(this.applicationStorageDatabase)?.hasPendingUpdate || ++ getIIndexedDBStorageDatabase(this.profileStorageDatabase)?.hasPendingUpdate || ++ getIIndexedDBStorageDatabase(this.workspaceStorageDatabase)?.hasPendingUpdate + ); + } + +@@ -71,9 +80,7 @@ export class BrowserStorageService extends AbstractStorageService { + } + + private async createApplicationStorage(): Promise { +- const applicationStorageIndexedDB = await IndexedDBStorageDatabase.createApplicationStorage(this.logService); +- +- this.applicationStorageDatabase = this._register(applicationStorageIndexedDB); ++ this.applicationStorageDatabase = await this.createApplicationStorageDatabase(this.applicationStorageDisposables); + this.applicationStorage = this._register(new Storage(this.applicationStorageDatabase)); + + this._register(this.applicationStorage.onDidChangeStorage(e => this.emitDidChangeValue(StorageScope.APPLICATION, e))); +@@ -82,7 +89,13 @@ export class BrowserStorageService extends AbstractStorageService { + + this.updateIsNew(this.applicationStorage); + +- this.applicationStoragePromise.complete({ indexedDb: applicationStorageIndexedDB, storage: this.applicationStorage }); ++ this.applicationStoragePromise.complete({ database: this.applicationStorageDatabase, storage: this.applicationStorage }); ++ } ++ ++ protected async createApplicationStorageDatabase(disposableStore: DisposableStore): Promise { ++ const applicationStorageIndexedDB = await IndexedDBStorageDatabase.createApplicationStorage(this.logService); ++ ++ return disposableStore.add(applicationStorageIndexedDB); + } + + private async createProfileStorage(profile: IUserDataProfile): Promise { +@@ -100,16 +113,14 @@ export class BrowserStorageService extends AbstractStorageService { + // avoid creating the storage library a second time on + // the same DB. + +- const { indexedDb: applicationStorageIndexedDB, storage: applicationStorage } = await this.applicationStoragePromise.p; ++ const { database: applicationStorageDatabase, storage: applicationStorage } = await this.applicationStoragePromise.p; + +- this.profileStorageDatabase = applicationStorageIndexedDB; ++ this.profileStorageDatabase = applicationStorageDatabase; + this.profileStorage = applicationStorage; + + this.profileStorageDisposables.add(this.profileStorage.onDidChangeStorage(e => this.emitDidChangeValue(StorageScope.PROFILE, e))); + } else { +- const profileStorageIndexedDB = await IndexedDBStorageDatabase.createProfileStorage(this.profileStorageProfile, this.logService); +- +- this.profileStorageDatabase = this.profileStorageDisposables.add(profileStorageIndexedDB); ++ this.profileStorageDatabase = await this.createProfileStorageDatabase(this.profileStorageProfile, this.profileStorageDisposables); + this.profileStorage = this.profileStorageDisposables.add(new Storage(this.profileStorageDatabase)); + + this.profileStorageDisposables.add(this.profileStorage.onDidChangeStorage(e => this.emitDidChangeValue(StorageScope.PROFILE, e))); +@@ -120,13 +131,18 @@ export class BrowserStorageService extends AbstractStorageService { + } + } + ++ protected async createProfileStorageDatabase(profile: IUserDataProfile, disposableStore: DisposableStore): Promise { ++ const profileStorageIndexedDB = await IndexedDBStorageDatabase.createProfileStorage(profile, this.logService); ++ ++ return disposableStore.add(profileStorageIndexedDB); ++ } ++ + private async createWorkspaceStorage(): Promise { + // First clear any previously associated disposables + this.workspaceStorageDisposables.clear(); + +- const workspaceStorageIndexedDB = await IndexedDBStorageDatabase.createWorkspaceStorage(this.workspace.id, this.logService); + +- this.workspaceStorageDatabase = this.workspaceStorageDisposables.add(workspaceStorageIndexedDB); ++ this.workspaceStorageDatabase = await this.createWorkspaceStorageDatabase(this.workspace, this.workspaceStorageDisposables); + this.workspaceStorage = this.workspaceStorageDisposables.add(new Storage(this.workspaceStorageDatabase)); + + this.workspaceStorageDisposables.add(this.workspaceStorage.onDidChangeStorage(e => this.emitDidChangeValue(StorageScope.WORKSPACE, e))); +@@ -136,6 +152,11 @@ export class BrowserStorageService extends AbstractStorageService { + this.updateIsNew(this.workspaceStorage); + } + ++ protected async createWorkspaceStorageDatabase(workspace: IAnyWorkspaceIdentifier, disposableStore: DisposableStore): Promise { ++ const workspaceStorageIndexedDB = await IndexedDBStorageDatabase.createWorkspaceStorage(workspace.id, this.logService); ++ return disposableStore.add(workspaceStorageIndexedDB); ++ } ++ + private updateIsNew(storage: IStorage): void { + const firstOpen = storage.getBoolean(IS_NEW_KEY); + if (firstOpen === undefined) { +@@ -159,11 +180,11 @@ export class BrowserStorageService extends AbstractStorageService { + protected getLogDetails(scope: StorageScope): string | undefined { + switch (scope) { + case StorageScope.APPLICATION: +- return this.applicationStorageDatabase?.name; ++ return getIIndexedDBStorageDatabase(this.applicationStorageDatabase)?.name; + case StorageScope.PROFILE: +- return this.profileStorageDatabase?.name; ++ return getIIndexedDBStorageDatabase(this.profileStorageDatabase)?.name; + default: +- return this.workspaceStorageDatabase?.name; ++ return getIIndexedDBStorageDatabase(this.workspaceStorageDatabase)?.name; + } + } + +@@ -251,9 +272,9 @@ export class BrowserStorageService extends AbstractStorageService { + + // Clear databases + await Promises.settled([ +- this.applicationStorageDatabase?.clear() ?? Promise.resolve(), +- this.profileStorageDatabase?.clear() ?? Promise.resolve(), +- this.workspaceStorageDatabase?.clear() ?? Promise.resolve() ++ getIIndexedDBStorageDatabase(this.applicationStorageDatabase)?.clear() ?? Promise.resolve(), ++ getIIndexedDBStorageDatabase(this.profileStorageDatabase)?.clear() ?? Promise.resolve(), ++ getIIndexedDBStorageDatabase(this.workspaceStorageDatabase)?.clear() ?? Promise.resolve() + ]); + } + diff --git a/vscode-patches/0092-feat-allow-defining-the-workspace-id.patch b/vscode-patches/0092-feat-allow-defining-the-workspace-id.patch new file mode 100644 index 00000000..27c7eb1f --- /dev/null +++ b/vscode-patches/0092-feat-allow-defining-the-workspace-id.patch @@ -0,0 +1,70 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Lo=C3=AFc=20Mangeonjean?= +Date: Wed, 18 Feb 2026 16:48:57 +0100 +Subject: [PATCH] feat: allow defining the workspace id + +--- + src/vs/platform/window/common/window.ts | 1 + + src/vs/workbench/browser/web.main.ts | 4 ++-- + .../workbench/services/workspaces/browser/workspaces.ts | 8 ++++---- + 3 files changed, 7 insertions(+), 6 deletions(-) + +diff --git a/src/vs/platform/window/common/window.ts b/src/vs/platform/window/common/window.ts +index 256c55eba50..f2fc4f0be3e 100644 +--- a/src/vs/platform/window/common/window.ts ++++ b/src/vs/platform/window/common/window.ts +@@ -100,6 +100,7 @@ export interface IOpenEmptyWindowOptions extends IBaseOpenWindowsOptions { } + export type IWindowOpenable = IWorkspaceToOpen | IFolderToOpen | IFileToOpen; + + export interface IBaseWindowOpenable { ++ id?: string; + label?: string; + } + +diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts +index 39fc24b9891..1b80d9e5a48 100644 +--- a/src/vs/workbench/browser/web.main.ts ++++ b/src/vs/workbench/browser/web.main.ts +@@ -622,12 +622,12 @@ export class BrowserMain extends Disposable { + + // Multi-root workspace + if (workspace && isWorkspaceToOpen(workspace)) { +- return getWorkspaceIdentifier(workspace.workspaceUri); ++ return getWorkspaceIdentifier(workspace.workspaceUri, workspace.id); + } + + // Single-folder workspace + if (workspace && isFolderToOpen(workspace)) { +- return getSingleFolderWorkspaceIdentifier(workspace.folderUri); ++ return getSingleFolderWorkspaceIdentifier(workspace.folderUri, workspace.id); + } + + // Empty window workspace +diff --git a/src/vs/workbench/services/workspaces/browser/workspaces.ts b/src/vs/workbench/services/workspaces/browser/workspaces.ts +index 27fa5abae82..276d5053db6 100644 +--- a/src/vs/workbench/services/workspaces/browser/workspaces.ts ++++ b/src/vs/workbench/services/workspaces/browser/workspaces.ts +@@ -11,9 +11,9 @@ import { hash } from '../../../../base/common/hash.js'; + // NOTE: DO NOT CHANGE. IDENTIFIERS HAVE TO REMAIN STABLE + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +-export function getWorkspaceIdentifier(workspaceUri: URI): IWorkspaceIdentifier { ++export function getWorkspaceIdentifier(workspaceUri: URI, id?: string): IWorkspaceIdentifier { + return { +- id: getWorkspaceId(workspaceUri), ++ id: id ?? getWorkspaceId(workspaceUri), + configPath: workspaceUri + }; + } +@@ -22,9 +22,9 @@ export function getWorkspaceIdentifier(workspaceUri: URI): IWorkspaceIdentifier + // NOTE: DO NOT CHANGE. IDENTIFIERS HAVE TO REMAIN STABLE + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +-export function getSingleFolderWorkspaceIdentifier(folderUri: URI): ISingleFolderWorkspaceIdentifier { ++export function getSingleFolderWorkspaceIdentifier(folderUri: URI, id?: string): ISingleFolderWorkspaceIdentifier { + return { +- id: getWorkspaceId(folderUri), ++ id: id ?? getWorkspaceId(folderUri), + uri: folderUri + }; + }