"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.startCommonLanguageServer = void 0;
const language_service_1 = require("@volar/language-service");
const l10n = require("@vscode/l10n");
const vscode = require("vscode-languageserver");
const vscode_uri_1 = require("vscode-uri");
const types_1 = require("../types");
const cancellationPipe_1 = require("./cancellationPipe");
const configurationHost_1 = require("./configurationHost");
const documents_1 = require("./documents");
const registerFeatures_1 = require("./utils/registerFeatures");
const serverConfig_1 = require("./utils/serverConfig");
const workspaces_1 = require("./workspaces");
const request_light_1 = require("request-light");
async function startCommonLanguageServer(connection, _plugins, getRuntimeEnv) {
    let initParams;
    let options;
    let roots = [];
    let projects;
    let plugins;
    let documents;
    let context;
    const didChangeWatchedFilesCallbacks = new Set();
    connection.onInitialize(async (_params) => {
        initParams = _params;
        options = initParams.initializationOptions;
        const env = getRuntimeEnv(initParams, options);
        context = {
            server: {
                initializeParams: initParams,
                connection,
                runtimeEnv: {
                    ...env,
                    fs: createFsWithCache(env.fs),
                },
                plugins: _plugins,
                configurationHost: initParams.capabilities.workspace?.configuration ? (0, configurationHost_1.createConfigurationHost)(initParams, connection) : undefined,
                onDidChangeWatchedFiles: cb => {
                    didChangeWatchedFilesCallbacks.add(cb);
                    return {
                        dispose: () => {
                            didChangeWatchedFilesCallbacks.delete(cb);
                        },
                    };
                },
            },
        };
        plugins = context.server.plugins.map(plugin => plugin(options, {
            typescript: options.typescript ? context.server.runtimeEnv.loadTypescript(options.typescript.tsdk) : undefined
        }));
        documents = (0, documents_1.createDocuments)(context.server.runtimeEnv, connection);
        if (options.l10n) {
            await l10n.config({ uri: options.l10n.location });
        }
        if (initParams.capabilities.workspace?.workspaceFolders && initParams.workspaceFolders) {
            roots = initParams.workspaceFolders.map(folder => vscode_uri_1.URI.parse(folder.uri));
        }
        else if (initParams.rootUri) {
            roots = [vscode_uri_1.URI.parse(initParams.rootUri)];
        }
        else if (initParams.rootPath) {
            roots = [vscode_uri_1.URI.file(initParams.rootPath)];
        }
        const result = {
            capabilities: {
                textDocumentSync: vscode.TextDocumentSyncKind.Incremental,
                workspace: {
                    // #18
                    workspaceFolders: {
                        supported: true,
                        changeNotifications: true,
                    },
                },
            },
        };
        let config = {};
        for (const root of roots) {
            if (root.scheme === 'file') {
                const workspaceConfig = (0, serverConfig_1.loadConfig)(root.path, options.configFilePath);
                if (workspaceConfig) {
                    config = workspaceConfig;
                    break;
                }
            }
        }
        for (const plugin of plugins) {
            if (plugin.resolveConfig) {
                config = await plugin.resolveConfig(config, undefined);
            }
        }
        (0, registerFeatures_1.setupCapabilities)(result.capabilities, options, plugins, getSemanticTokensLegend(), config.services ?? {});
        await createLanguageServiceHost();
        try {
            // show version on LSP logs
            const packageJson = require('../package.json');
            result.serverInfo = {
                name: packageJson.name,
                version: packageJson.version,
            };
        }
        catch { }
        return result;
    });
    connection.onInitialized(async () => {
        context.server.configurationHost?.ready();
        context.server.configurationHost?.onDidChangeConfiguration?.(updateHttpSettings);
        updateHttpSettings();
        if (initParams.capabilities.workspace?.workspaceFolders) {
            connection.workspace.onDidChangeWorkspaceFolders(e => {
                for (const folder of e.added) {
                    projects?.add(vscode_uri_1.URI.parse(folder.uri));
                }
                for (const folder of e.removed) {
                    projects?.remove(vscode_uri_1.URI.parse(folder.uri));
                }
            });
        }
        if (options.serverMode !== types_1.ServerMode.Syntactic
            && initParams.capabilities.workspace?.didChangeWatchedFiles?.dynamicRegistration) {
            const exts = plugins.map(plugin => plugin.watchFileExtensions).flat();
            if (exts.length) {
                connection.client.register(vscode.DidChangeWatchedFilesNotification.type, {
                    watchers: [
                        {
                            globPattern: `**/*.{${exts.join(',')}}`
                        },
                    ]
                });
                connection.onDidChangeWatchedFiles(e => {
                    for (const cb of didChangeWatchedFilesCallbacks) {
                        cb(e);
                    }
                });
            }
        }
        async function updateHttpSettings() {
            const httpSettings = await context.server.configurationHost?.getConfiguration?.('http');
            (0, request_light_1.configure)(httpSettings?.proxy, httpSettings?.proxyStrictSSL ?? false);
        }
    });
    connection.onShutdown(async () => {
        if (projects) {
            for (const workspace of projects.workspaces) {
                (await workspace[1]).dispose();
            }
        }
    });
    connection.listen();
    function createFsWithCache(fs) {
        const readFileCache = new Map();
        const statCache = new Map();
        const readDirectoryCache = new Map();
        didChangeWatchedFilesCallbacks.add(({ changes }) => {
            for (const change of changes) {
                if (change.type === vscode.FileChangeType.Deleted) {
                    readFileCache.set(change.uri, undefined);
                    statCache.set(change.uri, undefined);
                    const dir = change.uri.substring(0, change.uri.lastIndexOf('/'));
                    readDirectoryCache.delete(dir);
                }
                else if (change.type === vscode.FileChangeType.Changed) {
                    readFileCache.delete(change.uri);
                    statCache.delete(change.uri);
                }
                else if (change.type === vscode.FileChangeType.Created) {
                    readFileCache.delete(change.uri);
                    statCache.delete(change.uri);
                    const dir = change.uri.substring(0, change.uri.lastIndexOf('/'));
                    readDirectoryCache.delete(dir);
                }
            }
        });
        return {
            readFile: uri => {
                if (!readFileCache.has(uri)) {
                    readFileCache.set(uri, fs.readFile(uri));
                }
                return readFileCache.get(uri);
            },
            stat: uri => {
                if (!statCache.has(uri)) {
                    statCache.set(uri, fs.stat(uri));
                }
                return statCache.get(uri);
            },
            readDirectory: uri => {
                if (!readDirectoryCache.has(uri)) {
                    readDirectoryCache.set(uri, fs.readDirectory(uri));
                }
                return readDirectoryCache.get(uri);
            },
        };
    }
    async function createLanguageServiceHost() {
        const ts = options.typescript ? context.server.runtimeEnv.loadTypescript(options.typescript.tsdk) : undefined;
        const tsLocalized = options.typescript && initParams.locale ? await context.server.runtimeEnv.loadTypescriptLocalized(options.typescript.tsdk, initParams.locale) : undefined;
        const cancelTokenHost = (0, cancellationPipe_1.createCancellationTokenHost)(options.cancellationPipeName);
        projects = (0, workspaces_1.createWorkspaces)({
            ...context,
            workspaces: {
                ts,
                tsLocalized,
                initParams,
                initOptions: options,
                documents,
                cancelTokenHost,
                plugins,
            },
        });
        for (const root of roots) {
            projects.add(root);
        }
        (await Promise.resolve().then(() => require('./features/customFeatures'))).register(connection, projects, context.server.runtimeEnv);
        (await Promise.resolve().then(() => require('./features/languageFeatures'))).register(connection, projects, initParams, options, cancelTokenHost, getSemanticTokensLegend(), context.server.runtimeEnv, documents);
        for (const plugin of plugins) {
            plugin.onInitialized?.(getLanguageService, context.server.runtimeEnv);
        }
        async function getLanguageService(uri) {
            const project = (await projects.getProject(uri))?.project;
            return project?.getLanguageService();
        }
    }
    function getSemanticTokensLegend() {
        if (!options.semanticTokensLegend) {
            return language_service_1.standardSemanticTokensLegend;
        }
        return {
            tokenTypes: [...language_service_1.standardSemanticTokensLegend.tokenTypes, ...options.semanticTokensLegend.tokenTypes],
            tokenModifiers: [...language_service_1.standardSemanticTokensLegend.tokenModifiers, ...options.semanticTokensLegend.tokenModifiers],
        };
    }
}
exports.startCommonLanguageServer = startCommonLanguageServer;
//# sourceMappingURL=server.js.map