Skip to content

Commit bf6f9d1

Browse files
committed
Merge branch 'main' of github.com:redhat-developer/yaml-language-server into fix-links-error
Signed-off-by: Yevhen Vydolob <[email protected]>
2 parents 17a67e1 + 520a328 commit bf6f9d1

12 files changed

+383
-59
lines changed

README.md

+84
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,90 @@ docker run -it quay.io/redhat-developer/yaml-language-server:latest
254254

255255
`yaml-language-server` use `[email protected]` which implements [LSP 3.16](https://github.com/Microsoft/language-server-protocol/blob/gh-pages/_specifications/specification-3-16.md)
256256

257+
## Language Server Protocol extensions
258+
259+
### SchemaSelectionRequests
260+
261+
#### SupportSchemaSelection Notification
262+
263+
The support schema selection notification is sent from a client to the server to inform server that client supports JSON Schema selection.
264+
265+
_Notification:_
266+
- method: `'yaml/supportSchemaSelection'`
267+
- params: `void`
268+
269+
#### SchemaStoreInitialized Notification
270+
271+
The schema store initialized notification is sent from the server to a client to inform client that server has finished initializing/loading schemas from schema store, and client now can ask for schemas.
272+
273+
_Notification:_
274+
- method: `'yaml/schema/store/initialized'`
275+
- params: `void`
276+
277+
#### GetAllSchemas Request
278+
279+
The get all schemas request sent from a client to server to get all known schemas.
280+
281+
_Request:_
282+
- method: `'yaml/get/all/jsonSchemas'`;
283+
- params: the document uri, server will mark used schema for document
284+
285+
_Response:_
286+
287+
- result: `JSONSchemaDescriptionExt[]`
288+
```typescript
289+
interface JSONSchemaDescriptionExt {
290+
/**
291+
* Schema URI
292+
*/
293+
uri: string;
294+
/**
295+
* Schema name, from schema store
296+
*/
297+
name?: string;
298+
/**
299+
* Schema description, from schema store
300+
*/
301+
description?: string;
302+
/**
303+
* Is schema used for current document
304+
*/
305+
usedForCurrentFile: boolean;
306+
/**
307+
* Is schema from schema store
308+
*/
309+
fromStore: boolean;
310+
}
311+
```
312+
313+
#### GetSchemas Request
314+
315+
The request sent from a client to server to get schemas used for current document. Client can use this method to indicate in UI which schemas used for current YAML document.
316+
317+
_Request:_
318+
- method: `'yaml/get/jsonSchema'`;
319+
- params: the document uri to get used schemas
320+
321+
_Response:_
322+
- result: `JSONSchemaDescription[]`
323+
324+
```typescript
325+
interface JSONSchemaDescriptionExt {
326+
/**
327+
* Schema URI
328+
*/
329+
uri: string;
330+
/**
331+
* Schema name, from schema store
332+
*/
333+
name?: string;
334+
/**
335+
* Schema description, from schema store
336+
*/
337+
description?: string;
338+
}
339+
```
340+
257341
## Clients
258342

259343
This repository only contains the server implementation. Here are some known clients consuming this server:

src/languageserver/handlers/notificationHandlers.ts

+6
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
CustomSchemaRequest,
1010
DynamicCustomSchemaRequestRegistration,
1111
SchemaAssociationNotification,
12+
SchemaSelectionRequests,
1213
VSCodeContentRequestRegistration,
1314
} from '../../requestTypes';
1415
import { SettingsState } from '../../yamlSettings';
@@ -36,6 +37,7 @@ export class NotificationHandlers {
3637
);
3738
this.connection.onNotification(DynamicCustomSchemaRequestRegistration.type, () => this.dynamicSchemaRequestHandler());
3839
this.connection.onNotification(VSCodeContentRequestRegistration.type, () => this.vscodeContentRequestHandler());
40+
this.connection.onNotification(SchemaSelectionRequests.type, () => this.schemaSelectionRequestHandler());
3941
}
4042

4143
/**
@@ -68,4 +70,8 @@ export class NotificationHandlers {
6870
private vscodeContentRequestHandler(): void {
6971
this.yamlSettings.useVSCodeContentRequest = true;
7072
}
73+
74+
private schemaSelectionRequestHandler(): void {
75+
this.yamlSettings.useSchemaSelectionRequests = true;
76+
}
7177
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Red Hat, Inc. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { Connection } from 'vscode-languageserver/node';
7+
import { JSONSchema } from '../../languageservice/jsonSchema';
8+
import { yamlDocumentsCache } from '../../languageservice/parser/yaml-documents';
9+
import { YAMLSchemaService } from '../../languageservice/services/yamlSchemaService';
10+
import { getSchemaUrls } from '../../languageservice/utils/schemaUrls';
11+
import { SettingsState } from '../../yamlSettings';
12+
import { JSONSchemaDescription, JSONSchemaDescriptionExt, SchemaSelectionRequests } from '../../requestTypes';
13+
14+
export class JSONSchemaSelection {
15+
constructor(
16+
private readonly schemaService: YAMLSchemaService,
17+
private readonly yamlSettings: SettingsState,
18+
private readonly connection: Connection
19+
) {
20+
this.connection.onRequest(SchemaSelectionRequests.getSchema, (fileUri) => {
21+
return this.getSchemas(fileUri);
22+
});
23+
this.connection.onRequest(SchemaSelectionRequests.getAllSchemas, (fileUri) => {
24+
return this.getAllSchemas(fileUri);
25+
});
26+
}
27+
28+
async getSchemas(docUri: string): Promise<JSONSchemaDescription[]> {
29+
const schemas = await this.getSchemasForFile(docUri);
30+
const result = Array.from(schemas).map((val) => {
31+
return {
32+
name: val[1].title,
33+
uri: val[0],
34+
description: val[1].description,
35+
};
36+
});
37+
38+
return result;
39+
}
40+
41+
private async getSchemasForFile(docUri: string): Promise<Map<string, JSONSchema>> {
42+
const document = this.yamlSettings.documents.get(docUri);
43+
const schemas = new Map<string, JSONSchema>();
44+
if (!document) {
45+
return schemas;
46+
}
47+
48+
const yamlDoc = yamlDocumentsCache.getYamlDocument(document);
49+
50+
for (const currentYAMLDoc of yamlDoc.documents) {
51+
const schema = await this.schemaService.getSchemaForResource(document.uri, currentYAMLDoc);
52+
if (schema?.schema) {
53+
const schemaUrls = getSchemaUrls(schema?.schema);
54+
if (schemaUrls.size === 0) {
55+
continue;
56+
}
57+
for (const urlToSchema of schemaUrls) {
58+
schemas.set(urlToSchema[0], urlToSchema[1]);
59+
}
60+
}
61+
}
62+
return schemas;
63+
}
64+
65+
async getAllSchemas(docUri: string): Promise<JSONSchemaDescriptionExt[]> {
66+
const fileSchemas = await this.getSchemasForFile(docUri);
67+
const fileSchemasHandle: JSONSchemaDescriptionExt[] = Array.from(fileSchemas.entries()).map((val) => {
68+
return {
69+
uri: val[0],
70+
fromStore: false,
71+
usedForCurrentFile: true,
72+
name: val[1].title,
73+
description: val[1].description,
74+
};
75+
});
76+
const result = [];
77+
let allSchemas = this.schemaService.getAllSchemas();
78+
allSchemas = allSchemas.filter((val) => !fileSchemas.has(val.uri));
79+
result.push(...fileSchemasHandle);
80+
result.push(...allSchemas);
81+
82+
return result;
83+
}
84+
}

src/languageserver/handlers/settingsHandlers.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { DocumentFormattingRequest, Connection, DidChangeConfigurationNotificati
77
import { isRelativePath, relativeToAbsolutePath } from '../../languageservice/utils/paths';
88
import { checkSchemaURI, JSON_SCHEMASTORE_URL, KUBERNETES_SCHEMA_URL } from '../../languageservice/utils/schemaUrls';
99
import { LanguageService, LanguageSettings, SchemaPriority } from '../../languageservice/yamlLanguageService';
10+
import { SchemaSelectionRequests } from '../../requestTypes';
1011
import { Settings, SettingsState } from '../../yamlSettings';
1112
import { Telemetry } from '../telemetry';
1213
import { ValidationHandler } from './validationHandlers';
@@ -53,7 +54,7 @@ export class SettingsHandler {
5354
this.setConfiguration(settings);
5455
}
5556

56-
setConfiguration(settings: Settings): void {
57+
async setConfiguration(settings: Settings): Promise<void> {
5758
configureHttpRequests(settings.http && settings.http.proxy, settings.http && settings.http.proxyStrictSSL);
5859

5960
this.yamlSettings.specificValidatorPaths = [];
@@ -120,8 +121,11 @@ export class SettingsHandler {
120121
this.yamlSettings.schemaConfigurationSettings.push(schemaObj);
121122
}
122123

123-
this.setSchemaStoreSettingsIfNotSet();
124+
await this.setSchemaStoreSettingsIfNotSet();
124125
this.updateConfiguration();
126+
if (this.yamlSettings.useSchemaSelectionRequests) {
127+
this.connection.sendNotification(SchemaSelectionRequests.schemaStoreInitialized, {});
128+
}
125129

126130
// dynamically enable & disable the formatter
127131
if (this.yamlSettings.clientDynamicRegisterSupport) {

src/languageservice/services/yamlCodeLens.ts

+3-43
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ import { yamlDocumentsCache } from '../parser/yaml-documents';
1010
import { YAMLSchemaService } from './yamlSchemaService';
1111
import { URI } from 'vscode-uri';
1212
import * as path from 'path';
13-
import { JSONSchema, JSONSchemaRef } from '../jsonSchema';
13+
import { JSONSchema } from '../jsonSchema';
1414
import { CodeLensParams } from 'vscode-languageserver-protocol';
15-
import { isBoolean } from '../utils/objects';
1615
import { Telemetry } from '../../languageserver/telemetry';
16+
import { getSchemaUrls } from '../utils/schemaUrls';
1717

1818
export class YamlCodeLens {
1919
constructor(private schemaService: YAMLSchemaService, private readonly telemetry: Telemetry) {}
@@ -26,7 +26,7 @@ export class YamlCodeLens {
2626
for (const currentYAMLDoc of yamlDocument.documents) {
2727
const schema = await this.schemaService.getSchemaForResource(document.uri, currentYAMLDoc);
2828
if (schema?.schema) {
29-
const schemaUrls = getSchemaUrl(schema?.schema);
29+
const schemaUrls = getSchemaUrls(schema?.schema);
3030
if (schemaUrls.size === 0) {
3131
continue;
3232
}
@@ -66,43 +66,3 @@ function getCommandTitle(url: string, schema: JSONSchema): string {
6666

6767
return baseName;
6868
}
69-
70-
function getSchemaUrl(schema: JSONSchema): Map<string, JSONSchema> {
71-
const result = new Map<string, JSONSchema>();
72-
if (!schema) {
73-
return result;
74-
}
75-
const url = schema.url;
76-
if (url) {
77-
if (url.startsWith('schemaservice://combinedSchema/')) {
78-
addSchemasForOf(schema, result);
79-
} else {
80-
result.set(schema.url, schema);
81-
}
82-
} else {
83-
addSchemasForOf(schema, result);
84-
}
85-
return result;
86-
}
87-
88-
function addSchemasForOf(schema: JSONSchema, result: Map<string, JSONSchema>): void {
89-
if (schema.allOf) {
90-
addInnerSchemaUrls(schema.allOf, result);
91-
}
92-
if (schema.anyOf) {
93-
addInnerSchemaUrls(schema.anyOf, result);
94-
}
95-
if (schema.oneOf) {
96-
addInnerSchemaUrls(schema.oneOf, result);
97-
}
98-
}
99-
100-
function addInnerSchemaUrls(schemas: JSONSchemaRef[], result: Map<string, JSONSchema>): void {
101-
for (const subSchema of schemas) {
102-
if (!isBoolean(subSchema)) {
103-
if (subSchema.url && !result.has(subSchema.url)) {
104-
result.set(subSchema.url, subSchema);
105-
}
106-
}
107-
}
108-
}

src/languageservice/utils/schemaUrls.ts

+47
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { WorkspaceFolder } from 'vscode-languageserver';
22
import { URI } from 'vscode-uri';
33
import { Telemetry } from '../../languageserver/telemetry';
4+
import { JSONSchema, JSONSchemaRef } from '../jsonSchema';
5+
import { isBoolean } from './objects';
46
import { isRelativePath, relativeToAbsolutePath } from './paths';
57

68
export const KUBERNETES_SCHEMA_URL =
@@ -22,3 +24,48 @@ export function checkSchemaURI(
2224
return uri;
2325
}
2426
}
27+
28+
/**
29+
* Collect all urls of sub schemas
30+
* @param schema the root schema
31+
* @returns map url to schema
32+
*/
33+
export function getSchemaUrls(schema: JSONSchema): Map<string, JSONSchema> {
34+
const result = new Map<string, JSONSchema>();
35+
if (!schema) {
36+
return result;
37+
}
38+
39+
if (schema.url) {
40+
if (schema.url.startsWith('schemaservice://combinedSchema/')) {
41+
addSchemasForOf(schema, result);
42+
} else {
43+
result.set(schema.url, schema);
44+
}
45+
} else {
46+
addSchemasForOf(schema, result);
47+
}
48+
return result;
49+
}
50+
51+
function addSchemasForOf(schema: JSONSchema, result: Map<string, JSONSchema>): void {
52+
if (schema.allOf) {
53+
addInnerSchemaUrls(schema.allOf, result);
54+
}
55+
if (schema.anyOf) {
56+
addInnerSchemaUrls(schema.anyOf, result);
57+
}
58+
if (schema.oneOf) {
59+
addInnerSchemaUrls(schema.oneOf, result);
60+
}
61+
}
62+
63+
function addInnerSchemaUrls(schemas: JSONSchemaRef[], result: Map<string, JSONSchema>): void {
64+
for (const subSchema of schemas) {
65+
if (!isBoolean(subSchema)) {
66+
if (subSchema.url && !result.has(subSchema.url)) {
67+
result.set(subSchema.url, subSchema);
68+
}
69+
}
70+
}
71+
}

src/languageservice/yamlLanguageService.ts

+6
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ import { Telemetry } from '../languageserver/telemetry';
5252
import { YamlVersion } from './parser/yamlParser07';
5353
import { YamlCompletion } from './services/yamlCompletion';
5454
import { yamlDocumentsCache } from './parser/yaml-documents';
55+
import { SettingsState } from '../yamlSettings';
56+
import { JSONSchemaSelection } from '../languageserver/handlers/schemaSelectionHandlers';
5557
import { getDefinition } from './services/yamlDefinition';
5658

5759
export enum SchemaPriority {
@@ -159,6 +161,7 @@ export function getLanguageService(
159161
workspaceContext: WorkspaceContextService,
160162
connection: Connection,
161163
telemetry: Telemetry,
164+
yamlSettings: SettingsState,
162165
clientCapabilities?: ClientCapabilities
163166
): LanguageService {
164167
const schemaService = new YAMLSchemaService(schemaRequestService, workspaceContext);
@@ -170,6 +173,9 @@ export function getLanguageService(
170173
const yamlCodeActions = new YamlCodeActions(clientCapabilities);
171174
const yamlCodeLens = new YamlCodeLens(schemaService, telemetry);
172175
const yamlLinks = new YamlLinks(telemetry);
176+
177+
new JSONSchemaSelection(schemaService, yamlSettings, connection);
178+
173179
// register all commands
174180
registerCommands(commandExecutor, connection);
175181
return {

src/requestTypes.ts

+7
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,10 @@ export namespace ColorSymbolRequest {
7070
export namespace SchemaModificationNotification {
7171
export const type: RequestType<SchemaAdditions | SchemaDeletions, void, {}> = new RequestType('json/schema/modify');
7272
}
73+
74+
export namespace SchemaSelectionRequests {
75+
export const type: NotificationType<void> = new NotificationType('yaml/supportSchemaSelection');
76+
export const getSchema: RequestType<string, JSONSchemaDescription[], {}> = new RequestType('yaml/get/jsonSchema');
77+
export const getAllSchemas: RequestType<string, JSONSchemaDescriptionExt[], {}> = new RequestType('yaml/get/all/jsonSchemas');
78+
export const schemaStoreInitialized: NotificationType<{}> = new NotificationType('yaml/schema/store/initialized');
79+
}

0 commit comments

Comments
 (0)