"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.startServer = void 0;
const vscode_languageserver_1 = require("vscode-languageserver");
const node_1 = require("vscode-languageserver/node");
const DiagnosticsManager_1 = require("./lib/DiagnosticsManager");
const documents_1 = require("./lib/documents");
const semanticTokenLegend_1 = require("./lib/semanticToken/semanticTokenLegend");
const logger_1 = require("./logger");
const ls_config_1 = require("./ls-config");
const plugins_1 = require("./plugins");
const utils_1 = require("./utils");
const FallbackWatcher_1 = require("./lib/FallbackWatcher");
const configLoader_1 = require("./lib/documents/configLoader");
const importPackage_1 = require("./importPackage");
const CodeActionsProvider_1 = require("./plugins/typescript/features/CodeActionsProvider");
const service_1 = require("./plugins/css/service");
const FileSystemProvider_1 = require("./plugins/css/FileSystemProvider");
var TagCloseRequest;
(function (TagCloseRequest) {
    TagCloseRequest.type = new vscode_languageserver_1.RequestType('html/tag');
})(TagCloseRequest || (TagCloseRequest = {}));
/**
 * Starts the language server.
 *
 * @param options Options to customize behavior
 */
function startServer(options) {
    let connection = options === null || options === void 0 ? void 0 : options.connection;
    if (!connection) {
        if (process.argv.includes('--stdio')) {
            console.log = (...args) => {
                console.warn(...args);
            };
            connection = (0, node_1.createConnection)(process.stdin, process.stdout);
        }
        else {
            connection = (0, node_1.createConnection)(new node_1.IPCMessageReader(process), new node_1.IPCMessageWriter(process));
        }
    }
    if ((options === null || options === void 0 ? void 0 : options.logErrorsOnly) !== undefined) {
        logger_1.Logger.setLogErrorsOnly(options.logErrorsOnly);
    }
    const docManager = new documents_1.DocumentManager((textDocument) => new documents_1.Document(textDocument.uri, textDocument.text));
    const configManager = new ls_config_1.LSConfigManager();
    const pluginHost = new plugins_1.PluginHost(docManager);
    let sveltePlugin = undefined;
    let watcher;
    connection.onInitialize((evt) => {
        var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19;
        const workspaceUris = (_b = (_a = evt.workspaceFolders) === null || _a === void 0 ? void 0 : _a.map((folder) => folder.uri.toString())) !== null && _b !== void 0 ? _b : [
            (_c = evt.rootUri) !== null && _c !== void 0 ? _c : ''
        ];
        logger_1.Logger.log('Initialize language server at ', workspaceUris.join(', '));
        if (workspaceUris.length === 0) {
            logger_1.Logger.error('No workspace path set');
        }
        if (!((_d = evt.capabilities.workspace) === null || _d === void 0 ? void 0 : _d.didChangeWatchedFiles)) {
            const workspacePaths = workspaceUris.map(utils_1.urlToPath).filter(utils_1.isNotNullOrUndefined);
            watcher = new FallbackWatcher_1.FallbackWatcher('**/*.{ts,js}', workspacePaths);
            watcher.onDidChangeWatchedFiles(onDidChangeWatchedFiles);
        }
        const isTrusted = (_f = (_e = evt.initializationOptions) === null || _e === void 0 ? void 0 : _e.isTrusted) !== null && _f !== void 0 ? _f : true;
        configLoader_1.configLoader.setDisabled(!isTrusted);
        (0, importPackage_1.setIsTrusted)(isTrusted);
        configManager.updateIsTrusted(isTrusted);
        if (!isTrusted) {
            logger_1.Logger.log('Workspace is not trusted, running with reduced capabilities.');
        }
        logger_1.Logger.setDebug((_l = (_k = (((_h = (_g = evt.initializationOptions) === null || _g === void 0 ? void 0 : _g.configuration) === null || _h === void 0 ? void 0 : _h.svelte) ||
            ((_j = evt.initializationOptions) === null || _j === void 0 ? void 0 : _j.config))) === null || _k === void 0 ? void 0 : _k['language-server']) === null || _l === void 0 ? void 0 : _l.debug);
        // Backwards-compatible way of setting initialization options (first `||` is the old style)
        configManager.update(((_p = (_o = (_m = evt.initializationOptions) === null || _m === void 0 ? void 0 : _m.configuration) === null || _o === void 0 ? void 0 : _o.svelte) === null || _p === void 0 ? void 0 : _p.plugin) ||
            ((_q = evt.initializationOptions) === null || _q === void 0 ? void 0 : _q.config) ||
            {});
        configManager.updateTsJsUserPreferences(((_r = evt.initializationOptions) === null || _r === void 0 ? void 0 : _r.configuration) ||
            ((_s = evt.initializationOptions) === null || _s === void 0 ? void 0 : _s.typescriptConfig) ||
            {});
        configManager.updateTsJsFormateConfig(((_t = evt.initializationOptions) === null || _t === void 0 ? void 0 : _t.configuration) ||
            ((_u = evt.initializationOptions) === null || _u === void 0 ? void 0 : _u.typescriptConfig) ||
            {});
        configManager.updateEmmetConfig(((_w = (_v = evt.initializationOptions) === null || _v === void 0 ? void 0 : _v.configuration) === null || _w === void 0 ? void 0 : _w.emmet) ||
            ((_x = evt.initializationOptions) === null || _x === void 0 ? void 0 : _x.emmetConfig) ||
            {});
        configManager.updatePrettierConfig(((_z = (_y = evt.initializationOptions) === null || _y === void 0 ? void 0 : _y.configuration) === null || _z === void 0 ? void 0 : _z.prettier) ||
            ((_0 = evt.initializationOptions) === null || _0 === void 0 ? void 0 : _0.prettierConfig) ||
            {});
        // no old style as these were added later
        configManager.updateCssConfig((_2 = (_1 = evt.initializationOptions) === null || _1 === void 0 ? void 0 : _1.configuration) === null || _2 === void 0 ? void 0 : _2.css);
        configManager.updateScssConfig((_4 = (_3 = evt.initializationOptions) === null || _3 === void 0 ? void 0 : _3.configuration) === null || _4 === void 0 ? void 0 : _4.scss);
        configManager.updateLessConfig((_6 = (_5 = evt.initializationOptions) === null || _5 === void 0 ? void 0 : _5.configuration) === null || _6 === void 0 ? void 0 : _6.less);
        configManager.updateHTMLConfig((_8 = (_7 = evt.initializationOptions) === null || _7 === void 0 ? void 0 : _7.configuration) === null || _8 === void 0 ? void 0 : _8.html);
        configManager.updateClientCapabilities(evt.capabilities);
        pluginHost.initialize({
            filterIncompleteCompletions: !((_9 = evt.initializationOptions) === null || _9 === void 0 ? void 0 : _9.dontFilterIncompleteCompletions),
            definitionLinkSupport: !!((_11 = (_10 = evt.capabilities.textDocument) === null || _10 === void 0 ? void 0 : _10.definition) === null || _11 === void 0 ? void 0 : _11.linkSupport)
        });
        // Order of plugin registration matters for FirstNonNull, which affects for example hover info
        pluginHost.register((sveltePlugin = new plugins_1.SveltePlugin(configManager)));
        pluginHost.register(new plugins_1.HTMLPlugin(docManager, configManager));
        const cssLanguageServices = (0, service_1.createLanguageServices)({
            clientCapabilities: evt.capabilities,
            fileSystemProvider: new FileSystemProvider_1.FileSystemProvider()
        });
        const workspaceFolders = (_12 = evt.workspaceFolders) !== null && _12 !== void 0 ? _12 : [{ name: '', uri: (_13 = evt.rootUri) !== null && _13 !== void 0 ? _13 : '' }];
        pluginHost.register(new plugins_1.CSSPlugin(docManager, configManager, workspaceFolders, cssLanguageServices));
        const normalizedWorkspaceUris = workspaceUris.map(utils_1.normalizeUri);
        pluginHost.register(new plugins_1.TypeScriptPlugin(configManager, new plugins_1.LSAndTSDocResolver(docManager, normalizedWorkspaceUris, configManager, {
            notifyExceedSizeLimit: notifyTsServiceExceedSizeLimit,
            onProjectReloaded: refreshCrossFilesSemanticFeatures,
            watchTsConfig: true
        }), normalizedWorkspaceUris));
        const clientSupportApplyEditCommand = !!((_14 = evt.capabilities.workspace) === null || _14 === void 0 ? void 0 : _14.applyEdit);
        const clientCodeActionCapabilities = (_15 = evt.capabilities.textDocument) === null || _15 === void 0 ? void 0 : _15.codeAction;
        const clientSupportedCodeActionKinds = (_16 = clientCodeActionCapabilities === null || clientCodeActionCapabilities === void 0 ? void 0 : clientCodeActionCapabilities.codeActionLiteralSupport) === null || _16 === void 0 ? void 0 : _16.codeActionKind.valueSet;
        return {
            capabilities: {
                textDocumentSync: {
                    openClose: true,
                    change: vscode_languageserver_1.TextDocumentSyncKind.Incremental,
                    save: {
                        includeText: false
                    }
                },
                hoverProvider: true,
                completionProvider: {
                    resolveProvider: true,
                    triggerCharacters: [
                        '.',
                        '"',
                        "'",
                        '`',
                        '/',
                        '@',
                        '<',
                        // Emmet
                        '>',
                        '*',
                        '#',
                        '$',
                        '+',
                        '^',
                        '(',
                        '[',
                        '@',
                        '-',
                        // No whitespace because
                        // it makes for weird/too many completions
                        // of other completion providers
                        // Svelte
                        ':',
                        '|'
                    ],
                    completionItem: {
                        labelDetailsSupport: true
                    }
                },
                documentFormattingProvider: true,
                colorProvider: true,
                documentSymbolProvider: true,
                definitionProvider: true,
                codeActionProvider: (clientCodeActionCapabilities === null || clientCodeActionCapabilities === void 0 ? void 0 : clientCodeActionCapabilities.codeActionLiteralSupport)
                    ? {
                        codeActionKinds: [
                            vscode_languageserver_1.CodeActionKind.QuickFix,
                            vscode_languageserver_1.CodeActionKind.SourceOrganizeImports,
                            CodeActionsProvider_1.SORT_IMPORT_CODE_ACTION_KIND,
                            ...(clientSupportApplyEditCommand ? [vscode_languageserver_1.CodeActionKind.Refactor] : [])
                        ].filter(clientSupportedCodeActionKinds &&
                            ((_17 = evt.initializationOptions) === null || _17 === void 0 ? void 0 : _17.shouldFilterCodeActionKind)
                            ? (kind) => clientSupportedCodeActionKinds.includes(kind)
                            : () => true),
                        resolveProvider: true
                    }
                    : true,
                executeCommandProvider: clientSupportApplyEditCommand
                    ? {
                        commands: [
                            'function_scope_0',
                            'function_scope_1',
                            'function_scope_2',
                            'function_scope_3',
                            'constant_scope_0',
                            'constant_scope_1',
                            'constant_scope_2',
                            'constant_scope_3',
                            'extract_to_svelte_component',
                            'Infer function return type'
                        ]
                    }
                    : undefined,
                renameProvider: ((_19 = (_18 = evt.capabilities.textDocument) === null || _18 === void 0 ? void 0 : _18.rename) === null || _19 === void 0 ? void 0 : _19.prepareSupport)
                    ? { prepareProvider: true }
                    : true,
                referencesProvider: true,
                selectionRangeProvider: true,
                signatureHelpProvider: {
                    triggerCharacters: ['(', ',', '<'],
                    retriggerCharacters: [')']
                },
                semanticTokensProvider: {
                    legend: (0, semanticTokenLegend_1.getSemanticTokenLegends)(),
                    range: true,
                    full: true
                },
                linkedEditingRangeProvider: true,
                implementationProvider: true,
                typeDefinitionProvider: true,
                inlayHintProvider: true,
                callHierarchyProvider: true
            }
        };
    });
    function notifyTsServiceExceedSizeLimit() {
        connection === null || connection === void 0 ? void 0 : connection.sendNotification(vscode_languageserver_1.ShowMessageNotification.type, {
            message: 'Svelte language server detected a large amount of JS/Svelte files. ' +
                'To enable project-wide JavaScript/TypeScript language features for Svelte files, ' +
                'exclude large folders in the tsconfig.json or jsconfig.json with source files that you do not work on.',
            type: vscode_languageserver_1.MessageType.Warning
        });
    }
    connection.onExit(() => {
        watcher === null || watcher === void 0 ? void 0 : watcher.dispose();
    });
    connection.onRenameRequest((req) => pluginHost.rename(req.textDocument, req.position, req.newName));
    connection.onPrepareRename((req) => pluginHost.prepareRename(req.textDocument, req.position));
    connection.onDidChangeConfiguration(({ settings }) => {
        var _a, _b, _c;
        configManager.update((_a = settings.svelte) === null || _a === void 0 ? void 0 : _a.plugin);
        configManager.updateTsJsUserPreferences(settings);
        configManager.updateTsJsFormateConfig(settings);
        configManager.updateEmmetConfig(settings.emmet);
        configManager.updatePrettierConfig(settings.prettier);
        configManager.updateCssConfig(settings.css);
        configManager.updateScssConfig(settings.scss);
        configManager.updateLessConfig(settings.less);
        configManager.updateHTMLConfig(settings.html);
        logger_1.Logger.setDebug((_c = (_b = settings.svelte) === null || _b === void 0 ? void 0 : _b['language-server']) === null || _c === void 0 ? void 0 : _c.debug);
    });
    connection.onDidOpenTextDocument((evt) => {
        docManager.openDocument(evt.textDocument);
        docManager.markAsOpenedInClient(evt.textDocument.uri);
    });
    connection.onDidCloseTextDocument((evt) => docManager.closeDocument(evt.textDocument.uri));
    connection.onDidChangeTextDocument((evt) => {
        docManager.updateDocument(evt.textDocument, evt.contentChanges);
        pluginHost.didUpdateDocument();
    });
    connection.onHover((evt) => pluginHost.doHover(evt.textDocument, evt.position));
    connection.onCompletion((evt, cancellationToken) => pluginHost.getCompletions(evt.textDocument, evt.position, evt.context, cancellationToken));
    connection.onDocumentFormatting((evt) => pluginHost.formatDocument(evt.textDocument, evt.options));
    connection.onRequest(TagCloseRequest.type, (evt) => pluginHost.doTagComplete(evt.textDocument, evt.position));
    connection.onDocumentColor((evt) => pluginHost.getDocumentColors(evt.textDocument));
    connection.onColorPresentation((evt) => pluginHost.getColorPresentations(evt.textDocument, evt.range, evt.color));
    connection.onDocumentSymbol((evt, cancellationToken) => pluginHost.getDocumentSymbols(evt.textDocument, cancellationToken));
    connection.onDefinition((evt) => pluginHost.getDefinitions(evt.textDocument, evt.position));
    connection.onReferences((evt) => pluginHost.findReferences(evt.textDocument, evt.position, evt.context));
    connection.onCodeAction((evt, cancellationToken) => pluginHost.getCodeActions(evt.textDocument, evt.range, evt.context, cancellationToken));
    connection.onExecuteCommand(async (evt) => {
        var _a;
        const result = await pluginHost.executeCommand({ uri: (_a = evt.arguments) === null || _a === void 0 ? void 0 : _a[0] }, evt.command, evt.arguments);
        if (vscode_languageserver_1.WorkspaceEdit.is(result)) {
            const edit = { edit: result };
            connection === null || connection === void 0 ? void 0 : connection.sendRequest(vscode_languageserver_1.ApplyWorkspaceEditRequest.type.method, edit);
        }
        else if (result) {
            connection === null || connection === void 0 ? void 0 : connection.sendNotification(vscode_languageserver_1.ShowMessageNotification.type.method, {
                message: result,
                type: vscode_languageserver_1.MessageType.Error
            });
        }
    });
    connection.onCodeActionResolve((codeAction, cancellationToken) => {
        const data = codeAction.data;
        return pluginHost.resolveCodeAction(data, codeAction, cancellationToken);
    });
    connection.onCompletionResolve((completionItem, cancellationToken) => {
        const data = completionItem.data;
        if (!data) {
            return completionItem;
        }
        return pluginHost.resolveCompletion(data, completionItem, cancellationToken);
    });
    connection.onSignatureHelp((evt, cancellationToken) => pluginHost.getSignatureHelp(evt.textDocument, evt.position, evt.context, cancellationToken));
    connection.onSelectionRanges((evt) => pluginHost.getSelectionRanges(evt.textDocument, evt.positions));
    connection.onImplementation((evt) => pluginHost.getImplementation(evt.textDocument, evt.position));
    connection.onTypeDefinition((evt) => pluginHost.getTypeDefinition(evt.textDocument, evt.position));
    const diagnosticsManager = new DiagnosticsManager_1.DiagnosticsManager(connection.sendDiagnostics, docManager, pluginHost.getDiagnostics.bind(pluginHost));
    const updateAllDiagnostics = (0, utils_1.debounceThrottle)(() => diagnosticsManager.updateAll(), 1000);
    const refreshSemanticTokens = (0, utils_1.debounceThrottle)(() => {
        var _a, _b, _c;
        if ((_c = (_b = (_a = configManager === null || configManager === void 0 ? void 0 : configManager.getClientCapabilities()) === null || _a === void 0 ? void 0 : _a.workspace) === null || _b === void 0 ? void 0 : _b.semanticTokens) === null || _c === void 0 ? void 0 : _c.refreshSupport) {
            connection === null || connection === void 0 ? void 0 : connection.sendRequest(vscode_languageserver_1.SemanticTokensRefreshRequest.method);
        }
    }, 1500);
    const refreshInlayHints = (0, utils_1.debounceThrottle)(() => {
        var _a, _b, _c;
        if ((_c = (_b = (_a = configManager === null || configManager === void 0 ? void 0 : configManager.getClientCapabilities()) === null || _a === void 0 ? void 0 : _a.workspace) === null || _b === void 0 ? void 0 : _b.inlayHint) === null || _c === void 0 ? void 0 : _c.refreshSupport) {
            connection === null || connection === void 0 ? void 0 : connection.sendRequest(vscode_languageserver_1.InlayHintRefreshRequest.method);
        }
    }, 1500);
    const refreshCrossFilesSemanticFeatures = () => {
        updateAllDiagnostics();
        refreshInlayHints();
        refreshSemanticTokens();
    };
    connection.onDidChangeWatchedFiles(onDidChangeWatchedFiles);
    function onDidChangeWatchedFiles(para) {
        const onWatchFileChangesParas = para.changes
            .map((change) => ({
            fileName: (0, utils_1.urlToPath)(change.uri),
            changeType: change.type
        }))
            .filter((change) => !!change.fileName);
        pluginHost.onWatchFileChanges(onWatchFileChangesParas);
        refreshCrossFilesSemanticFeatures();
    }
    connection.onDidSaveTextDocument(updateAllDiagnostics);
    connection.onNotification('$/onDidChangeTsOrJsFile', async (e) => {
        const path = (0, utils_1.urlToPath)(e.uri);
        if (path) {
            pluginHost.updateTsOrJsFile(path, e.changes);
        }
        refreshCrossFilesSemanticFeatures();
    });
    connection.onRequest(vscode_languageserver_1.SemanticTokensRequest.type, (evt, cancellationToken) => pluginHost.getSemanticTokens(evt.textDocument, undefined, cancellationToken));
    connection.onRequest(vscode_languageserver_1.SemanticTokensRangeRequest.type, (evt, cancellationToken) => pluginHost.getSemanticTokens(evt.textDocument, evt.range, cancellationToken));
    connection.onRequest(vscode_languageserver_1.LinkedEditingRangeRequest.type, async (evt) => await pluginHost.getLinkedEditingRanges(evt.textDocument, evt.position));
    connection.onRequest(vscode_languageserver_1.InlayHintRequest.type, (evt, cancellationToken) => pluginHost.getInlayHints(evt.textDocument, evt.range, cancellationToken));
    connection.onRequest(vscode_languageserver_1.CallHierarchyPrepareRequest.type, async (evt, token) => await pluginHost.prepareCallHierarchy(evt.textDocument, evt.position, token));
    connection.onRequest(vscode_languageserver_1.CallHierarchyIncomingCallsRequest.type, async (evt, token) => await pluginHost.getIncomingCalls(evt.item, token));
    connection.onRequest(vscode_languageserver_1.CallHierarchyOutgoingCallsRequest.type, async (evt, token) => await pluginHost.getOutgoingCalls(evt.item, token));
    docManager.on('documentChange', (0, utils_1.debounceThrottle)(async (document) => diagnosticsManager.update(document), 750));
    docManager.on('documentClose', (document) => diagnosticsManager.removeDiagnostics(document));
    // The language server protocol does not have a specific "did rename/move files" event,
    // so we create our own in the extension client and handle it here
    connection.onRequest('$/getEditsForFileRename', async (fileRename) => pluginHost.updateImports(fileRename));
    connection.onRequest('$/getFileReferences', async (uri) => {
        return pluginHost.fileReferences(uri);
    });
    connection.onRequest('$/getComponentReferences', async (uri) => {
        return pluginHost.findComponentReferences(uri);
    });
    connection.onRequest('$/getCompiledCode', async (uri) => {
        const doc = docManager.get(uri);
        if (!doc) {
            return null;
        }
        if (doc) {
            const compiled = await sveltePlugin.getCompiledResult(doc);
            if (compiled) {
                const js = compiled.js;
                const css = compiled.css;
                return { js, css };
            }
            else {
                return null;
            }
        }
    });
    connection.listen();
}
exports.startServer = startServer;
//# sourceMappingURL=server.js.map