"use strict";
/*
 * Copyright (C) 2018 TypeFox and others.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.isValidFunctionCompletionContext = exports.asResolvedCompletionItem = exports.asCompletionItem = void 0;
const lsp = __importStar(require("vscode-languageserver/node"));
const tsp_command_types_1 = require("./tsp-command-types");
const protocol_translation_1 = require("./protocol-translation");
const commands_1 = require("./commands");
const ts_protocol_1 = require("./ts-protocol");
const SnippetString_1 = __importDefault(require("./utils/SnippetString"));
const typeConverters = __importStar(require("./utils/typeConverters"));
function asCompletionItem(entry, file, position, document) {
    const item = {
        label: entry.name,
        kind: asCompletionItemKind(entry.kind),
        sortText: entry.sortText,
        commitCharacters: asCommitCharacters(entry.kind),
        preselect: entry.isRecommended,
        data: {
            file,
            line: position.line + 1,
            offset: position.character + 1,
            entryNames: [
                entry.source || entry.data ? {
                    name: entry.name,
                    source: entry.source,
                    data: entry.data
                } : entry.name
            ]
        }
    };
    if (entry.source && entry.hasAction) {
        // De-prioritze auto-imports
        // https://github.com/Microsoft/vscode/issues/40311
        item.sortText = '\uffff' + entry.sortText;
    }
    const { sourceDisplay, isSnippet } = entry;
    if (sourceDisplay) {
        item.detail = (0, protocol_translation_1.asPlainText)(sourceDisplay);
    }
    if (entry.isImportStatementCompletion || isSnippet || item.kind === lsp.CompletionItemKind.Function || item.kind === lsp.CompletionItemKind.Method) {
        item.insertTextFormat = lsp.InsertTextFormat.Snippet;
    }
    let insertText = entry.insertText;
    let replacementRange = entry.replacementSpan && (0, protocol_translation_1.asRange)(entry.replacementSpan);
    // Make sure we only replace a single line at most
    if (replacementRange && replacementRange.start.line !== replacementRange.end.line) {
        replacementRange = lsp.Range.create(replacementRange.start, document.getLineEnd(replacementRange.start.line));
    }
    if (insertText && replacementRange && insertText[0] === '[') { // o.x -> o['x']
        item.filterText = '.' + item.label;
    }
    if (entry.kindModifiers) {
        const kindModifiers = new Set(entry.kindModifiers.split(/,|\s+/g));
        if (kindModifiers.has(tsp_command_types_1.KindModifiers.optional)) {
            if (!insertText) {
                insertText = item.label;
            }
            if (!item.filterText) {
                item.filterText = item.label;
            }
            item.label += '?';
        }
        if (kindModifiers.has(tsp_command_types_1.KindModifiers.deprecated)) {
            item.tags = [lsp.CompletionItemTag.Deprecated];
        }
        if (kindModifiers.has(tsp_command_types_1.KindModifiers.color)) {
            item.kind = lsp.CompletionItemKind.Color;
        }
        if (entry.kind === tsp_command_types_1.ScriptElementKind.scriptElement) {
            for (const extModifier of tsp_command_types_1.KindModifiers.fileExtensionKindModifiers) {
                if (kindModifiers.has(extModifier)) {
                    if (entry.name.toLowerCase().endsWith(extModifier)) {
                        item.detail = entry.name;
                    }
                    else {
                        item.detail = entry.name + extModifier;
                    }
                    break;
                }
            }
        }
    }
    if (insertText && replacementRange) {
        item.textEdit = lsp.TextEdit.replace(replacementRange, insertText);
    }
    else {
        item.insertText = insertText;
    }
    return item;
}
exports.asCompletionItem = asCompletionItem;
function asCompletionItemKind(kind) {
    switch (kind) {
        case tsp_command_types_1.ScriptElementKind.primitiveType:
        case tsp_command_types_1.ScriptElementKind.keyword:
            return lsp.CompletionItemKind.Keyword;
        case tsp_command_types_1.ScriptElementKind.constElement:
            return lsp.CompletionItemKind.Constant;
        case tsp_command_types_1.ScriptElementKind.letElement:
        case tsp_command_types_1.ScriptElementKind.variableElement:
        case tsp_command_types_1.ScriptElementKind.localVariableElement:
        case tsp_command_types_1.ScriptElementKind.alias:
            return lsp.CompletionItemKind.Variable;
        case tsp_command_types_1.ScriptElementKind.memberVariableElement:
        case tsp_command_types_1.ScriptElementKind.memberGetAccessorElement:
        case tsp_command_types_1.ScriptElementKind.memberSetAccessorElement:
            return lsp.CompletionItemKind.Field;
        case tsp_command_types_1.ScriptElementKind.functionElement:
            return lsp.CompletionItemKind.Function;
        case tsp_command_types_1.ScriptElementKind.memberFunctionElement:
        case tsp_command_types_1.ScriptElementKind.constructSignatureElement:
        case tsp_command_types_1.ScriptElementKind.callSignatureElement:
        case tsp_command_types_1.ScriptElementKind.indexSignatureElement:
            return lsp.CompletionItemKind.Method;
        case tsp_command_types_1.ScriptElementKind.enumElement:
            return lsp.CompletionItemKind.Enum;
        case tsp_command_types_1.ScriptElementKind.moduleElement:
        case tsp_command_types_1.ScriptElementKind.externalModuleName:
            return lsp.CompletionItemKind.Module;
        case tsp_command_types_1.ScriptElementKind.classElement:
        case tsp_command_types_1.ScriptElementKind.typeElement:
            return lsp.CompletionItemKind.Class;
        case tsp_command_types_1.ScriptElementKind.interfaceElement:
            return lsp.CompletionItemKind.Interface;
        case tsp_command_types_1.ScriptElementKind.warning:
        case tsp_command_types_1.ScriptElementKind.scriptElement:
            return lsp.CompletionItemKind.File;
        case tsp_command_types_1.ScriptElementKind.directory:
            return lsp.CompletionItemKind.Folder;
        case tsp_command_types_1.ScriptElementKind.string:
            return lsp.CompletionItemKind.Constant;
    }
    return lsp.CompletionItemKind.Property;
}
function asCommitCharacters(kind) {
    const commitCharacters = [];
    switch (kind) {
        case tsp_command_types_1.ScriptElementKind.memberGetAccessorElement:
        case tsp_command_types_1.ScriptElementKind.memberSetAccessorElement:
        case tsp_command_types_1.ScriptElementKind.constructSignatureElement:
        case tsp_command_types_1.ScriptElementKind.callSignatureElement:
        case tsp_command_types_1.ScriptElementKind.indexSignatureElement:
        case tsp_command_types_1.ScriptElementKind.enumElement:
        case tsp_command_types_1.ScriptElementKind.interfaceElement:
            commitCharacters.push('.');
            break;
        case tsp_command_types_1.ScriptElementKind.moduleElement:
        case tsp_command_types_1.ScriptElementKind.alias:
        case tsp_command_types_1.ScriptElementKind.constElement:
        case tsp_command_types_1.ScriptElementKind.letElement:
        case tsp_command_types_1.ScriptElementKind.variableElement:
        case tsp_command_types_1.ScriptElementKind.localVariableElement:
        case tsp_command_types_1.ScriptElementKind.memberVariableElement:
        case tsp_command_types_1.ScriptElementKind.classElement:
        case tsp_command_types_1.ScriptElementKind.functionElement:
        case tsp_command_types_1.ScriptElementKind.memberFunctionElement:
            commitCharacters.push('.', ',');
            commitCharacters.push('(');
            break;
    }
    return commitCharacters.length === 0 ? undefined : commitCharacters;
}
function asResolvedCompletionItem(item, details, client, options) {
    var _a;
    return __awaiter(this, void 0, void 0, function* () {
        item.detail = asDetail(details);
        item.documentation = (0, protocol_translation_1.asDocumentation)(details);
        const filepath = (0, protocol_translation_1.normalizePath)(item.data.file);
        if ((_a = details.codeActions) === null || _a === void 0 ? void 0 : _a.length) {
            item.additionalTextEdits = asAdditionalTextEdits(details.codeActions, filepath);
            item.command = asCommand(details.codeActions, item.data.file);
        }
        if (options.completeFunctionCalls && item.insertTextFormat === lsp.InsertTextFormat.Snippet
            && (item.kind === lsp.CompletionItemKind.Function || item.kind === lsp.CompletionItemKind.Method)) {
            const { line, offset } = item.data;
            const position = typeConverters.Position.fromLocation({ line, offset });
            const shouldCompleteFunction = yield isValidFunctionCompletionContext(filepath, position, client);
            if (shouldCompleteFunction) {
                createSnippetOfFunctionCall(item, details);
            }
        }
        return item;
    });
}
exports.asResolvedCompletionItem = asResolvedCompletionItem;
function isValidFunctionCompletionContext(filepath, position, client) {
    return __awaiter(this, void 0, void 0, function* () {
        // Workaround for https://github.com/Microsoft/TypeScript/issues/12677
        // Don't complete function calls inside of destructive assigments or imports
        try {
            const args = typeConverters.Position.toFileLocationRequestArgs(filepath, position);
            const response = yield client.request("quickinfo" /* Quickinfo */, args);
            if (response.type !== 'response') {
                return true;
            }
            const { body } = response;
            switch (body === null || body === void 0 ? void 0 : body.kind) {
                case 'var':
                case 'let':
                case 'const':
                case 'alias':
                    return false;
                default:
                    return true;
            }
        }
        catch (_a) {
            return true;
        }
    });
}
exports.isValidFunctionCompletionContext = isValidFunctionCompletionContext;
function createSnippetOfFunctionCall(item, detail) {
    const { displayParts } = detail;
    const parameterListParts = getParameterListParts(displayParts);
    const snippet = new SnippetString_1.default();
    snippet.appendText(`${item.insertText || item.label}(`);
    appendJoinedPlaceholders(snippet, parameterListParts.parts, ', ');
    if (parameterListParts.hasOptionalParameters) {
        snippet.appendTabstop();
    }
    snippet.appendText(')');
    snippet.appendTabstop(0);
    item.insertText = snippet.value;
}
function getParameterListParts(displayParts) {
    const parts = [];
    let isInMethod = false;
    let hasOptionalParameters = false;
    let parenCount = 0;
    let braceCount = 0;
    outer: for (let i = 0; i < displayParts.length; ++i) {
        const part = displayParts[i];
        switch (part.kind) {
            case ts_protocol_1.DisplayPartKind.methodName:
            case ts_protocol_1.DisplayPartKind.functionName:
            case ts_protocol_1.DisplayPartKind.text:
            case ts_protocol_1.DisplayPartKind.propertyName:
                if (parenCount === 0 && braceCount === 0) {
                    isInMethod = true;
                }
                break;
            case ts_protocol_1.DisplayPartKind.parameterName:
                if (parenCount === 1 && braceCount === 0 && isInMethod) {
                    // Only take top level paren names
                    const next = displayParts[i + 1];
                    // Skip optional parameters
                    const nameIsFollowedByOptionalIndicator = next && next.text === '?';
                    // Skip this parameter
                    const nameIsThis = part.text === 'this';
                    if (!nameIsFollowedByOptionalIndicator && !nameIsThis) {
                        parts.push(part);
                    }
                    hasOptionalParameters = hasOptionalParameters || nameIsFollowedByOptionalIndicator;
                }
                break;
            case ts_protocol_1.DisplayPartKind.punctuation:
                if (part.text === '(') {
                    ++parenCount;
                }
                else if (part.text === ')') {
                    --parenCount;
                    if (parenCount <= 0 && isInMethod) {
                        break outer;
                    }
                }
                else if (part.text === '...' && parenCount === 1) {
                    // Found rest parmeter. Do not fill in any further arguments
                    hasOptionalParameters = true;
                    break outer;
                }
                else if (part.text === '{') {
                    ++braceCount;
                }
                else if (part.text === '}') {
                    --braceCount;
                }
                break;
        }
    }
    return { hasOptionalParameters, parts };
}
function appendJoinedPlaceholders(snippet, parts, joiner) {
    for (let i = 0; i < parts.length; ++i) {
        const paramterPart = parts[i];
        snippet.appendPlaceholder(paramterPart.text);
        if (i !== parts.length - 1) {
            snippet.appendText(joiner);
        }
    }
}
function asAdditionalTextEdits(codeActions, filepath) {
    // Try to extract out the additionalTextEdits for the current file.
    const additionalTextEdits = [];
    for (const tsAction of codeActions) {
        // Apply all edits in the current file using `additionalTextEdits`
        if (tsAction.changes) {
            for (const change of tsAction.changes) {
                if (change.fileName === filepath) {
                    for (const textChange of change.textChanges) {
                        additionalTextEdits.push((0, protocol_translation_1.toTextEdit)(textChange));
                    }
                }
            }
        }
    }
    return additionalTextEdits.length ? additionalTextEdits : undefined;
}
function asCommand(codeActions, filepath) {
    let hasRemainingCommandsOrEdits = false;
    for (const tsAction of codeActions) {
        if (tsAction.commands) {
            hasRemainingCommandsOrEdits = true;
            break;
        }
        if (tsAction.changes) {
            for (const change of tsAction.changes) {
                if (change.fileName !== filepath) {
                    hasRemainingCommandsOrEdits = true;
                    break;
                }
            }
        }
    }
    if (hasRemainingCommandsOrEdits) {
        // Create command that applies all edits not in the current file.
        return {
            title: '',
            command: commands_1.Commands.APPLY_COMPLETION_CODE_ACTION,
            arguments: [filepath, codeActions.map(codeAction => ({
                    commands: codeAction.commands,
                    description: codeAction.description,
                    changes: codeAction.changes.filter(x => x.fileName !== filepath)
                }))]
        };
    }
}
function asDetail({ displayParts, sourceDisplay, source: deprecatedSource }) {
    const result = [];
    const source = sourceDisplay || deprecatedSource;
    if (source) {
        result.push(`Auto import from '${(0, protocol_translation_1.asPlainText)(source)}'`);
    }
    const detail = (0, protocol_translation_1.asPlainText)(displayParts);
    if (detail) {
        result.push(detail);
    }
    return result.join('\n');
}
//# sourceMappingURL=completion.js.map