"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?.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?.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) => {
        const workspaceUris = evt.workspaceFolders?.map((folder) => folder.uri.toString()) ?? [
            evt.rootUri ?? ''
        ];
        logger_1.Logger.log('Initialize language server at ', workspaceUris.join(', '));
        if (workspaceUris.length === 0) {
            logger_1.Logger.error('No workspace path set');
        }
        if (!evt.capabilities.workspace?.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 = evt.initializationOptions?.isTrusted ?? 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((evt.initializationOptions?.configuration?.svelte ||
            evt.initializationOptions?.config)?.['language-server']?.debug);
        // Backwards-compatible way of setting initialization options (first `||` is the old style)
        configManager.update(evt.initializationOptions?.configuration?.svelte?.plugin ||
            evt.initializationOptions?.config ||
            {});
        configManager.updateTsJsUserPreferences(evt.initializationOptions?.configuration ||
            evt.initializationOptions?.typescriptConfig ||
            {});
        configManager.updateTsJsFormateConfig(evt.initializationOptions?.configuration ||
            evt.initializationOptions?.typescriptConfig ||
            {});
        configManager.updateEmmetConfig(evt.initializationOptions?.configuration?.emmet ||
            evt.initializationOptions?.emmetConfig ||
            {});
        configManager.updatePrettierConfig(evt.initializationOptions?.configuration?.prettier ||
            evt.initializationOptions?.prettierConfig ||
            {});
        // no old style as these were added later
        configManager.updateCssConfig(evt.initializationOptions?.configuration?.css);
        configManager.updateScssConfig(evt.initializationOptions?.configuration?.scss);
        configManager.updateLessConfig(evt.initializationOptions?.configuration?.less);
        configManager.updateHTMLConfig(evt.initializationOptions?.configuration?.html);
        configManager.updateClientCapabilities(evt.capabilities);
        pluginHost.initialize({
            filterIncompleteCompletions: !evt.initializationOptions?.dontFilterIncompleteCompletions,
            definitionLinkSupport: !!evt.capabilities.textDocument?.definition?.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 = evt.workspaceFolders ?? [{ name: '', uri: evt.rootUri ?? '' }];
        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,
            watch: true
        }), normalizedWorkspaceUris));
        const clientSupportApplyEditCommand = !!evt.capabilities.workspace?.applyEdit;
        const clientCodeActionCapabilities = evt.capabilities.textDocument?.codeAction;
        const clientSupportedCodeActionKinds = clientCodeActionCapabilities?.codeActionLiteralSupport?.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?.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 &&
                            evt.initializationOptions?.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: evt.capabilities.textDocument?.rename?.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
            }
        };
    });
    connection.onInitialized(() => {
        if (!watcher &&
            configManager.getClientCapabilities()?.workspace?.didChangeWatchedFiles
                ?.dynamicRegistration) {
            connection?.client.register(vscode_languageserver_1.DidChangeWatchedFilesNotification.type, {
                watchers: [
                    {
                        globPattern: '**/*.{ts,js,mts,mjs,cjs,cts,json}'
                    }
                ]
            });
        }
    });
    function notifyTsServiceExceedSizeLimit() {
        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?.dispose();
    });
    connection.onRenameRequest((req) => pluginHost.rename(req.textDocument, req.position, req.newName));
    connection.onPrepareRename((req) => pluginHost.prepareRename(req.textDocument, req.position));
    connection.onDidChangeConfiguration(({ settings }) => {
        configManager.update(settings.svelte?.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(settings.svelte?.['language-server']?.debug);
    });
    connection.onDidOpenTextDocument((evt) => {
        const document = docManager.openDocument(evt.textDocument);
        docManager.markAsOpenedInClient(evt.textDocument.uri);
        diagnosticsManager.scheduleUpdate(document);
    });
    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) => {
        const result = await pluginHost.executeCommand({ uri: evt.arguments?.[0] }, evt.command, evt.arguments);
        if (vscode_languageserver_1.WorkspaceEdit.is(result)) {
            const edit = { edit: result };
            connection?.sendRequest(vscode_languageserver_1.ApplyWorkspaceEditRequest.type.method, edit);
        }
        else if (result) {
            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 refreshSemanticTokens = (0, utils_1.debounceThrottle)(() => {
        if (configManager?.getClientCapabilities()?.workspace?.semanticTokens?.refreshSupport) {
            connection?.sendRequest(vscode_languageserver_1.SemanticTokensRefreshRequest.method);
        }
    }, 1500);
    const refreshInlayHints = (0, utils_1.debounceThrottle)(() => {
        if (configManager?.getClientCapabilities()?.workspace?.inlayHint?.refreshSupport) {
            connection?.sendRequest(vscode_languageserver_1.InlayHintRefreshRequest.method);
        }
    }, 1500);
    const refreshCrossFilesSemanticFeatures = () => {
        diagnosticsManager.scheduleUpdateAll();
        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(diagnosticsManager.scheduleUpdateAll);
    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', diagnosticsManager.scheduleUpdate.bind(diagnosticsManager));
    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