UNPKG

yaml-language-server

Version:
1,473 lines 82.2 kB
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Red Hat, Inc. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
(function (factory) {
    if (typeof module === "object" && typeof module.exports === "object") {
        var v = factory(require, exports);
        if (v !== undefined) module.exports = v;
    }
    else if (typeof define === "function" && define.amd) {
        define(["require", "exports", "vscode-languageserver-types", "yaml", "../utils/arrUtils", "../utils/indentationGuesser", "../utils/textBuffer", "../utils/json", "../utils/objects", "vscode-nls", "../parser/isKubernetes", "../parser/jsonParser07", "../utils/astUtils", "./modelineUtil", "../utils/schemaUtils"], factory);
    }
})(function (require, exports) {
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    exports.YamlCompletion = void 0;
    const vscode_languageserver_types_1 = require("vscode-languageserver-types");
    const yaml_1 = require("yaml");
    const arrUtils_1 = require("../utils/arrUtils");
    const indentationGuesser_1 = require("../utils/indentationGuesser");
    const textBuffer_1 = require("../utils/textBuffer");
    const json_1 = require("../utils/json");
    const objects_1 = require("../utils/objects");
    const nls = require("vscode-nls");
    const isKubernetes_1 = require("../parser/isKubernetes");
    const jsonParser07_1 = require("../parser/jsonParser07");
    const astUtils_1 = require("../utils/astUtils");
    const modelineUtil_1 = require("./modelineUtil");
    const schemaUtils_1 = require("../utils/schemaUtils");
    const localize = nls.loadMessageBundle();
    const doubleQuotesEscapeRegExp = /[\\]+"/g;
    const parentCompletionKind = vscode_languageserver_types_1.CompletionItemKind.Class;
    const existingProposeItem = '__';
    class YamlCompletion {
        constructor(schemaService, clientCapabilities = {}, yamlDocument, telemetry) {
            this.schemaService = schemaService;
            this.clientCapabilities = clientCapabilities;
            this.yamlDocument = yamlDocument;
            this.telemetry = telemetry;
            this.completionEnabled = true;
            this.arrayPrefixIndentation = '';
        }
        configure(languageSettings) {
            if (languageSettings) {
                this.completionEnabled = languageSettings.completion;
            }
            this.customTags = languageSettings.customTags;
            this.yamlVersion = languageSettings.yamlVersion;
            this.configuredIndentation = languageSettings.indentation;
            this.disableDefaultProperties = languageSettings.disableDefaultProperties;
            this.parentSkeletonSelectedFirst = languageSettings.parentSkeletonSelectedFirst;
        }
        async doComplete(document, position, isKubernetes = false, doComplete = true) {
            const result = vscode_languageserver_types_1.CompletionList.create([], false);
            if (!this.completionEnabled) {
                return result;
            }
            const doc = this.yamlDocument.getYamlDocument(document, { customTags: this.customTags, yamlVersion: this.yamlVersion }, true);
            const textBuffer = new textBuffer_1.TextBuffer(document);
            if (!this.configuredIndentation) {
                const indent = (0, indentationGuesser_1.guessIndentation)(textBuffer, 2, true);
                this.indentation = indent.insertSpaces ? ' '.repeat(indent.tabSize) : '\t';
            }
            else {
                this.indentation = this.configuredIndentation;
            }
            (0, isKubernetes_1.setKubernetesParserOption)(doc.documents, isKubernetes);
            // set parser options
            for (const jsonDoc of doc.documents) {
                jsonDoc.uri = document.uri;
            }
            const offset = document.offsetAt(position);
            const text = document.getText();
            if (text.charAt(offset - 1) === ':') {
                return Promise.resolve(result);
            }
            let currentDoc = (0, arrUtils_1.matchOffsetToDocument)(offset, doc);
            if (currentDoc === null) {
                return Promise.resolve(result);
            }
            // as we modify AST for completion, we need to use copy of original document
            currentDoc = currentDoc.clone();
            let [node, foundByClosest] = currentDoc.getNodeFromPosition(offset, textBuffer, this.indentation.length);
            const currentWord = this.getCurrentWord(document, offset);
            let lineContent = textBuffer.getLineContent(position.line);
            const lineAfterPosition = lineContent.substring(position.character);
            const areOnlySpacesAfterPosition = /^[ ]+\n?$/.test(lineAfterPosition);
            this.arrayPrefixIndentation = '';
            let overwriteRange = null;
            if (areOnlySpacesAfterPosition) {
                overwriteRange = vscode_languageserver_types_1.Range.create(position, vscode_languageserver_types_1.Position.create(position.line, lineContent.length));
                const isOnlyWhitespace = lineContent.trim().length === 0;
                const isOnlyDash = lineContent.match(/^\s*(-)\s*$/);
                if (node && (0, yaml_1.isScalar)(node) && !isOnlyWhitespace && !isOnlyDash) {
                    const lineToPosition = lineContent.substring(0, position.character);
                    const matches = 
                    // get indentation of unfinished property (between indent and cursor)
                    lineToPosition.match(/^[\s-]*([^:]+)?$/) ||
                        // OR get unfinished value (between colon and cursor)
                        lineToPosition.match(/:[ \t]((?!:[ \t]).*)$/);
                    if (matches?.[1]) {
                        overwriteRange = vscode_languageserver_types_1.Range.create(vscode_languageserver_types_1.Position.create(position.line, position.character - matches[1].length), vscode_languageserver_types_1.Position.create(position.line, lineContent.length));
                    }
                }
            }
            else if (node && (0, yaml_1.isScalar)(node) && node.value === 'null') {
                const nodeStartPos = document.positionAt(node.range[0]);
                nodeStartPos.character += 1;
                const nodeEndPos = document.positionAt(node.range[2]);
                nodeEndPos.character += 1;
                overwriteRange = vscode_languageserver_types_1.Range.create(nodeStartPos, nodeEndPos);
            }
            else if (node && (0, yaml_1.isScalar)(node) && node.value) {
                const start = document.positionAt(node.range[0]);
                overwriteRange = vscode_languageserver_types_1.Range.create(start, document.positionAt(node.range[1]));
            }
            else if (node && (0, yaml_1.isScalar)(node) && node.value === null && currentWord === '-') {
                overwriteRange = vscode_languageserver_types_1.Range.create(position, position);
                this.arrayPrefixIndentation = ' ';
            }
            else {
                let overwriteStart = offset - currentWord.length;
                if (overwriteStart > 0 && text[overwriteStart - 1] === '"') {
                    overwriteStart--;
                }
                overwriteRange = vscode_languageserver_types_1.Range.create(document.positionAt(overwriteStart), position);
            }
            const proposed = {};
            const collector = {
                add: (completionItem, oneOfSchema) => {
                    const addSuggestionForParent = function (completionItem) {
                        const existsInYaml = proposed[completionItem.label]?.label === existingProposeItem;
                        //don't put to parent suggestion if already in yaml
                        if (existsInYaml) {
                            return;
                        }
                        const schema = completionItem.parent.schema;
                        const schemaType = (0, schemaUtils_1.getSchemaTypeName)(schema);
                        const schemaDescription = schema.markdownDescription || schema.description;
                        let parentCompletion = result.items.find((item) => item.parent?.schema === schema && item.kind === parentCompletionKind);
                        if (parentCompletion && parentCompletion.parent.insertTexts.includes(completionItem.insertText)) {
                            // already exists in the parent
                            return;
                        }
                        else if (!parentCompletion) {
                            // create a new parent
                            parentCompletion = {
                                ...completionItem,
                                label: schemaType,
                                documentation: schemaDescription,
                                sortText: '_' + schemaType,
                                kind: parentCompletionKind,
                            };
                            parentCompletion.label = parentCompletion.label || completionItem.label;
                            parentCompletion.parent.insertTexts = [completionItem.insertText];
                            result.items.push(parentCompletion);
                        }
                        else {
                            // add to the existing parent
                            parentCompletion.parent.insertTexts.push(completionItem.insertText);
                        }
                    };
                    const isForParentCompletion = !!completionItem.parent;
                    let label = completionItem.label;
                    if (!label) {
                        // we receive not valid CompletionItem as `label` is mandatory field, so just ignore it
                        console.warn(`Ignoring CompletionItem without label: ${JSON.stringify(completionItem)}`);
                        return;
                    }
                    if (!(0, objects_1.isString)(label)) {
                        label = String(label);
                    }
                    label = label.replace(/[\n]/g, '↵');
                    if (label.length > 60) {
                        const shortendedLabel = label.substr(0, 57).trim() + '...';
                        if (!proposed[shortendedLabel]) {
                            label = shortendedLabel;
                        }
                    }
                    // trim $1 from end of completion
                    if (completionItem.insertText.endsWith('$1') && !isForParentCompletion) {
                        completionItem.insertText = completionItem.insertText.substr(0, completionItem.insertText.length - 2);
                    }
                    if (overwriteRange && overwriteRange.start.line === overwriteRange.end.line) {
                        completionItem.textEdit = vscode_languageserver_types_1.TextEdit.replace(overwriteRange, completionItem.insertText);
                    }
                    completionItem.label = label;
                    if (isForParentCompletion) {
                        addSuggestionForParent(completionItem);
                        return;
                    }
                    if (this.arrayPrefixIndentation) {
                        this.updateCompletionText(completionItem, this.arrayPrefixIndentation + completionItem.insertText);
                    }
                    const existing = proposed[label];
                    const isInsertTextDifferent = existing?.label !== existingProposeItem && existing?.insertText !== completionItem.insertText;
                    if (!existing) {
                        proposed[label] = completionItem;
                        result.items.push(completionItem);
                    }
                    else if (isInsertTextDifferent) {
                        // try to merge simple insert values
                        const mergedText = this.mergeSimpleInsertTexts(label, existing.insertText, completionItem.insertText, oneOfSchema);
                        if (mergedText) {
                            this.updateCompletionText(existing, mergedText);
                        }
                        else {
                            // add to result when it wasn't able to merge (even if the item is already there but with a different value)
                            proposed[label] = completionItem;
                            result.items.push(completionItem);
                        }
                    }
                    if (existing && !existing.documentation && completionItem.documentation) {
                        existing.documentation = completionItem.documentation;
                    }
                },
                error: (message) => {
                    this.telemetry?.sendError('yaml.completion.error', { error: (0, objects_1.convertErrorToTelemetryMsg)(message) });
                },
                log: (message) => {
                    console.log(message);
                },
                getNumberOfProposals: () => {
                    return result.items.length;
                },
                result,
                proposed,
            };
            if (this.customTags.length > 0) {
                this.getCustomTagValueCompletions(collector);
            }
            if (lineContent.endsWith('\n')) {
                lineContent = lineContent.substr(0, lineContent.length - 1);
            }
            try {
                const schema = await this.schemaService.getSchemaForResource(document.uri, currentDoc);
                if (!schema || schema.errors.length) {
                    if (position.line === 0 && position.character === 0 && !(0, modelineUtil_1.isModeline)(lineContent)) {
                        const inlineSchemaCompletion = {
                            kind: vscode_languageserver_types_1.CompletionItemKind.Text,
                            label: 'Inline schema',
                            insertText: '# yaml-language-server: $schema=',
                            insertTextFormat: vscode_languageserver_types_1.InsertTextFormat.PlainText,
                        };
                        result.items.push(inlineSchemaCompletion);
                    }
                }
                if ((0, modelineUtil_1.isModeline)(lineContent) || (0, astUtils_1.isInComment)(doc.tokens, offset)) {
                    const schemaIndex = lineContent.indexOf('$schema=');
                    if (schemaIndex !== -1 && schemaIndex + '$schema='.length <= position.character) {
                        this.schemaService.getAllSchemas().forEach((schema) => {
                            const schemaIdCompletion = {
                                kind: vscode_languageserver_types_1.CompletionItemKind.Constant,
                                label: schema.name ?? schema.uri,
                                detail: schema.description,
                                insertText: schema.uri,
                                insertTextFormat: vscode_languageserver_types_1.InsertTextFormat.PlainText,
                                insertTextMode: vscode_languageserver_types_1.InsertTextMode.asIs,
                            };
                            result.items.push(schemaIdCompletion);
                        });
                    }
                    return result;
                }
                if (!schema || schema.errors.length) {
                    return result;
                }
                let currentProperty = null;
                if (!node) {
                    if (!currentDoc.internalDocument.contents || (0, yaml_1.isScalar)(currentDoc.internalDocument.contents)) {
                        const map = currentDoc.internalDocument.createNode({});
                        map.range = [offset, offset + 1, offset + 1];
                        currentDoc.internalDocument.contents = map;
                        currentDoc.updateFromInternalDocument();
                        node = map;
                    }
                    else {
                        node = currentDoc.findClosestNode(offset, textBuffer);
                        foundByClosest = true;
                    }
                }
                const originalNode = node;
                if (node) {
                    if (lineContent.length === 0) {
                        node = currentDoc.internalDocument.contents;
                    }
                    else {
                        const parent = currentDoc.getParent(node);
                        if (parent) {
                            if ((0, yaml_1.isScalar)(node)) {
                                if (node.value) {
                                    if ((0, yaml_1.isPair)(parent)) {
                                        if (parent.value === node) {
                                            if (lineContent.trim().length > 0 && lineContent.indexOf(':') < 0) {
                                                const map = this.createTempObjNode(currentWord, node, currentDoc);
                                                const parentParent = currentDoc.getParent(parent);
                                                if ((0, yaml_1.isSeq)(currentDoc.internalDocument.contents)) {
                                                    const index = (0, astUtils_1.indexOf)(currentDoc.internalDocument.contents, parent);
                                                    if (typeof index === 'number') {
                                                        currentDoc.internalDocument.set(index, map);
                                                        currentDoc.updateFromInternalDocument();
                                                    }
                                                }
                                                else if (parentParent && ((0, yaml_1.isMap)(parentParent) || (0, yaml_1.isSeq)(parentParent))) {
                                                    parentParent.set(parent.key, map);
                                                    currentDoc.updateFromInternalDocument();
                                                }
                                                else {
                                                    currentDoc.internalDocument.set(parent.key, map);
                                                    currentDoc.updateFromInternalDocument();
                                                }
                                                currentProperty = map.items[0];
                                                node = map;
                                            }
                                            else if (lineContent.trim().length === 0) {
                                                const parentParent = currentDoc.getParent(parent);
                                                if (parentParent) {
                                                    node = parentParent;
                                                }
                                            }
                                        }
                                        else if (parent.key === node) {
                                            const parentParent = currentDoc.getParent(parent);
                                            currentProperty = parent;
                                            if (parentParent) {
                                                node = parentParent;
                                            }
                                        }
                                    }
                                    else if ((0, yaml_1.isSeq)(parent)) {
                                        if (lineContent.trim().length > 0) {
                                            const map = this.createTempObjNode(currentWord, node, currentDoc);
                                            parent.delete(node);
                                            parent.add(map);
                                            currentDoc.updateFromInternalDocument();
                                            node = map;
                                        }
                                        else {
                                            node = parent;
                                        }
                                    }
                                }
                                else if (node.value === null) {
                                    if ((0, yaml_1.isPair)(parent)) {
                                        if (parent.key === node) {
                                            node = parent;
                                        }
                                        else {
                                            if ((0, yaml_1.isNode)(parent.key) && parent.key.range) {
                                                const parentParent = currentDoc.getParent(parent);
                                                if (foundByClosest && parentParent && (0, yaml_1.isMap)(parentParent) && (0, astUtils_1.isMapContainsEmptyPair)(parentParent)) {
                                                    node = parentParent;
                                                }
                                                else {
                                                    const parentPosition = document.positionAt(parent.key.range[0]);
                                                    //if cursor has bigger indentation that parent key, then we need to complete new empty object
                                                    if (position.character > parentPosition.character && position.line !== parentPosition.line) {
                                                        const map = this.createTempObjNode(currentWord, node, currentDoc);
                                                        if (parentParent && ((0, yaml_1.isMap)(parentParent) || (0, yaml_1.isSeq)(parentParent))) {
                                                            parentParent.set(parent.key, map);
                                                            currentDoc.updateFromInternalDocument();
                                                        }
                                                        else {
                                                            currentDoc.internalDocument.set(parent.key, map);
                                                            currentDoc.updateFromInternalDocument();
                                                        }
                                                        currentProperty = map.items[0];
                                                        node = map;
                                                    }
                                                    else if (parentPosition.character === position.character) {
                                                        if (parentParent) {
                                                            node = parentParent;
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    }
                                    else if ((0, yaml_1.isSeq)(parent)) {
                                        if (lineContent.charAt(position.character - 1) !== '-') {
                                            const map = this.createTempObjNode(currentWord, node, currentDoc);
                                            parent.delete(node);
                                            parent.add(map);
                                            currentDoc.updateFromInternalDocument();
                                            node = map;
                                        }
                                        else if (lineContent.charAt(position.character - 1) === '-') {
                                            const map = this.createTempObjNode('', node, currentDoc);
                                            parent.delete(node);
                                            parent.add(map);
                                            currentDoc.updateFromInternalDocument();
                                            node = map;
                                        }
                                        else {
                                            node = parent;
                                        }
                                    }
                                }
                            }
                            else if ((0, yaml_1.isMap)(node)) {
                                if (!foundByClosest && lineContent.trim().length === 0 && (0, yaml_1.isSeq)(parent)) {
                                    const nextLine = textBuffer.getLineContent(position.line + 1);
                                    if (textBuffer.getLineCount() === position.line + 1 || nextLine.trim().length === 0) {
                                        node = parent;
                                    }
                                }
                            }
                        }
                        else if ((0, yaml_1.isScalar)(node)) {
                            const map = this.createTempObjNode(currentWord, node, currentDoc);
                            currentDoc.internalDocument.contents = map;
                            currentDoc.updateFromInternalDocument();
                            currentProperty = map.items[0];
                            node = map;
                        }
                        else if ((0, yaml_1.isMap)(node)) {
                            for (const pair of node.items) {
                                if ((0, yaml_1.isNode)(pair.value) && pair.value.range && pair.value.range[0] === offset + 1) {
                                    node = pair.value;
                                }
                            }
                        }
                        else if ((0, yaml_1.isSeq)(node)) {
                            if (lineContent.charAt(position.character - 1) !== '-') {
                                const map = this.createTempObjNode(currentWord, node, currentDoc);
                                map.items = [];
                                currentDoc.updateFromInternalDocument();
                                for (const pair of node.items) {
                                    if ((0, yaml_1.isMap)(pair)) {
                                        pair.items.forEach((value) => {
                                            map.items.push(value);
                                        });
                                    }
                                }
                                node = map;
                            }
                        }
                    }
                }
                // completion for object keys
                if (node && (0, yaml_1.isMap)(node)) {
                    // don't suggest properties that are already present
                    const properties = node.items;
                    for (const p of properties) {
                        if (!currentProperty || currentProperty !== p) {
                            if ((0, yaml_1.isScalar)(p.key)) {
                                proposed[p.key.value + ''] = vscode_languageserver_types_1.CompletionItem.create(existingProposeItem);
                            }
                        }
                    }
                    this.addPropertyCompletions(schema, currentDoc, node, originalNode, '', collector, textBuffer, overwriteRange, doComplete);
                    if (!schema && currentWord.length > 0 && text.charAt(offset - currentWord.length - 1) !== '"') {
                        collector.add({
                            kind: vscode_languageserver_types_1.CompletionItemKind.Property,
                            label: currentWord,
                            insertText: this.getInsertTextForProperty(currentWord, null, ''),
                            insertTextFormat: vscode_languageserver_types_1.InsertTextFormat.Snippet,
                        });
                    }
                }
                // proposals for values
                const types = {};
                this.getValueCompletions(schema, currentDoc, node, offset, document, collector, types, doComplete);
            }
            catch (err) {
                this.telemetry?.sendError('yaml.completion.error', { error: (0, objects_1.convertErrorToTelemetryMsg)(err) });
            }
            this.finalizeParentCompletion(result);
            const uniqueItems = result.items.filter((arr, index, self) => index ===
                self.findIndex((item) => item.label === arr.label && item.insertText === arr.insertText && item.kind === arr.kind));
            if (uniqueItems?.length > 0) {
                result.items = uniqueItems;
            }
            return result;
        }
        updateCompletionText(completionItem, text) {
            completionItem.insertText = text;
            if (completionItem.textEdit) {
                completionItem.textEdit.newText = text;
            }
        }
        mergeSimpleInsertTexts(label, existingText, addingText, oneOfSchema) {
            const containsNewLineAfterColon = (value) => {
                return value.includes('\n');
            };
            const startWithNewLine = (value) => {
                return value.startsWith('\n');
            };
            const isNullObject = (value) => {
                const index = value.indexOf('\n');
                return index > 0 && value.substring(index, value.length).trim().length === 0;
            };
            if (containsNewLineAfterColon(existingText) || containsNewLineAfterColon(addingText)) {
                //if the exisiting object null one then replace with the non-null object
                if (oneOfSchema && isNullObject(existingText) && !isNullObject(addingText) && !startWithNewLine(addingText)) {
                    return addingText;
                }
                return undefined;
            }
            const existingValues = this.getValuesFromInsertText(existingText);
            const addingValues = this.getValuesFromInsertText(addingText);
            const newValues = Array.prototype.concat(existingValues, addingValues);
            if (!newValues.length) {
                return undefined;
            }
            else if (newValues.length === 1) {
                return `${label}: \${1:${newValues[0]}}`;
            }
            else {
                return `${label}: \${1|${newValues.join(',')}|}`;
            }
        }
        getValuesFromInsertText(insertText) {
            const value = insertText.substring(insertText.indexOf(':') + 1).trim();
            if (!value) {
                return [];
            }
            const valueMath = value.match(/^\${1[|:]([^|]*)+\|?}$/); // ${1|one,two,three|}  or  ${1:one}
            if (valueMath) {
                return valueMath[1].split(',');
            }
            return [value];
        }
        finalizeParentCompletion(result) {
            const reindexText = (insertTexts) => {
                //modify added props to have unique $x
                let max$index = 0;
                return insertTexts.map((text) => {
                    const match = text.match(/\$([0-9]+)|\${[0-9]+:/g);
                    if (!match) {
                        return text;
                    }
                    const max$indexLocal = match
                        .map((m) => +m.replace(/\${([0-9]+)[:|]/g, '$1').replace('$', '')) // get numbers form $1 or ${1:...}
                        .reduce((p, n) => (n > p ? n : p), 0); // find the max one
                    const reindexedStr = text
                        .replace(/\$([0-9]+)/g, (s, args) => '$' + (+args + max$index)) // increment each by max$index
                        .replace(/\${([0-9]+)[:|]/g, (s, args) => '${' + (+args + max$index) + ':'); // increment each by max$index
                    max$index += max$indexLocal;
                    return reindexedStr;
                });
            };
            result.items.forEach((completionItem) => {
                if (isParentCompletionItem(completionItem)) {
                    const indent = completionItem.parent.indent || '';
                    const reindexedTexts = reindexText(completionItem.parent.insertTexts);
                    // add indent to each object property and join completion item texts
                    let insertText = reindexedTexts.join(`\n${indent}`);
                    // trim $1 from end of completion
                    if (insertText.endsWith('$1')) {
                        insertText = insertText.substring(0, insertText.length - 2);
                    }
                    completionItem.insertText = this.arrayPrefixIndentation + insertText;
                    if (completionItem.textEdit) {
                        completionItem.textEdit.newText = completionItem.insertText;
                    }
                    // remove $x or use {$x:value} in documentation
                    const mdText = insertText.replace(/\${[0-9]+[:|](.*)}/g, (s, arg) => arg).replace(/\$([0-9]+)/g, '');
                    const originalDocumentation = completionItem.documentation ? [completionItem.documentation, '', '----', ''] : [];
                    completionItem.documentation = {
                        kind: vscode_languageserver_types_1.MarkupKind.Markdown,
                        value: [...originalDocumentation, '```yaml', indent + mdText, '```'].join('\n'),
                    };
                    delete completionItem.parent;
                }
            });
        }
        createTempObjNode(currentWord, node, currentDoc) {
            const obj = {};
            obj[currentWord] = null;
            const map = currentDoc.internalDocument.createNode(obj);
            map.range = node.range;
            map.items[0].key.range = node.range;
            map.items[0].value.range = node.range;
            return map;
        }
        addPropertyCompletions(schema, doc, node, originalNode, separatorAfter, collector, textBuffer, overwriteRange, doComplete) {
            const matchingSchemas = doc.getMatchingSchemas(schema.schema, -1, null, doComplete);
            const existingKey = textBuffer.getText(overwriteRange);
            const lineContent = textBuffer.getLineContent(overwriteRange.start.line);
            const hasOnlyWhitespace = lineContent.trim().length === 0;
            const hasColon = lineContent.indexOf(':') !== -1;
            const isInArray = lineContent.trimLeft().indexOf('-') === 0;
            const nodeParent = doc.getParent(node);
            const matchOriginal = matchingSchemas.find((it) => it.node.internalNode === originalNode && it.schema.properties);
            const oneOfSchema = matchingSchemas.filter((schema) => schema.schema.oneOf).map((oneOfSchema) => oneOfSchema.schema.oneOf)[0];
            let didOneOfSchemaMatches = false;
            if (oneOfSchema?.length < matchingSchemas.length) {
                oneOfSchema?.forEach((property, index) => {
                    if (!matchingSchemas[index]?.schema.oneOf && matchingSchemas[index]?.schema.properties === property.properties) {
                        didOneOfSchemaMatches = true;
                    }
                });
            }
            for (const schema of matchingSchemas) {
                if (((schema.node.internalNode === node && !matchOriginal) ||
                    (schema.node.internalNode === originalNode && !hasColon) ||
                    (schema.node.parent?.internalNode === originalNode && !hasColon)) &&
                    !schema.inverted) {
                    this.collectDefaultSnippets(schema.schema, separatorAfter, collector, {
                        newLineFirst: false,
                        indentFirstObject: false,
                        shouldIndentWithTab: isInArray,
                    });
                    const schemaProperties = schema.schema.properties;
                    if (schemaProperties) {
                        const maxProperties = schema.schema.maxProperties;
                        if (maxProperties === undefined ||
                            node.items === undefined ||
                            node.items.length < maxProperties ||
                            (node.items.length === maxProperties && !hasOnlyWhitespace)) {
                            for (const key in schemaProperties) {
                                if (Object.prototype.hasOwnProperty.call(schemaProperties, key)) {
                                    const propertySchema = schemaProperties[key];
                                    if (typeof propertySchema === 'object' && !propertySchema.deprecationMessage && !propertySchema['doNotSuggest']) {
                                        let identCompensation = '';
                                        if (nodeParent && (0, yaml_1.isSeq)(nodeParent) && node.items.length <= 1 && !hasOnlyWhitespace) {
                                            // because there is a slash '-' to prevent the properties generated to have the correct
                                            // indent
                                            const sourceText = textBuffer.getText();
                                            const indexOfSlash = sourceText.lastIndexOf('-', node.range[0] - 1);
                                            if (indexOfSlash >= 0) {
                                                // add one space to compensate the '-'
                                                const overwriteChars = overwriteRange.end.character - overwriteRange.start.character;
                                                identCompensation = ' ' + sourceText.slice(indexOfSlash + 1, node.range[1] - overwriteChars);
                                            }
                                        }
                                        identCompensation += this.arrayPrefixIndentation;
                                        // if check that current node has last pair with "null" value and key witch match key from schema,
                                        // and if schema has array definition it add completion item for array item creation
                                        let pair;
                                        if (propertySchema.type === 'array' &&
                                            (pair = node.items.find((it) => (0, yaml_1.isScalar)(it.key) &&
                                                it.key.range &&
                                                it.key.value === key &&
                                                (0, yaml_1.isScalar)(it.value) &&
                                                !it.value.value &&
                                                textBuffer.getPosition(it.key.range[2]).line === overwriteRange.end.line - 1)) &&
                                            pair) {
                                            if (Array.isArray(propertySchema.items)) {
                                                this.addSchemaValueCompletions(propertySchema.items[0], separatorAfter, collector, {}, 'property');
                                            }
                                            else if (typeof propertySchema.items === 'object' && propertySchema.items.type === 'object') {
                                                this.addArrayItemValueCompletion(propertySchema.items, separatorAfter, collector);
                                            }
                                        }
                                        let insertText = key;
                                        if (!key.startsWith(existingKey) || !hasColon) {
                                            insertText = this.getInsertTextForProperty(key, propertySchema, separatorAfter, identCompensation + this.indentation);
                                        }
                                        const isNodeNull = ((0, yaml_1.isScalar)(originalNode) && originalNode.value === null) ||
                                            ((0, yaml_1.isMap)(originalNode) && originalNode.items.length === 0);
                                        const existsParentCompletion = schema.schema.required?.length > 0;
                                        if (!this.parentSkeletonSelectedFirst || !isNodeNull || !existsParentCompletion) {
                                            collector.add({
                                                kind: vscode_languageserver_types_1.CompletionItemKind.Property,
                                                label: key,
                                                insertText,
                                                insertTextFormat: vscode_languageserver_types_1.InsertTextFormat.Snippet,
                                                documentation: this.fromMarkup(propertySchema.markdownDescription) || propertySchema.description || '',
                                            }, didOneOfSchemaMatches);
                                        }
                                        // if the prop is required add it also to parent suggestion
                                        if (schema.schema.required?.includes(key)) {
                                            collector.add({
                                                label: key,
                                                insertText: this.getInsertTextForProperty(key, propertySchema, separatorAfter, identCompensation + this.indentation),
                                                insertTextFormat: vscode_languageserver_types_1.InsertTextFormat.Snippet,
                                                documentation: this.fromMarkup(propertySchema.markdownDescription) || propertySchema.description || '',
                                                parent: {
                                                    schema: schema.schema,
                                                    indent: identCompensation,
                                                },
                                            });
                                        }
                                    }
                                }
                            }
                        }
                    }
                    // Error fix
                    // If this is a array of string/boolean/number
                    //  test:
                    //    - item1
                    // it will treated as a property key since `:` has been appended
                    if (nodeParent && (0, yaml_1.isSeq)(nodeParent) && (0, schemaUtils_1.isPrimitiveType)(schema.schema)) {
                        this.addSchemaValueCompletions(schema.schema, separatorAfter, collector, {}, 'property', Array.isArray(nodeParent.items));
                    }
                    if (schema.schema.propertyNames && schema.schema.additionalProperties && schema.schema.type === 'object') {
                        const propertyNameSchema = (0, jsonParser07_1.asSchema)(schema.schema.propertyNames);
                        const label = propertyNameSchema.title || 'property';
                        collector.add({
                            kind: vscode_languageserver_types_1.CompletionItemKind.Property,
                            label,
                            insertText: '$' + `{1:${label}}: `,
                            insertTextFormat: vscode_languageserver_types_1.InsertTextFormat.Snippet,
                            documentation: this.fromMarkup(propertyNameSchema.markdownDescription) || propertyNameSchema.description || '',
                        });
                    }
                }
                if (nodeParent && schema.node.internalNode === nodeParent && schema.schema.defaultSnippets) {
                    // For some reason the first item in the array needs to be treated differently, otherwise
                    // the indentation will not be correct
                    if (node.items.length === 1) {
                        this.collectDefaultSnippets(schema.schema, separatorAfter, collector, {
                            newLineFirst: false,
                            indentFirstObject: false,
                            shouldIndentWithTab: true,
                        }, 1);
                    }
                    else {
                        this.collectDefaultSnippets(schema.schema, separatorAfter, collector, {
                            newLineFirst: false,
                            indentFirstObject: true,
                            shouldIndentWithTab: false,
                        }, 1);
                    }
                }
            }
        }
        getValueCompletions(schema, doc, node, offset, document, collector, types, doComplete) {
            let parentKey = null;
            if (node && (0, yaml_1.isScalar)(node)) {
                node = doc.getParent(node);
            }
            if (!node) {
                this.addSchemaValueCompletions(schema.schema, '', collector, types, 'value');
                return;
            }
            if ((0, yaml_1.isPair)(node)) {
                const valueNode = node.value;
                if (valueNode && valueNode.range && offset > valueNode.range[0] + valueNode.range[2]) {
                    return; // we are past the value node
                }
                parentKey = (0, yaml_1.isScalar)(node.key) ? node.key.value + '' : null;
                node = doc.getParent(node);
            }
            if (node && (parentKey !== null || (0, yaml_1.isSeq)(node))) {
                const separatorAfter = '';
                const matchingSchemas = doc.getMatchingSchemas(schema.schema, -1, null, doComplete);
                for (const s of matchingSchemas) {
                    if (s.node.internalNode === node && !s.inverted && s.schema) {
                        if (s.schema.items) {
                            this.collectDefaultSnippets(s.schema, separatorAfter, collector, {
                                newLineFirst: false,
                                indentFirstObject: false,
                                shouldIndentWithTab: false,
                            });
                            if ((0, yaml_1.isSeq)(node) && node.items) {
                                if (Array.isArray(s.schema.items)) {
                                    const index = this.findItemAtOffset(node, document, offset);
                                    if (index < s.schema.items.length) {
                                        this.addSchemaValueCompletions(s.schema.items[index], separatorAfter, collector, types, 'value');
                                    }
                                }
                                else if (typeof s.schema.items === 'object' &&
                                    (s.schema.items.type === 'object' || (0, schemaUtils_1.isAnyOfAllOfOneOfType)(s.schema.items))) {
                                    this.addSchemaValueCompletions(s.schema.items, separatorAfter, collector, types, 'value', true);
                                }
                                else {
                                    this.addSchemaValueCompletions(s.schema.items, separatorAfter, collector, types, 'value');
                                }
                            }
                        }
                        if (s.schema.properties) {
                            const propertySchema = s.schema.properties[parentKey];
                            if (propertySchema) {
                                this.addSchemaValueCompletions(propertySchema, separatorAfter, collector, types, 'value');
                            }
                        }
                        else if (s.schema.additionalProperties) {
                            this.addSchemaValueCompletions(s.schema.additionalProperties, separatorAfter, collector, types, 'value');
                        }
                    }
                }
                if (types['boolean']) {
                    this.addBooleanValueCompletion(true, separatorAfter, collector);
                    this.addBooleanValueCompletion(false, separatorAfter, collector);
                }
                if (types['null']) {
                    this.addNullValueCompletion(separatorAfter, collector);
                }
            }
        }
        addArrayItemValueCompletion(schema, separatorAfter, collector, index) {
            const schemaType = (0, schemaUtils_1.getSchemaTypeName)(schema);
            const insertText = `- ${this.getInsertTextForObject(schema, separatorAfter).insertText.trimLeft()}`;
            //append insertText to documentation
            const schemaTypeTitle = schemaType ? ' type `' + schemaType + '`' : '';
            const schemaDescription = schema.description ? ' (' + schema.description + ')' : '';
            const documentation = this.getDocumentationWithMarkdownText(`Create an item of an array${schemaTypeTitle}${schemaDescription}`, insertText);
            collector.add({
                kind: this.getSuggestionKind(schema.type),
                label: '- (array item) ' + (schemaType || index),
                documentation: documentation,
                insertText: insertText,
                insertTextFormat: vscode_languageserver_types_1.InsertTextFormat.Snippet,
            });
        }
        getInsertTextForProperty(key, propertySchema, separatorAfter, indent = this.indentation) {
            const propertyText = this.getInsertTextForValue(key, '', 'string');
            const resultText = propertyText + ':';
            let value;
            let nValueProposals = 0;
            if (propertySchema) {
                let type = Array.isArray(propertySchema.type) ? propertySchema.type[0] : propertySchema.type;
                if (!type) {
                    if (propertySchema.properties) {
                        type = 'object';
                    }
                    else if (propertySchema.items) {
                        type = 'array';
                    }
                    else if (propertySchema.anyOf) {
                        type = 'anyOf';
                    }
                }
                if (Array.isArray(propertySchema.defaultSnippets)) {
                    if (propertySchema.defaultSnippets.length === 1) {
                        const body = propertySchema.defaultSnippets[0].body;
                        if ((0, objects_1.isDefined)(body)) {
                            value = this.getInsertTextForSnippetValue(body, '', {
                                newLineFirst: true,
                                indentFirstObject: false,
                                shouldIndentWithTab: false,
                            }, [], 1);
                            // add space before default snippet value
                            if (!value.startsWith(' ') && !value.startsWith('\n')) {
                                value = ' ' + value;
                            }
                        }
                    }
                    nValueProposals += propertySchema.defaultSnippets.length;
                }
                if (propertySchema.enum) {
                    if (!value && propertySchema.enum.length === 1) {
                        value = ' ' + this.getInsertTextForGuessedValue(propertySchema.enum[0], '', type);
                    }
                    nValueProposals += propertySchema.enum.length;
                }
                if (propertySchema.const) {
                    if (!value) {
                        value = this.getInsertTextForGuessedValue(propertySchema.const, '', type);
                        value = evaluateTab1Symbol(value); // prevent const being selected after snippet insert
                        value = ' ' + value;
                    }
                    nValueProposals++;
                }
                if ((0, objects_1.isDefined)(propertySchema.default)) {
                    if (!value) {
                        value = ' ' + this.getInsertTextForGuessedValue(propertySchema.default, '', type);
                    }
                    nValueProposals++;
                }
                if (Array.isArray(propertySchema.examples) && propertySchema.examples.length) {
                    if (!value) {
                        value = ' ' + this.getInsertTextForGuessedValue(propertySchema.examples[0], '', type);
                    }
                    nValueProposals += propertySchema.examples.length;
                }
                if (propertySchema.properties) {
                    return `${resultText}\n${this.getInsertTextForObject(propertySchema, separatorAfter, indent).insertText}`;
                }
                else if (propertySchema.items) {
                    return `${resultText}\n${indent}- ${this.getInsertTextForArray(propertySchema.items, separatorAfter, 1, indent).insertText}`;
                }
                if (nValueProposals === 0) {
                    switch (type) {
                        case 'boolean':
                            value = ' $1';
                            break;
                        case 'string':
                            value = ' $1';
                            break;
                        case 'object':
                            value = `\n${indent}`;
                            break;
                        case 'array':
                            value = `\n${indent}- `;
                            break;
                        case 'number':
                        case 'integer':
                            value = ' ${1:0}';
                            break;
                        case 'null':
                            value = ' ${1:null}';
                            break;
                        case 'anyOf':
                            value = ' $1';
                            break;
                        default:
                            return propertyText;
                    }
                }
            }
            if (!value || nValueProposals > 1) {
                value = ' $1';
            }
            return resultText + value + separatorAfter;
        }
        getInsertTextForObject(schema, separatorAfter, indent = this.indentation, insertIndex = 1) {
            let insertText = '';
            if (!schema.properties) {
                insertText = `${indent}$${insertIndex++}\n`;
                return { insertText, insertIndex };
            }
            Object.keys(schema.properties).forEach((key) => {
                const propertySchema = schema.properties[key];
                let type = Array.isArray(propertySchema.type) ? propertySchema.type[0] : propertySchema.type;
                if (!type) {
                    if (propertySchema.anyOf) {
                        type = 'anyOf';
                    }
                    if (propertySchema.properties) {
                        type = 'object';
                    }
                    if (propertySchema.items) {
                        type = 'array';
                    }
                }
                if (schema.required && schema.required.indexOf(key) > -1) {
                    switch (type) {
                        case 'boolean':
                        case 'string':
                        case 'number':
                        case 'integer':
                        case 'anyOf': {
                            let value = propertySchema.default || propertySchema.const;
                            if (value) {
                                if (type === 'string') {
                                    value = convertToStringValue(value);
                                }
                                insertText += `${indent}${key}: \${${insertIndex++}:${value}}\n`;
                            }
                            else {
                                insertText += `${indent}${key}: $${insertIndex++}\n`;
                            }
                            break;
                        }
                        case 'array':
                            {
                                const arrayInsertResult = this.getInsertTextForArray(propertySchema.items, separatorAfter, insertIndex++, indent);
                                const arrayInsertLines = arrayInsertResult.insertText.split('\n');
                                let arrayTemplate = arrayInsertResult.insertText;
                                if (arrayInsertLines.length > 1) {
                                    for (let index = 1; index < arrayInsertLines.length; index++) {
                                        const element = arrayInsertLines[index];
                                        arrayInsertLines[index] = `  ${element}`;
                                    }
                                    arrayTemplate = arrayInsertLines.join('\n');
                                }
                                insertIndex = arrayInsertResult.insertIndex;
                                insertText += `${indent}${key}:\n${indent}${this.indentation}- ${arrayTemplate}\n`;
                            }
                            break;
                        case 'object':
                            {
                                const objectInsertResult = this.getInsertTextForObject(propertySchema, separatorAfter, `${indent}${this.indentation}`, insertIndex++);
                                insertIndex = objectInsertResult.insertIndex;
                                insertText += `${indent}${key}:\n${objectInsertResult.insertText}\n`;
                            }
                            break;
                    }
                }
                else if (!this.disableDefaultProperties && propertySchema.default !== undefined) {
                    switch (type) {
                        case 'boolean':
                        case 'number':
                        case 'integer':
                            insertText += `${indent}${
                            //added quote if key is null
                            key === 'null' ? this.getInsertTextForValue(key, '', 'string') : key}: \${${insertIndex++}:${propertySchema.default}}\n`;
                            break;
                        case 'string':
                            insertText += `${indent}${key}: \${${insertIndex++}:${convertToStringValue(propertySchema.default)}}\n`;
                            break;
                        case 'array':
                        case 'object':
                            // TODO: support default value for array object
                            break;
                    }
                }
            });
            if (insertText.trim().length === 0) {
                insertText = `${indent}$${insertIndex++}\n`;
            }
            insertText = insertText.trimRight() + separatorAfter;
            return { insertText, insertIndex };
        }
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        getInsertTextForArray(schema, separatorAfter, insertIndex = 1, indent = this.indentation) {
            let insertText = '';
            if (!schema) {
                insertText = `$${insertIndex++}`;
                return { insertText, insertIndex };
            }
            let type = Array.isArray(schema.type) ? schema.type[0] : schema.type;
            if (!type) {
                if (schema.properties) {
                    type = 'object';
                }
                if (schema.items) {
                    type = 'array';
                }
            }
            switch (schema.type) {
                case 'boolean':
                    insertText = `\${${insertIndex++}:false}`;
                    break;
                case 'number':
                case 'integer':
                    insertText = `\${${insertIndex++}:0}`;
                    break;
                case 'string':
                    insertText = `\${${insertIndex++}:""}`;
                    break;
                case 'object':
                    {
                        const objectInsertResult = this.getInsertTextForObject(schema, separatorAfter, `${indent}  `, insertIndex++);
                        insertText = objectInsertResult.insertText.trimLeft();
                        insertIndex = objectInsertResult.insertIndex;
                    }
                    break;
            }
            return { insertText, insertIndex };
        }
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        getInsertTextForGuessedValue(value, separatorAfter, type) {
            switch (typeof value) {
                case 'object':
                    if (value === null) {
                        return '${1:null}' + separatorAfter;
                    }
                    return this.getInsertTextForValue(value, separatorAfter, type);
                case 'string': {
                    let snippetValue = JSON.stringify(value);
                    snippetValue = snippetValue.substr(1, snippetValue.length - 2); // remove quotes
                    snippetValue = this.getInsertTextForPlainText(snippetValue); // escape \ and }
                    if (type === 'string') {
                        snippetValue = convertToStringValue(snippetValue);
                    }
                    return '${1:' + snippetValue + '}' + separatorAfter;
                }
                case 'number':
                case 'boolean':
                    return '${1:' + value + '}' + separatorAfter;
            }
            return this.getInsertTextForValue(value, separatorAfter, type);
        }
        getInsertTextForPlainText(text) {
            return text.replace(/[\\$}]/g, '\\$&'); // escape $, \ and }
        }
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        getInsertTextForValue(value, separatorAfter, type) {
            if (value === null) {
                return 'null'; // replace type null with string 'null'
            }
            switch (typeof value) {
                case 'object': {
                    const indent = this.indentation;
                    return this.getInsertTemplateForValue(value, indent, { index: 1 }, separatorAfter);
                }
                case 'number':
                case 'boolean':
                    return this.getInsertTextForPlainText(value + separatorAfter);
            }
            type = Array.isArray(type) ? type[0] : type;
            if (type === 'string') {
                value = convertToStringValue(value);
            }
            return this.getInsertTextForPlainText(value + separatorAfter);
        }
        getInsertTemplateForValue(value, indent, navOrder, separatorAfter) {
            if (Array.isArray(value)) {
                let insertText = '\n';
                for (const arrValue of value) {
                    insertText += `${indent}- \${${navOrder.index++}:${arrValue}}\n`;
                }
                return insertText;
            }
            else if (typeof value === 'object') {
                let insertText = '\n';
                for (const key in value) {
                    if (Object.prototype.hasOwnProperty.call(value, key)) {
                        const element = value[key];
                        insertText += `${indent}\${${navOrder.index++}:${key}}:`;
                        let valueTemplate;
                        if (typeof element === 'object') {
                            valueTemplate = `${this.getInsertTemplateForValue(element, indent + this.indentation, navOrder, separatorAfter)}`;
                        }
                        else {
                            valueTemplate = ` \${${navOrder.index++}:${this.getInsertTextForPlainText(element + separatorAfter)}}\n`;
                        }
                        insertText += `${valueTemplate}`;
                    }
                }
                return insertText;
            }
            return this.getInsertTextForPlainText(value + separatorAfter);
        }
        addSchemaValueCompletions(schema, separatorAfter, collector, types, completionType, isArray) {
            if (typeof schema === 'object') {
                this.addEnumValueCompletions(schema, separatorAfter, collector, isArray);
                this.addDefaultValueCompletions(schema, separatorAfter, collector);
                this.collectTypes(schema, types);
                if (isArray && completionType === 'value' && !(0, schemaUtils_1.isAnyOfAllOfOneOfType)(schema)) {
                    // add array only for final types (no anyOf, allOf, oneOf)
                    this.addArrayItemValueCompletion(schema, separatorAfter, collector);
                }
                if (Array.isArray(schema.allOf)) {
                    schema.allOf.forEach((s) => {
                        return this.addSchemaValueCompletions(s, separatorAfter, collector, types, completionType, isArray);
                    });
                }
                if (Array.isArray(schema.anyOf)) {
                    schema.anyOf.forEach((s) => {
                        return this.addSchemaValueCompletions(s, separatorAfter, collector, types, completionType, isArray);
                    });
                }
                if (Array.isArray(schema.oneOf)) {
                    schema.oneOf.forEach((s) => {
                        return this.addSchemaValueCompletions(s, separatorAfter, collector, types, completionType, isArray);
                    });
                }
            }
        }
        collectTypes(schema, types) {
            if (Array.isArray(schema.enum) || (0, objects_1.isDefined)(schema.const)) {
                return;
            }
            const type = schema.type;
            if (Array.isArray(type)) {
                type.forEach(function (t) {
                    return (types[t] = true);
                });
            }
            else if (type) {
                types[type] = true;
            }
        }
        addDefaultValueCompletions(schema, separatorAfter, collector, arrayDepth = 0) {
            let hasProposals = false;
            if ((0, objects_1.isDefined)(schema.default)) {
                let type = schema.type;
                let value = schema.default;
                for (let i = arrayDepth; i > 0; i--) {
                    value = [value];
                    type = 'array';
                }
                let label;
                if (typeof value == 'object') {
                    label = 'Default value';
                }
                else {
                    label = value.toString().replace(doubleQuotesEscapeRegExp, '"');
                }
                collector.add({
                    kind: this.getSuggestionKind(type),
                    label,
                    insertText: this.getInsertTextForValue(value, separatorAfter, type),
                    insertTextFormat: vscode_languageserver_types_1.InsertTextFormat.Snippet,
                    detail: localize('json.suggest.default', 'Default value'),
                });
                hasProposals = true;
            }
            if (Array.isArray(schema.examples)) {
                schema.examples.forEach((example) => {
                    let type = schema.type;
                    let value = example;
                    for (let i = arrayDepth; i > 0; i--) {
                        value = [value];
                        type = 'array';
                    }
                    collector.add({
                        kind: this.getSuggestionKind(type),
                        label: this.getLabelForValue(value),
                        insertText: this.getInsertTextForValue(value, separatorAfter, type),
                        insertTextFormat: vscode_languageserver_types_1.InsertTextFormat.Snippet,
                    });
                    hasProposals = true;
                });
            }
            this.collectDefaultSnippets(schema, separatorAfter, collector, {
                newLineFirst: true,
                indentFirstObject: true,
                shouldIndentWithTab: true,
            });
            if (!hasProposals && typeof schema.items === 'object' && !Array.isArray(schema.items)) {
                this.addDefaultValueCompletions(schema.items, separatorAfter, collector, arrayDepth + 1);
            }
        }
        addEnumValueCompletions(schema, separatorAfter, collector, isArray) {
            if ((0, objects_1.isDefined)(schema.const) && !isArray) {
                collector.add({
                    kind: this.getSuggestionKind(schema.type),
                    label: this.getLabelForValue(schema.const),
                    insertText: this.getInsertTextForValue(schema.const, separatorAfter, schema.type),
                    insertTextFormat: vscode_languageserver_types_1.InsertTextFormat.Snippet,
                    documentation: this.fromMarkup(schema.markdownDescription) || schema.description,
                });
            }
            if (Array.isArray(schema.enum)) {
                for (let i = 0, length = schema.enum.length; i < length; i++) {
                    const enm = schema.enum[i];
                    let documentation = this.fromMarkup(schema.markdownDescription) || schema.description;
                    if (schema.markdownEnumDescriptions && i < schema.markdownEnumDescriptions.length && this.doesSupportMarkdown()) {
                        documentation = this.fromMarkup(schema.markdownEnumDescriptions[i]);
                    }
                    else if (schema.enumDescriptions && i < schema.enumDescriptions.length) {
                        documentation = schema.enumDescriptions[i];
                    }
                    collector.add({
                        kind: this.getSuggestionKind(schema.type),
                        label: this.getLabelForValue(enm),
                        insertText: this.getInsertTextForValue(enm, separatorAfter, schema.type),
                        insertTextFormat: vscode_languageserver_types_1.InsertTextFormat.Snippet,
                        documentation: documentation,
                    });
                }
            }
        }
        getLabelForValue(value) {
            if (value === null) {
                return 'null'; // return string with 'null' value if schema contains null as possible value
            }
            if (Array.isArray(value)) {
                return JSON.stringify(value);
            }
            return '' + value;
        }
        collectDefaultSnippets(schema, separatorAfter, collector, settings, arrayDepth = 0) {
            if (Array.isArray(schema.defaultSnippets)) {
                for (const s of schema.defaultSnippets) {
                    let type = schema.type;
                    let value = s.body;
                    let label = s.label;
                    let insertText;
                    let filterText;
                    if ((0, objects_1.isDefined)(value)) {
                        const type = s.type || schema.type;
                        if (arrayDepth === 0 && type === 'array') {
                            // We know that a - isn't present yet so we need to add one
                            const fixedObj = {};
                            Object.keys(value).forEach((val, index) => {
                                if (index === 0 && !val.startsWith('-')) {
                                    fixedObj[`- ${val}`] = value[val];
                                }
                                else {
                                    fixedObj[`  ${val}`] = value[val];
                                }
                            });
                            value = fixedObj;
                        }
                        const existingProps = Object.keys(collector.proposed).filter((proposedProp) => collector.proposed[proposedProp].label === existingProposeItem);
                        insertText = this.getInsertTextForSnippetValue(value, separatorAfter, settings, existingProps);
                        // if snippet result is empty and value has a real value, don't add it as a completion
                        if (insertText === '' && value) {
                            continue;
                        }
                        label = label || this.getLabelForSnippetValue(value);
                    }
                    else if (typeof s.bodyText === 'string') {
                        let prefix = '', suffix = '', indent = '';
                        for (let i = arrayDepth; i > 0; i--) {
                            prefix = prefix + indent + '[\n';
                            suffix = suffix + '\n' + indent + ']';
                            indent += this.indentation;
                            type = 'array';
                        }
                        insertText = prefix + indent + s.bodyText.split('\n').join('\n' + indent) + suffix + separatorAfter;
                        label = label || insertText;
                        filterText = insertText.replace(/[\n]/g, ''); // remove new lines
                    }
                    collector.add({
                        kind: s.suggestionKind || this.getSuggestionKind(type),
                        label,
                        sortText: s.sortText || s.label,
                        documentation: this.fromMarkup(s.markdownDescription) || s.description,
                        insertText,
                        insertTextFormat: vscode_languageserver_types_1.InsertTextFormat.Snippet,
                        filterText,
                    });
                }
            }
        }
        getInsertTextForSnippetValue(value, separatorAfter, settings, existingProps, depth) {
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            const replacer = (value) => {
                if (typeof value === 'string') {
                    if (value[0] === '^') {
                        return value.substr(1);
                    }
                    if (value === 'true' || value === 'false') {
                        return `"${value}"`;
                    }
                }
                return value;
            };
            return ((0, json_1.stringifyObject)(value, '', replacer, { ...settings, indentation: this.indentation, existingProps }, depth) + separatorAfter);
        }
        addBooleanValueCompletion(value, separatorAfter, collector) {
            collector.add({
                kind: this.getSuggestionKind('boolean'),
                label: value ? 'true' : 'false',
                insertText: this.getInsertTextForValue(value, separatorAfter, 'boolean'),
                insertTextFormat: vscode_languageserver_types_1.InsertTextFormat.Snippet,
                documentation: '',
            });
        }
        addNullValueCompletion(separatorAfter, collector) {
            collector.add({
                kind: this.getSuggestionKind('null'),
                label: 'null',
                insertText: 'null' + separatorAfter,
                insertTextFormat: vscode_languageserver_types_1.InsertTextFormat.Snippet,
                documentation: '',
            });
        }
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        getLabelForSnippetValue(value) {
            const label = JSON.stringify(value);
            return label.replace(/\$\{\d+:([^}]+)\}|\$\d+/g, '$1');
        }
        getCustomTagValueCompletions(collector) {
            const validCustomTags = (0, arrUtils_1.filterInvalidCustomTags)(this.customTags);
            validCustomTags.forEach((validTag) => {
                // Valid custom tags are guarenteed to be strings
                const label = validTag.split(' ')[0];
                this.addCustomTagValueCompletion(collector, ' ', label);
            });
        }
        addCustomTagValueCompletion(collector, separatorAfter, label) {
            collector.add({
                kind: this.getSuggestionKind('string'),
                label: label,
                insertText: label + separatorAfter,
                insertTextFormat: vscode_languageserver_types_1.InsertTextFormat.Snippet,
                documentation: '',
            });
        }
        getDocumentationWithMarkdownText(documentation, insertText) {
            let res = documentation;
            if (this.doesSupportMarkdown()) {
                insertText = insertText
                    .replace(/\${[0-9]+[:|](.*)}/g, (s, arg) => {
                    return arg;
                })
                    .replace(/\$([0-9]+)/g, '');
                res = this.fromMarkup(`${documentation}\n \`\`\`\n${insertText}\n\`\`\``);
            }
            return res;
        }
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        getSuggestionKind(type) {
            if (Array.isArray(type)) {
                // eslint-disable-next-line @typescript-eslint/no-explicit-any
                const array = type;
                type = array.length > 0 ? array[0] : null;
            }
            if (!type) {
                return vscode_languageserver_types_1.CompletionItemKind.Value;
            }
            switch (type) {
                case 'string':
                    return vscode_languageserver_types_1.CompletionItemKind.Value;
                case 'object':
                    return vscode_languageserver_types_1.CompletionItemKind.Module;
                case 'property':
                    return vscode_languageserver_types_1.CompletionItemKind.Property;
                default:
                    return vscode_languageserver_types_1.CompletionItemKind.Value;
            }
        }
        getCurrentWord(doc, offset) {
            let i = offset - 1;
            const text = doc.getText();
            while (i >= 0 && ' \t\n\r\v":{[,]}'.indexOf(text.charAt(i)) === -1) {
                i--;
            }
            return text.substring(i + 1, offset);
        }
        fromMarkup(markupString) {
            if (markupString && this.doesSupportMarkdown()) {
                return {
                    kind: vscode_languageserver_types_1.MarkupKind.Markdown,
                    value: markupString,
                };
            }
            return undefined;
        }
        doesSupportMarkdown() {
            if (this.supportsMarkdown === undefined) {
                const completion = this.clientCapabilities.textDocument && this.clientCapabilities.textDocument.completion;
                this.supportsMarkdown =
                    completion &&
                        completion.completionItem &&
                        Array.isArray(completion.completionItem.documentationFormat) &&
                        completion.completionItem.documentationFormat.indexOf(vscode_languageserver_types_1.MarkupKind.Markdown) !== -1;
            }
            return this.supportsMarkdown;
        }
        findItemAtOffset(seqNode, doc, offset) {
            for (let i = seqNode.items.length - 1; i >= 0; i--) {
                const node = seqNode.items[i];
                if ((0, yaml_1.isNode)(node)) {
                    if (node.range) {
                        if (offset > node.range[1]) {
                            return i;
                        }
                        else if (offset >= node.range[0]) {
                            return i;
                        }
                    }
                }
            }
            return 0;
        }
    }
    exports.YamlCompletion = YamlCompletion;
    const isNumberExp = /^\d+$/;
    function convertToStringValue(param) {
        let value;
        if (typeof param === 'string') {
            value = param;
        }
        else {
            value = '' + param;
        }
        if (value.length === 0) {
            return value;
        }
        if (value === 'true' || value === 'false' || value === 'null' || isNumberExp.test(value)) {
            return `"${value}"`;
        }
        if (value.indexOf('"') !== -1) {
            value = value.replace(doubleQuotesEscapeRegExp, '"');
        }
        let doQuote = !isNaN(parseInt(value)) || value.charAt(0) === '@';
        if (!doQuote) {
            // need to quote value if in `foo: bar`, `foo : bar` (mapping) or `foo:` (partial map) format
            // but `foo:bar` and `:bar` (colon without white-space after it) are just plain string
            let idx = value.indexOf(':', 0);
            for (; idx > 0 && idx < value.length; idx = value.indexOf(':', idx + 1)) {
                if (idx === value.length - 1) {
                    // `foo:` (partial map) format
                    doQuote = true;
                    break;
                }
                // there are only two valid kinds of white-space in yaml: space or tab
                // ref: https://yaml.org/spec/1.2.1/#id2775170
                const nextChar = value.charAt(idx + 1);
                if (nextChar === '\t' || nextChar === ' ') {
                    doQuote = true;
                    break;
                }
            }
        }
        if (doQuote) {
            value = `"${value}"`;
        }
        return value;
    }
    /**
     * simplify `{$1:value}` to `value`
     */
    function evaluateTab1Symbol(value) {
        return value.replace(/\$\{1:(.*)\}/, '$1');
    }
    function isParentCompletionItem(item) {
        return 'parent' in item;
    }
});
//# sourceMappingURL=yamlCompletion.js.map