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

Show all json schemas for yaml file in codelens #424

Merged
merged 6 commits into from
Mar 24, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
17 changes: 16 additions & 1 deletion src/languageserver/handlers/languageHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ import {
FoldingRangeParams,
Connection,
TextDocumentPositionParams,
CodeLensParams,
} from 'vscode-languageserver';
import { DocumentSymbol, Hover, SymbolInformation, TextEdit } from 'vscode-languageserver-types';
import { CodeLens, DocumentSymbol, Hover, SymbolInformation, TextEdit } from 'vscode-languageserver-types';
import { isKubernetesAssociatedDocument } from '../../languageservice/parser/isKubernetes';
import { LanguageService } from '../../languageservice/yamlLanguageService';
import { SettingsState } from '../../yamlSettings';
Expand Down Expand Up @@ -49,6 +50,8 @@ export class LanguageHandlers {
this.connection.onFoldingRanges((params) => this.foldingRangeHandler(params));
this.connection.onCodeAction((params) => this.codeActionHandler(params));
this.connection.onDocumentOnTypeFormatting((params) => this.formatOnTypeHandler(params));
this.connection.onCodeLens((params) => this.codeLensHandler(params));
this.connection.onCodeLensResolve((params) => this.codeLensResolveHandler(params));
}

documentLinkHandler(params: DocumentLinkParams): Promise<DocumentLink[]> {
Expand Down Expand Up @@ -177,4 +180,16 @@ export class LanguageHandlers {

return this.languageService.getCodeAction(textDocument, params);
}

codeLensHandler(params: CodeLensParams): Thenable<CodeLens[] | undefined> | CodeLens[] | undefined {
const textDocument = this.yamlSettings.documents.get(params.textDocument.uri);
if (!textDocument) {
return;
}
return this.languageService.getCodeLens(textDocument, params);
}

codeLensResolveHandler(param: CodeLens): Thenable<CodeLens> | CodeLens {
return this.languageService.resolveCodeLens(param);
}
}
4 changes: 4 additions & 0 deletions src/languageservice/jsonSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,7 @@ export interface JSONSchema {
export interface JSONSchemaMap {
[name: string]: JSONSchemaRef;
}

export function isJSONSchema(schema: JSONSchemaRef): schema is JSONSchema {
return !(schema instanceof Boolean);
evidolob marked this conversation as resolved.
Show resolved Hide resolved
}
43 changes: 43 additions & 0 deletions src/languageservice/parser/yaml-documents.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Red Hat, Inc. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { TextDocument } from 'vscode-languageserver-textdocument';
import { YAMLDocument, parse as parseYAML } from './yamlParser07';

interface YamlCachedDocument {
version: number;
document: YAMLDocument;
}
export class YamlDocuments {
// a mapping of URIs to cached documents
private cache = new Map<string, YamlCachedDocument>();

getYamlDocument(document: TextDocument, customTags: string[] = []): YAMLDocument {
this.ensureCache(document, customTags);
return this.cache.get(document.uri).document;
}

/**
* For test purpose only!
*/
clear(): void {
this.cache.clear();
}

private ensureCache(document: TextDocument, customTags: string[]): void {
const key = document.uri;
if (!this.cache.has(key)) {
this.cache.set(key, { version: -1, document: new YAMLDocument([]) });
}

if (this.cache.get(key).version !== document.version) {
const doc = parseYAML(document.getText(), customTags);
this.cache.get(key).document = doc;
this.cache.get(key).version = document.version;
}
}
}

export const yamlDocumentsCache = new YamlDocuments();
10 changes: 5 additions & 5 deletions src/languageservice/services/documentSymbols.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
*--------------------------------------------------------------------------------------------*/
'use strict';

import { parse as parseYAML } from '../parser/yamlParser07';

import { SymbolInformation, TextDocument, DocumentSymbol } from 'vscode-languageserver-types';
import { SymbolInformation, DocumentSymbol } from 'vscode-languageserver-types';
import { YAMLSchemaService } from './yamlSchemaService';
import { JSONDocumentSymbols } from 'vscode-json-languageservice/lib/umd/services/jsonDocumentSymbols';
import { TextDocument } from 'vscode-languageserver-textdocument';
import { yamlDocumentsCache } from '../parser/yaml-documents';

export class YAMLDocumentSymbols {
private jsonDocumentSymbols;
Expand All @@ -30,7 +30,7 @@ export class YAMLDocumentSymbols {
}

public findDocumentSymbols(document: TextDocument): SymbolInformation[] {
const doc = parseYAML(document.getText());
const doc = yamlDocumentsCache.getYamlDocument(document);
if (!doc || doc['documents'].length === 0) {
return null;
}
Expand All @@ -46,7 +46,7 @@ export class YAMLDocumentSymbols {
}

public findHierarchicalDocumentSymbols(document: TextDocument): DocumentSymbol[] {
const doc = parseYAML(document.getText());
const doc = yamlDocumentsCache.getYamlDocument(document);
if (!doc || doc['documents'].length === 0) {
return null;
}
Expand Down
20 changes: 2 additions & 18 deletions src/languageservice/services/yamlCodeActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
CodeActionKind,
CodeActionParams,
Command,
Connection,
Diagnostic,
Position,
Range,
Expand All @@ -19,7 +18,6 @@ import {
} from 'vscode-languageserver';
import { YamlCommands } from '../../commands';
import * as path from 'path';
import { CommandExecutor } from '../../languageserver/commandExecutor';
import { TextBuffer } from '../utils/textBuffer';
import { LanguageSettings } from '../yamlLanguageService';

Expand All @@ -29,21 +27,7 @@ interface YamlDiagnosticData {
export class YamlCodeActions {
private indentation = ' ';

constructor(commandExecutor: CommandExecutor, connection: Connection, private readonly clientCapabilities: ClientCapabilities) {
commandExecutor.registerCommand(YamlCommands.JUMP_TO_SCHEMA, async (uri: string) => {
if (!uri) {
return;
}
if (!uri.startsWith('file')) {
uri = 'json-schema' + uri.substring(uri.indexOf('://'), uri.length);
}

const result = await connection.window.showDocument({ uri: uri, external: false, takeFocus: true });
if (!result) {
connection.window.showErrorMessage(`Cannot open ${uri}`);
}
});
}
constructor(private readonly clientCapabilities: ClientCapabilities) {}

configure(settings: LanguageSettings): void {
this.indentation = settings.indentation;
Expand Down Expand Up @@ -71,7 +55,7 @@ export class YamlCodeActions {
for (const diagnostic of diagnostics) {
const schemaUri = (diagnostic.data as YamlDiagnosticData)?.schemaUri || [];
for (const schemaUriStr of schemaUri) {
if (schemaUriStr && (schemaUriStr.startsWith('file') || schemaUriStr.startsWith('https'))) {
if (schemaUriStr /* && (schemaUriStr.startsWith('file') || schemaUriStr.startsWith('https')) */) {
evidolob marked this conversation as resolved.
Show resolved Hide resolved
if (!schemaUriToDiagnostic.has(schemaUriStr)) {
schemaUriToDiagnostic.set(schemaUriStr, []);
}
Expand Down
102 changes: 102 additions & 0 deletions src/languageservice/services/yamlCodeLens.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Red Hat, Inc. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { TextDocument } from 'vscode-languageserver-textdocument';
import { CodeLens, Range } from 'vscode-languageserver-types';
import { YamlCommands } from '../../commands';
import { yamlDocumentsCache } from '../parser/yaml-documents';
import { YAMLSchemaService } from './yamlSchemaService';
JPinkney marked this conversation as resolved.
Show resolved Hide resolved
import { URI } from 'vscode-uri';
import * as path from 'path';
import { isJSONSchema, JSONSchema, JSONSchemaRef } from '../jsonSchema';
import { CodeLensParams } from 'vscode-languageserver-protocol';

export class YamlCodeLens {
constructor(private schemaService: YAMLSchemaService) {}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
async getCodeLens(document: TextDocument, params: CodeLensParams): Promise<CodeLens[]> {
const yamlDocument = yamlDocumentsCache.getYamlDocument(document);
const result = [];
for (const currentYAMLDoc of yamlDocument.documents) {
const schema = await this.schemaService.getSchemaForResource(document.uri, currentYAMLDoc);
if (schema?.schema) {
const schemaUrls = getSchemaUrl(schema?.schema);
if (schemaUrls.size === 0) {
continue;
}
for (const urlToSchema of schemaUrls) {
const lens = CodeLens.create(Range.create(0, 0, 0, 0));
lens.command = {
title: getCommandTitle(urlToSchema[0], urlToSchema[1]),
command: YamlCommands.JUMP_TO_SCHEMA,
arguments: [urlToSchema[0]],
};
result.push(lens);
}
}
}

return result;
}
resolveCodeLens(param: CodeLens): Thenable<CodeLens> | CodeLens {
return param;
}
}

function getCommandTitle(url: string, schema: JSONSchema): string {
const uri = URI.parse(url);
let baseName = path.basename(uri.fsPath);
if (!path.extname(uri.fsPath)) {
baseName += '.json';
}
if (Object.getOwnPropertyDescriptor(schema, 'name')) {
return Object.getOwnPropertyDescriptor(schema, 'name').value + ` (${baseName})`;
} else if (schema.title) {
return schema.title + ` (${baseName})`;
}

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 (isJSONSchema(subSchema)) {
if (subSchema.url && !result.has(subSchema.url)) {
result.set(subSchema.url, subSchema);
}
}
}
}
32 changes: 32 additions & 0 deletions src/languageservice/services/yamlCommands.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*---------------------------------------------------------------------------------------------
* 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 { YamlCommands } from '../../commands';
import { CommandExecutor } from '../../languageserver/commandExecutor';
import { URI } from 'vscode-uri';

export function registerCommands(commandExecutor: CommandExecutor, connection: Connection): void {
commandExecutor.registerCommand(YamlCommands.JUMP_TO_SCHEMA, async (uri: string) => {
if (!uri) {
return;
}
if (!uri.startsWith('file')) {
const origUri = URI.parse(uri);
const customUri = URI.from({
scheme: 'json-schema',
authority: origUri.authority,
path: origUri.path.endsWith('.json') ? origUri.path : origUri.path + '.json',
fragment: uri,
});
uri = customUri.toString();
}

const result = await connection.window.showDocument({ uri: uri, external: false, takeFocus: true });
if (!result) {
connection.window.showErrorMessage(`Cannot open ${uri}`);
}
});
}
2 changes: 1 addition & 1 deletion src/languageservice/services/yamlCompletion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ import {
CompletionItem,
CompletionItemKind,
CompletionList,
TextDocument,
Position,
Range,
TextEdit,
InsertTextFormat,
} from 'vscode-languageserver-types';
import { TextDocument } from 'vscode-languageserver-textdocument';
import * as nls from 'vscode-nls';
import { getLineOffsets, filterInvalidCustomTags, matchOffsetToDocument } from '../utils/arrUtils';
import { LanguageSettings } from '../yamlLanguageService';
Expand Down
7 changes: 4 additions & 3 deletions src/languageservice/services/yamlFolding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,18 @@
* Copyright (c) Red Hat, Inc. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { TextDocument, FoldingRange, Range } from 'vscode-languageserver';
import { FoldingRange, Range } from 'vscode-languageserver';
import { FoldingRangesContext } from '../yamlTypes';
import { parse as parseYAML } from '../parser/yamlParser07';
import { ASTNode } from '../jsonASTTypes';
import { yamlDocumentsCache } from '../parser/yaml-documents';
import { TextDocument } from 'vscode-languageserver-textdocument';

export function getFoldingRanges(document: TextDocument, context: FoldingRangesContext): FoldingRange[] | undefined {
if (!document) {
return;
}
const result: FoldingRange[] = [];
const doc = parseYAML(document.getText());
const doc = yamlDocumentsCache.getYamlDocument(document);
for (const ymlDoc of doc.documents) {
ymlDoc.visit((node) => {
if (
Expand Down
4 changes: 2 additions & 2 deletions src/languageservice/services/yamlHover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@
import { Hover, Position } from 'vscode-languageserver-types';
import { matchOffsetToDocument } from '../utils/arrUtils';
import { LanguageSettings } from '../yamlLanguageService';
import { parse as parseYAML } from '../parser/yamlParser07';
import { YAMLSchemaService } from './yamlSchemaService';
import { JSONHover } from 'vscode-json-languageservice/lib/umd/services/jsonHover';
import { setKubernetesParserOption } from '../parser/isKubernetes';
import { TextDocument } from 'vscode-languageserver-textdocument';
import { yamlDocumentsCache } from '../parser/yaml-documents';

export class YAMLHover {
private shouldHover: boolean;
Expand All @@ -33,7 +33,7 @@ export class YAMLHover {
if (!this.shouldHover || !document) {
return Promise.resolve(undefined);
}
const doc = parseYAML(document.getText());
const doc = yamlDocumentsCache.getYamlDocument(document);
const offset = document.offsetAt(position);
const currentDoc = matchOffsetToDocument(offset, doc);
if (currentDoc === null) {
Expand Down
4 changes: 2 additions & 2 deletions src/languageservice/services/yamlLinks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
* Copyright (c) Red Hat, Inc. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { parse as parseYAML } from '../parser/yamlParser07';
import { findLinks as JSONFindLinks } from 'vscode-json-languageservice/lib/umd/services/jsonLinks';
import { DocumentLink } from 'vscode-languageserver';
import { TextDocument } from 'vscode-languageserver-textdocument';
import { yamlDocumentsCache } from '../parser/yaml-documents';

export function findLinks(document: TextDocument): Promise<DocumentLink[]> {
const doc = parseYAML(document.getText());
const doc = yamlDocumentsCache.getYamlDocument(document);
// Find links across all YAML Documents then report them back once finished
const linkPromises = [];
for (const yamlDoc of doc.documents) {
Expand Down
Loading