"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.startServer = startServer;
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("./lib/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;
    let pendingWatchPatterns = [];
    let watchDirectory = (patterns) => {
        pendingWatchPatterns = patterns;
    };
    // Include Svelte files to better deal with scenarios such as switching git branches
    // where files that are not opened in the client could change
    const watchExtensions = ['.ts', '.js', '.mts', '.mjs', '.cjs', '.cts', '.json', '.svelte'];
    const nonRecursiveWatchPattern = '*.{' + watchExtensions.map((ext) => ext.slice(1)).join(',') + '}';
    const recursiveWatchPattern = '**/' + nonRecursiveWatchPattern;
    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?.dynamicRegistration) {
            const workspacePaths = workspaceUris.map(utils_1.urlToPath).filter(utils_1.isNotNullOrUndefined);
            watcher = new FallbackWatcher_1.FallbackWatcher(watchExtensions, workspacePaths);
            watcher.onDidChangeWatchedFiles(onDidChangeWatchedFiles);
            watchDirectory = (patterns) => {
                watcher?.watchDirectory(patterns);
            };
        }
        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
        });
        const fileSystemProvider = new FileSystemProvider_1.FileSystemProvider();
        const workspaceFolders = evt.workspaceFolders ?? [{ name: '', uri: evt.rootUri ?? '' }];
        // 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, fileSystemProvider, workspaceFolders));
        const cssLanguageServices = (0, service_1.createLanguageServices)({
            clientCapabilities: evt.capabilities,
            fileSystemProvider: fileSystemProvider
        });
        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,
            nonRecursiveWatchPattern,
            watchDirectory: (patterns) => watchDirectory(patterns),
            reportConfigError(diagnostic) {
                connection?.sendDiagnostics(diagnostic);
            }
        }), normalizedWorkspaceUris, docManager));
        const clientSupportApplyEditCommand = !!evt.capabilities.workspace?.applyEdit;
        const clientCodeActionCapabilities = evt.capabilities.textDocument?.codeAction;
        const clientSupportedCodeActionKinds = clientCodeActionCapabilities?.codeActionLiteralSupport?.codeActionKind.valueSet;
        if (evt.capabilities.textDocument?.diagnostic) {
            const refreshDiagnostics = evt.capabilities.workspace?.diagnostics?.refreshSupport;
            diagnosticsManager = new DiagnosticsManager_1.PullDiagnosticsManager(connection.sendDiagnostics, refreshDiagnostics
                ? () => connection.sendRequest(vscode_languageserver_1.DiagnosticRefreshRequest.method)
                : () => { });
            connection.onRequest(vscode_languageserver_1.DocumentDiagnosticRequest.type, async (evt, token) => {
                await new Promise((resolve) => setTimeout(resolve, 200));
                if (token.isCancellationRequested) {
                    return null;
                }
                const diagnostics = await pluginHost.getDiagnosticsForPullMode(evt.textDocument, evt.previousResultId, token);
                return diagnostics;
            });
        }
        else {
            connection.onDidSaveTextDocument(diagnosticsManager.scheduleUpdateAll.bind(diagnosticsManager));
        }
        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,
                            CodeActionsProvider_1.ADD_MISSING_IMPORTS_CODE_ACTION_KIND,
                            CodeActionsProvider_1.REMOVE_UNUSED_IMPORTS_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',
                            'migrate_to_svelte_5',
                            '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,
                foldingRangeProvider: true,
                codeLensProvider: {
                    resolveProvider: true
                },
                documentHighlightProvider: evt.initializationOptions?.configuration?.svelte?.plugin?.svelte
                    ?.documentHighlight?.enable ?? true,
                workspaceSymbolProvider: true,
                diagnosticProvider: {
                    interFileDependencies: true,
                    workspaceDiagnostics: false
                }
            }
        };
    });
    connection.onInitialized(() => {
        if (watcher) {
            return;
        }
        const didChangeWatchedFiles = configManager.getClientCapabilities()?.workspace?.didChangeWatchedFiles;
        if (!didChangeWatchedFiles?.dynamicRegistration) {
            return;
        }
        // still watch the roots since some files might be referenced but not included in the project
        connection?.client.register(vscode_languageserver_1.DidChangeWatchedFilesNotification.type, {
            watchers: [
                {
                    // Editors have exclude configs, such as VSCode with `files.watcherExclude`,
                    // which means it's safe to watch recursively here
                    globPattern: recursiveWatchPattern
                }
            ]
        });
        if (didChangeWatchedFiles.relativePatternSupport) {
            watchDirectory = (patterns) => {
                connection?.client.register(vscode_languageserver_1.DidChangeWatchedFilesNotification.type, {
                    watchers: patterns.map((pattern) => ({
                        globPattern: pattern
                    }))
                });
            };
            if (pendingWatchPatterns.length) {
                watchDirectory(pendingWatchPatterns);
                pendingWatchPatterns = [];
            }
        }
    });
    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.openClientDocument(evt.textDocument);
        diagnosticsManager.scheduleUpdate(document);
    });
    connection.onDidCloseTextDocument((evt) => docManager.closeDocument(evt.textDocument.uri));
    connection.onDidChangeTextDocument((evt) => {
        diagnosticsManager.cancelStarted(evt.textDocument.uri);
        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) => {
        if (configManager.getClientCapabilities()?.textDocument?.documentSymbol
            ?.hierarchicalDocumentSymbolSupport) {
            return pluginHost.getHierarchicalDocumentSymbols(evt.textDocument, cancellationToken);
        }
        else {
            return pluginHost.getDocumentSymbols(evt.textDocument, cancellationToken);
        }
    });
    connection.onDefinition((evt) => pluginHost.getDefinitions(evt.textDocument, evt.position));
    connection.onReferences((evt, cancellationToken) => pluginHost.findReferences(evt.textDocument, evt.position, evt.context, cancellationToken));
    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, cancellationToken) => pluginHost.getImplementation(evt.textDocument, evt.position, cancellationToken));
    connection.onTypeDefinition((evt) => pluginHost.getTypeDefinition(evt.textDocument, evt.position));
    connection.onFoldingRanges((evt) => pluginHost.getFoldingRanges(evt.textDocument));
    connection.onCodeLens((evt) => pluginHost.getCodeLens(evt.textDocument));
    connection.onCodeLensResolve((codeLens, token) => {
        const data = codeLens.data;
        if (!data) {
            return codeLens;
        }
        return pluginHost.resolveCodeLens(data, codeLens, token);
    });
    connection.onDocumentHighlight((evt) => pluginHost.findDocumentHighlight(evt.textDocument, evt.position));
    connection.onWorkspaceSymbol((evt, token) => pluginHost.getWorkspaceSymbols(evt.query, token));
    let diagnosticsManager = new DiagnosticsManager_1.PushDiagnosticsManager(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.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', (document) => diagnosticsManager.scheduleUpdate(document));
    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;
        }
        const compiled = await sveltePlugin.getCompiledResult(doc);
        if (compiled) {
            const js = compiled.js;
            const css = compiled.css;
            return { js, css };
        }
        else {
            return null;
        }
    });
    connection.listen();
}
//# sourceMappingURL=server.js.map