diff --git a/README.md b/README.md index d68e8d7..3e010d3 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ YAML language plugin for the Monaco Editor. It provides the following features w * Formatting * Document Symbols * Syntax highlighting -* Automatically load remote schema files +* Automatically load remote schema files (by enabling DiagnosticsOptions.enableSchemaRequest) Schemas can also be provided by configuration. See [here](https://github.com/Microsoft/monaco-json/blob/master/src/monaco.d.ts) for the API that the JSON plugin offers to configure the JSON language support. diff --git a/package.json b/package.json index dd53d85..4336820 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,13 @@ { "name": "monaco-yaml", - "version": "1.1.0", + "version": "1.2.0", "description": "YAML plugin for the Monaco Editor", "scripts": { - "compile": "rimraf ./out && tsc -p ./src/tsconfig.json && tsc -p ./src/tsconfig.esm.json", + "compile": "rimraf ./out && yarn compile:umd && yarn compile:esm", + "compile:umd": "tsc -p ./src/tsconfig.json", + "compile:esm": "tsc -p ./src/tsconfig.esm.json", "watch": "tsc -p ./src --watch", - "prepublish": "rimraf ./release && yarn run compile && node ./scripts/release.js && node ./scripts/bundle && mcopy ./src/monaco.d.ts ./release/monaco.d.ts" + "prepublish": "rimraf ./out && rimraf ./release && yarn compile:umd && node ./scripts/bundle && mcopy ./src/monaco.d.ts ./release/monaco.d.ts" }, "author": "Kevin Decker (http://incaseofstairs.com)", "maintainers": [ @@ -26,12 +28,12 @@ "@types/node": "^10.9.3", "js-yaml": "^3.12.0", "jsonc-parser": "^2.0.2", - "monaco-editor-core": "0.14.6", - "monaco-languages": "1.5.1", + "monaco-editor-core": "0.15.0", + "monaco-languages": "1.6.0", "monaco-plugin-helpers": "^1.0.2", "requirejs": "^2.3.5", "rimraf": "^2.6.2", - "typescript": "^3.0.3", + "typescript": "^3.1.6", "uglify-es": "^3.3.9", "vscode-json-languageservice": "^3.1.6", "vscode-languageserver-types": "3.12.0" diff --git a/src/languageFeatures.ts b/src/languageFeatures.ts index b518e9e..d8a9d9a 100644 --- a/src/languageFeatures.ts +++ b/src/languageFeatures.ts @@ -14,7 +14,6 @@ import Position = monaco.Position; import Range = monaco.Range; import IRange = monaco.IRange; import Thenable = monaco.Thenable; -import Promise = monaco.Promise; import CancellationToken = monaco.CancellationToken; import IDisposable = monaco.IDisposable; @@ -225,54 +224,6 @@ function toTextEdit(textEdit: ls.TextEdit): monaco.editor.ISingleEditOperation { } } -interface DataCompletionItem extends monaco.languages.CompletionItem { - data?: any; -} - -function toCompletionItem(entry: ls.CompletionItem): DataCompletionItem { - return { - label: entry.label, - insertText: entry.insertText, - sortText: entry.sortText, - filterText: entry.filterText, - documentation: entry.documentation, - detail: entry.detail, - kind: toCompletionItemKind(entry.kind), - textEdit: toTextEdit(entry.textEdit), - data: entry.data - }; -} - -function fromMarkdownString(entry: string | monaco.IMarkdownString): ls.MarkupContent { - return { - kind: (typeof entry === 'string' ? ls.MarkupKind.PlainText : ls.MarkupKind.Markdown), - value: (typeof entry === 'string' ? entry : entry.value) - } -} - -function fromCompletionItem(entry: DataCompletionItem): ls.CompletionItem { - let item: ls.CompletionItem = { - label: entry.label, - sortText: entry.sortText, - filterText: entry.filterText, - documentation: fromMarkdownString(entry.documentation), - detail: entry.detail, - kind: fromCompletionItemKind(entry.kind), - data: entry.data - }; - if (typeof entry.insertText === 'object' && typeof entry.insertText.value === 'string') { - item.insertText = entry.insertText.value; - item.insertTextFormat = ls.InsertTextFormat.Snippet - } else { - item.insertText = entry.insertText; - } - if (entry.range) { - item.textEdit = ls.TextEdit.replace(fromRange(entry.range), item.insertText); - } - return item; -} - - export class CompletionAdapter implements monaco.languages.CompletionItemProvider { constructor(private _worker: WorkerAccessor) { @@ -282,11 +233,11 @@ export class CompletionAdapter implements monaco.languages.CompletionItemProvide return [' ', ':']; } - provideCompletionItems(model: monaco.editor.IReadOnlyModel, position: Position, token: CancellationToken): Thenable { + provideCompletionItems(model: monaco.editor.IReadOnlyModel, position: Position, context: monaco.languages.CompletionContext, token: CancellationToken): Thenable { const wordInfo = model.getWordUntilPosition(position); const resource = model.uri; - return wireCancellationToken(token, this._worker(resource).then(worker => { + return this._worker(resource).then(worker => { return worker.doComplete(resource.toString(), fromPosition(position)); }).then(info => { if (!info) { @@ -295,7 +246,7 @@ export class CompletionAdapter implements monaco.languages.CompletionItemProvide let items: monaco.languages.CompletionItem[] = info.items.map(entry => { let item: monaco.languages.CompletionItem = { label: entry.label, - insertText: entry.insertText, + insertText: entry.insertText || entry.label, sortText: entry.sortText, filterText: entry.filterText, documentation: entry.documentation, @@ -306,17 +257,20 @@ export class CompletionAdapter implements monaco.languages.CompletionItemProvide item.range = toRange(entry.textEdit.range); item.insertText = entry.textEdit.newText; } + if (entry.additionalTextEdits) { + item.additionalTextEdits = entry.additionalTextEdits.map(toTextEdit) + } if (entry.insertTextFormat === ls.InsertTextFormat.Snippet) { - item.insertText = { value: item.insertText }; + item.insertTextRules = monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet; } return item; }); return { isIncomplete: info.isIncomplete, - items: items + suggestions: items }; - })); + }); } } @@ -359,13 +313,12 @@ function toMarkedStringArray(contents: ls.MarkupContent | ls.MarkedString | ls.M export class HoverAdapter implements monaco.languages.HoverProvider { - constructor(private _worker: WorkerAccessor) { - } + constructor(private _worker: WorkerAccessor) {} provideHover(model: monaco.editor.IReadOnlyModel, position: Position, token: CancellationToken): Thenable { let resource = model.uri; - return wireCancellationToken(token, this._worker(resource).then(worker => { + return this._worker(resource).then(worker => { return worker.doHover(resource.toString(), fromPosition(position)); }).then(info => { if (!info) { @@ -375,20 +328,10 @@ export class HoverAdapter implements monaco.languages.HoverProvider { range: toRange(info.range), contents: toMarkedStringArray(info.contents) }; - })); + }); } } -// --- definition ------ - -function toLocation(location: ls.Location): monaco.languages.Location { - return { - uri: Uri.parse(location.uri), - range: toRange(location.range) - }; -} - - // --- document symbols ------ function toSymbolKind(kind: ls.SymbolKind): monaco.languages.SymbolKind { @@ -426,7 +369,7 @@ export class DocumentSymbolAdapter implements monaco.languages.DocumentSymbolPro public provideDocumentSymbols(model: monaco.editor.IReadOnlyModel, token: CancellationToken): Thenable { const resource = model.uri; - return wireCancellationToken(token, this._worker(resource).then(worker => worker.findDocumentSymbols(resource.toString())).then(items => { + return this._worker(resource).then(worker => worker.findDocumentSymbols(resource.toString())).then(items => { if (!items) { return; } @@ -438,7 +381,7 @@ export class DocumentSymbolAdapter implements monaco.languages.DocumentSymbolPro range: toRange(item.location.range), selectionRange: toRange(item.location.range) })); - })); + }); } } @@ -458,14 +401,14 @@ export class DocumentFormattingEditProvider implements monaco.languages.Document public provideDocumentFormattingEdits(model: monaco.editor.IReadOnlyModel, options: monaco.languages.FormattingOptions, token: CancellationToken): Thenable { const resource = model.uri; - return wireCancellationToken(token, this._worker(resource).then(worker => { + return this._worker(resource).then(worker => { return worker.format(resource.toString(), null, fromFormattingOptions(options)).then(edits => { if (!edits || edits.length === 0) { return; } return edits.map(toTextEdit); }); - })); + }); } } @@ -477,23 +420,13 @@ export class DocumentRangeFormattingEditProvider implements monaco.languages.Doc public provideDocumentRangeFormattingEdits(model: monaco.editor.IReadOnlyModel, range: Range, options: monaco.languages.FormattingOptions, token: CancellationToken): Thenable { const resource = model.uri; - return wireCancellationToken(token, this._worker(resource).then(worker => { + return this._worker(resource).then(worker => { return worker.format(resource.toString(), fromRange(range), fromFormattingOptions(options)).then(edits => { if (!edits || edits.length === 0) { return; } return edits.map(toTextEdit); }); - })); + }); } } - -/** - * Hook a cancellation token to a WinJS Promise - */ -function wireCancellationToken(token: CancellationToken, promise: Thenable): Thenable { - if ((>promise).cancel) { - token.onCancellationRequested(() => (>promise).cancel()); - } - return promise; -} diff --git a/src/languageservice/parser/jsonParser.ts b/src/languageservice/parser/jsonParser.ts index 584341e..b523f97 100644 --- a/src/languageservice/parser/jsonParser.ts +++ b/src/languageservice/parser/jsonParser.ts @@ -835,7 +835,7 @@ export interface ISchemaCollector { schemas: IApplicableSchema[]; add(schema: IApplicableSchema): void; merge(other: ISchemaCollector): void; - include(node: ASTNode): void; + include(node: ASTNode): boolean; newSub(): ISchemaCollector; } diff --git a/src/languageservice/parser/scalar-type.ts b/src/languageservice/parser/scalar-type.ts new file mode 100644 index 0000000..87b99de --- /dev/null +++ b/src/languageservice/parser/scalar-type.ts @@ -0,0 +1,16 @@ +/** + * Parse a boolean according to the specification + * + * Return: + * true if its a true value + * false if its a false value + */ +export function parseYamlBoolean(input: string): boolean { + if (["true", "True", "TRUE", 'y', 'Y', 'yes', 'Yes', 'YES', 'on', 'On', 'ON'].lastIndexOf(input) >= 0) { + return true; + } + else if (["false", "False", "FALSE", 'n', 'N', 'no', 'No', 'NO', 'off', 'Off', 'OFF'].lastIndexOf(input) >= 0) { + return false; + } + throw `Invalid boolean "${input}"` +} diff --git a/src/languageservice/parser/yamlParser.ts b/src/languageservice/parser/yamlParser.ts index 15af94f..cc76289 100644 --- a/src/languageservice/parser/yamlParser.ts +++ b/src/languageservice/parser/yamlParser.ts @@ -15,7 +15,7 @@ import { Kind } from '../../yaml-ast-parser/index' import { Schema, Type } from 'js-yaml'; import { getLineStartPositions, getPosition } from '../utils/documentPositionCalculator' -import YAMLException from '../../yaml-ast-parser/exception'; +import { parseYamlBoolean } from './scalar-type'; export class SingleYAMLDocument extends JSONDocument { private lines; @@ -153,8 +153,8 @@ function recursivelyBuildAst(parent: ASTNode, node: Yaml.YAMLNode): ASTNode { //This is a patch for redirecting values with these strings to be boolean nodes because its not supported in the parser. let possibleBooleanValues = ['y', 'Y', 'yes', 'Yes', 'YES', 'n', 'N', 'no', 'No', 'NO', 'on', 'On', 'ON', 'off', 'Off', 'OFF']; - if (possibleBooleanValues.indexOf(value.toString()) !== -1) { - return new BooleanASTNode(parent, name, value, node.startPosition, node.endPosition) + if (instance.plainScalar && possibleBooleanValues.indexOf(value.toString()) !== -1) { + return new BooleanASTNode(parent, name, parseYamlBoolean(value), node.startPosition, node.endPosition) } switch (type) { @@ -199,7 +199,7 @@ function recursivelyBuildAst(parent: ASTNode, node: Yaml.YAMLNode): ASTNode { } } -function convertError(e: YAMLException) { +function convertError(e: Yaml.Error) { return { message: `${e.reason}`, location: { start: e.mark.position, end: e.mark.position + e.mark.column, code: ErrorCode.Undefined } } } @@ -215,7 +215,7 @@ function createJSONDocument(yamlDoc: Yaml.YAMLNode, startPositions: number[], te const duplicateKeyReason = 'duplicate key' //Patch ontop of yaml-ast-parser to disable duplicate key message on merge key - let isDuplicateAndNotMergeKey = function (error: YAMLException, yamlText: string) { + let isDuplicateAndNotMergeKey = function (error: Yaml.Error, yamlText: string) { let errorConverted = convertError(error); let errorStart = errorConverted.location.start; let errorEnd = errorConverted.location.end; @@ -271,4 +271,4 @@ export function parse(text: string, customTags = []): YAMLDocument { Yaml.loadAll(text, doc => yamlDocs.push(doc), additionalOptions); return new YAMLDocument(yamlDocs.map(doc => createJSONDocument(doc, startPositions, text))); -} +} \ No newline at end of file diff --git a/src/languageservice/services/jsonSchemaService.ts b/src/languageservice/services/jsonSchemaService.ts index 761dc10..359915c 100644 --- a/src/languageservice/services/jsonSchemaService.ts +++ b/src/languageservice/services/jsonSchemaService.ts @@ -243,14 +243,14 @@ export class JSONSchemaService implements IJSONSchemaService { private callOnDispose: Function[]; private requestService: SchemaRequestService; private promiseConstructor: PromiseConstructor; - private customSchemaProvider: CustomSchemaProvider; + private customSchemaProvider: CustomSchemaProvider | undefined; - constructor(requestService: SchemaRequestService, contextService?: WorkspaceContextService, customSchemaProvider?: CustomSchemaProvider, promiseConstructor?: PromiseConstructor) { + constructor(requestService: SchemaRequestService, contextService?: WorkspaceContextService, promiseConstructor?: PromiseConstructor) { this.contextService = contextService; this.requestService = requestService; this.promiseConstructor = promiseConstructor || Promise; this.callOnDispose = []; - this.customSchemaProvider = customSchemaProvider; + this.customSchemaProvider = undefined; this.contributionSchemas = {}; this.contributionAssociations = {}; this.schemasById = {}; @@ -259,6 +259,10 @@ export class JSONSchemaService implements IJSONSchemaService { this.registeredSchemasIds = {}; } + registerCustomSchemaProvider(customSchemaProvider: CustomSchemaProvider) { + this.customSchemaProvider = customSchemaProvider; + } + public getRegisteredSchemaIds(filter?: (scheme) => boolean): string[] { return Object.keys(this.registeredSchemasIds).filter(id => { let scheme = URI.parse(id).scheme; diff --git a/src/languageservice/services/yamlCompletion.ts b/src/languageservice/services/yamlCompletion.ts index 72457b6..a3230de 100644 --- a/src/languageservice/services/yamlCompletion.ts +++ b/src/languageservice/services/yamlCompletion.ts @@ -17,6 +17,7 @@ import { CompletionItem, CompletionItemKind, CompletionList, TextDocument, Posit import * as nls from 'vscode-nls'; import { matchOffsetToDocument } from '../utils/arrUtils'; +import { LanguageSettings } from '../yamlLanguageService'; const localize = nls.loadMessageBundle(); @@ -26,15 +27,20 @@ export class YAMLCompletion { private contributions: JSONWorkerContribution[]; private promise: PromiseConstructor; private customTags: Array; + private completion: boolean; constructor(schemaService: SchemaService.IJSONSchemaService, contributions: JSONWorkerContribution[] = [], promiseConstructor?: PromiseConstructor) { this.schemaService = schemaService; this.contributions = contributions; this.promise = promiseConstructor || Promise; this.customTags = []; + this.completion = true; } - public configure(customTags: Array){ + public configure(languageSettings: LanguageSettings, customTags: Array){ + if (languageSettings) { + this.completion = languageSettings.completion; + } this.customTags = customTags; } @@ -57,6 +63,10 @@ export class YAMLCompletion { isIncomplete: false }; + if (!this.completion) { + return Promise.resolve(result); + } + let offset = document.offsetAt(position); if(document.getText()[offset] === ":"){ return Promise.resolve(result); diff --git a/src/languageservice/services/yamlFormatter.ts b/src/languageservice/services/yamlFormatter.ts index f299224..6937e05 100644 --- a/src/languageservice/services/yamlFormatter.ts +++ b/src/languageservice/services/yamlFormatter.ts @@ -5,6 +5,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; + import * as jsyaml from 'js-yaml'; import * as Yaml from '../../yaml-ast-parser/index' import { EOL } from '../../fillers/os'; diff --git a/src/languageservice/services/yamlHover.ts b/src/languageservice/services/yamlHover.ts index 2b794a5..9d74bb4 100644 --- a/src/languageservice/services/yamlHover.ts +++ b/src/languageservice/services/yamlHover.ts @@ -13,23 +13,32 @@ 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 { - if(!document){ - this.promise.resolve(void 0); + if(!this.shouldHover || !document){ + return this.promise.resolve(void 0); } let offset = document.offsetAt(position); diff --git a/src/languageservice/utils/errorHandler.ts b/src/languageservice/utils/errorHandler.ts index eff20f1..dac47b4 100644 --- a/src/languageservice/utils/errorHandler.ts +++ b/src/languageservice/utils/errorHandler.ts @@ -2,7 +2,6 @@ * Copyright (c) Red Hat, Inc. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - export class ErrorHandler { private errorResultsList; private textDocument; diff --git a/src/languageservice/yamlLanguageService.ts b/src/languageservice/yamlLanguageService.ts index d43aa17..abc4703 100644 --- a/src/languageservice/yamlLanguageService.ts +++ b/src/languageservice/yamlLanguageService.ts @@ -3,24 +3,22 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { JSONSchemaService } from "./services/jsonSchemaService"; -import { - TextDocument, - Position, - CompletionList, - FormattingOptions, - Diagnostic -} from "vscode-languageserver-types"; -import { JSONSchema } from "./jsonSchema"; -import { YAMLDocumentSymbols } from "./services/documentSymbols"; -import { YAMLCompletion } from "./services/yamlCompletion"; -import { JSONDocument } from "vscode-json-languageservice"; -import { YAMLHover } from "./services/yamlHover"; -import { YAMLValidation } from "./services/yamlValidation"; -import { format } from "./services/yamlFormatter"; + +import { JSONSchemaService, CustomSchemaProvider } from './services/jsonSchemaService' +import { TextDocument, Position, CompletionList, Diagnostic, FormattingOptions } from 'vscode-languageserver-types'; +import { JSONSchema } from './jsonSchema'; +import { YAMLDocumentSymbols } from './services/documentSymbols'; +import { YAMLCompletion } from './services/yamlCompletion'; +import { YAMLHover } from './services/yamlHover'; +import { YAMLValidation } from './services/yamlValidation'; +import { format } from './services/yamlFormatter'; +import { JSONDocument } from 'vscode-json-languageservice'; import { parse as parseYAML } from "./parser/yamlParser"; + export interface LanguageSettings { validate?: boolean; //Setting for whether we want to validate the schema + hover?: boolean; //Setting for whether we want to have hover results + completion?: boolean; //Setting for whether we want to have completion results isKubernetes?: boolean; //If true then its validating against kubernetes schemas?: any[]; //List of schemas, customTags?: Array; //Array of Custom Tags @@ -35,12 +33,7 @@ export interface PromiseConstructor { * a resolve callback used resolve the promise with a value or the result of another promise, * and a reject callback used to reject the promise with a provided reason or error. */ - new ( - executor: ( - resolve: (value?: T | Thenable) => void, - reject: (reason?: any) => void - ) => void - ): Thenable; + new (executor: (resolve: (value?: T | Thenable) => void, reject: (reason?: any) => void) => void): Thenable; /** * Creates a Promise that is resolved with an array of results when all of the provided Promises @@ -72,14 +65,8 @@ export interface Thenable { * @param onrejected The callback to execute when the Promise is rejected. * @returns A Promise for the completion of which ever callback is executed. */ - then( - onfulfilled?: (value: R) => TResult | Thenable, - onrejected?: (reason: any) => TResult | Thenable - ): Thenable; - then( - onfulfilled?: (value: R) => TResult | Thenable, - onrejected?: (reason: any) => void - ): Thenable; + then(onfulfilled?: (value: R) => TResult | Thenable, onrejected?: (reason: any) => TResult | Thenable): Thenable; + then(onfulfilled?: (value: R) => TResult | Thenable, onrejected?: (reason: any) => void): Thenable; } export interface WorkspaceContextService { @@ -110,7 +97,8 @@ export interface SchemaConfiguration { } export interface LanguageService { - configure(settings): void; + configure(settings: LanguageSettings): void; + registerCustomSchemaProvider(schemaProvider: CustomSchemaProvider): void; // Register a custom schema provider doComplete(document: TextDocument, position: Position, doc): Thenable; doValidation(document: TextDocument, yamlDocument): Thenable; doHover(document: TextDocument, position: Position, doc); @@ -121,10 +109,10 @@ export interface LanguageService { parseYAMLDocument(document: TextDocument): YAMLDocument; } -export function getLanguageService(schemaRequestService, workspaceContext, contributions, customSchemaProvider, promiseConstructor?): LanguageService { +export function getLanguageService(schemaRequestService, workspaceContext, contributions, promiseConstructor?): LanguageService { let promise = promiseConstructor || Promise; - let schemaService = new JSONSchemaService(schemaRequestService, workspaceContext, customSchemaProvider); + let schemaService = new JSONSchemaService(schemaRequestService, workspaceContext); let completer = new YAMLCompletion(schemaService, contributions, promise); let hover = new YAMLHover(schemaService, contributions, promise); @@ -140,8 +128,12 @@ export function getLanguageService(schemaRequestService, workspaceContext, contr }); } yamlValidation.configure(settings); - let customTagsSetting = settings && settings["customTags"] ? settings["customTags"] : []; - completer.configure(customTagsSetting); + hover.configure(settings); + let customTagsSetting = settings && settings['customTags'] ? settings['customTags'] : []; + completer.configure(settings, customTagsSetting); + }, + registerCustomSchemaProvider: (schemaProvider: CustomSchemaProvider) => { + schemaService.registerCustomSchemaProvider(schemaProvider); }, doComplete: completer.doComplete.bind(completer), doResolve: completer.doResolve.bind(completer), @@ -150,6 +142,6 @@ export function getLanguageService(schemaRequestService, workspaceContext, contr findDocumentSymbols: yamlDocumentSymbols.findDocumentSymbols.bind(yamlDocumentSymbols), resetSchema: (uri: string) => schemaService.onResourceChange(uri), doFormat: format, - parseYAMLDocument: (document: TextDocument) => parseYAML(document.getText()) + parseYAMLDocument: (document: TextDocument) => parseYAML(document.getText()), } } diff --git a/src/monaco.contribution.ts b/src/monaco.contribution.ts index d9b9d39..e2f7acc 100644 --- a/src/monaco.contribution.ts +++ b/src/monaco.contribution.ts @@ -44,7 +44,8 @@ export class LanguageServiceDefaultsImpl implements monaco.languages.yaml.Langua const diagnosticDefault: monaco.languages.yaml.DiagnosticsOptions = { validate: true, - schemas: [] + schemas: [], + enableSchemaRequest: false, } const yamlDefaults = new LanguageServiceDefaultsImpl('yaml', diagnosticDefault); diff --git a/src/monaco.d.ts b/src/monaco.d.ts index 76e6aa4..6765f45 100644 --- a/src/monaco.d.ts +++ b/src/monaco.d.ts @@ -4,36 +4,41 @@ *--------------------------------------------------------------------------------------------*/ declare module monaco.languages.yaml { - export interface DiagnosticsOptions { + export interface DiagnosticsOptions { + /** + * If set, the validator will be enabled and perform syntax validation as well as schema based validation. + */ + readonly validate?: boolean; + + /** + * A list of known schemas and/or associations of schemas to file names. + */ + readonly schemas?: { /** - * If set, the validator will be enabled and perform syntax validation as well as schema based validation. + * The URI of the schema, which is also the identifier of the schema. */ - readonly validate?: boolean; - + readonly uri: string; /** - * A list of known schemas and/or associations of schemas to file names. + * A list of file names that are associated to the schema. The '*' wildcard can be used. For example '*.schema.json', 'package.json' */ - readonly schemas?: { - /** - * The URI of the schema, which is also the identifier of the schema. - */ - readonly uri: string; - /** - * A list of file names that are associated to the schema. The '*' wildcard can be used. For example '*.schema.json', 'package.json' - */ - readonly fileMatch?: string[]; - /** - * The schema for the given URI. - */ - readonly schema?: any; - }[]; - } + readonly fileMatch?: string[]; + /** + * The schema for the given URI. + */ + readonly schema?: any; + }[]; - export interface LanguageServiceDefaults { - readonly onDidChange: IEvent; - readonly diagnosticsOptions: DiagnosticsOptions; - setDiagnosticsOptions(options: DiagnosticsOptions): void; - } - - export var yamlDefaults: LanguageServiceDefaults; + /** + * If set, the schema service would load schema content on-demand with 'fetch' if available + */ + readonly enableSchemaRequest?: boolean; } + + export interface LanguageServiceDefaults { + readonly onDidChange: IEvent; + readonly diagnosticsOptions: DiagnosticsOptions; + setDiagnosticsOptions(options: DiagnosticsOptions): void; + } + + export const yamlDefaults: LanguageServiceDefaults; +} diff --git a/src/workerManager.ts b/src/workerManager.ts index db6ef9e..c388e82 100644 --- a/src/workerManager.ts +++ b/src/workerManager.ts @@ -69,7 +69,8 @@ export class WorkerManager { // passed in to the create() method createData: { languageSettings: this._defaults.diagnosticsOptions, - languageId: this._defaults.languageId + languageId: this._defaults.languageId, + enableSchemaRequest: this._defaults.diagnosticsOptions.enableSchemaRequest } }); @@ -81,26 +82,10 @@ export class WorkerManager { getLanguageServiceWorker(...resources: Uri[]): Promise { let _client: YAMLWorker; - return toShallowCancelPromise( - this._getClient().then((client) => { - _client = client - }).then(_ => { - return this._worker.withSyncedResources(resources) - }).then(_ => _client) - ); + return this._getClient().then((client) => { + _client = client + }).then(_ => { + return this._worker.withSyncedResources(resources) + }).then(_ => _client); } } - -function toShallowCancelPromise(p: Promise): Promise { - let completeCallback: (value: T) => void; - let errorCallback: (err: any) => void; - - let r = new Promise((c, e) => { - completeCallback = c; - errorCallback = e; - }, () => { }); - - p.then(completeCallback, errorCallback); - - return r; -} diff --git a/src/yamlWorker.ts b/src/yamlWorker.ts index 0cdf407..2447b6f 100644 --- a/src/yamlWorker.ts +++ b/src/yamlWorker.ts @@ -12,7 +12,11 @@ import IWorkerContext = monaco.worker.IWorkerContext; import * as ls from 'vscode-languageserver-types'; import * as yamlService from './languageservice/yamlLanguageService'; -import { SchemaRequestService } from './languageservice/yamlLanguageService'; + +let defaultSchemaRequestService; +if (typeof fetch !== 'undefined'){ + defaultSchemaRequestService = function (url) { return fetch(url).then(response => response.text())}; +} class PromiseAdapter implements yamlService.Thenable { private wrapped: monaco.Promise; @@ -21,17 +25,14 @@ class PromiseAdapter implements yamlService.Thenable { this.wrapped = new monaco.Promise(executor); } public then(onfulfilled?: (value: T) => TResult | yamlService.Thenable, onrejected?: (reason: any) => void): yamlService.Thenable { - let thenable : yamlService.Thenable = this.wrapped; + let thenable: yamlService.Thenable = this.wrapped; return thenable.then(onfulfilled, onrejected); } public getWrapped(): monaco.Thenable { return this.wrapped; } - public cancel(): void { - this.wrapped.cancel(); - } public static resolve(v: T | Thenable): yamlService.Thenable { - return > monaco.Promise.as(v); + return >monaco.Promise.as(v); } public static reject(v: T): yamlService.Thenable { return monaco.Promise.wrapError(v); @@ -41,25 +42,6 @@ class PromiseAdapter implements yamlService.Thenable { } } -// Currently we only support loading schemas via xhr: -const ajax = (url: string) => - new Promise((resolve, reject) => { - const request = new XMLHttpRequest(); - request.onreadystatechange = () => { - if (request.readyState === XMLHttpRequest.DONE) { - const response = request.responseText; - if (request.status < 400) { - resolve(response); - } else { - reject(response); - } - } - }; - request.onerror = reject; - request.open('GET', url); - request.send(); - }); - export class YAMLWorker { private _ctx: IWorkerContext; @@ -71,8 +53,10 @@ export class YAMLWorker { this._ctx = ctx; this._languageSettings = createData.languageSettings; this._languageId = createData.languageId; - this._languageService = yamlService.getLanguageService(ajax, null, [], null, PromiseAdapter); - this._languageService.configure(this._languageSettings); + this._languageService = yamlService.getLanguageService(createData.enableSchemaRequest && defaultSchemaRequestService, + null, [], PromiseAdapter, + ); + this._languageService.configure({ ...this._languageSettings, hover: true, isKubernetes: true }); } doValidation(uri: string): Thenable { @@ -83,10 +67,8 @@ export class YAMLWorker { } return Promise.as([]); } - doComplete(uri: string, position: ls.Position): Thenable { let document = this._getTextDocument(uri); - let completionFix = completionHelper(document, position); let yamlDocument = this._languageService.parseYAMLDocument(document); return this._languageService.doComplete(document, position, yamlDocument); } @@ -95,7 +77,7 @@ export class YAMLWorker { } doHover(uri: string, position: ls.Position): Thenable { let document = this._getTextDocument(uri); - let yamlDocument = this._languageService.parseYAMLDocument(document) + let yamlDocument = this._languageService.parseYAMLDocument(document); return this._languageService.doHover(document, position, yamlDocument); } format(uri: string, range: ls.Range, options: ls.FormattingOptions): Thenable { @@ -126,89 +108,9 @@ export class YAMLWorker { export interface ICreateData { languageId: string; languageSettings: yamlService.LanguageSettings; - schemaRequestService?: SchemaRequestService; + enableSchemaRequest: boolean; } export function create(ctx: IWorkerContext, createData: ICreateData): YAMLWorker { return new YAMLWorker(ctx, createData); -} - -export function getLineOffsets(textDocString: String): number[] { - - let lineOffsets: number[] = []; - let text = textDocString; - let isLineStart = true; - for (let i = 0; i < text.length; i++) { - if (isLineStart) { - lineOffsets.push(i); - isLineStart = false; - } - let ch = text.charAt(i); - isLineStart = (ch === '\r' || ch === '\n'); - if (ch === '\r' && i + 1 < text.length && text.charAt(i + 1) === '\n') { - i++; - } - } - if (isLineStart && text.length > 0) { - lineOffsets.push(text.length); - } - - return lineOffsets; -} - -// https://github.com/redhat-developer/yaml-language-server/blob/5e069c0e9d7004d57f1fa6e93df670d4895883d1/src/server.ts#L453 -function completionHelper(document: ls.TextDocument, textDocumentPosition: ls.Position) { - - //Get the string we are looking at via a substring - let linePos = textDocumentPosition.line; - let position = textDocumentPosition; - let lineOffset = getLineOffsets(document.getText()); - let start = lineOffset[linePos]; //Start of where the autocompletion is happening - let end = 0; //End of where the autocompletion is happening - if (lineOffset[linePos + 1]) { - end = lineOffset[linePos + 1]; - } else { - end = document.getText().length; - } - let textLine = document.getText().substring(start, end); - - //Check if the string we are looking at is a node - if (textLine.indexOf(":") === -1) { - //We need to add the ":" to load the nodes - let newText = ""; - - //This is for the empty line case - let trimmedText = textLine.trim(); - if (trimmedText.length === 0 || (trimmedText.length === 1 && trimmedText[0] === '-')) { - //Add a temp node that is in the document but we don't use at all. - if (lineOffset[linePos + 1]) { - newText = document.getText().substring(0, start + (textLine.length - 1)) + "holder:\r\n" + document.getText().substr(end + 2); - } else { - newText = document.getText().substring(0, start + (textLine.length)) + "holder:\r\n" + document.getText().substr(end + 2); - } - //For when missing semi colon case - } else { - //Add a semicolon to the end of the current line so we can validate the node - if (lineOffset[linePos + 1]) { - newText = document.getText().substring(0, start + (textLine.length - 1)) + ":\r\n" + document.getText().substr(end + 2); - } else { - newText = document.getText().substring(0, start + (textLine.length)) + ":\r\n" + document.getText().substr(end + 2); - } - } - - return { - "newText": newText, - "newPosition": textDocumentPosition - } - - } else { - - //All the nodes are loaded - position.character = position.character - 1; - return { - "newText": document.getText(), - "newPosition": position - } - } - -} +} \ No newline at end of file diff --git a/test/index.html b/test/index.html index 4182187..6ae731a 100644 --- a/test/index.html +++ b/test/index.html @@ -73,6 +73,7 @@ spec: }); monaco.languages.yaml.yamlDefaults.setDiagnosticsOptions({ + enableSchemaRequest: true, validate: true, schemas: [ { @@ -81,27 +82,6 @@ spec: }, ], }); - - // See: https://github.com/Microsoft/vscode/blob/master/src/vs/editor/contrib/quickOpen/quickOpen.ts - require(['vs/editor/contrib/quickOpen/quickOpen'], quickOpen => { - - // Breadcrumbs emulation: - editor.onDidChangeCursorSelection(({ selection }) => { - quickOpen.getDocumentSymbols(editor.getModel()).then(symbols => { - symbols = symbols.filter(symbol => symbol.range.containsPosition(selection.getPosition())); - symbols = symbols.map(symbol => { - if (symbol.kind === 17) { - return `[]${symbol.name}`; - } else if (symbol.kind === 18 || symbol.kind === 1) { - return `{}${symbol.name}`; - } else { - return symbol.name; - } - }); - document.querySelector('#path').innerHTML = symbols.join(' > '); - }) - }) - }) }); diff --git a/yarn.lock b/yarn.lock index 1b557f6..27bc91b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -86,13 +86,15 @@ minimatch@^3.0.4: dependencies: brace-expansion "^1.1.7" -monaco-editor-core@0.14.6: - version "0.14.6" - resolved "https://registry.yarnpkg.com/monaco-editor-core/-/monaco-editor-core-0.14.6.tgz#25fae6a2e7c7da6eb2a3bba653a283627dc624e4" +monaco-editor-core@0.15.0: + version "0.15.0" + resolved "https://registry.yarnpkg.com/monaco-editor-core/-/monaco-editor-core-0.15.0.tgz#1ad5f130ed8efae9d9ed85d8a58a55067e14d983" + integrity sha512-s1zuo+p6Gl6IC4WJP6HBkr4pWULm+HdFfacB8vOFPQLLi2oJseO20UuSkxYuZTUJQIjvhNrQfLNAmPKLZaf7tg== -monaco-languages@1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/monaco-languages/-/monaco-languages-1.5.1.tgz#e0b754a3db9de2133859d88abbf3b2b376439b58" +monaco-languages@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/monaco-languages/-/monaco-languages-1.6.0.tgz#54ec5510b31f3132939014f171ade235e84ef0bc" + integrity sha512-LBEWj8tngYwsq4kasQr+dIhnO4xUIEN36ns+cRepWAQiXZnzcrZ84gFHXm8f4mR4tssxvHVU5Vw7xMUYro6h3g== monaco-plugin-helpers@^1.0.2: version "1.0.2" @@ -132,9 +134,10 @@ typescript@^2.7.2: version "2.9.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.9.2.tgz#1cbf61d05d6b96269244eb6a3bce4bd914e0f00c" -typescript@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.0.3.tgz#4853b3e275ecdaa27f78fda46dc273a7eb7fc1c8" +typescript@^3.1.6: + version "3.1.6" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.1.6.tgz#b6543a83cfc8c2befb3f4c8fba6896f5b0c9be68" + integrity sha512-tDMYfVtvpb96msS1lDX9MEdHrW4yOuZ4Kdc4Him9oU796XldPYF/t2+uKoX0BBa0hXXwDlqYQbXY5Rzjzc5hBA== uglify-es@^3.3.9: version "3.3.9"