"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CompletionsProviderImpl = void 0;
const path_1 = require("path");
const typescript_1 = __importDefault(require("typescript"));
const vscode_languageserver_1 = require("vscode-languageserver");
const documents_1 = require("../../../lib/documents");
const parseHtml_1 = require("../../../lib/documents/parseHtml");
const utils_1 = require("../../../utils");
const previewer_1 = require("../previewer");
const utils_2 = require("../utils");
const getJsDocTemplateCompletion_1 = require("./getJsDocTemplateCompletion");
const utils_3 = require("./utils");
const svelte_ast_utils_1 = require("../svelte-ast-utils");
class CompletionsProviderImpl {
    constructor(lsAndTsDocResolver, configManager) {
        this.lsAndTsDocResolver = lsAndTsDocResolver;
        this.configManager = configManager;
        /**
         * The language service throws an error if the character is not a valid trigger character.
         * Also, the completions are worse.
         * Therefore, only use the characters the typescript compiler treats as valid.
         */
        this.validTriggerCharacters = ['.', '"', "'", '`', '/', '@', '<', '#'];
        this.commitCharacters = ['.', ',', ';', '('];
    }
    isValidTriggerCharacter(character) {
        return this.validTriggerCharacters.includes(character);
    }
    async getCompletions(document, position, completionContext, cancellationToken) {
        var _a, _b, _c, _d, _e, _f, _g;
        if ((0, documents_1.isInTag)(position, document.styleInfo)) {
            return null;
        }
        const { lang, tsDoc, userPreferences } = await this.lsAndTsDocResolver.getLSAndTSDoc(document);
        const filePath = tsDoc.filePath;
        if (!filePath) {
            return null;
        }
        const triggerCharacter = completionContext === null || completionContext === void 0 ? void 0 : completionContext.triggerCharacter;
        const triggerKind = completionContext === null || completionContext === void 0 ? void 0 : completionContext.triggerKind;
        const validTriggerCharacter = this.isValidTriggerCharacter(triggerCharacter)
            ? triggerCharacter
            : undefined;
        const isCustomTriggerCharacter = triggerKind === vscode_languageserver_1.CompletionTriggerKind.TriggerCharacter;
        const isJsDocTriggerCharacter = triggerCharacter === '*';
        const isEventOrSlotLetTriggerCharacter = triggerCharacter === ':';
        // ignore any custom trigger character specified in server capabilities
        //  and is not allow by ts
        if (isCustomTriggerCharacter &&
            !validTriggerCharacter &&
            !isJsDocTriggerCharacter &&
            !isEventOrSlotLetTriggerCharacter) {
            return null;
        }
        if (this.canReuseLastCompletion(this.lastCompletion, triggerKind, triggerCharacter, document, position)) {
            this.lastCompletion.position = position;
            return this.lastCompletion.completionList;
        }
        else {
            this.lastCompletion = undefined;
        }
        if (!tsDoc.isInGenerated(position)) {
            return null;
        }
        const originalOffset = document.offsetAt(position);
        const offset = tsDoc.offsetAt(tsDoc.getGeneratedPosition(position));
        if (isJsDocTriggerCharacter) {
            return (0, getJsDocTemplateCompletion_1.getJsDocTemplateCompletion)(tsDoc, lang, filePath, offset);
        }
        const svelteNode = tsDoc.svelteNodeAt(originalOffset);
        if (
        // Cursor is somewhere in regular HTML text
        ((svelteNode === null || svelteNode === void 0 ? void 0 : svelteNode.type) === 'Text' &&
            ['Element', 'InlineComponent', 'Fragment', 'SlotTemplate'].includes((_a = svelteNode.parent) === null || _a === void 0 ? void 0 : _a.type)) ||
            // Cursor is at <div>|</div> in which case there's no TextNode inbetween
            document.getText().substring(originalOffset - 1, originalOffset + 2) === '></') {
            return null;
        }
        if (cancellationToken === null || cancellationToken === void 0 ? void 0 : cancellationToken.isCancellationRequested) {
            return null;
        }
        const wordRange = (0, documents_1.getWordRangeAt)(document.getText(), originalOffset, {
            left: /[^\s.]+$/,
            right: /[^\w$:]/
        });
        const componentInfo = (0, utils_3.getComponentAtPosition)(lang, document, tsDoc, position);
        const attributeContext = componentInfo && (0, parseHtml_1.getAttributeContextAtPosition)(document, position);
        const eventAndSlotLetCompletions = this.getEventAndSlotLetCompletions(componentInfo, document, attributeContext, wordRange);
        if (isEventOrSlotLetTriggerCharacter) {
            return vscode_languageserver_1.CompletionList.create(eventAndSlotLetCompletions, !!tsDoc.parserError);
        }
        const formatSettings = await this.configManager.getFormatCodeSettingsForFile(document, tsDoc.scriptKind);
        if (cancellationToken === null || cancellationToken === void 0 ? void 0 : cancellationToken.isCancellationRequested) {
            return null;
        }
        const response = lang.getCompletionsAtPosition(filePath, offset, {
            ...userPreferences,
            triggerCharacter: validTriggerCharacter
        }, formatSettings);
        const addCommitCharacters = 
        // replicating VS Code behavior https://github.com/microsoft/vscode/blob/main/extensions/typescript-language-features/src/languageFeatures/completions.ts
        (response === null || response === void 0 ? void 0 : response.isNewIdentifierLocation) !== true &&
            (!tsDoc.parserError || (0, utils_2.isInScript)(position, tsDoc));
        let completions = (response === null || response === void 0 ? void 0 : response.entries) || [];
        if (completions.length === 0 && eventAndSlotLetCompletions.length === 0) {
            return tsDoc.parserError ? vscode_languageserver_1.CompletionList.create([], true) : null;
        }
        if (completions.length > 500 &&
            (svelteNode === null || svelteNode === void 0 ? void 0 : svelteNode.type) === 'Element' &&
            completions[0].kind !== typescript_1.default.ScriptElementKind.memberVariableElement) {
            // False global completions inside element start tag
            return null;
        }
        if (completions.length > 500 &&
            (svelteNode === null || svelteNode === void 0 ? void 0 : svelteNode.type) === 'InlineComponent' &&
            ['  ', ' >', ' /'].includes(document.getText().substring(originalOffset - 1, originalOffset + 1))) {
            // Very likely false global completions inside component start tag -> narrow
            const props = (!(attributeContext === null || attributeContext === void 0 ? void 0 : attributeContext.inValue) &&
                (componentInfo === null || componentInfo === void 0 ? void 0 : componentInfo.getProps().map((entry) => this.componentInfoToCompletionEntry(entry, '', vscode_languageserver_1.CompletionItemKind.Field, document, wordRange)))) ||
                [];
            return vscode_languageserver_1.CompletionList.create([...eventAndSlotLetCompletions, ...props], !!tsDoc.parserError);
        }
        // moved here due to perf reasons
        const existingImports = this.getExistingImports(document);
        const wordRangeStartPosition = document.positionAt(wordRange.start);
        const word = document.getText().substring(wordRange.start, wordRange.end);
        const fileUrl = (0, utils_1.pathToUrl)(tsDoc.filePath);
        const isCompletionInTag = (0, svelte_ast_utils_1.isInTag)(svelteNode, originalOffset);
        // If completion is about a store which is not imported yet, do another
        // completion request at the beginning of the file to get all global
        // import completions and then filter them down to likely matches.
        if (word.charAt(0) === '$') {
            const storeName = word.substring(1);
            const text = '__sveltets_2_store_get(' + storeName;
            if (!tsDoc.getFullText().includes(text)) {
                const storeImportCompletions = ((_b = lang
                    .getCompletionsAtPosition(filePath, 0, {
                    ...userPreferences,
                    triggerCharacter: validTriggerCharacter
                }, formatSettings)) === null || _b === void 0 ? void 0 : _b.entries.filter((entry) => entry.source && entry.name.startsWith(storeName))) || [];
                completions.push(...storeImportCompletions);
            }
        }
        const completionItems = completions
            .filter(isValidCompletion(document, position, !!tsDoc.parserError))
            .map((comp) => this.toCompletionItem(tsDoc, comp, fileUrl, position, isCompletionInTag, addCommitCharacters, existingImports))
            .filter(utils_1.isNotNullOrUndefined)
            .map((comp) => (0, documents_1.mapCompletionItemToOriginal)(tsDoc, comp))
            .map((comp) => this.fixTextEditRange(wordRangeStartPosition, comp))
            .concat(eventAndSlotLetCompletions);
        // Add ./$types imports for SvelteKit since TypeScript is bad at it
        if ((0, path_1.basename)(filePath).startsWith('+')) {
            const $typeImports = new Map();
            for (const c of completionItems) {
                if ((0, utils_3.isKitTypePath)((_c = c.data) === null || _c === void 0 ? void 0 : _c.source)) {
                    $typeImports.set(c.label, c);
                }
            }
            for (const $typeImport of $typeImports.values()) {
                // resolve path from filePath to svelte-kit/types
                // src/routes/foo/+page.svelte -> .svelte-kit/types/foo/$types.d.ts
                const routesFolder = ((_f = (_e = (_d = document.config) === null || _d === void 0 ? void 0 : _d.kit) === null || _e === void 0 ? void 0 : _e.files) === null || _f === void 0 ? void 0 : _f.routes) || 'src/routes';
                const relativeFileName = (_g = filePath.split(routesFolder)[1]) === null || _g === void 0 ? void 0 : _g.slice(1);
                if (relativeFileName) {
                    const relativePath = (0, path_1.dirname)(relativeFileName) === '.' ? '' : `${(0, path_1.dirname)(relativeFileName)}/`;
                    const modifiedSource = $typeImport.data.source.split('.svelte-kit/types')[0] +
                        // note the missing .d.ts at the end - TS wants it that way for some reason
                        `.svelte-kit/types/${routesFolder}/${relativePath}$types`;
                    completionItems.push({
                        ...$typeImport,
                        // Ensure it's sorted above the other imports
                        sortText: !isNaN(Number($typeImport.sortText))
                            ? String(Number($typeImport.sortText) - 1)
                            : $typeImport.sortText,
                        data: {
                            ...$typeImport.data,
                            __is_sveltekit$typeImport: true,
                            source: modifiedSource,
                            data: undefined
                        }
                    });
                }
            }
        }
        const completionList = vscode_languageserver_1.CompletionList.create(completionItems, !!tsDoc.parserError);
        this.lastCompletion = { key: document.getFilePath() || '', position, completionList };
        return completionList;
    }
    canReuseLastCompletion(lastCompletion, triggerKind, triggerCharacter, document, position) {
        return (!!lastCompletion &&
            lastCompletion.key === document.getFilePath() &&
            lastCompletion.position.line === position.line &&
            ((Math.abs(lastCompletion.position.character - position.character) < 2 &&
                (triggerKind === vscode_languageserver_1.CompletionTriggerKind.TriggerForIncompleteCompletions ||
                    // Special case: `.` is a trigger character, but inside import path completions
                    // it shouldn't trigger another completion because we can reuse the old one
                    (triggerCharacter === '.' &&
                        (0, utils_3.isPartOfImportStatement)(document.getText(), position)))) ||
                // `let:` or `on:` -> up to 3 previous characters allowed
                (Math.abs(lastCompletion.position.character - position.character) < 4 &&
                    triggerCharacter === ':' &&
                    !!(0, documents_1.getNodeIfIsInStartTag)(document.html, document.offsetAt(position)))));
    }
    getExistingImports(document) {
        const rawImports = (0, utils_1.getRegExpMatches)(scriptImportRegex, document.getText()).map((match) => { var _a; return ((_a = match[1]) !== null && _a !== void 0 ? _a : match[2]).split(','); });
        const tidiedImports = (0, utils_1.flatten)(rawImports).map((match) => match.trim());
        return new Set(tidiedImports);
    }
    getEventAndSlotLetCompletions(componentInfo, document, attributeContext, wordRange) {
        if (componentInfo === null) {
            return [];
        }
        if (attributeContext === null || attributeContext === void 0 ? void 0 : attributeContext.inValue) {
            return [];
        }
        return [
            ...componentInfo
                .getEvents()
                .map((event) => this.componentInfoToCompletionEntry(event, 'on:', undefined, document, wordRange)),
            ...componentInfo
                .getSlotLets()
                .map((slot) => this.componentInfoToCompletionEntry(slot, 'let:', undefined, document, wordRange))
        ];
    }
    componentInfoToCompletionEntry(info, prefix, kind, doc, wordRange) {
        const { start, end } = wordRange;
        const name = prefix + info.name;
        return {
            label: name,
            kind,
            sortText: '-1',
            detail: info.name + ': ' + info.type,
            documentation: info.doc && { kind: vscode_languageserver_1.MarkupKind.Markdown, value: info.doc },
            textEdit: start !== end
                ? vscode_languageserver_1.TextEdit.replace((0, documents_1.toRange)(doc.getText(), start, end), name)
                : undefined
        };
    }
    toCompletionItem(snapshot, comp, uri, position, isCompletionInTag, addCommitCharacters, existingImports) {
        var _a;
        const completionLabelAndInsert = this.getCompletionLabelAndInsert(snapshot, comp);
        if (!completionLabelAndInsert) {
            return null;
        }
        let { label, insertText, isSvelteComp, replacementSpan } = completionLabelAndInsert;
        // TS may suggest another Svelte component even if there already exists an import
        // with the same name, because under the hood every Svelte component is postfixed
        // with `__SvelteComponent`. In this case, filter out this completion by returning null.
        if (isSvelteComp && existingImports.has(label)) {
            return null;
        }
        // Remove wrong quotes, for example when using --css-props
        if (isCompletionInTag &&
            !insertText &&
            label[0] === '"' &&
            label[label.length - 1] === '"') {
            label = label.slice(1, -1);
        }
        const textEdit = replacementSpan
            ? vscode_languageserver_1.TextEdit.replace((0, utils_2.convertRange)(snapshot, replacementSpan), insertText !== null && insertText !== void 0 ? insertText : label)
            : undefined;
        const labelDetails = (_a = comp.labelDetails) !== null && _a !== void 0 ? _a : (comp.sourceDisplay
            ? {
                description: typescript_1.default.displayPartsToString(comp.sourceDisplay)
            }
            : undefined);
        return {
            label,
            insertText,
            kind: (0, utils_2.scriptElementKindToCompletionItemKind)(comp.kind),
            commitCharacters: addCommitCharacters ? this.commitCharacters : undefined,
            // Make sure svelte component takes precedence
            sortText: isSvelteComp ? '-1' : comp.sortText,
            preselect: isSvelteComp ? true : comp.isRecommended,
            insertTextFormat: comp.isSnippet ? vscode_languageserver_1.InsertTextFormat.Snippet : undefined,
            labelDetails,
            textEdit,
            // pass essential data for resolving completion
            data: {
                ...comp,
                uri,
                position
            }
        };
    }
    getCompletionLabelAndInsert(snapshot, comp) {
        let { name, insertText, kindModifiers } = comp;
        const isScriptElement = comp.kind === typescript_1.default.ScriptElementKind.scriptElement;
        const hasModifier = Boolean(comp.kindModifiers);
        const isSvelteComp = (0, utils_2.isGeneratedSvelteComponentName)(name);
        if (isSvelteComp) {
            name = (0, utils_2.changeSvelteComponentName)(name);
            if (this.isExistingSvelteComponentImport(snapshot, name, comp.source)) {
                return null;
            }
        }
        if (isScriptElement && hasModifier) {
            const label = kindModifiers && !name.endsWith(kindModifiers) ? name + kindModifiers : name;
            return {
                insertText: name,
                label,
                isSvelteComp
            };
        }
        if (comp.replacementSpan) {
            return {
                label: name,
                isSvelteComp,
                insertText: insertText ? (0, utils_2.changeSvelteComponentName)(insertText) : undefined,
                replacementSpan: comp.replacementSpan
            };
        }
        return {
            label: name,
            insertText,
            isSvelteComp
        };
    }
    isExistingSvelteComponentImport(snapshot, name, source) {
        const importStatement = new RegExp(`import ${name} from ["'\`][\\s\\S]+\\.svelte["'\`]`);
        return !!source && !!snapshot.getFullText().match(importStatement);
    }
    /**
     * If the textEdit is out of the word range of the triggered position
     * vscode would refuse to show the completions
     * split those edits into additionalTextEdit to fix it
     */
    fixTextEditRange(wordRangePosition, completionItem) {
        const { textEdit } = completionItem;
        if (!textEdit || !vscode_languageserver_1.TextEdit.is(textEdit)) {
            return completionItem;
        }
        const { newText, range: { start } } = textEdit;
        const wordRangeStartCharacter = wordRangePosition.character;
        if (wordRangePosition.line !== wordRangePosition.line ||
            start.character > wordRangePosition.character) {
            return completionItem;
        }
        textEdit.newText = newText.substring(wordRangeStartCharacter - start.character);
        textEdit.range.start = {
            line: start.line,
            character: wordRangeStartCharacter
        };
        completionItem.additionalTextEdits = [
            vscode_languageserver_1.TextEdit.replace({
                start,
                end: {
                    line: start.line,
                    character: wordRangeStartCharacter
                }
            }, newText.substring(0, wordRangeStartCharacter - start.character))
        ];
        return completionItem;
    }
    /**
     * TypeScript throws a debug assertion error if the importModuleSpecifierEnding config is
     * 'js' and there's an unknown file extension - which is the case for `.svelte`. Therefore
     * rewrite the importModuleSpecifierEnding for this case to silence the error.
     */
    fixUserPreferencesForSvelteComponentImport(userPreferences) {
        if (userPreferences.importModuleSpecifierEnding === 'js') {
            return {
                ...userPreferences,
                importModuleSpecifierEnding: 'index'
            };
        }
        return userPreferences;
    }
    async resolveCompletion(document, completionItem, cancellationToken) {
        var _a, _b;
        const { data: comp } = completionItem;
        const { tsDoc, lang, userPreferences } = await this.lsAndTsDocResolver.getLSAndTSDoc(document);
        const filePath = tsDoc.filePath;
        const formatCodeOptions = await this.configManager.getFormatCodeSettingsForFile(document, tsDoc.scriptKind);
        if (!comp || !filePath || (cancellationToken === null || cancellationToken === void 0 ? void 0 : cancellationToken.isCancellationRequested)) {
            return completionItem;
        }
        const is$typeImport = !!comp.__is_sveltekit$typeImport;
        const errorPreventingUserPreferences = ((_a = comp.source) === null || _a === void 0 ? void 0 : _a.endsWith('.svelte'))
            ? this.fixUserPreferencesForSvelteComponentImport(userPreferences)
            : userPreferences;
        const detail = lang.getCompletionEntryDetails(filePath, tsDoc.offsetAt(tsDoc.getGeneratedPosition(comp.position)), comp.name, formatCodeOptions, comp.source, errorPreventingUserPreferences, comp.data);
        if (detail) {
            const { detail: itemDetail, documentation: itemDocumentation } = this.getCompletionDocument(detail, is$typeImport);
            // VSCode + tsserver won't have this pop-in effect
            // because tsserver has internal APIs for caching
            // TODO: consider if we should adopt the internal APIs
            if (detail.sourceDisplay && !completionItem.labelDetails) {
                completionItem.labelDetails = {
                    description: typescript_1.default.displayPartsToString(detail.sourceDisplay)
                };
            }
            completionItem.detail = itemDetail;
            completionItem.documentation = itemDocumentation;
        }
        const actions = detail === null || detail === void 0 ? void 0 : detail.codeActions;
        const isImport = !!(detail === null || detail === void 0 ? void 0 : detail.source);
        if (actions) {
            const edit = [];
            const formatCodeBasis = (0, utils_3.getFormatCodeBasis)(formatCodeOptions);
            for (const action of actions) {
                for (const change of action.changes) {
                    edit.push(...this.codeActionChangesToTextEdit(document, tsDoc, change, isImport, comp.position, formatCodeBasis.newLine, is$typeImport));
                }
            }
            completionItem.additionalTextEdits = ((_b = completionItem.additionalTextEdits) !== null && _b !== void 0 ? _b : []).concat(edit);
        }
        return completionItem;
    }
    getCompletionDocument(compDetail, is$typeImport) {
        var _a, _b;
        const { sourceDisplay, documentation: tsDocumentation, displayParts, tags } = compDetail;
        let parts = (_b = (_a = compDetail.codeActions) === null || _a === void 0 ? void 0 : _a.map((codeAction) => codeAction.description)) !== null && _b !== void 0 ? _b : [];
        if (sourceDisplay && is$typeImport) {
            const importPath = typescript_1.default.displayPartsToString(sourceDisplay);
            // Take into account Node16 moduleResolution
            parts = parts.map((detail) => detail.replace(importPath, `'./$types${importPath.endsWith('.js') ? '.js' : ''}'`));
        }
        parts.push((0, utils_2.changeSvelteComponentName)(typescript_1.default.displayPartsToString(displayParts)));
        const markdownDoc = (0, previewer_1.getMarkdownDocumentation)(tsDocumentation, tags);
        const documentation = markdownDoc
            ? { value: markdownDoc, kind: vscode_languageserver_1.MarkupKind.Markdown }
            : undefined;
        return {
            documentation,
            detail: parts.filter(Boolean).join('\n\n')
        };
    }
    codeActionChangesToTextEdit(doc, snapshot, changes, isImport, originalTriggerPosition, newLine, is$typeImport) {
        return changes.textChanges.map((change) => this.codeActionChangeToTextEdit(doc, snapshot, change, isImport, originalTriggerPosition, newLine, is$typeImport));
    }
    codeActionChangeToTextEdit(doc, snapshot, change, isImport, originalTriggerPosition, newLine, is$typeImport, isCombinedCodeAction) {
        var _a, _b, _c, _d;
        change.newText = isCombinedCodeAction
            ? (0, utils_1.modifyLines)(change.newText, (line) => this.fixImportNewText(line, (0, utils_2.isInScript)(originalTriggerPosition, doc), is$typeImport))
            : this.fixImportNewText(change.newText, (0, utils_2.isInScript)(originalTriggerPosition, doc), is$typeImport);
        const scriptTagInfo = snapshot.scriptInfo || snapshot.moduleScriptInfo;
        // no script tag defined yet, add it.
        if (!scriptTagInfo) {
            if (isCombinedCodeAction) {
                return vscode_languageserver_1.TextEdit.insert(vscode_languageserver_1.Position.create(0, 0), change.newText);
            }
            const config = this.configManager.getConfig();
            return vscode_languageserver_1.TextEdit.replace(beginOfDocumentRange, `${(0, utils_3.getNewScriptStartTag)(config)}${change.newText}</script>${newLine}`);
        }
        const { span } = change;
        const virtualRange = (0, utils_2.convertRange)(snapshot, span);
        let range;
        const isNewImport = isImport && virtualRange.start.character === 0;
        // Since new import always can't be mapped, we'll have special treatment here
        //  but only hack this when there is multiple line in script
        if (isNewImport && virtualRange.start.line > 1) {
            range = this.mapRangeForNewImport(snapshot, virtualRange);
        }
        else {
            range = (0, documents_1.mapRangeToOriginal)(snapshot, virtualRange);
        }
        // If range is somehow not mapped in parent,
        // the import is mapped wrong or is outside script tag,
        // use script starting point instead.
        // This happens among other things if the completion is the first import of the file.
        if (range.start.line === -1 ||
            (range.start.line === 0 && range.start.character <= 1 && span.length === 0) ||
            !(0, utils_2.isInScript)(range.start, snapshot)) {
            range = (0, utils_2.convertRange)(doc, {
                start: (0, documents_1.isInTag)(originalTriggerPosition, doc.scriptInfo)
                    ? ((_a = snapshot.scriptInfo) === null || _a === void 0 ? void 0 : _a.start) || scriptTagInfo.start
                    : (0, documents_1.isInTag)(originalTriggerPosition, doc.moduleScriptInfo)
                        ? ((_b = snapshot.moduleScriptInfo) === null || _b === void 0 ? void 0 : _b.start) || scriptTagInfo.start
                        : scriptTagInfo.start,
                length: span.length
            });
        }
        // prevent newText from being placed like this: <script>import {} from ''
        const editOffset = doc.offsetAt(range.start);
        if ((editOffset === ((_c = snapshot.scriptInfo) === null || _c === void 0 ? void 0 : _c.start) ||
            editOffset === ((_d = snapshot.moduleScriptInfo) === null || _d === void 0 ? void 0 : _d.start)) &&
            !change.newText.startsWith('\r\n') &&
            !change.newText.startsWith('\n')) {
            change.newText = newLine + change.newText;
        }
        const after = doc.getText().slice(doc.offsetAt(range.end));
        // typescript add empty line after import when the generated ts file
        // doesn't have new line at the start of the file
        if (after.startsWith('\r\n') || after.startsWith('\n')) {
            change.newText = change.newText.trimEnd() + newLine;
        }
        return vscode_languageserver_1.TextEdit.replace(range, change.newText);
    }
    mapRangeForNewImport(snapshot, virtualRange) {
        const sourceMappableRange = this.offsetLinesAndMovetoStartOfLine(virtualRange, -1);
        const mappableRange = (0, documents_1.mapRangeToOriginal)(snapshot, sourceMappableRange);
        return this.offsetLinesAndMovetoStartOfLine(mappableRange, 1);
    }
    offsetLinesAndMovetoStartOfLine({ start, end }, offsetLines) {
        return vscode_languageserver_1.Range.create(vscode_languageserver_1.Position.create(start.line + offsetLines, 0), vscode_languageserver_1.Position.create(end.line + offsetLines, 0));
    }
    fixImportNewText(importText, actionTriggeredInScript, is$typeImport) {
        if (is$typeImport && importText.trim().startsWith('import ')) {
            // Take into account Node16 moduleResolution
            return importText.replace(/(['"])(.+?)['"]/, (_match, quote, path) => `${quote}./$types${path.endsWith('.js') ? '.js' : ''}${quote}`);
        }
        const changedName = (0, utils_2.changeSvelteComponentName)(importText);
        if (importText !== changedName || !actionTriggeredInScript) {
            // For some reason, TS sometimes adds the `type` modifier. Remove it
            // in case of Svelte component imports or if import triggered from markup.
            return changedName.replace(' type ', ' ');
        }
        return importText;
    }
}
exports.CompletionsProviderImpl = CompletionsProviderImpl;
const beginOfDocumentRange = vscode_languageserver_1.Range.create(vscode_languageserver_1.Position.create(0, 0), vscode_languageserver_1.Position.create(0, 0));
// `import {...} from '..'` or `import ... from '..'`
// Note: Does not take into account if import is within a comment.
const scriptImportRegex = /\bimport\s+{([^}]*?)}\s+?from\s+['"`].+?['"`]|\bimport\s+(\w+?)\s+from\s+['"`].+?['"`]/g;
// Type definitions from svelte-shims.d.ts that shouldn't appear in completion suggestions
// because they are meant to be used "behind the scenes"
const svelte2tsxTypes = new Set([
    'Svelte2TsxComponent',
    'Svelte2TsxComponentConstructorParameters',
    'SvelteComponentConstructor',
    'SvelteActionReturnType',
    'SvelteTransitionConfig',
    'SvelteTransitionReturnType',
    'SvelteAnimationReturnType',
    'SvelteWithOptionalProps',
    'SvelteAllProps',
    'SveltePropsAnyFallback',
    'SvelteSlotsAnyFallback',
    'SvelteRestProps',
    'SvelteSlots',
    'SvelteStore'
]);
const startsWithUppercase = /^[A-Z]/;
function isValidCompletion(document, position, hasParserError) {
    // Make fallback completions for tags inside the template a bit better
    const isAtStartTag = !(0, documents_1.isInTag)(position, document.scriptInfo) &&
        /<\w*$/.test(document.getText(vscode_languageserver_1.Range.create(position.line, 0, position.line, position.character)));
    const noWrongCompletionAtStartTag = isAtStartTag && hasParserError
        ? (value) => startsWithUppercase.test(value.name)
        : () => true;
    const isNoSvelte2tsxCompletion = (value) => {
        if (value.kindModifiers === 'declare') {
            return !value.name.startsWith('__sveltets_') && !svelte2tsxTypes.has(value.name);
        }
        return !value.name.startsWith('$$_');
    };
    const isCompletionInHTMLStartTag = !!(0, documents_1.getNodeIfIsInHTMLStartTag)(document.html, document.offsetAt(position));
    if (!isCompletionInHTMLStartTag) {
        return isNoSvelte2tsxCompletion;
    }
    // TODO with the new transformation this is ts.ScriptElementKind.memberVariableElement
    // which is also true for all properties of any other object -> how reliably filter this out?
    // ---> another /*ignore*/ pragma?
    // ---> OR: make these lower priority if we find out they are inside a html start tag
    return (value) => isNoSvelte2tsxCompletion(value) && noWrongCompletionAtStartTag(value);
}
//# sourceMappingURL=CompletionProvider.js.map