diff --git a/src/languageservice/parser/yamlParser.ts b/src/languageservice/parser/yamlParser.ts index dce2327..56deaca 100644 --- a/src/languageservice/parser/yamlParser.ts +++ b/src/languageservice/parser/yamlParser.ts @@ -72,7 +72,7 @@ function recursivelyBuildAst(parent: ASTNode, node: Yaml.YAMLNode): ASTNode { // TODO: calculate the correct NULL range. const valueNode = instance.value ? recursivelyBuildAst(result, instance.value) - : new NullASTNodeImpl(parent, instance.endPosition); + : new NullASTNodeImpl(result, instance.endPosition); result.keyNode = keyNode; result.valueNode = valueNode; diff --git a/src/languageservice/services/jsonSchemaService.ts b/src/languageservice/services/jsonSchemaService.ts index 0ffcb12..eac57b3 100644 --- a/src/languageservice/services/jsonSchemaService.ts +++ b/src/languageservice/services/jsonSchemaService.ts @@ -556,7 +556,8 @@ export class JSONSchemaService implements IJSONSchemaService { next.anyOf, next.allOf, next.oneOf, - next.items as JSONSchema[] + next.items as JSONSchema[], + next.schemaSequence, ); }; diff --git a/src/languageservice/services/yamlCompletion.ts b/src/languageservice/services/yamlCompletion.ts index b88b67e..1ca6fd8 100644 --- a/src/languageservice/services/yamlCompletion.ts +++ b/src/languageservice/services/yamlCompletion.ts @@ -35,22 +35,19 @@ import { ClientCapabilities, ObjectASTNode, PropertyASTNode, - StringASTNode, } from '../jsonLanguageTypes'; import { matchOffsetToDocument } from '../utils/arrUtils'; import { stringifyObject } from '../utils/json'; import { isDefined } from '../utils/objects'; import { endsWith } from '../utils/strings'; import { YAMLDocument } from '../yamlLanguageTypes'; +import { LanguageSettings } from '../yamlLanguageService'; const localize = nls.loadMessageBundle(); -// !! FIXME: this implementation is buggy - export class YAMLCompletion { + private completionEnabled = true; private supportsMarkdown: boolean | undefined; - private templateVarIdCounter = 0; - constructor( private schemaService: SchemaService.IJSONSchemaService, private contributions: JSONWorkerContribution[] = [], @@ -60,6 +57,12 @@ export class YAMLCompletion { this.contributions = contributions; } + public configure(settings: LanguageSettings) { + if (settings) { + this.completionEnabled = settings.completion; + } + } + public doResolve(item: CompletionItem): Thenable { for (let i = this.contributions.length - 1; i >= 0; i--) { if (this.contributions[i].resolveCompletion) { @@ -88,7 +91,7 @@ export class YAMLCompletion { const currentDocIndex = doc.documents.indexOf(currentDoc); - if (currentDoc === null) { + if (currentDoc === null || !this.completionEnabled) { return Promise.resolve(result); } diff --git a/src/languageservice/services/yamlFormatter.ts b/src/languageservice/services/yamlFormatter.ts index 86f230b..1be9dcd 100644 --- a/src/languageservice/services/yamlFormatter.ts +++ b/src/languageservice/services/yamlFormatter.ts @@ -15,60 +15,70 @@ import { } from 'vscode-languageserver-types'; import { EOL } from '../../fillers/os'; import * as Yaml from '../../yaml-ast-parser/index'; +import { LanguageSettings } from '../yamlLanguageService'; -export function format( - document: TextDocument, - options: FormattingOptions, - customTags: String[] -): TextEdit[] { - const text = document.getText(); - customTags = customTags || []; +export class YamlFormatter { + private customTags: string[]; - const schemaWithAdditionalTags = jsyaml.Schema.create( - customTags.map(tag => { - const typeInfo = tag.split(' '); - return new jsyaml.Type(typeInfo[0], { kind: typeInfo[1] || 'scalar' }); - }) - ); - - // We need compiledTypeMap to be available from schemaWithAdditionalTags before we add the new custom properties - customTags.map(tag => { - const typeInfo = tag.split(' '); - schemaWithAdditionalTags.compiledTypeMap[typeInfo[0]] = new jsyaml.Type( - typeInfo[0], - { kind: typeInfo[1] || 'scalar' } - ); - }); - - const additionalOptions: Yaml.LoadOptions = { - schema: schemaWithAdditionalTags, - }; - - const documents = []; - jsyaml.loadAll(text, doc => documents.push(doc), additionalOptions); - - const dumpOptions = { indent: options.tabSize, noCompatMode: true }; - - let newText; - if (documents.length == 1) { - const yaml = documents[0]; - newText = jsyaml.safeDump(yaml, dumpOptions); - } else { - const formatted = documents.map(d => jsyaml.safeDump(d, dumpOptions)); - newText = - '%YAML 1.2' + - EOL + - '---' + - EOL + - formatted.join('...' + EOL + '---' + EOL) + - '...' + - EOL; + configure(settings: LanguageSettings) { + if (settings) { + this.customTags = settings.customTags || []; + } } - return [ - TextEdit.replace( - Range.create(Position.create(0, 0), document.positionAt(text.length)), - newText - ), - ]; + doFormat( + document: TextDocument, + options: FormattingOptions, + ): TextEdit[] { + const text = document.getText(); + const customTags = this.customTags || []; + + const schemaWithAdditionalTags = jsyaml.Schema.create( + customTags.map(tag => { + const typeInfo = tag.split(' '); + return new jsyaml.Type(typeInfo[0], { kind: typeInfo[1] || 'scalar' }); + }) + ); + + // We need compiledTypeMap to be available from schemaWithAdditionalTags before we add the new custom properties + customTags.map(tag => { + const typeInfo = tag.split(' '); + schemaWithAdditionalTags.compiledTypeMap[typeInfo[0]] = new jsyaml.Type( + typeInfo[0], + { kind: typeInfo[1] || 'scalar' } + ); + }); + + const additionalOptions: Yaml.LoadOptions = { + schema: schemaWithAdditionalTags, + }; + + const documents = []; + jsyaml.loadAll(text, doc => documents.push(doc), additionalOptions); + + const dumpOptions = { indent: options.tabSize, noCompatMode: true }; + + let newText; + if (documents.length == 1) { + const yaml = documents[0]; + newText = jsyaml.safeDump(yaml, dumpOptions); + } else { + const formatted = documents.map(d => jsyaml.safeDump(d, dumpOptions)); + newText = + '%YAML 1.2' + + EOL + + '---' + + EOL + + formatted.join('...' + EOL + '---' + EOL) + + '...' + + EOL; + } + + return [ + TextEdit.replace( + Range.create(Position.create(0, 0), document.positionAt(text.length)), + newText + ), + ]; + } } diff --git a/src/languageservice/services/yamlHover.ts b/src/languageservice/services/yamlHover.ts index f316a99..7b1c9b9 100644 --- a/src/languageservice/services/yamlHover.ts +++ b/src/languageservice/services/yamlHover.ts @@ -19,13 +19,22 @@ import { } from 'vscode-languageserver-types'; import { matchOffsetToDocument } from '../utils/arrUtils'; import { YAMLDocument } from '../yamlLanguageTypes'; +import { LanguageSettings } from '../yamlLanguageService'; export class YAMLHover { + private shouldHover = true; + constructor( private schemaService: SchemaService.IJSONSchemaService, private contributions: JSONWorkerContribution[] = [] ) {} + public configure(languageSettings: LanguageSettings){ + if(languageSettings){ + this.shouldHover = !!languageSettings.hover; + } + } + public doHover( document: TextDocument, position: Position, @@ -33,11 +42,11 @@ export class YAMLHover { ): Thenable { const offset = document.offsetAt(position); const currentDoc = matchOffsetToDocument(offset, doc); - if (currentDoc === null) { + if (currentDoc === null || !this.shouldHover) { return Promise.resolve(void 0); } - const currentDocIndex = doc.documents.indexOf(currentDoc); let node = currentDoc.getNodeFromOffset(offset); + const currentDocIndex = doc.documents.indexOf(currentDoc); if ( !node || ((node.type === 'object' || node.type === 'array') && @@ -85,8 +94,13 @@ export class YAMLHover { .getSchemaForResource(document.uri, currentDoc) .then(schema => { if (schema) { + let newSchema = schema; + if (schema.schema && schema.schema.schemaSequence && schema.schema.schemaSequence[currentDocIndex]) { + newSchema = new SchemaService.ResolvedSchema(schema.schema.schemaSequence[currentDocIndex]); + } + const matchingSchemas = currentDoc.getMatchingSchemas( - schema.schema, + newSchema.schema, node.offset ); diff --git a/src/languageservice/yamlLanguageService.ts b/src/languageservice/yamlLanguageService.ts index 0662b2d..486a71d 100644 --- a/src/languageservice/yamlLanguageService.ts +++ b/src/languageservice/yamlLanguageService.ts @@ -27,7 +27,7 @@ import { JSONSchemaService, } from './services/jsonSchemaService'; import { YAMLCompletion } from './services/yamlCompletion'; -import { format } from './services/yamlFormatter'; +import { YamlFormatter } from './services/yamlFormatter'; import { YAMLHover } from './services/yamlHover'; import { YAMLValidation } from './services/yamlValidation'; import { YAMLDocument } from './yamlLanguageTypes'; @@ -38,7 +38,7 @@ export interface LanguageSettings { 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?: String[]; // Array of Custom Tags + customTags?: string[]; // Array of Custom Tags } export interface Thenable { @@ -134,6 +134,7 @@ export function getLanguageService( const hover = new YAMLHover(schemaService, contributions); const yamlDocumentSymbols = new YAMLDocumentSymbols(schemaService); const yamlValidation = new YAMLValidation(schemaService); + const yamlFormatter = new YamlFormatter(); return { configure: settings => { @@ -147,6 +148,11 @@ export function getLanguageService( ); }); } + + yamlValidation.configure(settings); + hover.configure(settings); + completer.configure(settings); + yamlFormatter.configure(settings); }, registerCustomSchemaProvider: (schemaProvider: CustomSchemaProvider) => { schemaService.registerCustomSchemaProvider(schemaProvider); @@ -165,7 +171,7 @@ export function getLanguageService( yamlDocumentSymbols ), resetSchema: (uri: string) => schemaService.onResourceChange(uri), - doFormat: format, + doFormat: yamlFormatter.doFormat.bind(yamlFormatter), parseYAMLDocument: (document: TextDocument) => parseYAML(document.getText()), }; diff --git a/test/autoCompletion.test.ts b/test/autoCompletion.test.ts index 46496bf..6792de3 100644 --- a/test/autoCompletion.test.ts +++ b/test/autoCompletion.test.ts @@ -38,7 +38,6 @@ describe('Auto Completion Tests', () => { function parseSetup(content: string, position) { const testTextDocument = setup(content); - const yDoc = parseYAML(testTextDocument.getText()); return completionHelper( testTextDocument, testTextDocument.positionAt(position), @@ -70,7 +69,7 @@ describe('Auto Completion Tests', () => { const completion = parseSetup(content, 12); completion .then(function (result) { - expect(result.items.length).toEqual(0); + expect(result.items.length).toEqual(1); }) .then(done, done); }); diff --git a/test/formatter.test.ts b/test/formatter.test.ts index 4c86757..adb9fbc 100644 --- a/test/formatter.test.ts +++ b/test/formatter.test.ts @@ -48,7 +48,7 @@ describe('Formatter Tests', () => { assert.equal(edits[0].newText, 'cwd: test\n'); }); - it('Formatting works without custom tags', () => { + it('Formatting works with custom tags', () => { const content = `cwd: !Test test`; const testTextDocument = setup(content); const edits = languageService.doFormat( diff --git a/test/hover3.test.ts b/test/hover3.test.ts index 131312d..2a46174 100644 --- a/test/hover3.test.ts +++ b/test/hover3.test.ts @@ -50,7 +50,7 @@ describe('Hover Setting Tests', () => { const hover = parseSetup(content, 1); hover .then(function (result) { - assert.notEqual(result, undefined); + assert.equal(result, undefined); }) .then(done, done); }); diff --git a/test/tsconfig.json b/test/tsconfig.json index 9e3ad39..d051c4a 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -7,7 +7,6 @@ "esModuleInterop": true, "sourceMap": true, "lib": ["es2016"], - "outDir": "./out/server", "downlevelIteration": true }, "exclude": ["node_modules", "out"]