From b5e9eb661979a19d23590a4376ca131bcb1745bf Mon Sep 17 00:00:00 2001 From: Yevhen Vydolob Date: Mon, 7 Mar 2022 17:01:14 +0200 Subject: [PATCH 1/3] Support JSON Schema versions from schemastore Signed-off-by: Yevhen Vydolob --- src/schema-status-bar-item.ts | 135 +++++++++++++++++++++++++++++----- 1 file changed, 116 insertions(+), 19 deletions(-) diff --git a/src/schema-status-bar-item.ts b/src/schema-status-bar-item.ts index 650a7ebe..14433830 100644 --- a/src/schema-status-bar-item.ts +++ b/src/schema-status-bar-item.ts @@ -16,10 +16,12 @@ import { import { CommonLanguageClient, RequestType } from 'vscode-languageclient/node'; type FileUri = string; +type SchemaVersions = { [version: string]: string }; interface JSONSchema { name?: string; description?: string; uri: string; + versions?: SchemaVersions; } interface MatchingJSONSchema extends JSONSchema { @@ -31,6 +33,11 @@ interface SchemaItem extends QuickPickItem { schema?: MatchingJSONSchema; } +interface SchemaVersionItem extends QuickPickItem { + version: string; + url: string; +} + // eslint-disable-next-line @typescript-eslint/ban-types const getJSONSchemas: RequestType = new RequestType('yaml/get/all/jsonSchemas'); @@ -40,6 +47,7 @@ const getSchema: RequestType = new RequestType('yaml/ export let statusBarItem: StatusBarItem; let client: CommonLanguageClient; +let versionSelection: SchemaItem = undefined; export function createJSONSchemaStatusBarItem(context: ExtensionContext, languageclient: CommonLanguageClient): void { if (statusBarItem) { updateStatusBar(window.activeTextEditor); @@ -61,6 +69,7 @@ export function createJSONSchemaStatusBarItem(context: ExtensionContext, languag async function updateStatusBar(editor: TextEditor): Promise { if (editor && editor.document.languageId === 'yaml') { + versionSelection = undefined; // get schema info there const schema = await client.sendRequest(getSchema, editor.document.uri.toString()); if (!schema || schema.length === 0) { @@ -69,6 +78,20 @@ async function updateStatusBar(editor: TextEditor): Promise { statusBarItem.backgroundColor = undefined; } else if (schema.length === 1) { statusBarItem.text = schema[0].name ?? schema[0].uri; + let version; + if (schema[0].versions) { + version = findUsedVersion(schema[0].versions, schema[0].uri); + } else { + const schemas = await client.sendRequest(getJSONSchemas, window.activeTextEditor.document.uri.toString()); + let versionSchema: JSONSchema; + [version, versionSchema] = findSchemaStoreItem(schemas, schema[0].uri); + (versionSchema as MatchingJSONSchema).usedForCurrentFile = true; + versionSchema.uri = schema[0].uri; + versionSelection = createSelectVersionItem(version, versionSchema as MatchingJSONSchema); + } + if (version) { + statusBarItem.text += `(${version})`; + } statusBarItem.tooltip = 'Select JSON Schema'; statusBarItem.backgroundColor = undefined; } else { @@ -86,9 +109,13 @@ async function updateStatusBar(editor: TextEditor): Promise { async function showSchemaSelection(): Promise { const schemas = await client.sendRequest(getJSONSchemas, window.activeTextEditor.document.uri.toString()); const schemasPick = window.createQuickPick(); - const pickItems: SchemaItem[] = []; + let pickItems: SchemaItem[] = []; for (const val of schemas) { + if (val.usedForCurrentFile && val.versions) { + const item = createSelectVersionItem(findUsedVersion(val.versions, val.uri), val); + pickItems.unshift(item); + } const item = { label: val.name ?? val.uri, description: val.description, @@ -98,8 +125,17 @@ async function showSchemaSelection(): Promise { }; pickItems.push(item); } + if (versionSelection) { + pickItems.unshift(versionSelection); + } - pickItems.sort((a, b) => { + pickItems = pickItems.sort((a, b) => { + if (a.schema?.usedForCurrentFile && a.schema?.versions) { + return -1; + } + if (b.schema?.usedForCurrentFile && b.schema?.versions) { + return 1; + } if (a.schema?.usedForCurrentFile) { return -1; } @@ -117,23 +153,10 @@ async function showSchemaSelection(): Promise { schemasPick.onDidChangeSelection((selection) => { try { if (selection.length > 0) { - if (selection[0].schema) { - const settings: Record = workspace.getConfiguration('yaml').get('schemas'); - const fileUri = window.activeTextEditor.document.uri.toString(); - const newSettings = Object.assign({}, settings); - deleteExistingFilePattern(newSettings, fileUri); - const schemaURI = selection[0].schema.uri; - const schemaSettings = newSettings[schemaURI]; - if (schemaSettings) { - if (Array.isArray(schemaSettings)) { - (schemaSettings as Array).push(fileUri); - } else if (typeof schemaSettings === 'string') { - newSettings[schemaURI] = [schemaSettings, fileUri]; - } - } else { - newSettings[schemaURI] = fileUri; - } - workspace.getConfiguration('yaml').update('schemas', newSettings); + if (selection[0].label === 'Select Another Version') { + handleSchemaVersionSelection(selection[0].schema); + } else if (selection[0].schema) { + writeSchemaUriMapping(selection[0].schema.uri); } } } catch (err) { @@ -162,3 +185,77 @@ function deleteExistingFilePattern(settings: Record, fileUri: s return settings; } + +function createSelectVersionItem(version: string, schema: MatchingJSONSchema): SchemaItem { + return { + label: 'Select Another Version', + detail: `Current: ${version}`, + alwaysShow: true, + schema: schema, + }; +} +function findSchemaStoreItem(schemas: JSONSchema[], url: string): [string, JSONSchema] | undefined { + for (const schema of schemas) { + if (schema.versions) { + for (const version in schema.versions) { + if (url === schema.versions[version]) { + return [version, schema]; + } + } + } + } +} + +function writeSchemaUriMapping(schemaUrl: string): void { + const settings: Record = workspace.getConfiguration('yaml').get('schemas'); + const fileUri = window.activeTextEditor.document.uri.toString(); + const newSettings = Object.assign({}, settings); + deleteExistingFilePattern(newSettings, fileUri); + const schemaSettings = newSettings[schemaUrl]; + if (schemaSettings) { + if (Array.isArray(schemaSettings)) { + (schemaSettings as Array).push(fileUri); + } else if (typeof schemaSettings === 'string') { + newSettings[schemaUrl] = [schemaSettings, fileUri]; + } + } else { + newSettings[schemaUrl] = fileUri; + } + workspace.getConfiguration('yaml').update('schemas', newSettings); +} + +function handleSchemaVersionSelection(schema: MatchingJSONSchema): void { + const versionPick = window.createQuickPick(); + const versionItems: SchemaVersionItem[] = []; + const usedVersion = findUsedVersion(schema.versions, schema.uri); + for (const version in schema.versions) { + versionItems.push({ + label: version + (usedVersion === version ? '$(check)' : ''), + url: schema.versions[version], + version: version, + }); + } + + versionPick.items = versionItems; + versionPick.title = `Select JSON Schema version for ${schema.name}`; + versionPick.placeholder = 'Version'; + versionPick.onDidHide(() => versionPick.dispose()); + + versionPick.onDidChangeSelection((items) => { + if (items && items.length === 1) { + writeSchemaUriMapping(items[0].url); + } + versionPick.hide(); + }); + versionPick.show(); +} + +function findUsedVersion(versions: SchemaVersions, uri: string): string { + for (const version in versions) { + const versionUri = versions[version]; + if (versionUri === uri) { + return version; + } + } + return 'latest'; +} From 38af579e43ba84a13ff2e887c9d6f37bd77c1d19 Mon Sep 17 00:00:00 2001 From: Yevhen Vydolob Date: Thu, 31 Mar 2022 14:36:14 +0300 Subject: [PATCH 2/3] Fix test Signed-off-by: Yevhen Vydolob --- src/schema-status-bar-item.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/schema-status-bar-item.ts b/src/schema-status-bar-item.ts index 14433830..d881597a 100644 --- a/src/schema-status-bar-item.ts +++ b/src/schema-status-bar-item.ts @@ -48,6 +48,9 @@ export let statusBarItem: StatusBarItem; let client: CommonLanguageClient; let versionSelection: SchemaItem = undefined; + +const selectVersionLabel = 'Select Different Version'; + export function createJSONSchemaStatusBarItem(context: ExtensionContext, languageclient: CommonLanguageClient): void { if (statusBarItem) { updateStatusBar(window.activeTextEditor); @@ -84,10 +87,13 @@ async function updateStatusBar(editor: TextEditor): Promise { } else { const schemas = await client.sendRequest(getJSONSchemas, window.activeTextEditor.document.uri.toString()); let versionSchema: JSONSchema; - [version, versionSchema] = findSchemaStoreItem(schemas, schema[0].uri); - (versionSchema as MatchingJSONSchema).usedForCurrentFile = true; - versionSchema.uri = schema[0].uri; - versionSelection = createSelectVersionItem(version, versionSchema as MatchingJSONSchema); + const schemaStoreItem = findSchemaStoreItem(schemas, schema[0].uri); + if (schemaStoreItem) { + [version, versionSchema] = schemaStoreItem; + (versionSchema as MatchingJSONSchema).usedForCurrentFile = true; + versionSchema.uri = schema[0].uri; + versionSelection = createSelectVersionItem(version, versionSchema as MatchingJSONSchema); + } } if (version) { statusBarItem.text += `(${version})`; @@ -153,7 +159,7 @@ async function showSchemaSelection(): Promise { schemasPick.onDidChangeSelection((selection) => { try { if (selection.length > 0) { - if (selection[0].label === 'Select Another Version') { + if (selection[0].label === selectVersionLabel) { handleSchemaVersionSelection(selection[0].schema); } else if (selection[0].schema) { writeSchemaUriMapping(selection[0].schema.uri); @@ -188,7 +194,7 @@ function deleteExistingFilePattern(settings: Record, fileUri: s function createSelectVersionItem(version: string, schema: MatchingJSONSchema): SchemaItem { return { - label: 'Select Another Version', + label: selectVersionLabel, detail: `Current: ${version}`, alwaysShow: true, schema: schema, From 8d679b4c2add6744a04eb83001db259f1df9f4af Mon Sep 17 00:00:00 2001 From: Yevhen Vydolob Date: Thu, 31 Mar 2022 16:32:12 +0300 Subject: [PATCH 3/3] add tests Signed-off-by: Yevhen Vydolob --- src/schema-status-bar-item.ts | 12 ++++---- test/json-schema-selection.test.ts | 49 ++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/src/schema-status-bar-item.ts b/src/schema-status-bar-item.ts index d881597a..a33d0130 100644 --- a/src/schema-status-bar-item.ts +++ b/src/schema-status-bar-item.ts @@ -39,10 +39,10 @@ interface SchemaVersionItem extends QuickPickItem { } // eslint-disable-next-line @typescript-eslint/ban-types -const getJSONSchemas: RequestType = new RequestType('yaml/get/all/jsonSchemas'); +const getJSONSchemasRequestType: RequestType = new RequestType('yaml/get/all/jsonSchemas'); // eslint-disable-next-line @typescript-eslint/ban-types -const getSchema: RequestType = new RequestType('yaml/get/jsonSchema'); +const getSchemaRequestType: RequestType = new RequestType('yaml/get/jsonSchema'); export let statusBarItem: StatusBarItem; @@ -74,7 +74,7 @@ async function updateStatusBar(editor: TextEditor): Promise { if (editor && editor.document.languageId === 'yaml') { versionSelection = undefined; // get schema info there - const schema = await client.sendRequest(getSchema, editor.document.uri.toString()); + const schema = await client.sendRequest(getSchemaRequestType, editor.document.uri.toString()); if (!schema || schema.length === 0) { statusBarItem.text = 'No JSON Schema'; statusBarItem.tooltip = 'Select JSON Schema'; @@ -85,7 +85,7 @@ async function updateStatusBar(editor: TextEditor): Promise { if (schema[0].versions) { version = findUsedVersion(schema[0].versions, schema[0].uri); } else { - const schemas = await client.sendRequest(getJSONSchemas, window.activeTextEditor.document.uri.toString()); + const schemas = await client.sendRequest(getJSONSchemasRequestType, window.activeTextEditor.document.uri.toString()); let versionSchema: JSONSchema; const schemaStoreItem = findSchemaStoreItem(schemas, schema[0].uri); if (schemaStoreItem) { @@ -95,7 +95,7 @@ async function updateStatusBar(editor: TextEditor): Promise { versionSelection = createSelectVersionItem(version, versionSchema as MatchingJSONSchema); } } - if (version) { + if (version && !statusBarItem.text.includes(version)) { statusBarItem.text += `(${version})`; } statusBarItem.tooltip = 'Select JSON Schema'; @@ -113,7 +113,7 @@ async function updateStatusBar(editor: TextEditor): Promise { } async function showSchemaSelection(): Promise { - const schemas = await client.sendRequest(getJSONSchemas, window.activeTextEditor.document.uri.toString()); + const schemas = await client.sendRequest(getJSONSchemasRequestType, window.activeTextEditor.document.uri.toString()); const schemasPick = window.createQuickPick(); let pickItems: SchemaItem[] = []; diff --git a/test/json-schema-selection.test.ts b/test/json-schema-selection.test.ts index eec38bd4..100a0101 100644 --- a/test/json-schema-selection.test.ts +++ b/test/json-schema-selection.test.ts @@ -11,6 +11,7 @@ import { CommonLanguageClient } from 'vscode-languageclient'; import * as vscode from 'vscode'; import { TestLanguageClient } from './helper'; import * as jsonStatusBar from '../src/schema-status-bar-item'; + const expect = chai.expect; chai.use(sinonChai); @@ -105,4 +106,52 @@ describe('Status bar should work in multiple different scenarios', () => { expect(statusBar.backgroundColor).to.eql({ id: 'statusBarItem.warningBackground' }); expect(statusBar.show).calledTwice; }); + + it('Should show JSON Schema Store schema version', async () => { + const context: vscode.ExtensionContext = { + subscriptions: [], + } as vscode.ExtensionContext; + const statusBar = ({ show: sandbox.stub() } as unknown) as vscode.StatusBarItem; + createStatusBarItemStub.returns(statusBar); + onDidChangeActiveTextEditorStub.returns({ document: { uri: vscode.Uri.parse('/foo.yaml') } }); + clcStub.sendRequest + .withArgs(sinon.match.has('method', 'yaml/get/jsonSchema'), sinon.match('/foo.yaml')) + .resolves([{ uri: 'https://foo.com/bar.json', name: 'bar schema' }]); + clcStub.sendRequest + .withArgs(sinon.match.has('method', 'yaml/get/all/jsonSchemas'), sinon.match.any) + .resolves([{ versions: { '1.0.0': 'https://foo.com/bar.json' } }]); + + createJSONSchemaStatusBarItem(context, (clcStub as unknown) as CommonLanguageClient); + const callBackFn = onDidChangeActiveTextEditorStub.firstCall.firstArg; + await callBackFn({ document: { languageId: 'yaml', uri: vscode.Uri.parse('/foo.yaml') } }); + + expect(statusBar.text).to.equal('bar schema(1.0.0)'); + expect(statusBar.tooltip).to.equal('Select JSON Schema'); + expect(statusBar.backgroundColor).to.be.undefined; + expect(statusBar.show).calledTwice; + }); + + it('Should show JSON Schema Store schema version, dont include version', async () => { + const context: vscode.ExtensionContext = { + subscriptions: [], + } as vscode.ExtensionContext; + const statusBar = ({ show: sandbox.stub() } as unknown) as vscode.StatusBarItem; + createStatusBarItemStub.returns(statusBar); + onDidChangeActiveTextEditorStub.returns({ document: { uri: vscode.Uri.parse('/foo.yaml') } }); + clcStub.sendRequest + .withArgs(sinon.match.has('method', 'yaml/get/jsonSchema'), sinon.match('/foo.yaml')) + .resolves([{ uri: 'https://foo.com/bar.json', name: 'bar schema(1.0.0)' }]); + clcStub.sendRequest + .withArgs(sinon.match.has('method', 'yaml/get/all/jsonSchemas'), sinon.match.any) + .resolves([{ versions: { '1.0.0': 'https://foo.com/bar.json' } }]); + + createJSONSchemaStatusBarItem(context, (clcStub as unknown) as CommonLanguageClient); + const callBackFn = onDidChangeActiveTextEditorStub.firstCall.firstArg; + await callBackFn({ document: { languageId: 'yaml', uri: vscode.Uri.parse('/foo.yaml') } }); + + expect(statusBar.text).to.equal('bar schema(1.0.0)'); + expect(statusBar.tooltip).to.equal('Select JSON Schema'); + expect(statusBar.backgroundColor).to.be.undefined; + expect(statusBar.show).calledTwice; + }); });