Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add methods which allow client get schemas info #556

Merged
merged 5 commits into from
Nov 30, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,90 @@ docker run -it quay.io/redhat-developer/yaml-language-server:latest

`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)

## Language Server Protocol extensions

### SchemaSelectionRequests

#### SupportSchemaSelection Notification

The support schema selection notification is sent from a client to the server to inform server that client supports JSON Schema selection.

_Notification:_
- method: `'yaml/supportSchemaSelection'`
- params: `void`

#### SchemaStoreInitialized Notification

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.

_Notification:_
- method: `'yaml/schema/store/initialized'`
- params: `void`

#### GetAllSchemas Request

The get all schemas request sent from a client to server to get all known schemas.

_Request:_
- method: `'yaml/get/all/jsonSchemas'`;
- params: the document uri, server will mark used schema for document

_Response:_

- result: `JSONSchemaDescriptionExt[]`
```typescript
interface JSONSchemaDescriptionExt {
/**
* Schema URI
*/
uri: string;
/**
* Schema name, from schema store
*/
name?: string;
/**
* Schema description, from schema store
*/
description?: string;
/**
* Is schema used for current document
*/
usedForCurrentFile: boolean;
/**
* Is schema from schema store
*/
fromStore: boolean;
}
```

#### GetSchemas Request

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.

_Request:_
- method: `'yaml/get/jsonSchema'`;
- params: the document uri to get used schemas

_Response:_
- result: `JSONSchemaDescription[]`

```typescript
interface JSONSchemaDescriptionExt {
/**
* Schema URI
*/
uri: string;
/**
* Schema name, from schema store
*/
name?: string;
/**
* Schema description, from schema store
*/
description?: string;
}
```

## Clients

This repository only contains the server implementation. Here are some known clients consuming this server:
Expand Down
6 changes: 6 additions & 0 deletions src/languageserver/handlers/notificationHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
CustomSchemaRequest,
DynamicCustomSchemaRequestRegistration,
SchemaAssociationNotification,
SchemaSelectionRequests,
VSCodeContentRequestRegistration,
} from '../../requestTypes';
import { SettingsState } from '../../yamlSettings';
Expand Down Expand Up @@ -36,6 +37,7 @@ export class NotificationHandlers {
);
this.connection.onNotification(DynamicCustomSchemaRequestRegistration.type, () => this.dynamicSchemaRequestHandler());
this.connection.onNotification(VSCodeContentRequestRegistration.type, () => this.vscodeContentRequestHandler());
this.connection.onNotification(SchemaSelectionRequests.type, () => this.schemaSelectionRequestHandler());
}

/**
Expand Down Expand Up @@ -68,4 +70,8 @@ export class NotificationHandlers {
private vscodeContentRequestHandler(): void {
this.yamlSettings.useVSCodeContentRequest = true;
}

private schemaSelectionRequestHandler(): void {
this.yamlSettings.useSchemaSelectionRequests = true;
}
}
84 changes: 84 additions & 0 deletions src/languageserver/handlers/schemaSelectionHandlers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Red Hat, Inc. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { Connection } from 'vscode-languageserver/node';
import { JSONSchema } from '../../languageservice/jsonSchema';
import { yamlDocumentsCache } from '../../languageservice/parser/yaml-documents';
import { YAMLSchemaService } from '../../languageservice/services/yamlSchemaService';
import { getSchemaUrls } from '../../languageservice/utils/schemaUrls';
import { SettingsState } from '../../yamlSettings';
import { JSONSchemaDescription, JSONSchemaDescriptionExt, SchemaSelectionRequests } from '../../requestTypes';

export class JSONSchemaSelection {
constructor(
private readonly schemaService: YAMLSchemaService,
private readonly yamlSettings: SettingsState,
private readonly connection: Connection
) {
this.connection.onRequest(SchemaSelectionRequests.getSchema, (fileUri) => {
return this.getSchemas(fileUri);
});
this.connection.onRequest(SchemaSelectionRequests.getAllSchemas, (fileUri) => {
return this.getAllSchemas(fileUri);
});
}

async getSchemas(docUri: string): Promise<JSONSchemaDescription[]> {
const schemas = await this.getSchemasForFile(docUri);
const result = Array.from(schemas).map((val) => {
return {
name: val[1].title,
uri: val[0],
description: val[1].description,
};
});

return result;
}

private async getSchemasForFile(docUri: string): Promise<Map<string, JSONSchema>> {
const document = this.yamlSettings.documents.get(docUri);
const schemas = new Map<string, JSONSchema>();
if (!document) {
return schemas;
}

const yamlDoc = yamlDocumentsCache.getYamlDocument(document);

for (const currentYAMLDoc of yamlDoc.documents) {
const schema = await this.schemaService.getSchemaForResource(document.uri, currentYAMLDoc);
if (schema?.schema) {
const schemaUrls = getSchemaUrls(schema?.schema);
if (schemaUrls.size === 0) {
continue;
}
for (const urlToSchema of schemaUrls) {
schemas.set(urlToSchema[0], urlToSchema[1]);
}
}
}
return schemas;
}

async getAllSchemas(docUri: string): Promise<JSONSchemaDescriptionExt[]> {
const fileSchemas = await this.getSchemasForFile(docUri);
const fileSchemasHandle: JSONSchemaDescriptionExt[] = Array.from(fileSchemas.entries()).map((val) => {
return {
uri: val[0],
fromStore: false,
usedForCurrentFile: true,
name: val[1].title,
description: val[1].description,
};
});
const result = [];
let allSchemas = this.schemaService.getAllSchemas();
allSchemas = allSchemas.filter((val) => !fileSchemas.has(val.uri));
result.push(...fileSchemasHandle);
result.push(...allSchemas);

return result;
}
}
8 changes: 6 additions & 2 deletions src/languageserver/handlers/settingsHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { DocumentFormattingRequest, Connection, DidChangeConfigurationNotificati
import { isRelativePath, relativeToAbsolutePath } from '../../languageservice/utils/paths';
import { checkSchemaURI, JSON_SCHEMASTORE_URL, KUBERNETES_SCHEMA_URL } from '../../languageservice/utils/schemaUrls';
import { LanguageService, LanguageSettings, SchemaPriority } from '../../languageservice/yamlLanguageService';
import { SchemaSelectionRequests } from '../../requestTypes';
import { Settings, SettingsState } from '../../yamlSettings';
import { Telemetry } from '../telemetry';
import { ValidationHandler } from './validationHandlers';
Expand Down Expand Up @@ -53,7 +54,7 @@ export class SettingsHandler {
this.setConfiguration(settings);
}

setConfiguration(settings: Settings): void {
async setConfiguration(settings: Settings): Promise<void> {
configureHttpRequests(settings.http && settings.http.proxy, settings.http && settings.http.proxyStrictSSL);

this.yamlSettings.specificValidatorPaths = [];
Expand Down Expand Up @@ -120,8 +121,11 @@ export class SettingsHandler {
this.yamlSettings.schemaConfigurationSettings.push(schemaObj);
}

this.setSchemaStoreSettingsIfNotSet();
await this.setSchemaStoreSettingsIfNotSet();
this.updateConfiguration();
if (this.yamlSettings.useSchemaSelectionRequests) {
this.connection.sendNotification(SchemaSelectionRequests.schemaStoreInitialized, {});
}

// dynamically enable & disable the formatter
if (this.yamlSettings.clientDynamicRegisterSupport) {
Expand Down
46 changes: 3 additions & 43 deletions src/languageservice/services/yamlCodeLens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ import { yamlDocumentsCache } from '../parser/yaml-documents';
import { YAMLSchemaService } from './yamlSchemaService';
import { URI } from 'vscode-uri';
import * as path from 'path';
import { JSONSchema, JSONSchemaRef } from '../jsonSchema';
import { JSONSchema } from '../jsonSchema';
import { CodeLensParams } from 'vscode-languageserver-protocol';
import { isBoolean } from '../utils/objects';
import { Telemetry } from '../../languageserver/telemetry';
import { getSchemaUrls } from '../utils/schemaUrls';

export class YamlCodeLens {
constructor(private schemaService: YAMLSchemaService, private readonly telemetry: Telemetry) {}
Expand All @@ -26,7 +26,7 @@ export class YamlCodeLens {
for (const currentYAMLDoc of yamlDocument.documents) {
const schema = await this.schemaService.getSchemaForResource(document.uri, currentYAMLDoc);
if (schema?.schema) {
const schemaUrls = getSchemaUrl(schema?.schema);
const schemaUrls = getSchemaUrls(schema?.schema);
if (schemaUrls.size === 0) {
continue;
}
Expand Down Expand Up @@ -66,43 +66,3 @@ function getCommandTitle(url: string, schema: JSONSchema): string {

return baseName;
}

function getSchemaUrl(schema: JSONSchema): Map<string, JSONSchema> {
const result = new Map<string, JSONSchema>();
if (!schema) {
return result;
}
const url = schema.url;
if (url) {
if (url.startsWith('schemaservice://combinedSchema/')) {
addSchemasForOf(schema, result);
} else {
result.set(schema.url, schema);
}
} else {
addSchemasForOf(schema, result);
}
return result;
}

function addSchemasForOf(schema: JSONSchema, result: Map<string, JSONSchema>): void {
if (schema.allOf) {
addInnerSchemaUrls(schema.allOf, result);
}
if (schema.anyOf) {
addInnerSchemaUrls(schema.anyOf, result);
}
if (schema.oneOf) {
addInnerSchemaUrls(schema.oneOf, result);
}
}

function addInnerSchemaUrls(schemas: JSONSchemaRef[], result: Map<string, JSONSchema>): void {
for (const subSchema of schemas) {
if (!isBoolean(subSchema)) {
if (subSchema.url && !result.has(subSchema.url)) {
result.set(subSchema.url, subSchema);
}
}
}
}
47 changes: 47 additions & 0 deletions src/languageservice/utils/schemaUrls.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { WorkspaceFolder } from 'vscode-languageserver';
import { URI } from 'vscode-uri';
import { Telemetry } from '../../languageserver/telemetry';
import { JSONSchema, JSONSchemaRef } from '../jsonSchema';
import { isBoolean } from './objects';
import { isRelativePath, relativeToAbsolutePath } from './paths';

export const KUBERNETES_SCHEMA_URL =
Expand All @@ -22,3 +24,48 @@ export function checkSchemaURI(
return uri;
}
}

/**
* Collect all urls of sub schemas
* @param schema the root schema
* @returns map url to schema
*/
export function getSchemaUrls(schema: JSONSchema): Map<string, JSONSchema> {
const result = new Map<string, JSONSchema>();
if (!schema) {
return result;
}

if (schema.url) {
if (schema.url.startsWith('schemaservice://combinedSchema/')) {
addSchemasForOf(schema, result);
} else {
result.set(schema.url, schema);
evidolob marked this conversation as resolved.
Show resolved Hide resolved
}
} else {
addSchemasForOf(schema, result);
}
return result;
}

function addSchemasForOf(schema: JSONSchema, result: Map<string, JSONSchema>): void {
if (schema.allOf) {
addInnerSchemaUrls(schema.allOf, result);
}
if (schema.anyOf) {
addInnerSchemaUrls(schema.anyOf, result);
}
if (schema.oneOf) {
addInnerSchemaUrls(schema.oneOf, result);
}
}

function addInnerSchemaUrls(schemas: JSONSchemaRef[], result: Map<string, JSONSchema>): void {
for (const subSchema of schemas) {
if (!isBoolean(subSchema)) {
if (subSchema.url && !result.has(subSchema.url)) {
result.set(subSchema.url, subSchema);
}
}
}
}
6 changes: 6 additions & 0 deletions src/languageservice/yamlLanguageService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ import { Telemetry } from '../languageserver/telemetry';
import { YamlVersion } from './parser/yamlParser07';
import { YamlCompletion } from './services/yamlCompletion';
import { yamlDocumentsCache } from './parser/yaml-documents';
import { SettingsState } from '../yamlSettings';
import { JSONSchemaSelection } from '../languageserver/handlers/schemaSelectionHandlers';
import { getDefinition } from './services/yamlDefinition';

export enum SchemaPriority {
Expand Down Expand Up @@ -159,6 +161,7 @@ export function getLanguageService(
workspaceContext: WorkspaceContextService,
connection: Connection,
telemetry: Telemetry,
yamlSettings: SettingsState,
clientCapabilities?: ClientCapabilities
): LanguageService {
const schemaService = new YAMLSchemaService(schemaRequestService, workspaceContext);
Expand All @@ -169,6 +172,9 @@ export function getLanguageService(
const formatter = new YAMLFormatter();
const yamlCodeActions = new YamlCodeActions(clientCapabilities);
const yamlCodeLens = new YamlCodeLens(schemaService, telemetry);

new JSONSchemaSelection(schemaService, yamlSettings, connection);

// register all commands
registerCommands(commandExecutor, connection);
return {
Expand Down
7 changes: 7 additions & 0 deletions src/requestTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,10 @@ export namespace ColorSymbolRequest {
export namespace SchemaModificationNotification {
export const type: RequestType<SchemaAdditions | SchemaDeletions, void, {}> = new RequestType('json/schema/modify');
}

export namespace SchemaSelectionRequests {
export const type: NotificationType<void> = new NotificationType('yaml/supportSchemaSelection');
export const getSchema: RequestType<string, JSONSchemaDescription[], {}> = new RequestType('yaml/get/jsonSchema');
export const getAllSchemas: RequestType<string, JSONSchemaDescriptionExt[], {}> = new RequestType('yaml/get/all/jsonSchemas');
export const schemaStoreInitialized: NotificationType<{}> = new NotificationType('yaml/schema/store/initialized');
}
Loading