'use strict' ; import { Hover , MarkupContent , Position , Range } from 'vscode-languageserver-types' ; import { matchOffsetToDocument } from '../utils/arrUtils' ; import { LanguageSettings } from '../yamlLanguageService' ; import { YAMLSchemaService } from './yamlSchemaService' ; import { setKubernetesParserOption } from '../parser/isKubernetes' ; import { TextDocument } from 'vscode-languageserver-textdocument' ; import { yamlDocumentsCache } from '../parser/yaml-documents' ; import { SingleYAMLDocument } from '../parser/yamlParser07' ; import { getNodeValue } from '../parser/jsonParser07' ; import { JSONSchema } from '../jsonSchema' ; import { URI } from 'vscode-uri' ; import * as path from 'path' ; import { Telemetry } from '../../languageserver/telemetry' ; import { convertErrorToTelemetryMsg } from '../utils/objects' ; export class YAMLHover { private shouldHover : boolean ; private schemaService : YAMLSchemaService ; constructor ( schemaService , private readonly telemetry ) { this . shouldHover = true ; this . schemaService = schemaService ; } configure ( languageSettings ) { if ( languageSettings ) { this . shouldHover = languageSettings . hover ; } } doHover ( document , position , isKubernetes = false ) { try { if ( ! this . shouldHover || ! document ) { return Promise . resolve ( undefined ) ; } const doc = yamlDocumentsCache . getYamlDocument ( document ) ; const offset = document . offsetAt ( position ) ; const currentDoc = matchOffsetToDocument ( offset , doc ) ; if ( currentDoc === null ) { return Promise . resolve ( undefined ) ; } setKubernetesParserOption ( doc . documents , isKubernetes ) ; const currentDocIndex = doc . documents . indexOf ( currentDoc ) ; currentDoc . currentDocIndex = currentDocIndex ; return this . getHover ( document , position , currentDoc ) ; } catch ( error ) { this . telemetry . sendError ( 'yaml.hover.error' , { error : convertErrorToTelemetryMsg ( error ) } ) ; } } private getHover ( document , position , doc ) { const offset = document . offsetAt ( position ) ; let node = doc . getNodeFromOffset ( offset ) ; if ( ! node || ( ( node . type === 'object' || node . type === 'array' ) && offset > node . offset + 1 && offset < node . offset + node . length - 1 ) ) { return Promise . resolve ( null ) ; } const hoverRangeNode = node ; if ( node . type === 'string' ) { const parent = node . parent ; if ( parent && parent . type === 'property' && parent . keyNode === node ) { node = parent . valueNode ; if ( ! node ) { return Promise . resolve ( null ) ; } } } const hoverRange = Range . create ( document . positionAt ( hoverRangeNode . offset ) , document . positionAt ( hoverRangeNode . offset + hoverRangeNode . length ) ) ; const createHover = ( contents ) : Hover => { const markupContent = { kind : 'markdown' , value : contents , } ; const result = { contents : markupContent , range : hoverRange , } ; return result ; } ; return this . schemaService . getSchemaForResource ( document . uri , doc ) . then ( ( schema ) => { if ( schema && node && ! schema . errors . length ) { const matchingSchemas = doc . getMatchingSchemas ( schema . schema , node . offset ) ; let title = undefined ; let markdownDescription = undefined ; let markdownEnumValueDescription = undefined , enumValue = undefined ; matchingSchemas . every ( ( s ) => { if ( s . node === node && ! s . inverted && s . schema ) { title = title || s . schema . title ; 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 ] ; } else if ( s . schema . enumDescriptions ) { markdownEnumValueDescription = toMarkdown ( s . schema . enumDescriptions [ idx ] ) ; } if ( markdownEnumValueDescription ) { enumValue = s . schema . enum [ idx ] ; if ( typeof enumValue !== 'string' ) { enumValue = JSON . stringify ( enumValue ) ; } } } } return true ; } ) ; let result = '' ; if ( title ) { result = '#### ' + toMarkdown ( title ) ; } if ( markdownDescription ) { if ( result . length > 0 ) { result += '\n\n' ; } result += markdownDescription ; } if ( markdownEnumValueDescription ) { if ( result . length > 0 ) { result += '\n\n' ; } result += `\`${ toMarkdownCodeBlock ( enumValue ) }\`: ${ markdownEnumValueDescription }` ; } if ( result . length > 0 && schema . schema . url ) { result += `\n\nSource: [${ getSchemaName ( schema . schema ) }](${ schema . schema . url })` ; } return createHover ( result ) ; } return null ; } ) ; } } function getSchemaName ( schema ) { let result = 'JSON Schema' ; const urlString = schema . url ; if ( urlString ) { const url = URI . parse ( urlString ) ; result = path . basename ( url . fsPath ) ; } else if ( schema . title ) { result = schema . title ; } return result ; } function toMarkdown ( plain ) ; function toMarkdown ( plain ) ; function toMarkdown ( plain ) { if ( plain ) { const res = plain . replace ( /([^\n\r])(\r?\n)([^\n\r])/gm , '$1\n\n$3' ) ; return res . replace ( /[\\`*_{}[\]()#+\-.!]/g , '\\$&' ) ; } return undefined ; } function toMarkdownCodeBlock ( content ) { if ( content . indexOf ( '`' ) !== - 1 ) { return '`` ' + content + ' ``' ; } return content ; }