/
yamlHover.ts
148 lines (131 loc) · 5.17 KB
/
yamlHover.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
/*---------------------------------------------------------------------------------------------
* Copyright (c) Red Hat, Inc. All rights reserved.
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import Parser = require('../parser/jsonParser');
import SchemaService = require('./jsonSchemaService');
import {JSONWorkerContribution} from '../jsonContributions';
import {PromiseConstructor, Thenable} from 'vscode-json-languageservice';
import {Hover, TextDocument, Position, Range, MarkedString} from 'vscode-languageserver-types';
import { matchOffsetToDocument } from '../utils/arrUtils';
import { LanguageSettings } from '../yamlLanguageService';
export class YAMLHover {
private schemaService: SchemaService.IJSONSchemaService;
private contributions: JSONWorkerContribution[];
private promise: PromiseConstructor;
private shouldHover: boolean;
constructor(schemaService: SchemaService.IJSONSchemaService, contributions: JSONWorkerContribution[] = [], promiseConstructor: PromiseConstructor) {
this.schemaService = schemaService;
this.contributions = contributions;
this.promise = promiseConstructor || Promise;
this.shouldHover = true;
}
public configure(languageSettings: LanguageSettings){
if(languageSettings){
this.shouldHover = languageSettings.hover;
}
}
public doHover(document: TextDocument, position: Position, doc): Thenable<Hover> {
if(!this.shouldHover || !document){
return this.promise.resolve(void 0);
}
let offset = document.offsetAt(position);
let currentDoc = matchOffsetToDocument(offset, doc);
if(currentDoc === null){
return this.promise.resolve(void 0);
}
const currentDocIndex = doc.documents.indexOf(currentDoc);
let node = currentDoc.getNodeFromOffset(offset);
if (!node || (node.type === 'object' || node.type === 'array') && offset > node.start + 1 && offset < node.end - 1) {
return this.promise.resolve(void 0);
}
let hoverRangeNode = node;
// use the property description when hovering over an object key
if (node.type === 'string') {
let stringNode = <Parser.StringASTNode>node;
if (stringNode.isKey) {
let propertyNode = <Parser.PropertyASTNode>node.parent;
node = propertyNode.value;
if (!node) {
return this.promise.resolve(void 0);
}
}
}
let hoverRange = Range.create(document.positionAt(hoverRangeNode.start), document.positionAt(hoverRangeNode.end));
var createHover = (contents: MarkedString[]) => {
let result: Hover = {
contents: contents,
range: hoverRange
};
return result;
};
let location = node.getPath();
for (let i = this.contributions.length - 1; i >= 0; i--) {
let contribution = this.contributions[i];
let promise = contribution.getInfoContribution(document.uri, location);
if (promise) {
return promise.then(htmlContent => createHover(htmlContent));
}
}
return this.schemaService.getSchemaForResource(document.uri).then((schema) => {
if (schema) {
let newSchema = schema;
if (schema.schema && schema.schema.schemaSequence && schema.schema.schemaSequence[currentDocIndex]) {
newSchema = new SchemaService.ResolvedSchema(schema.schema.schemaSequence[currentDocIndex]);
}
let matchingSchemas = currentDoc.getMatchingSchemas(newSchema.schema, node.start);
let title: string = null;
let markdownDescription: string = null;
let markdownEnumValueDescription = null, enumValue = null;
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) {
let idx = s.schema.enum.indexOf(node.getValue());
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 += `\`${toMarkdown(enumValue)}\`: ${markdownEnumValueDescription}`;
}
return createHover([result]);
}
return void 0;
});
}
}
function toMarkdown(plain: string) {
if (plain) {
let res = plain.replace(/([^\n\r])(\r?\n)([^\n\r])/gm, '$1\n\n$3'); // single new lines to \n\n (Markdown paragraph)
return res.replace(/[\\`*_{}[\]()#+\-.!]/g, "\\$&"); // escape markdown syntax tokens: http://daringfireball.net/projects/markdown/syntax#backslash
}
return void 0;
}