diff --git a/src/languageservice/parser/jsonParser.ts b/src/languageservice/parser/jsonParser.ts index 9178e79..1da620c 100644 --- a/src/languageservice/parser/jsonParser.ts +++ b/src/languageservice/parser/jsonParser.ts @@ -40,7 +40,7 @@ export abstract class ASTNodeImpl { constructor(parent: ASTNode, offset: number, length?: number) { this.offset = offset; - this.length = length; + this.length = length || 0; this.parent = parent; } @@ -290,6 +290,30 @@ export function contains(node: ASTNode, offset: number, includeRightBound = fals return offset >= node.offset && offset < (node.offset + node.length) || includeRightBound && offset === (node.offset + node.length); } +// export function contains(node: ASTNode, offset: number, includeRightBound = false): boolean { +// let flag = offset >= node.offset && offset <= (node.offset + node.length); +// if (!flag && includeRightBound) { +// if (node.parent && node.parent.children && ) +// const nextSibling = node.parent +// } +// return flag; +// } + +// export function findNodeAtOffset(node: ASTNode, offset: number, includeRightBound = false): ASTNode | undefined { +// if (contains(node, offset, includeRightBound)) { +// const children = node.children; +// if (Array.isArray(children)) { +// for (var i = 0; i < children.length && children[i].offset <= offset; i++) { +// const item = findNodeAtOffset(children[i], offset, includeRightBound); +// if (item) { +// return item; +// } +// } +// } +// return node; +// } +// } + export class JSONDocument { constructor(public root: ASTNode, public readonly syntaxErrors: Diagnostic[] = [], public readonly comments: Range[] = []) { @@ -298,6 +322,7 @@ export class JSONDocument { public getNodeFromOffset(offset: number, includeRightBound = false): ASTNode | undefined { if (this.root) { return Json.findNodeAtOffset(this.root, offset, includeRightBound); + //return findNodeAtOffset(this.root, offset, includeRightBound); } return void 0; } diff --git a/src/languageservice/parser/yamlParser.ts b/src/languageservice/parser/yamlParser.ts index 61189fc..083693e 100644 --- a/src/languageservice/parser/yamlParser.ts +++ b/src/languageservice/parser/yamlParser.ts @@ -9,13 +9,14 @@ import * as nls from 'vscode-nls'; const localize = nls.loadMessageBundle(); import * as Yaml from '../../yaml-ast-parser/index' -import { Schema, Type } from 'js-yaml'; import { getLineStartPositions } from '../utils/documentPositionCalculator' import { parseYamlBoolean } from './scalar-type'; import { ObjectASTNodeImpl, StringASTNodeImpl, PropertyASTNodeImpl, NullASTNodeImpl, ArrayASTNodeImpl, BooleanASTNodeImpl, NumberASTNodeImpl } from './jsonParser'; import { ASTNode, PropertyASTNode, ErrorCode } from '../jsonLanguageTypes'; import { SingleYAMLDocument, YAMLDocument } from '../yamlLanguageTypes'; +import { Schema } from '../../yaml-ast-parser/schema'; +import { Type } from '../../yaml-ast-parser/type'; function recursivelyBuildAst(parent: ASTNode, node: Yaml.YAMLNode): ASTNode { @@ -47,7 +48,8 @@ function recursivelyBuildAst(parent: ASTNode, node: Yaml.YAMLNode): ASTNode { const keyNode = new StringASTNodeImpl(result, key.startPosition, key.endPosition - key.startPosition); keyNode.value = key.value; - const valueNode = (instance.value) ? recursivelyBuildAst(result, instance.value) : new NullASTNodeImpl(parent, instance.startPosition) + // TODO: calculate the correct NULL range. + const valueNode = (instance.value) ? recursivelyBuildAst(result, instance.value) : new NullASTNodeImpl(parent, instance.endPosition); result.keyNode = keyNode; result.valueNode = valueNode; diff --git a/src/languageservice/services/yamlCompletion.ts b/src/languageservice/services/yamlCompletion.ts index 704c29d..e6e97d8 100644 --- a/src/languageservice/services/yamlCompletion.ts +++ b/src/languageservice/services/yamlCompletion.ts @@ -58,11 +58,13 @@ export class YAMLCompletion { let currentDoc = matchOffsetToDocument(offset, doc); - if(currentDoc === null){ - return Promise.resolve(result); - } + const currentDocIndex = doc.documents.indexOf(currentDoc); - let node = currentDoc.getNodeFromOffset(offset, true); + if (currentDoc === null) { + return Promise.resolve(result); + } + + let node = currentDoc.getNodeFromOffsetEndInclusive(offset); if (this.isInComment(document, node ? node.offset : 0, offset)) { return Promise.resolve(result); } @@ -131,12 +133,13 @@ export class YAMLCompletion { } } + // Support multiple doc per YAML + if (schema && schema.schema && schema.schema.schemaSequence && schema.schema.schemaSequence[currentDocIndex]) { + schema = new SchemaService.ResolvedSchema(schema.schema.schemaSequence[currentDocIndex]); + } + // proposals for properties if (node && node.type === 'object') { - // don't suggest keys when the cursor is just before the opening curly brace - if (node.offset === offset) { - return result; - } // don't suggest properties that are already present let properties = node.properties; properties.forEach(p => { @@ -362,7 +365,6 @@ export class YAMLCompletion { } } - private getValueCompletions(schema: SchemaService.ResolvedSchema, doc: Parser.JSONDocument, node: ASTNode, offset: number, document: TextDocument, collector: CompletionsCollector, types: { [type: string]: boolean }): void { let offsetForSeparator = offset; let parentKey: string = null; @@ -679,11 +681,11 @@ export class YAMLCompletion { } private getInsertTextForValue(value: any, separatorAfter: string): string { - var text = JSON.stringify(value, null, '\t'); + const text = value; if (text === '{}') { - return '{$1}' + separatorAfter; + return '{\n\t$1\n}' + separatorAfter; } else if (text === '[]') { - return '[$1]' + separatorAfter; + return '[\n\t$1\n]' + separatorAfter; } return this.getInsertTextForPlainText(text + separatorAfter); } @@ -767,7 +769,7 @@ export class YAMLCompletion { if (!addValue) { return propertyText; } - let resultText = propertyText + ': '; + let resultText = propertyText + ':'; let value; let nValueProposals = 0; @@ -804,23 +806,23 @@ export class YAMLCompletion { } switch (type) { case 'boolean': - value = '$1'; + value = ' $1'; break; case 'string': - value = '"$1"'; + value = ' $1'; break; case 'object': - value = '{$1}'; + value = '\n\t'; break; case 'array': - value = '[$1]'; + value = '\n\t- '; break; case 'number': case 'integer': - value = '${1:0}'; + value = ' ${1:0}'; break; case 'null': - value = '${1:null}'; + value = ' ${1:null}'; break; default: return propertyText; @@ -834,8 +836,8 @@ export class YAMLCompletion { } private getCurrentWord(document: TextDocument, offset: number) { - var i = offset - 1; - var text = document.getText(); + let i = offset - 1; + const text = document.getText(); while (i >= 0 && ' \t\n\r\v":{[,]}'.indexOf(text.charAt(i)) === -1) { i--; } @@ -853,7 +855,7 @@ export class YAMLCompletion { case Json.SyntaxKind.EOF: return ''; default: - return ','; + return ''; } } diff --git a/src/languageservice/yamlLanguageTypes.ts b/src/languageservice/yamlLanguageTypes.ts index 00d8366..18660ff 100644 --- a/src/languageservice/yamlLanguageTypes.ts +++ b/src/languageservice/yamlLanguageTypes.ts @@ -2,36 +2,65 @@ import { JSONDocument } from './parser/jsonParser'; import { ASTNode } from './jsonLanguageTypes'; export class SingleYAMLDocument extends JSONDocument { - public lines; - public errors; - public warnings; + public lines; + public errors; + public warnings; - constructor(lines: number[]) { - super(null, []); - this.lines = lines; - this.errors = []; - this.warnings = []; - } + constructor(lines: number[]) { + super(null, []); + this.lines = lines; + this.errors = []; + this.warnings = []; + } - public getSchemas(schema, doc, node) { - let matchingSchemas = []; - doc.validate(schema, matchingSchemas, node.start); - return matchingSchemas; - } + public getSchemas(schema, doc, node) { + let matchingSchemas = []; + doc.validate(schema, matchingSchemas, node.start); + return matchingSchemas; + } - public getNodeFromOffset(offset: number, includeRightBound = false): ASTNode { - return super.getNodeFromOffset(offset, includeRightBound); + public getNodeFromOffset(offset: number): ASTNode { + return super.getNodeFromOffset(offset, true); + } + + public getNodeFromOffsetEndInclusive(offset: number): ASTNode { + let collector: ASTNode[] = []; + let findNode = (node: ASTNode): ASTNode => { + if (offset >= node.offset && offset <= node.offset + node.length) { + let children = node.children; + for (let i = 0; i < children.length && children[i].offset <= offset; i++) { + let item = findNode(children[i]); + if (item) { + collector.push(item); + } + } + return node; + } + return null; + }; + let foundNode = findNode(this.root); + let currMinDist = Number.MAX_VALUE; + let currMinNode = null; + for (let possibleNode in collector) { + let currNode = collector[possibleNode]; + let minDist = (currNode.offset + currNode.length - offset) + (offset - currNode.offset); + if (minDist < currMinDist) { + currMinNode = currNode; + currMinDist = minDist; + } + } + return currMinNode || foundNode; } } export class YAMLDocument { - public documents: SingleYAMLDocument[] - public errors; - public warnings; + public documents: SingleYAMLDocument[] + public errors; + public warnings; - constructor(documents: SingleYAMLDocument[]) { - this.documents = documents; - this.errors = []; - this.warnings = []; - } + constructor(documents: SingleYAMLDocument[]) { + this.documents = documents; + this.errors = []; + this.warnings = []; + } } diff --git a/src/yaml-ast-parser/schema.ts b/src/yaml-ast-parser/schema.ts index a4a5262..8f4ed88 100644 --- a/src/yaml-ast-parser/schema.ts +++ b/src/yaml-ast-parser/schema.ts @@ -75,18 +75,19 @@ export class Schema { } static DEFAULT = null; - static create = function createSchema() { - var schemas, types; + static create = function createSchema(...args: [Schema | Schema[], Type[]] | [Type[]]) { + let schemas: Schema | Schema[]; + let types: Type[]; - switch (arguments.length) { + switch (args.length) { case 1: schemas = Schema.DEFAULT; - types = arguments[0]; + types = args[0]; break; case 2: - schemas = arguments[0]; - types = arguments[1]; + schemas = args[0]; + types = args[1]; break; default: @@ -109,4 +110,4 @@ export class Schema { explicit: types }); } -} \ No newline at end of file +} diff --git a/src/yamlMode.ts b/src/yamlMode.ts index 8537627..d557732 100644 --- a/src/yamlMode.ts +++ b/src/yamlMode.ts @@ -26,15 +26,16 @@ export function setupMode(defaults: LanguageServiceDefaultsImpl): void { let languageId = defaults.languageId; - // TODO: - // disposables.push(monaco.languages.registerCompletionItemProvider(languageId, new languageFeatures.CompletionAdapter(worker))); + disposables.push(monaco.languages.registerCompletionItemProvider(languageId, new languageFeatures.CompletionAdapter(worker))); disposables.push(monaco.languages.registerHoverProvider(languageId, new languageFeatures.HoverAdapter(worker))); disposables.push(monaco.languages.registerDocumentSymbolProvider(languageId, new languageFeatures.DocumentSymbolAdapter(worker))); - disposables.push(monaco.languages.registerColorProvider(languageId, new languageFeatures.DocumentColorAdapter(worker))); disposables.push(monaco.languages.registerDocumentFormattingEditProvider(languageId, new languageFeatures.DocumentFormattingEditProvider(worker))); disposables.push(monaco.languages.registerDocumentRangeFormattingEditProvider(languageId, new languageFeatures.DocumentRangeFormattingEditProvider(worker))); disposables.push(new languageFeatures.DiagnosticsAdapter(languageId, worker, defaults)); - disposables.push(monaco.languages.setLanguageConfiguration(languageId, richEditConfiguration)); + disposables.push(monaco.languages.setLanguageConfiguration(languageId, richEditConfiguration)); + + // Color adapter should be necessary most of the time: + // disposables.push(monaco.languages.registerColorProvider(languageId, new languageFeatures.DocumentColorAdapter(worker))); } diff --git a/test/index.html b/test/index.html index 27bf279..df3b4cb 100644 --- a/test/index.html +++ b/test/index.html @@ -47,41 +47,42 @@ 'vs/basic-languages/monaco.contribution', 'vs/language/yaml/monaco.contribution' ], function () { - const yaml = `apiVersion: apps/v1 -kind: Deployment -metadata: - name: nginx-deployment - namespace: default - labels: - app: nginx -spec: - replicas: 1 - selector: - matchLabels: - apps.deployment: nginx - template: - metadata: - labels: - apps.deployment: nginx - spec: - containers: - - name: nginx - image: nginx:alpine`; - - var editor = monaco.editor.create(document.getElementById('container'), { - value: yaml, - language: 'yaml' + const yaml = `p1: `; + const modelUri = monaco.Uri.parse("a://b/foo.json"); + const editor = monaco.editor.create(document.getElementById('container'), { + language: 'yaml', + showFoldingControls: 'always', + model: monaco.editor.createModel(yaml, "yaml", modelUri), }); monaco.languages.yaml.yamlDefaults.setDiagnosticsOptions({ enableSchemaRequest: true, validate: true, - schemas: [ - { - uri: 'https://raw.githubusercontent.com/garethr/kubernetes-json-schema/master/master/deployment.json', - fileMatch: ['*'], - }, - ], + schemas: [{ + uri: "http://myserver/foo-schema.json", // id of the first schema + fileMatch: [modelUri.toString()], // associate with our model + schema: { + type: "object", + properties: { + p1: { + enum: ["v1", "v2"] + }, + p2: { + $ref: "http://myserver/bar-schema.json" // reference the second schema + } + } + } + }, { + uri: "http://myserver/bar-schema.json", // id of the first schema + schema: { + type: "object", + properties: { + q1: { + enum: ["x1", "x2"] + } + } + } + }] }); require(["vs/editor/contrib/quickOpen/quickOpen"], async quickOpen => { @@ -117,7 +118,8 @@ spec: const position = selection.getPosition(); const symbol = await _getSymbolForPosition(model, position); - if (symbol) { + console.log(`${symbol.name}: ${symbol.range}`); + if (symbol && symbol.range) { const decoration = { range: symbol.range, options: { @@ -130,8 +132,6 @@ spec: oldDecorations, decoration ? [decoration] : [], ) - - console.log(`${symbol.name}: ${symbol.range}`); } });