"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.LSConfigManager = void 0;
const typescript_1 = __importDefault(require("typescript"));
const importPackage_1 = require("./importPackage");
const utils_1 = require("./utils");
const path_1 = __importDefault(require("path"));
const fileCollection_1 = require("./lib/documents/fileCollection");
const defu_1 = require("defu");
/**
 * Default config for the language server.
 */
const defaultLSConfig = {
    typescript: {
        enable: true,
        diagnostics: { enable: true },
        hover: { enable: true },
        completions: { enable: true },
        documentSymbols: { enable: true },
        codeActions: { enable: true },
        selectionRange: { enable: true },
        signatureHelp: { enable: true },
        semanticTokens: { enable: true },
        workspaceSymbols: { enable: true }
    },
    css: {
        enable: true,
        globals: '',
        diagnostics: { enable: true },
        hover: { enable: true },
        completions: { enable: true, emmet: true },
        documentColors: { enable: true },
        colorPresentations: { enable: true },
        documentSymbols: { enable: true },
        selectionRange: { enable: true }
    },
    html: {
        enable: true,
        hover: { enable: true },
        completions: { enable: true, emmet: true },
        tagComplete: { enable: true },
        documentSymbols: { enable: true },
        linkedEditing: { enable: true }
    },
    svelte: {
        enable: true,
        compilerWarnings: {},
        diagnostics: { enable: true },
        rename: { enable: true },
        format: {
            enable: true,
            config: {
                svelteSortOrder: 'options-scripts-markup-styles',
                svelteStrictMode: false,
                svelteAllowShorthand: true,
                svelteBracketNewLine: true,
                svelteIndentScriptAndStyle: true,
                printWidth: 80,
                singleQuote: false
            }
        },
        completions: { enable: true },
        hover: { enable: true },
        codeActions: { enable: true },
        selectionRange: { enable: true },
        runesLegacyModeCodeLens: { enable: true },
        defaultScriptLanguage: 'none'
    }
};
class LSConfigManager {
    constructor() {
        this.config = defaultLSConfig;
        this.listeners = [];
        this.tsUserPreferences = {
            // populate default with _updateTsUserPreferences
            typescript: {},
            javascript: {}
        };
        this.rawTsUserConfig = {
            typescript: {},
            javascript: {}
        };
        this.resolvedAutoImportExcludeCache = new fileCollection_1.FileMap();
        this.tsFormatCodeOptions = {
            typescript: this.getDefaultFormatCodeOptions(),
            javascript: this.getDefaultFormatCodeOptions()
        };
        this.prettierConfig = {};
        this.emmetConfig = {};
        this.isTrusted = true;
        this._updateTsUserPreferences('javascript', {});
        this._updateTsUserPreferences('typescript', {});
    }
    /**
     * Updates config.
     */
    update(config) {
        // Ideally we shouldn't need the merge here because all updates should be valid and complete configs.
        // But since those configs come from the client they might be out of sync with the valid config:
        // We might at some point in the future forget to sync config settings in all packages after updating the config.
        this.config = (0, defu_1.defu)(config, this.config, defaultLSConfig);
        // Merge will keep arrays/objects if the new one is empty/has less entries,
        // therefore we need some extra checks if there are new settings
        if (config?.svelte?.compilerWarnings) {
            this.config.svelte.compilerWarnings = config.svelte.compilerWarnings;
        }
        this.notifyListeners();
    }
    /**
     * Whether or not specified config is enabled
     * @param key a string which is a path. Example: 'svelte.diagnostics.enable'.
     */
    enabled(key) {
        return !!this.get(key);
    }
    /**
     * Get specific config
     * @param key a string which is a path. Example: 'svelte.diagnostics.enable'.
     */
    get(key) {
        // @ts-expect-error we know it works within expectations
        return key.split('.').reduce((acc, s) => (acc ? acc[s] : undefined), this.config);
    }
    /**
     * Get the whole config
     */
    getConfig() {
        return this.config;
    }
    /**
     * Register a listener which is invoked when the config changed.
     */
    onChange(callback) {
        this.listeners.push(callback);
    }
    updateEmmetConfig(config) {
        this.emmetConfig = config || {};
        this.notifyListeners();
    }
    getEmmetConfig() {
        return this.emmetConfig;
    }
    updatePrettierConfig(config) {
        this.prettierConfig = config || {};
        this.notifyListeners();
    }
    getPrettierConfig() {
        return this.prettierConfig;
    }
    getPrettierConfigLoadingOptions() {
        return {
            editorconfig: this.prettierConfig?.useEditorConfig ?? true
        };
    }
    /**
     * Returns a merged Prettier config following these rules:
     * - If `prettierFromFileConfig` exists, that one is returned
     * - Else the Svelte extension's Prettier config is used as a starting point,
     *   and overridden by a possible Prettier config from the Prettier extension,
     *   or, if that doesn't exist, a possible fallback override.
     */
    getMergedPrettierConfig(prettierFromFileConfig, overridesWhenNoPrettierConfig = {}) {
        return ((0, utils_1.returnObjectIfHasKeys)(prettierFromFileConfig) ||
            (0, defu_1.defu)((0, utils_1.returnObjectIfHasKeys)(this.getPrettierConfig()) ||
                overridesWhenNoPrettierConfig ||
                {}, this.get('svelte.format.config')));
    }
    updateTsJsUserPreferences(config) {
        const shared = config['js/ts'];
        ['typescript', 'javascript'].forEach((lang) => {
            if (config[lang]) {
                this._updateTsUserPreferences(lang, config[lang], shared);
                this.rawTsUserConfig[lang] = config[lang];
            }
        });
        this.notifyListeners();
        this.resolvedAutoImportExcludeCache.clear();
    }
    /**
     * Whether or not the current workspace can be trusted.
     * If not, certain operations should be disabled.
     */
    getIsTrusted() {
        return this.isTrusted;
    }
    updateIsTrusted(isTrusted) {
        this.isTrusted = isTrusted;
        this.notifyListeners();
    }
    _updateTsUserPreferences(lang, config, shared) {
        const { inlayHints } = config;
        this.tsUserPreferences[lang] = {
            ...this.tsUserPreferences[lang],
            importModuleSpecifierPreference: config.preferences?.importModuleSpecifier,
            importModuleSpecifierEnding: config.preferences?.importModuleSpecifierEnding,
            includePackageJsonAutoImports: config.preferences?.includePackageJsonAutoImports,
            quotePreference: config.preferences?.quoteStyle,
            includeCompletionsForModuleExports: config.suggest?.autoImports ?? true,
            includeCompletionsForImportStatements: config.suggest?.includeCompletionsForImportStatements ?? true,
            includeAutomaticOptionalChainCompletions: config.suggest?.includeAutomaticOptionalChainCompletions ?? true,
            includeCompletionsWithInsertText: true,
            autoImportFileExcludePatterns: config.preferences?.autoImportFileExcludePatterns,
            useLabelDetailsInCompletionEntries: true,
            includeCompletionsWithSnippetText: config.suggest?.includeCompletionsWithSnippetText ?? true,
            includeCompletionsWithClassMemberSnippets: config.suggest?.classMemberSnippets?.enabled ?? true,
            includeCompletionsWithObjectLiteralMethodSnippets: config.suggest?.objectLiteralMethodSnippets?.enabled ?? true,
            preferTypeOnlyAutoImports: config.preferences?.preferTypeOnlyAutoImports,
            maximumHoverLength: shared?.hover?.maximumLength,
            // Although we don't support incompletion cache.
            // But this will make ts resolve the module specifier more aggressively
            // Which also makes the completion label detail show up in more cases
            allowIncompleteCompletions: true,
            includeInlayEnumMemberValueHints: inlayHints?.enumMemberValues?.enabled,
            includeInlayFunctionLikeReturnTypeHints: inlayHints?.functionLikeReturnTypes?.enabled,
            includeInlayParameterNameHints: inlayHints?.parameterNames?.enabled,
            includeInlayParameterNameHintsWhenArgumentMatchesName: inlayHints?.parameterNames?.suppressWhenArgumentMatchesName === false,
            includeInlayFunctionParameterTypeHints: inlayHints?.parameterTypes?.enabled,
            includeInlayVariableTypeHints: inlayHints?.variableTypes?.enabled,
            includeInlayPropertyDeclarationTypeHints: inlayHints?.propertyDeclarationTypes?.enabled,
            includeInlayVariableTypeHintsWhenTypeMatchesName: inlayHints?.variableTypes?.suppressWhenTypeMatchesName === false,
            interactiveInlayHints: true,
            autoImportSpecifierExcludeRegexes: config.preferences?.autoImportSpecifierExcludeRegexes,
            organizeImportsAccentCollation: config.preferences?.organizeImports?.accentCollation,
            organizeImportsCollation: config.preferences?.organizeImports?.collation,
            organizeImportsCaseFirst: this.withDefaultAsUndefined(config.preferences?.organizeImports?.caseFirst, 'default'),
            organizeImportsIgnoreCase: this.withDefaultAsUndefined(config.preferences?.organizeImports?.caseSensitivity, 'auto'),
            organizeImportsLocale: config.preferences?.organizeImports?.locale,
            organizeImportsNumericCollation: config.preferences?.organizeImports?.numericCollation,
            organizeImportsTypeOrder: this.withDefaultAsUndefined(config.preferences?.organizeImports?.typeOrder, 'auto'),
            excludeLibrarySymbolsInNavTo: config.workspaceSymbols?.excludeLibrarySymbols ?? true
        };
    }
    withDefaultAsUndefined(value, def) {
        return value === def ? undefined : value;
    }
    getTsUserPreferences(lang, normalizedWorkspacePath) {
        const userPreferences = this.tsUserPreferences[lang];
        if (!normalizedWorkspacePath || !userPreferences.autoImportFileExcludePatterns) {
            return userPreferences;
        }
        let autoImportFileExcludePatterns = this.resolvedAutoImportExcludeCache.get(normalizedWorkspacePath);
        if (!autoImportFileExcludePatterns) {
            const version = typescript_1.default.version.split('.');
            const major = parseInt(version[0]);
            const minor = parseInt(version[1]);
            const gte5_4 = major > 5 || (major === 5 && minor >= 4);
            autoImportFileExcludePatterns = userPreferences.autoImportFileExcludePatterns.map((p) => {
                // Normalization rules: https://github.com/microsoft/TypeScript/pull/49578
                const slashNormalized = p.replace(/\\/g, '/');
                const isRelative = /^\.\.?($|\/)/.test(slashNormalized);
                if (path_1.default.isAbsolute(p)) {
                    return p;
                }
                // https://github.com/microsoft/vscode/pull/202762
                // ts 5.4+ supports leading wildcards
                const wildcardPrefix = gte5_4 ? '' : path_1.default.parse(normalizedWorkspacePath).root;
                return p.startsWith('*')
                    ? wildcardPrefix + slashNormalized
                    : isRelative
                        ? path_1.default.join(normalizedWorkspacePath, p)
                        : wildcardPrefix + '**/' + slashNormalized;
            });
            this.resolvedAutoImportExcludeCache.set(normalizedWorkspacePath, autoImportFileExcludePatterns);
        }
        return {
            ...userPreferences,
            autoImportFileExcludePatterns
        };
    }
    getClientTsUserConfig(lang) {
        return this.rawTsUserConfig[lang];
    }
    updateCssConfig(config) {
        this.cssConfig = config;
        this.notifyListeners();
    }
    getCssConfig() {
        return this.cssConfig;
    }
    updateScssConfig(config) {
        this.scssConfig = config;
        this.notifyListeners();
    }
    getScssConfig() {
        return this.scssConfig;
    }
    updateLessConfig(config) {
        this.lessConfig = config;
        this.notifyListeners();
    }
    getLessConfig() {
        return this.lessConfig;
    }
    updateHTMLConfig(config) {
        this.htmlConfig = config;
        this.notifyListeners();
    }
    getHTMLConfig() {
        return this.htmlConfig;
    }
    updateTsJsFormateConfig(config) {
        ['typescript', 'javascript'].forEach((lang) => {
            if (config[lang]) {
                this._updateTsFormatConfig(lang, config[lang]);
            }
        });
        this.notifyListeners();
    }
    getDefaultFormatCodeOptions() {
        // https://github.com/microsoft/TypeScript/blob/394f51aeed80788dca72c6f6a90d1d27886b6972/src/services/types.ts#L1014
        return {
            indentSize: 4,
            tabSize: 4,
            convertTabsToSpaces: true,
            indentStyle: typescript_1.default.IndentStyle.Smart,
            insertSpaceAfterConstructor: false,
            insertSpaceAfterCommaDelimiter: true,
            insertSpaceAfterSemicolonInForStatements: true,
            insertSpaceBeforeAndAfterBinaryOperators: true,
            insertSpaceAfterKeywordsInControlFlowStatements: true,
            insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false,
            insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false,
            insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true,
            insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false,
            insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false,
            insertSpaceBeforeFunctionParenthesis: false,
            placeOpenBraceOnNewLineForFunctions: false,
            placeOpenBraceOnNewLineForControlBlocks: false,
            trimTrailingWhitespace: true,
            semicolons: typescript_1.default.SemicolonPreference.Ignore,
            // Override TypeScript's default because VSCode default to true
            // Also this matches the style of prettier
            insertSpaceAfterFunctionKeywordForAnonymousFunctions: true
        };
    }
    _updateTsFormatConfig(lang, config) {
        this.tsFormatCodeOptions[lang] = {
            ...this.tsFormatCodeOptions[lang],
            ...(config.format ?? {})
        };
    }
    async getFormatCodeSettingsForFile(document, scriptKind) {
        const filePath = document.getFilePath();
        const configLang = scriptKind === typescript_1.default.ScriptKind.TS || scriptKind === typescript_1.default.ScriptKind.TSX
            ? 'typescript'
            : 'javascript';
        const tsFormatCodeOptions = this.tsFormatCodeOptions[configLang];
        if (!filePath) {
            return tsFormatCodeOptions;
        }
        const prettierConfig = this.getMergedPrettierConfig(await (0, importPackage_1.importPrettier)(filePath).resolveConfig(filePath, this.getPrettierConfigLoadingOptions()));
        const useSemicolons = prettierConfig.semi ?? true;
        const documentUseLf = document.getText().includes('\n') && !document.getText().includes('\r\n');
        const indentSize = (typeof prettierConfig.tabWidth === 'number' ? prettierConfig.tabWidth : null) ??
            tsFormatCodeOptions.tabSize;
        return {
            ...tsFormatCodeOptions,
            newLineCharacter: documentUseLf ? '\n' : typescript_1.default.sys.newLine,
            baseIndentSize: prettierConfig.svelteIndentScriptAndStyle === false ? 0 : indentSize,
            indentSize,
            convertTabsToSpaces: !prettierConfig.useTabs,
            semicolons: useSemicolons
                ? typescript_1.default.SemicolonPreference.Insert
                : typescript_1.default.SemicolonPreference.Remove,
            tabSize: indentSize
        };
    }
    notifyListeners() {
        if (this.scheduledUpdate) {
            clearTimeout(this.scheduledUpdate);
        }
        this.scheduledUpdate = setTimeout(() => {
            this.scheduledUpdate = undefined;
            this.listeners.forEach((listener) => listener(this));
        });
    }
    updateClientCapabilities(clientCapabilities) {
        this.clientCapabilities = clientCapabilities;
        this.notifyListeners();
    }
    getClientCapabilities() {
        return this.clientCapabilities;
    }
}
exports.LSConfigManager = LSConfigManager;
//# sourceMappingURL=ls-config.js.map