Skip to content

Commit

Permalink
Merge branch 'main' into feat/extend-array-documentation-completion
Browse files Browse the repository at this point in the history
  • Loading branch information
Petr Spacek committed Dec 7, 2021
2 parents f8acfe2 + df7931c commit 02d6cb3
Show file tree
Hide file tree
Showing 21 changed files with 572 additions and 101 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
### 1.2.2
- Fix: LSP triggeringregisterCapability despite dynamicRegistration set to false [#583](https://github.com/redhat-developer/yaml-language-server/issues/583)
- Add methods which allow client get schemas info [#556](https://github.com/redhat-developer/yaml-language-server/pull/556)
- Fix: links error reporting [#596](https://github.com/redhat-developer/yaml-language-server/pull/596)

### 1.2.1
- Fix: Can not load schema file when the URL is redirected. [#586](https://github.com/redhat-developer/vscode-yaml/issues/586)
- docs: fix typos [#592](https://github.com/redhat-developer/yaml-language-server/pull/592)
Expand Down
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
21 changes: 15 additions & 6 deletions src/languageservice/parser/jsonParser07.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1012,12 +1012,21 @@ function validate(
} else {
const itemSchema = asSchema(schema.items);
if (itemSchema) {
for (const item of node.items) {
const itemValidationResult = new ValidationResult(isKubernetes);
validate(item, itemSchema, schema, itemValidationResult, matchingSchemas, options);
validationResult.mergePropertyMatch(itemValidationResult);
validationResult.mergeEnumValues(itemValidationResult);
}
const itemValidationResult = new ValidationResult(isKubernetes);
node.items.forEach((item) => {
if (itemSchema.oneOf && itemSchema.oneOf.length === 1) {
const subSchemaRef = itemSchema.oneOf[0];
const subSchema = asSchema(subSchemaRef);
subSchema.title = schema.title;
validate(item, subSchema, schema, itemValidationResult, matchingSchemas, options);
validationResult.mergePropertyMatch(itemValidationResult);
validationResult.mergeEnumValues(itemValidationResult);
} else {
validate(item, itemSchema, schema, itemValidationResult, matchingSchemas, options);
validationResult.mergePropertyMatch(itemValidationResult);
validationResult.mergeEnumValues(itemValidationResult);
}
});
}
}

Expand Down
48 changes: 4 additions & 44 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 All @@ -42,7 +42,7 @@ export class YamlCodeLens {
}
}
} catch (err) {
this.telemetry.sendError('yaml.codeLens.error', { error: err, documentUri: document.uri });
this.telemetry.sendError('yaml.codeLens.error', { error: err.toString() });
}

return result;
Expand All @@ -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);
}
}
}
}
39 changes: 22 additions & 17 deletions src/languageservice/services/yamlDefinition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,34 @@ import { DefinitionParams, LocationLink, Range } from 'vscode-languageserver-pro
import { TextDocument } from 'vscode-languageserver-textdocument';
import { DefinitionLink } from 'vscode-languageserver-types';
import { isAlias } from 'yaml';
import { Telemetry } from '../../languageserver/telemetry';
import { yamlDocumentsCache } from '../parser/yaml-documents';
import { matchOffsetToDocument } from '../utils/arrUtils';
import { TextBuffer } from '../utils/textBuffer';

export function getDefinition(document: TextDocument, params: DefinitionParams): DefinitionLink[] | undefined {
try {
const yamlDocument = yamlDocumentsCache.getYamlDocument(document);
const offset = document.offsetAt(params.position);
const currentDoc = matchOffsetToDocument(offset, yamlDocument);
if (currentDoc) {
const [node] = currentDoc.getNodeFromPosition(offset, new TextBuffer(document));
if (node && isAlias(node)) {
const defNode = node.resolve(currentDoc.internalDocument);
if (defNode && defNode.range) {
const targetRange = Range.create(document.positionAt(defNode.range[0]), document.positionAt(defNode.range[2]));
const selectionRange = Range.create(document.positionAt(defNode.range[0]), document.positionAt(defNode.range[1]));
return [LocationLink.create(document.uri, targetRange, selectionRange)];
export class YamlDefinition {
constructor(private readonly telemetry: Telemetry) {}

getDefinition(document: TextDocument, params: DefinitionParams): DefinitionLink[] | undefined {
try {
const yamlDocument = yamlDocumentsCache.getYamlDocument(document);
const offset = document.offsetAt(params.position);
const currentDoc = matchOffsetToDocument(offset, yamlDocument);
if (currentDoc) {
const [node] = currentDoc.getNodeFromPosition(offset, new TextBuffer(document));
if (node && isAlias(node)) {
const defNode = node.resolve(currentDoc.internalDocument);
if (defNode && defNode.range) {
const targetRange = Range.create(document.positionAt(defNode.range[0]), document.positionAt(defNode.range[2]));
const selectionRange = Range.create(document.positionAt(defNode.range[0]), document.positionAt(defNode.range[1]));
return [LocationLink.create(document.uri, targetRange, selectionRange)];
}
}
}
} catch (err) {
this.telemetry.sendError('yaml.definition.error', { error: err.toString() });
}
} catch (err) {
this.telemetry.sendError('yaml.definition.error', { error: err });
}

return undefined;
return undefined;
}
}
Loading

0 comments on commit 02d6cb3

Please sign in to comment.