diff --git a/README.md b/README.md index fd966cfb..6d6c052a 100755 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ The following settings are supported: In order to use the custom tags in your YAML file you need to first specify the custom tags in the setting of your code editor. For example, we can have the following custom tags: -```YAML +```yaml "yaml.customTags": [ "!Scalar-example scalar", "!Seq-example sequence", @@ -70,7 +70,7 @@ The !Scalar-example would map to a scalar custom tag, the !Seq-example would map We can then use the newly defined custom tags inside our YAML file: -```YAML +```yaml some_key: !Scalar-example some_value some_sequence: !Seq-example - some_seq_key_1: some_seq_value_1 @@ -93,7 +93,7 @@ myProject you can do -``` +```yaml yaml.schemas: { "https://json.schemastore.org/composer": "/myYamlFile.yaml" } @@ -109,7 +109,7 @@ and that will associate the composer schema with myYamlFile.yaml. When associating a schema it should follow the format below -```json +```yaml yaml.schemas: { "url": "globPattern", "Kubernetes": "globPattern" @@ -118,7 +118,7 @@ yaml.schemas: { e.g. -```json +```yaml yaml.schemas: { "https://json.schemastore.org/composer": "/*" } @@ -126,7 +126,7 @@ yaml.schemas: { e.g. -```json +```yaml yaml.schemas: { "kubernetes": "/myYamlFile.yaml" } @@ -134,7 +134,7 @@ yaml.schemas: { e.g. -```json +```yaml yaml.schemas: { "https://json.schemastore.org/composer": "/*", "kubernetes": "/myYamlFile.yaml" @@ -143,7 +143,7 @@ yaml.schemas: { On Windows with full path: -```json +```yaml yaml.schemas: { "C:\\Users\\user\\Documents\\custom_schema.json": "someFilePattern.yaml", } @@ -151,7 +151,7 @@ yaml.schemas: { On Mac/Linux with full path: -```json +```yaml yaml.schemas: { "/home/user/custom_schema.json": "someFilePattern.yaml", } @@ -159,13 +159,13 @@ yaml.schemas: { Since `0.11.0` YAML Schemas can be used for validation: -```json +```yaml "/home/user/custom_schema.yaml": "someFilePattern.yaml" ``` A schema can be associated with multiple globs using a json array, e.g. -```json +```yaml yaml.schemas: { "kubernetes": ["filePattern1.yaml", "filePattern2.yaml"] } @@ -173,7 +173,7 @@ yaml.schemas: { e.g. -```json +```yaml "yaml.schemas": { "http://json.schemastore.org/composer": ["/*"], "file:///home/johnd/some-schema.json": ["some.yaml"], @@ -184,7 +184,7 @@ e.g. e.g. -```json +```yaml "yaml.schemas": { "kubernetes": ["/myYamlFile.yaml"] } @@ -192,7 +192,7 @@ e.g. e.g. -```json +```yaml "yaml.schemas": { "http://json.schemastore.org/composer": ["/*"], "kubernetes": ["/myYamlFile.yaml"] @@ -205,7 +205,7 @@ You can also use relative paths when working with multi root workspaces. Suppose you have a multi root workspace that is laid out like: -``` +```yaml My_first_project: test.yaml my_schema.json @@ -216,7 +216,7 @@ My_second_project: You must then associate schemas relative to the root of the multi root workspace project. -``` +```yaml yaml.schemas: { "My_first_project/my_schema.json": "test.yaml", "My_second_project/my_schema2.json": "test2.yaml" @@ -229,7 +229,7 @@ yaml.schemas: { Suppose a file is meant to be a component of an existing schema (like a `job.yaml` file in a circleci orb), but there isn't a standalone schema that you can reference. If there is a nested schema definition for this subcomponent, you can reference it using a url fragment, e.g.: -``` +```yaml yaml.schemas: { "https://json.schemastore.org/circleciconfig#/definitions/jobs/additionalProperties": "/src/jobs/*.yaml", } @@ -275,7 +275,7 @@ The image is located at `quay.io/redhat-developer/yaml-language-server` To run the image you can use: -``` +```sh docker run -it quay.io/redhat-developer/yaml-language-server:latest ``` diff --git a/src/languageservice/services/yamlHover.ts b/src/languageservice/services/yamlHover.ts index d19ce53b..eb163f6a 100644 --- a/src/languageservice/services/yamlHover.ts +++ b/src/languageservice/services/yamlHover.ts @@ -12,7 +12,7 @@ import { setKubernetesParserOption } from '../parser/isKubernetes'; import { TextDocument } from 'vscode-languageserver-textdocument'; import { yamlDocumentsCache } from '../parser/yaml-documents'; import { SingleYAMLDocument } from '../parser/yamlParser07'; -import { getNodeValue, IApplicableSchema } from '../parser/jsonParser07'; +import { IApplicableSchema } from '../parser/jsonParser07'; import { JSONSchema } from '../jsonSchema'; import { URI } from 'vscode-uri'; import * as path from 'path'; @@ -113,27 +113,31 @@ export class YAMLHover { let title: string | undefined = undefined; let markdownDescription: string | undefined = undefined; - let markdownEnumValueDescription: string | undefined = undefined; - let enumValue: string | undefined = undefined; + let markdownEnumDescriptions: string[] = []; const markdownExamples: string[] = []; + const markdownEnums: markdownEnum[] = []; matchingSchemas.every((s) => { if ((s.node === node || (node.type === 'property' && node.valueNode === s.node)) && !s.inverted && s.schema) { title = title || s.schema.title || s.schema.closestTitle; markdownDescription = markdownDescription || s.schema.markdownDescription || toMarkdown(s.schema.description); if (s.schema.enum) { - const idx = s.schema.enum.indexOf(getNodeValue(node)); if (s.schema.markdownEnumDescriptions) { - markdownEnumValueDescription = s.schema.markdownEnumDescriptions[idx]; + markdownEnumDescriptions = s.schema.markdownEnumDescriptions; } else if (s.schema.enumDescriptions) { - markdownEnumValueDescription = toMarkdown(s.schema.enumDescriptions[idx]); + markdownEnumDescriptions = s.schema.enumDescriptions.map(toMarkdown); + } else { + markdownEnumDescriptions = []; } - if (markdownEnumValueDescription) { - enumValue = s.schema.enum[idx]; + s.schema.enum.forEach((enumValue, idx) => { if (typeof enumValue !== 'string') { enumValue = JSON.stringify(enumValue); } - } + markdownEnums.push({ + value: enumValue, + description: markdownEnumDescriptions[idx], + }); + }); } if (s.schema.anyOf && isAllSchemasMatched(node, matchingSchemas, s.schema)) { //if append title and description of all matched schemas on hover @@ -163,28 +167,30 @@ export class YAMLHover { result = '#### ' + toMarkdown(title); } if (markdownDescription) { - if (result.length > 0) { - result += '\n\n'; - } + result = ensureLineBreak(result); result += markdownDescription; } - if (markdownEnumValueDescription) { - if (result.length > 0) { - result += '\n\n'; - } - result += `\`${toMarkdownCodeBlock(enumValue)}\`: ${markdownEnumValueDescription}`; + if (markdownEnums.length !== 0) { + result = ensureLineBreak(result); + result += 'Allowed Values:\n\n'; + markdownEnums.forEach((me) => { + if (me.description) { + result += `* \`${toMarkdownCodeBlock(me.value)}\`: ${me.description}\n`; + } else { + result += `* \`${toMarkdownCodeBlock(me.value)}\`\n`; + } + }); } if (markdownExamples.length !== 0) { - if (result.length > 0) { - result += '\n\n'; - } - result += 'Examples:'; + result = ensureLineBreak(result); + result += 'Examples:\n\n'; markdownExamples.forEach((example) => { - result += `\n\n\`\`\`${example}\`\`\``; + result += `* \`\`\`${example}\`\`\`\n`; }); } if (result.length > 0 && schema.schema.url) { - result += `\n\nSource: [${getSchemaName(schema.schema)}](${schema.schema.url})`; + result = ensureLineBreak(result); + result += `Source: [${getSchemaName(schema.schema)}](${schema.schema.url})`; } return createHover(result); } @@ -193,6 +199,21 @@ export class YAMLHover { } } +interface markdownEnum { + value: string; + description: string; +} + +function ensureLineBreak(content: string): string { + if (content.length === 0) { + return content; + } + if (!content.endsWith('\n')) { + content += '\n'; + } + return content + '\n'; +} + function getSchemaName(schema: JSONSchema): string { let result = 'JSON Schema'; const urlString = schema.url; diff --git a/test/hover.test.ts b/test/hover.test.ts index afd28690..83cf9932 100644 --- a/test/hover.test.ts +++ b/test/hover.test.ts @@ -556,6 +556,37 @@ users: ); }); + it('Hover displays enum descriptions if present', async () => { + schemaProvider.addSchema(SCHEMA_ID, { + type: 'object', + properties: { + animal: { + type: 'string', + description: 'should return this description', + enum: ['cat', 'dog', 'non'], + enumDescriptions: ['', 'Canis familiaris'], + }, + }, + }); + const content = 'animal:\n ca|t|'; // len: 13, pos: 12 + const result = await parseSetup(content); + + assert.strictEqual(MarkupContent.is(result.contents), true); + assert.strictEqual((result.contents as MarkupContent).kind, 'markdown'); + assert.strictEqual( + (result.contents as MarkupContent).value, + `should return this description + +Allowed Values: + +* \`cat\` +* \`dog\`: Canis familiaris +* \`non\` + +Source: [${SCHEMA_ID}](file:///${SCHEMA_ID})` + ); + }); + it('Hover works on examples', async () => { schemaProvider.addSchema(SCHEMA_ID, { type: 'object', @@ -577,11 +608,15 @@ users: (result.contents as MarkupContent).value, `should return this description -Examples: +Allowed Values: + +* \`cat\` +* \`dog\` -\`\`\`"cat"\`\`\` +Examples: -\`\`\`"dog"\`\`\` +* \`\`\`"cat"\`\`\` +* \`\`\`"dog"\`\`\` Source: [${SCHEMA_ID}](file:///${SCHEMA_ID})` );