From 3177b496763c4d72e25b8c9c58ccc8ac758c4fee Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 23 Jul 2026 11:35:08 +0200 Subject: [PATCH 1/6] support for jsonValidationCatalogs --- .../client/src/jsonClient.ts | 83 ++++++++++++++----- .../json-language-features/package.json | 5 ++ .../platform/extensions/common/extensions.ts | 5 ++ .../common/jsonValidationExtensionPoint.ts | 56 +++++++++++++ .../common/settingsFilesystemProvider.ts | 3 +- .../extensionManifestPropertiesService.ts | 1 + .../extensions/common/extensionPoints.json | 1 + 7 files changed, 134 insertions(+), 20 deletions(-) diff --git a/extensions/json-language-features/client/src/jsonClient.ts b/extensions/json-language-features/client/src/jsonClient.ts index 06dfa8efe48db9..2e46b93bc816ca 100644 --- a/extensions/json-language-features/client/src/jsonClient.ts +++ b/extensions/json-language-features/client/src/jsonClient.ts @@ -544,13 +544,33 @@ async function startClientWithParticipants(_context: ExtensionContext, languageP client.sendNotification(SchemaAssociationNotification.type, await getSchemaAssociations(false)); - toDispose.push(extensions.onDidChange(async _ => { - client.sendNotification(SchemaAssociationNotification.type, await getSchemaAssociations(true)); - })); + const catalogWatchers = new Map(); + const updateCatalogWatchers = () => { + const catalogUris = new Set(getSchemaCatalogUris().map(uri => uri.toString())); + for (const [uri, watcher] of catalogWatchers) { + if (!catalogUris.has(uri)) { + watcher.dispose(); + catalogWatchers.delete(uri); + } + } + for (const uri of catalogUris) { + if (!catalogWatchers.has(uri)) { + const catalogUri = Uri.parse(uri); + const fileName = catalogUri.path.substring(catalogUri.path.lastIndexOf('/') + 1); + const parentUri = catalogUri.with({ path: catalogUri.path.substring(0, catalogUri.path.length - fileName.length), query: undefined, fragment: undefined }); + const watcher = workspace.createFileSystemWatcher(new RelativePattern(parentUri, fileName)); + const refresh = async () => { + client.sendNotification(SchemaAssociationNotification.type, await getSchemaAssociations(true)); + }; + catalogWatchers.set(uri, Disposable.from(watcher, watcher.onDidCreate(refresh), watcher.onDidChange(refresh), watcher.onDidDelete(refresh))); + } + } + }; + updateCatalogWatchers(); + toDispose.push(new Disposable(() => catalogWatchers.forEach(watcher => watcher.dispose()))); - const associationWatcher = workspace.createFileSystemWatcher(new RelativePattern(Uri.parse(`vscode://schemas-associations/`), '**/schemas-associations.json')); - toDispose.push(associationWatcher); - toDispose.push(associationWatcher.onDidChange(async _e => { + toDispose.push(extensions.onDidChange(async () => { + updateCatalogWatchers(); client.sendNotification(SchemaAssociationNotification.type, await getSchemaAssociations(true)); })); @@ -767,7 +787,11 @@ async function startClientWithParticipants(_context: ExtensionContext, languageP async function computeSchemaAssociations(): Promise { const extensionAssociations = getSchemaExtensionAssociations(); - return extensionAssociations.concat(await getDynamicSchemaAssociations()); + return extensionAssociations.concat(await getSchemaCatalogAssociations()); +} + +function resolveExtensionResource(extensionUri: Uri, resource: string): Uri { + return resource.startsWith('./') ? Uri.joinPath(extensionUri, resource) : Uri.parse(resource); } function getSchemaExtensionAssociations(): ISchemaAssociation[] { @@ -806,20 +830,41 @@ function getSchemaExtensionAssociations(): ISchemaAssociation[] { return associations; } -async function getDynamicSchemaAssociations(): Promise { +function getSchemaCatalogUris(): Uri[] { + const result: Uri[] = []; + for (const extension of extensions.allAcrossExtensionHosts) { + const catalogs = extension.packageJSON?.contributes?.jsonValidationCatalogs; + if (Array.isArray(catalogs)) { + for (const catalog of catalogs) { + if (typeof catalog.url === 'string') { + result.push(resolveExtensionResource(extension.extensionUri, catalog.url)); + } + } + } + } + return result; +} + +async function getSchemaCatalogAssociations(): Promise { const result: ISchemaAssociation[] = []; - try { - const data = await workspace.fs.readFile(Uri.parse(`vscode://schemas-associations/schemas-associations.json`)); - const rawStr = new TextDecoder().decode(data); - const obj = >JSON.parse(rawStr); - for (const item of Object.keys(obj)) { - result.push({ - fileMatch: obj[item], - uri: item - }); + for (const catalogUri of getSchemaCatalogUris()) { + try { + const data = await workspace.fs.readFile(catalogUri); + const rawStr = new TextDecoder().decode(data); + const catalog = <{ schemas?: { url?: string; fileMatch?: string[] }[] }>JSON.parse(rawStr); + if (Array.isArray(catalog.schemas)) { + for (const schema of catalog.schemas) { + if (typeof schema.url === 'string' && Array.isArray(schema.fileMatch) && schema.fileMatch.every(fileMatch => typeof fileMatch === 'string')) { + result.push({ + fileMatch: schema.fileMatch, + uri: schema.url + }); + } + } + } + } catch { + // Ignore unavailable or invalid catalogs. } - } catch { - // ignore } return result; } diff --git a/extensions/json-language-features/package.json b/extensions/json-language-features/package.json index c616bf19b19ecf..f7159da18f2ca5 100644 --- a/extensions/json-language-features/package.json +++ b/extensions/json-language-features/package.json @@ -174,6 +174,11 @@ "url": "http://json-schema.org/draft-07/schema#" } ], + "jsonValidationCatalogs": [ + { + "url": "vscode://schemas-associations/schemas-associations.json" + } + ], "commands": [ { "command": "json.clearCache", diff --git a/src/vs/platform/extensions/common/extensions.ts b/src/vs/platform/extensions/common/extensions.ts index 6a8cc98d148b24..452ba07dd70097 100644 --- a/src/vs/platform/extensions/common/extensions.ts +++ b/src/vs/platform/extensions/common/extensions.ts @@ -36,6 +36,10 @@ export interface IJSONValidation { url: string; } +export interface IJSONValidationCatalog { + url: string; +} + export interface IKeyBinding { command: string; key: string; @@ -214,6 +218,7 @@ export interface IExtensionContributions { debuggers?: IDebugger[]; grammars?: IGrammar[]; jsonValidation?: IJSONValidation[]; + jsonValidationCatalogs?: IJSONValidationCatalog[]; keybindings?: IKeyBinding[]; languages?: ILanguage[]; menus?: { [context: string]: IMenu[] }; diff --git a/src/vs/workbench/api/common/jsonValidationExtensionPoint.ts b/src/vs/workbench/api/common/jsonValidationExtensionPoint.ts index 24559bee3f800f..b8a265de68b4f6 100644 --- a/src/vs/workbench/api/common/jsonValidationExtensionPoint.ts +++ b/src/vs/workbench/api/common/jsonValidationExtensionPoint.ts @@ -19,6 +19,10 @@ interface IJSONValidationExtensionPoint { url: string; } +interface IJSONValidationCatalogExtensionPoint { + url: string; +} + const configurationExtPoint = ExtensionsRegistry.registerExtensionPoint({ extensionPoint: 'jsonValidation', defaultExtensionKind: ['workspace', 'web'], @@ -46,6 +50,26 @@ const configurationExtPoint = ExtensionsRegistry.registerExtensionPoint({ + extensionPoint: 'jsonValidationCatalogs', + defaultExtensionKind: ['workspace', 'web'], + jsonSchema: { + description: nls.localize('contributes.jsonValidationCatalogs', 'Contributes JSON validation catalogs. The catalog can be a dynamic resource from a filesystem provider and allows to change associations at runtime.'), + type: 'array', + defaultSnippets: [{ body: [{ url: '${1:url}' }] }], + items: { + type: 'object', + defaultSnippets: [{ body: { url: '${1:url}' } }], + properties: { + url: { + description: nls.localize('contributes.jsonValidationCatalogs.url', 'A catalog URI or relative path to the extension folder (\'./\').'), + type: 'string' + } + } + } + } +}); + export class JSONValidationExtensionPoint { constructor() { @@ -85,6 +109,38 @@ export class JSONValidationExtensionPoint { }); } }); + + catalogExtPoint.setHandler(extensions => { + for (const extension of extensions) { + const catalogs = extension.value; + const collector = extension.collector; + const extensionLocation = extension.description.extensionLocation; + + if (!Array.isArray(catalogs)) { + collector.error(nls.localize('invalid.jsonValidationCatalogs', "'configuration.jsonValidationCatalogs' must be an array")); + continue; + } + for (const catalog of catalogs) { + const uri = catalog.url; + if (!isString(uri)) { + collector.error(nls.localize('invalid.jsonValidationCatalogs.url', "'configuration.jsonValidationCatalogs.url' must be a URI or relative path")); + continue; + } + if (uri.startsWith('./')) { + try { + const catalogLocation = resources.joinPath(extensionLocation, uri); + if (!resources.isEqualOrParent(catalogLocation, extensionLocation)) { + collector.warn(nls.localize('invalid.jsonValidationCatalogs.path', "Expected `contributes.{0}.url` ({1}) to be included inside extension's folder ({2}). This might make the extension non-portable.", catalogExtPoint.name, catalogLocation.toString(), extensionLocation.path)); + } + } catch (e) { + collector.error(nls.localize('invalid.jsonValidationCatalogs.fileschema', "'configuration.jsonValidationCatalogs.url' is an invalid relative URI: {0}", e.message)); + } + } else if (!/^[^:/?#]+:\/\//.test(uri)) { + collector.error(nls.localize('invalid.jsonValidationCatalogs.schema', "'configuration.jsonValidationCatalogs.url' must be an absolute URI or start with './' to reference a catalog located in the extension.")); + } + } + } + }); } } diff --git a/src/vs/workbench/contrib/preferences/common/settingsFilesystemProvider.ts b/src/vs/workbench/contrib/preferences/common/settingsFilesystemProvider.ts index 171b2458587aba..00a73abea9e4bc 100644 --- a/src/vs/workbench/contrib/preferences/common/settingsFilesystemProvider.ts +++ b/src/vs/workbench/contrib/preferences/common/settingsFilesystemProvider.ts @@ -54,7 +54,8 @@ export class SettingsFileSystemProvider extends Disposable implements IFileSyste if (uri.authority === 'schemas') { content = this.getSchemaContent(uri); } else if (uri.authority === SettingsFileSystemProvider.SCHEMA_ASSOCIATIONS.authority) { - content = JSON.stringify(schemaRegistry.getSchemaAssociations()); + const schemas = Object.entries(schemaRegistry.getSchemaAssociations()).map(([url, fileMatch]) => ({ url, fileMatch })); + content = JSON.stringify({ schemas }); } else if (uri.authority === 'defaultsettings') { content = this.preferencesService.getDefaultSettingsContent(uri); } diff --git a/src/vs/workbench/services/extensions/common/extensionManifestPropertiesService.ts b/src/vs/workbench/services/extensions/common/extensionManifestPropertiesService.ts index 1226578ad61ca3..63c8a903953d87 100644 --- a/src/vs/workbench/services/extensions/common/extensionManifestPropertiesService.ts +++ b/src/vs/workbench/services/extensions/common/extensionManifestPropertiesService.ts @@ -31,6 +31,7 @@ const SESSIONS_WINDOW_ALLOWED_CONTRIBUTION_POINTS: ReadonlySet Date: Thu, 23 Jul 2026 11:44:36 +0200 Subject: [PATCH 2/6] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/vs/workbench/api/common/jsonValidationExtensionPoint.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/api/common/jsonValidationExtensionPoint.ts b/src/vs/workbench/api/common/jsonValidationExtensionPoint.ts index b8a265de68b4f6..b913c0e39dfd4a 100644 --- a/src/vs/workbench/api/common/jsonValidationExtensionPoint.ts +++ b/src/vs/workbench/api/common/jsonValidationExtensionPoint.ts @@ -121,7 +121,7 @@ export class JSONValidationExtensionPoint { continue; } for (const catalog of catalogs) { - const uri = catalog.url; + const uri = catalog?.url; if (!isString(uri)) { collector.error(nls.localize('invalid.jsonValidationCatalogs.url', "'configuration.jsonValidationCatalogs.url' must be a URI or relative path")); continue; From 6ed63577dd272e85caf3a112d0bb150947cd4448 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 23 Jul 2026 11:45:22 +0200 Subject: [PATCH 3/6] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- extensions/json-language-features/client/src/jsonClient.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/json-language-features/client/src/jsonClient.ts b/extensions/json-language-features/client/src/jsonClient.ts index 2e46b93bc816ca..f14b940159974b 100644 --- a/extensions/json-language-features/client/src/jsonClient.ts +++ b/extensions/json-language-features/client/src/jsonClient.ts @@ -836,7 +836,7 @@ function getSchemaCatalogUris(): Uri[] { const catalogs = extension.packageJSON?.contributes?.jsonValidationCatalogs; if (Array.isArray(catalogs)) { for (const catalog of catalogs) { - if (typeof catalog.url === 'string') { + if (typeof catalog?.url === 'string') { result.push(resolveExtensionResource(extension.extensionUri, catalog.url)); } } From 22fd5e9b778d480d45b64048618d0c0c614e1d81 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Thu, 23 Jul 2026 11:53:08 +0200 Subject: [PATCH 4/6] update --- .../client/src/jsonClient.ts | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/extensions/json-language-features/client/src/jsonClient.ts b/extensions/json-language-features/client/src/jsonClient.ts index f14b940159974b..7630141ea1d2a5 100644 --- a/extensions/json-language-features/client/src/jsonClient.ts +++ b/extensions/json-language-features/client/src/jsonClient.ts @@ -544,6 +544,24 @@ async function startClientWithParticipants(_context: ExtensionContext, languageP client.sendNotification(SchemaAssociationNotification.type, await getSchemaAssociations(false)); + let schemaAssociationRefreshGeneration = 0; + let schemaAssociationRefreshTrigger: Disposable | undefined; + const refreshSchemaAssociations = () => { + const generation = ++schemaAssociationRefreshGeneration; + schemaAssociationRefreshTrigger?.dispose(); + schemaAssociationRefreshTrigger = runtime.timer.setTimeout(async () => { + schemaAssociationRefreshTrigger = undefined; + const associations = await getSchemaAssociations(true); + if (generation === schemaAssociationRefreshGeneration) { + client.sendNotification(SchemaAssociationNotification.type, associations); + } + }, 500); + }; + toDispose.push(new Disposable(() => { + schemaAssociationRefreshGeneration++; + schemaAssociationRefreshTrigger?.dispose(); + })); + const catalogWatchers = new Map(); const updateCatalogWatchers = () => { const catalogUris = new Set(getSchemaCatalogUris().map(uri => uri.toString())); @@ -559,19 +577,16 @@ async function startClientWithParticipants(_context: ExtensionContext, languageP const fileName = catalogUri.path.substring(catalogUri.path.lastIndexOf('/') + 1); const parentUri = catalogUri.with({ path: catalogUri.path.substring(0, catalogUri.path.length - fileName.length), query: undefined, fragment: undefined }); const watcher = workspace.createFileSystemWatcher(new RelativePattern(parentUri, fileName)); - const refresh = async () => { - client.sendNotification(SchemaAssociationNotification.type, await getSchemaAssociations(true)); - }; - catalogWatchers.set(uri, Disposable.from(watcher, watcher.onDidCreate(refresh), watcher.onDidChange(refresh), watcher.onDidDelete(refresh))); + catalogWatchers.set(uri, Disposable.from(watcher, watcher.onDidCreate(refreshSchemaAssociations), watcher.onDidChange(refreshSchemaAssociations), watcher.onDidDelete(refreshSchemaAssociations))); } } }; updateCatalogWatchers(); toDispose.push(new Disposable(() => catalogWatchers.forEach(watcher => watcher.dispose()))); - toDispose.push(extensions.onDidChange(async () => { + toDispose.push(extensions.onDidChange(() => { updateCatalogWatchers(); - client.sendNotification(SchemaAssociationNotification.type, await getSchemaAssociations(true)); + refreshSchemaAssociations(); })); // manually register / deregister format provider based on the `json.format.enable` setting avoiding issues with late registration. See #71652. From cd707eb59bd7b3254ff087594a1de9ce94576575 Mon Sep 17 00:00:00 2001 From: Martin Aeschlimann Date: Fri, 24 Jul 2026 11:45:50 +0200 Subject: [PATCH 5/6] rename to registry --- .../client/src/jsonClient.ts | 8 +++---- .../json-language-features/package.json | 2 +- .../platform/extensions/common/extensions.ts | 4 ++-- src/vs/sessions/AI_CUSTOMIZATIONS.md | 2 +- .../common/jsonValidationExtensionPoint.ts | 22 +++++++++---------- .../extensionManifestPropertiesService.ts | 2 +- .../extensions/common/extensionPoints.json | 2 +- 7 files changed, 21 insertions(+), 21 deletions(-) diff --git a/extensions/json-language-features/client/src/jsonClient.ts b/extensions/json-language-features/client/src/jsonClient.ts index 7630141ea1d2a5..f9063c0ad1ea91 100644 --- a/extensions/json-language-features/client/src/jsonClient.ts +++ b/extensions/json-language-features/client/src/jsonClient.ts @@ -564,7 +564,7 @@ async function startClientWithParticipants(_context: ExtensionContext, languageP const catalogWatchers = new Map(); const updateCatalogWatchers = () => { - const catalogUris = new Set(getSchemaCatalogUris().map(uri => uri.toString())); + const catalogUris = new Set(getSchemaRegistryUris().map(uri => uri.toString())); for (const [uri, watcher] of catalogWatchers) { if (!catalogUris.has(uri)) { watcher.dispose(); @@ -845,10 +845,10 @@ function getSchemaExtensionAssociations(): ISchemaAssociation[] { return associations; } -function getSchemaCatalogUris(): Uri[] { +function getSchemaRegistryUris(): Uri[] { const result: Uri[] = []; for (const extension of extensions.allAcrossExtensionHosts) { - const catalogs = extension.packageJSON?.contributes?.jsonValidationCatalogs; + const catalogs = extension.packageJSON?.contributes?.jsonValidationRegistry; if (Array.isArray(catalogs)) { for (const catalog of catalogs) { if (typeof catalog?.url === 'string') { @@ -862,7 +862,7 @@ function getSchemaCatalogUris(): Uri[] { async function getSchemaCatalogAssociations(): Promise { const result: ISchemaAssociation[] = []; - for (const catalogUri of getSchemaCatalogUris()) { + for (const catalogUri of getSchemaRegistryUris()) { try { const data = await workspace.fs.readFile(catalogUri); const rawStr = new TextDecoder().decode(data); diff --git a/extensions/json-language-features/package.json b/extensions/json-language-features/package.json index f7159da18f2ca5..cf3e0d5ecc691e 100644 --- a/extensions/json-language-features/package.json +++ b/extensions/json-language-features/package.json @@ -174,7 +174,7 @@ "url": "http://json-schema.org/draft-07/schema#" } ], - "jsonValidationCatalogs": [ + "jsonValidationRegistry": [ { "url": "vscode://schemas-associations/schemas-associations.json" } diff --git a/src/vs/platform/extensions/common/extensions.ts b/src/vs/platform/extensions/common/extensions.ts index 452ba07dd70097..af9aa6e24857fa 100644 --- a/src/vs/platform/extensions/common/extensions.ts +++ b/src/vs/platform/extensions/common/extensions.ts @@ -36,7 +36,7 @@ export interface IJSONValidation { url: string; } -export interface IJSONValidationCatalog { +export interface IJSONValidationRegistry { url: string; } @@ -218,7 +218,7 @@ export interface IExtensionContributions { debuggers?: IDebugger[]; grammars?: IGrammar[]; jsonValidation?: IJSONValidation[]; - jsonValidationCatalogs?: IJSONValidationCatalog[]; + jsonValidationRegistry?: IJSONValidationRegistry[]; keybindings?: IKeyBinding[]; languages?: ILanguage[]; menus?: { [context: string]: IMenu[] }; diff --git a/src/vs/sessions/AI_CUSTOMIZATIONS.md b/src/vs/sessions/AI_CUSTOMIZATIONS.md index 94439f27c59001..307ba8b83c0239 100644 --- a/src/vs/sessions/AI_CUSTOMIZATIONS.md +++ b/src/vs/sessions/AI_CUSTOMIZATIONS.md @@ -73,7 +73,7 @@ The Tools section can browse the Marketplace in the core workbench, where extens Agent Host MCP **Show Output** actions prepare and register their target channel, close the modal management editor, then reveal the prepared channel. Closing before preparation can tear down the active harness context, while showing before close lets modal teardown reset the Output presentation. -When the active harness is an agent host (`agent-host-*` / `remote-*`), the overview can render a **Migrate** card. The card appears only when the core `IPromptsService` still discovers local/user `*.prompt.md` files, because those files are ignored by agent-host harnesses, and only when the experimental `chat.customizations.promptMigration.enabled` setting is enabled. The left sidebar also renders a bottom **Migrate Prompt Files** shortcut in that state so the flow is discoverable even when the overview is not visible. Choosing either entry opens a dedicated migration page where users can review all migratable prompt files, select the ones to migrate, and open individual files before running migration. The migrate action converts selected prompt files into skills under the harness-appropriate skill roots (for example `.github/skills` / `~/.copilot/skills` for Copilot, `.claude/skills` / `~/.claude/skills` for Claude), preserves manual invocation by setting `disable-model-invocation: true`, and removes the original prompt files. If multiple workspace skill roots are available, migration prompts once to choose the workspace target and reuses that target for all migrated workspace prompts. +When the active harness is an agent host (`agent-host-*` / `remote-*`), the overview can render a **Migrate** card. The card appears only when the core `IPromptsService` still discovers local/user `*.prompt.md` files, because those files are ignored by agent-host harnesses, and only when the experimental `chat.customizations.promptMigration.enabled` setting is enabled. The left sidebar also renders a bottom **Migrate Prompt Files** shortcut in that state so the flow is discoverable even when the overview is not visible. Choosing either entry opens a dedicated migration page where users can review all migratable prompt files, select the ones to migrate, and open individual files before running migration. Workspace and User prompt-file groups on that page are independently collapsible so large migrations stay scannable. The migrate action converts selected prompt files into skills under the harness-appropriate skill roots (for example `.github/skills` / `~/.copilot/skills` for Copilot, `.claude/skills` / `~/.claude/skills` for Claude), preserves manual invocation by setting `disable-model-invocation: true`, and removes the original prompt files. If multiple workspace skill roots are available, migration prompts once to choose the workspace target and reuses that target for all migrated workspace prompts. Automation run history stores the created session as a serialized URI. Its Open Session action uses the shared resource-first session opener, allowing the Agents window to route the URI through `ISessionsService` before the core workbench falls back to resolving an `IAgentSession`. diff --git a/src/vs/workbench/api/common/jsonValidationExtensionPoint.ts b/src/vs/workbench/api/common/jsonValidationExtensionPoint.ts index b913c0e39dfd4a..cd7f26af674066 100644 --- a/src/vs/workbench/api/common/jsonValidationExtensionPoint.ts +++ b/src/vs/workbench/api/common/jsonValidationExtensionPoint.ts @@ -19,7 +19,7 @@ interface IJSONValidationExtensionPoint { url: string; } -interface IJSONValidationCatalogExtensionPoint { +interface IJSONValidationRegistryExtensionPoint { url: string; } @@ -50,11 +50,11 @@ const configurationExtPoint = ExtensionsRegistry.registerExtensionPoint({ - extensionPoint: 'jsonValidationCatalogs', +const registryExtPoint = ExtensionsRegistry.registerExtensionPoint({ + extensionPoint: 'jsonValidationRegistry', defaultExtensionKind: ['workspace', 'web'], jsonSchema: { - description: nls.localize('contributes.jsonValidationCatalogs', 'Contributes JSON validation catalogs. The catalog can be a dynamic resource from a filesystem provider and allows to change associations at runtime.'), + description: nls.localize('contributes.jsonValidationRegistry', 'Contributes a JSON validation registry. The registry can be a dynamic resource from a filesystem provider and allows associations to change at runtime.'), type: 'array', defaultSnippets: [{ body: [{ url: '${1:url}' }] }], items: { @@ -62,7 +62,7 @@ const catalogExtPoint = ExtensionsRegistry.registerExtensionPoint { + registryExtPoint.setHandler(extensions => { for (const extension of extensions) { const catalogs = extension.value; const collector = extension.collector; const extensionLocation = extension.description.extensionLocation; if (!Array.isArray(catalogs)) { - collector.error(nls.localize('invalid.jsonValidationCatalogs', "'configuration.jsonValidationCatalogs' must be an array")); + collector.error(nls.localize('invalid.jsonValidationRegistry', "'configuration.jsonValidationRegistry' must be an array")); continue; } for (const catalog of catalogs) { const uri = catalog?.url; if (!isString(uri)) { - collector.error(nls.localize('invalid.jsonValidationCatalogs.url', "'configuration.jsonValidationCatalogs.url' must be a URI or relative path")); + collector.error(nls.localize('invalid.jsonValidationRegistry.url', "'configuration.jsonValidationRegistry.url' must be a URI or relative path")); continue; } if (uri.startsWith('./')) { try { const catalogLocation = resources.joinPath(extensionLocation, uri); if (!resources.isEqualOrParent(catalogLocation, extensionLocation)) { - collector.warn(nls.localize('invalid.jsonValidationCatalogs.path', "Expected `contributes.{0}.url` ({1}) to be included inside extension's folder ({2}). This might make the extension non-portable.", catalogExtPoint.name, catalogLocation.toString(), extensionLocation.path)); + collector.warn(nls.localize('invalid.jsonValidationRegistry.path', "Expected `contributes.{0}.url` ({1}) to be included inside extension's folder ({2}). This might make the extension non-portable.", registryExtPoint.name, catalogLocation.toString(), extensionLocation.path)); } } catch (e) { - collector.error(nls.localize('invalid.jsonValidationCatalogs.fileschema', "'configuration.jsonValidationCatalogs.url' is an invalid relative URI: {0}", e.message)); + collector.error(nls.localize('invalid.jsonValidationRegistry.fileschema', "'configuration.jsonValidationRegistry.url' is an invalid relative URI: {0}", e.message)); } } else if (!/^[^:/?#]+:\/\//.test(uri)) { - collector.error(nls.localize('invalid.jsonValidationCatalogs.schema', "'configuration.jsonValidationCatalogs.url' must be an absolute URI or start with './' to reference a catalog located in the extension.")); + collector.error(nls.localize('invalid.jsonValidationRegistry.schema', "'configuration.jsonValidationRegistry.url' must be an absolute URI or start with './' to reference a registry located in the extension.")); } } } diff --git a/src/vs/workbench/services/extensions/common/extensionManifestPropertiesService.ts b/src/vs/workbench/services/extensions/common/extensionManifestPropertiesService.ts index 63c8a903953d87..dad5d2898e0e0a 100644 --- a/src/vs/workbench/services/extensions/common/extensionManifestPropertiesService.ts +++ b/src/vs/workbench/services/extensions/common/extensionManifestPropertiesService.ts @@ -31,7 +31,7 @@ const SESSIONS_WINDOW_ALLOWED_CONTRIBUTION_POINTS: ReadonlySet Date: Fri, 24 Jul 2026 11:48:09 +0200 Subject: [PATCH 6/6] rename --- .../client/src/jsonClient.ts | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/extensions/json-language-features/client/src/jsonClient.ts b/extensions/json-language-features/client/src/jsonClient.ts index f9063c0ad1ea91..fa1c6d10209921 100644 --- a/extensions/json-language-features/client/src/jsonClient.ts +++ b/extensions/json-language-features/client/src/jsonClient.ts @@ -562,30 +562,30 @@ async function startClientWithParticipants(_context: ExtensionContext, languageP schemaAssociationRefreshTrigger?.dispose(); })); - const catalogWatchers = new Map(); - const updateCatalogWatchers = () => { - const catalogUris = new Set(getSchemaRegistryUris().map(uri => uri.toString())); - for (const [uri, watcher] of catalogWatchers) { - if (!catalogUris.has(uri)) { + const registryWatchers = new Map(); + const updateRegistryWatchers = () => { + const registryUris = new Set(getSchemaRegistryUris().map(uri => uri.toString())); + for (const [uri, watcher] of registryWatchers) { + if (!registryUris.has(uri)) { watcher.dispose(); - catalogWatchers.delete(uri); + registryWatchers.delete(uri); } } - for (const uri of catalogUris) { - if (!catalogWatchers.has(uri)) { - const catalogUri = Uri.parse(uri); - const fileName = catalogUri.path.substring(catalogUri.path.lastIndexOf('/') + 1); - const parentUri = catalogUri.with({ path: catalogUri.path.substring(0, catalogUri.path.length - fileName.length), query: undefined, fragment: undefined }); + for (const uri of registryUris) { + if (!registryWatchers.has(uri)) { + const registryUri = Uri.parse(uri); + const fileName = registryUri.path.substring(registryUri.path.lastIndexOf('/') + 1); + const parentUri = registryUri.with({ path: registryUri.path.substring(0, registryUri.path.length - fileName.length), query: undefined, fragment: undefined }); const watcher = workspace.createFileSystemWatcher(new RelativePattern(parentUri, fileName)); - catalogWatchers.set(uri, Disposable.from(watcher, watcher.onDidCreate(refreshSchemaAssociations), watcher.onDidChange(refreshSchemaAssociations), watcher.onDidDelete(refreshSchemaAssociations))); + registryWatchers.set(uri, Disposable.from(watcher, watcher.onDidCreate(refreshSchemaAssociations), watcher.onDidChange(refreshSchemaAssociations), watcher.onDidDelete(refreshSchemaAssociations))); } } }; - updateCatalogWatchers(); - toDispose.push(new Disposable(() => catalogWatchers.forEach(watcher => watcher.dispose()))); + updateRegistryWatchers(); + toDispose.push(new Disposable(() => registryWatchers.forEach(watcher => watcher.dispose()))); toDispose.push(extensions.onDidChange(() => { - updateCatalogWatchers(); + updateRegistryWatchers(); refreshSchemaAssociations(); })); @@ -802,7 +802,7 @@ async function startClientWithParticipants(_context: ExtensionContext, languageP async function computeSchemaAssociations(): Promise { const extensionAssociations = getSchemaExtensionAssociations(); - return extensionAssociations.concat(await getSchemaCatalogAssociations()); + return extensionAssociations.concat(await getSchemaRegistryAssociations()); } function resolveExtensionResource(extensionUri: Uri, resource: string): Uri { @@ -848,11 +848,11 @@ function getSchemaExtensionAssociations(): ISchemaAssociation[] { function getSchemaRegistryUris(): Uri[] { const result: Uri[] = []; for (const extension of extensions.allAcrossExtensionHosts) { - const catalogs = extension.packageJSON?.contributes?.jsonValidationRegistry; - if (Array.isArray(catalogs)) { - for (const catalog of catalogs) { - if (typeof catalog?.url === 'string') { - result.push(resolveExtensionResource(extension.extensionUri, catalog.url)); + const registrys = extension.packageJSON?.contributes?.jsonValidationRegistry; + if (Array.isArray(registrys)) { + for (const registry of registrys) { + if (typeof registry?.url === 'string') { + result.push(resolveExtensionResource(extension.extensionUri, registry.url)); } } } @@ -860,15 +860,15 @@ function getSchemaRegistryUris(): Uri[] { return result; } -async function getSchemaCatalogAssociations(): Promise { +async function getSchemaRegistryAssociations(): Promise { const result: ISchemaAssociation[] = []; - for (const catalogUri of getSchemaRegistryUris()) { + for (const registryUri of getSchemaRegistryUris()) { try { - const data = await workspace.fs.readFile(catalogUri); + const data = await workspace.fs.readFile(registryUri); const rawStr = new TextDecoder().decode(data); - const catalog = <{ schemas?: { url?: string; fileMatch?: string[] }[] }>JSON.parse(rawStr); - if (Array.isArray(catalog.schemas)) { - for (const schema of catalog.schemas) { + const registry = <{ schemas?: { url?: string; fileMatch?: string[] }[] }>JSON.parse(rawStr); + if (Array.isArray(registry.schemas)) { + for (const schema of registry.schemas) { if (typeof schema.url === 'string' && Array.isArray(schema.fileMatch) && schema.fileMatch.every(fileMatch => typeof fileMatch === 'string')) { result.push({ fileMatch: schema.fileMatch, @@ -878,7 +878,7 @@ async function getSchemaCatalogAssociations(): Promise { } } } catch { - // Ignore unavailable or invalid catalogs. + // Ignore unavailable or invalid registry. } } return result;