"use strict";
/*
 * Copyright (C) 2017, 2018 TypeFox and others.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.LspServer = void 0;
const path = __importStar(require("path"));
const tempy_1 = __importDefault(require("tempy"));
const lsp = __importStar(require("vscode-languageserver/node"));
const lspcalls = __importStar(require("./lsp-protocol.calls.proposed"));
const lspsemanticTokens = __importStar(require("./semantic-tokens"));
const fs = __importStar(require("fs-extra"));
const p_debounce_1 = __importDefault(require("p-debounce"));
const logger_1 = require("./logger");
const tsp_client_1 = require("./tsp-client");
const diagnostic_queue_1 = require("./diagnostic-queue");
const protocol_translation_1 = require("./protocol-translation");
const document_1 = require("./document");
const completion_1 = require("./completion");
const hover_1 = require("./hover");
const commands_1 = require("./commands");
const quickfix_1 = require("./quickfix");
const refactor_1 = require("./refactor");
const organize_imports_1 = require("./organize-imports");
const document_symbol_1 = require("./document-symbol");
const calls_1 = require("./calls");
const versionProvider_1 = require("./utils/versionProvider");
const fix_all_1 = require("./features/fix-all");
const types_1 = require("./utils/types");
class ServerInitializingIndicator {
    constructor(lspClient) {
        this.lspClient = lspClient;
    }
    reset() {
        if (this._loadingProjectName) {
            this._loadingProjectName = undefined;
            if (this._progressReporter) {
                this._progressReporter.end();
                this._progressReporter = undefined;
            }
        }
    }
    startedLoadingProject(projectName) {
        // TS projects are loaded sequentially. Cancel existing task because it should always be resolved before
        // the incoming project loading task is.
        this.reset();
        this._loadingProjectName = projectName;
        this._progressReporter = this.lspClient.createProgressReporter();
        this._progressReporter.begin('Initializing JS/TS language features…');
    }
    finishedLoadingProject(projectName) {
        if (this._loadingProjectName === projectName) {
            this._loadingProjectName = undefined;
            if (this._progressReporter) {
                this._progressReporter.end();
                this._progressReporter = undefined;
            }
        }
    }
}
class LspServer {
    constructor(options) {
        this.options = options;
        this.documents = new document_1.LspDocuments();
        // True if diagnostic request is currently debouncing or the request is in progress. False only if there are
        // no pending requests.
        this.pendingDebouncedRequest = false;
        this.doRequestDiagnosticsDebounced = (0, p_debounce_1.default)(() => this.doRequestDiagnostics(), 200);
        this.logger = new logger_1.PrefixingLogger(options.logger, '[lspserver]');
        this.workspaceConfiguration = {};
    }
    closeAll() {
        for (const file of [...this.documents.files]) {
            this.closeDocument(file);
        }
    }
    findTypescriptVersion() {
        const typescriptVersionProvider = new versionProvider_1.TypeScriptVersionProvider(this.options, this.logger);
        // User-provided tsserver path.
        const userSettingVersion = typescriptVersionProvider.getUserSettingVersion();
        if (userSettingVersion) {
            if (userSettingVersion.isValid) {
                return userSettingVersion;
            }
            this.logger.warn(`Typescript specified through --tsserver-path ignored due to invalid path "${userSettingVersion.path}"`);
        }
        // Workspace version.
        if (this.workspaceRoot) {
            const workspaceVersion = typescriptVersionProvider.getWorkspaceVersion([this.workspaceRoot]);
            if (workspaceVersion) {
                return workspaceVersion;
            }
        }
        // Bundled version
        const bundledVersion = typescriptVersionProvider.bundledVersion();
        if (bundledVersion && bundledVersion.isValid) {
            return bundledVersion;
        }
        return null;
    }
    initialize(params) {
        var _a, _b, _c;
        return __awaiter(this, void 0, void 0, function* () {
            this.logger.log('initialize', params);
            this.initializeParams = params;
            const clientCapabilities = this.initializeParams.capabilities;
            this.options.lspClient.setClientCapabilites(clientCapabilities);
            this.loadingIndicator = new ServerInitializingIndicator(this.options.lspClient);
            this.workspaceRoot = this.initializeParams.rootUri ? (0, protocol_translation_1.uriToPath)(this.initializeParams.rootUri) : this.initializeParams.rootPath || undefined;
            this.diagnosticQueue = new diagnostic_queue_1.DiagnosticEventQueue(diagnostics => this.options.lspClient.publishDiagnostics(diagnostics), this.documents, (_a = clientCapabilities.textDocument) === null || _a === void 0 ? void 0 : _a.publishDiagnostics, this.logger);
            const userInitializationOptions = this.initializeParams.initializationOptions || {};
            const { disableAutomaticTypingAcquisition, hostInfo, maxTsServerMemory, npmLocation, locale } = userInitializationOptions;
            const { logVerbosity, plugins, preferences } = {
                logVerbosity: userInitializationOptions.logVerbosity || this.options.tsserverLogVerbosity,
                plugins: userInitializationOptions.plugins || [],
                preferences: Object.assign({ allowIncompleteCompletions: true, allowRenameOfImportPath: true, allowTextChangesInNewFiles: true, displayPartsForJSDoc: true, generateReturnInDocTemplate: true, includeAutomaticOptionalChainCompletions: true, includeCompletionsForImportStatements: true, includeCompletionsForModuleExports: true, includeCompletionsWithClassMemberSnippets: true, includeCompletionsWithInsertText: true, includeCompletionsWithSnippetText: true, jsxAttributeCompletionStyle: 'auto' }, userInitializationOptions.preferences)
            };
            const logFile = this.getLogFile(logVerbosity);
            const globalPlugins = [];
            const pluginProbeLocations = [];
            for (const plugin of plugins) {
                globalPlugins.push(plugin.name);
                pluginProbeLocations.push(plugin.location);
            }
            const typescriptVersion = this.findTypescriptVersion();
            if (typescriptVersion) {
                this.logger.info(`Using Typescript version (${typescriptVersion.source}) ${typescriptVersion.versionString} from path "${typescriptVersion.tsServerPath}"`);
            }
            else {
                throw Error('Could not find a valid tsserver executable in the workspace or in the $PATH. Please ensure that the "typescript" dependency is installed in either location. Exiting.');
            }
            this.tspClient = new tsp_client_1.TspClient({
                tsserverPath: typescriptVersion.tsServerPath,
                logFile,
                logVerbosity,
                disableAutomaticTypingAcquisition,
                maxTsServerMemory,
                npmLocation,
                locale,
                globalPlugins,
                pluginProbeLocations,
                logger: this.options.logger,
                onEvent: this.onTsEvent.bind(this),
                onExit: (exitCode, signal) => {
                    this.logger.error(`tsserver process has exited (exit code: ${exitCode}, signal: ${signal}). Stopping the server.`);
                    // Allow the log to be dispatched to the client.
                    setTimeout(() => process.exit(1));
                }
            });
            const started = this.tspClient.start();
            if (!started) {
                throw new Error('tsserver process has failed to start.');
            }
            process.on('exit', () => {
                this.tspClient.shutdown();
                if (this.loadingIndicator) {
                    this.loadingIndicator.reset();
                }
            });
            process.on('SIGINT', () => {
                process.exit();
            });
            this.typeScriptAutoFixProvider = new fix_all_1.TypeScriptAutoFixProvider(this.tspClient);
            yield Promise.all([
                this.tspClient.request("configure" /* Configure */, Object.assign(Object.assign({}, hostInfo ? { hostInfo } : {}), { formatOptions: {
                        // We can use \n here since the editor should normalize later on to its line endings.
                        newLineCharacter: '\n'
                    }, preferences })),
                this.tspClient.request("compilerOptionsForInferredProjects" /* CompilerOptionsForInferredProjects */, {
                    options: {
                        module: "CommonJS" /* CommonJS */,
                        target: "ES2016" /* ES2016 */,
                        jsx: "Preserve" /* Preserve */,
                        allowJs: true,
                        allowSyntheticDefaultImports: true,
                        allowNonTsExtensions: true
                    }
                })
            ]);
            const logFileUri = logFile && (0, protocol_translation_1.pathToUri)(logFile, undefined);
            this.initializeResult = {
                capabilities: {
                    textDocumentSync: lsp.TextDocumentSyncKind.Incremental,
                    completionProvider: {
                        triggerCharacters: ['.', '"', '\'', '/', '@', '<'],
                        resolveProvider: true
                    },
                    codeActionProvider: ((_c = (_b = clientCapabilities.textDocument) === null || _b === void 0 ? void 0 : _b.codeAction) === null || _c === void 0 ? void 0 : _c.codeActionLiteralSupport)
                        ? { codeActionKinds: [...fix_all_1.TypeScriptAutoFixProvider.kinds.map(kind => kind.value), types_1.CodeActionKind.SourceOrganizeImportsTs.value] } : true,
                    definitionProvider: true,
                    documentFormattingProvider: true,
                    documentRangeFormattingProvider: true,
                    documentHighlightProvider: true,
                    documentSymbolProvider: true,
                    executeCommandProvider: {
                        commands: [
                            commands_1.Commands.APPLY_WORKSPACE_EDIT,
                            commands_1.Commands.APPLY_CODE_ACTION,
                            commands_1.Commands.APPLY_REFACTORING,
                            commands_1.Commands.ORGANIZE_IMPORTS,
                            commands_1.Commands.APPLY_RENAME_FILE
                        ]
                    },
                    hoverProvider: true,
                    renameProvider: true,
                    referencesProvider: true,
                    signatureHelpProvider: {
                        triggerCharacters: ['(', ',', '<']
                    },
                    workspaceSymbolProvider: true,
                    implementationProvider: true,
                    typeDefinitionProvider: true,
                    foldingRangeProvider: true,
                    semanticTokensProvider: {
                        documentSelector: null,
                        legend: {
                            // list taken from: https://github.com/microsoft/TypeScript/blob/main/src/services/classifier2020.ts#L10
                            tokenTypes: [
                                'class',
                                'enum',
                                'interface',
                                'namespace',
                                'typeParameter',
                                'type',
                                'parameter',
                                'variable',
                                'enumMember',
                                'property',
                                'function',
                                'member'
                            ],
                            // token from: https://github.com/microsoft/TypeScript/blob/main/src/services/classifier2020.ts#L14
                            tokenModifiers: [
                                'declaration',
                                'static',
                                'async',
                                'readonly',
                                'defaultLibrary',
                                'local'
                            ]
                        },
                        full: true,
                        range: true
                    }
                },
                logFileUri
            };
            this.initializeResult.capabilities.callsProvider = true;
            this.logger.log('onInitialize result', this.initializeResult);
            return this.initializeResult;
        });
    }
    getLogFile(logVerbosity) {
        if (logVerbosity === undefined || logVerbosity === 'off') {
            return undefined;
        }
        const logFile = this.doGetLogFile();
        if (logFile) {
            fs.ensureFileSync(logFile);
            return logFile;
        }
        return tempy_1.default.file({ name: 'tsserver.log' });
    }
    doGetLogFile() {
        if (process.env.TSSERVER_LOG_FILE) {
            return process.env.TSSERVER_LOG_FILE;
        }
        if (this.options.tsserverLogFile) {
            return this.options.tsserverLogFile;
        }
        if (this.workspaceRoot) {
            return path.join(this.workspaceRoot, '.log/tsserver.log');
        }
        return undefined;
    }
    didChangeConfiguration(params) {
        var _a, _b;
        this.workspaceConfiguration = params.settings || {};
        const ignoredDiagnosticCodes = ((_a = this.workspaceConfiguration.diagnostics) === null || _a === void 0 ? void 0 : _a.ignoredCodes) || [];
        (_b = this.diagnosticQueue) === null || _b === void 0 ? void 0 : _b.updateIgnoredDiagnosticCodes(ignoredDiagnosticCodes);
    }
    getWorkspacePreferencesForDocument(file) {
        var _a;
        const doc = this.documents.get(file);
        if (!doc) {
            return {};
        }
        const preferencesKey = doc.languageId.startsWith('typescript') ? 'typescript' : 'javascript';
        return (_a = this.workspaceConfiguration[preferencesKey]) !== null && _a !== void 0 ? _a : {};
    }
    interuptDiagnostics(f) {
        if (!this.diagnosticsTokenSource) {
            return f();
        }
        this.cancelDiagnostics();
        const result = f();
        this.requestDiagnostics();
        return result;
    }
    requestDiagnostics() {
        return __awaiter(this, void 0, void 0, function* () {
            this.pendingDebouncedRequest = true;
            yield this.doRequestDiagnosticsDebounced();
        });
    }
    doRequestDiagnostics() {
        return __awaiter(this, void 0, void 0, function* () {
            this.cancelDiagnostics();
            const geterrTokenSource = new lsp.CancellationTokenSource();
            this.diagnosticsTokenSource = geterrTokenSource;
            const { files } = this.documents;
            try {
                return yield this.tspClient.request("geterr" /* Geterr */, { delay: 0, files }, this.diagnosticsTokenSource.token);
            }
            finally {
                if (this.diagnosticsTokenSource === geterrTokenSource) {
                    this.diagnosticsTokenSource = undefined;
                    this.pendingDebouncedRequest = false;
                }
            }
        });
    }
    cancelDiagnostics() {
        if (this.diagnosticsTokenSource) {
            this.diagnosticsTokenSource = undefined;
        }
    }
    didOpenTextDocument(params) {
        const file = (0, protocol_translation_1.uriToPath)(params.textDocument.uri);
        this.logger.log('onDidOpenTextDocument', params, file);
        if (!file) {
            return;
        }
        if (this.documents.open(file, params.textDocument)) {
            this.tspClient.notify("open" /* Open */, {
                file,
                fileContent: params.textDocument.text,
                scriptKindName: this.getScriptKindName(params.textDocument.languageId),
                projectRootPath: this.workspaceRoot
            });
            this.requestDiagnostics();
        }
        else {
            this.logger.log(`Cannot open already opened doc '${params.textDocument.uri}'.`);
            this.didChangeTextDocument({
                textDocument: params.textDocument,
                contentChanges: [
                    {
                        text: params.textDocument.text
                    }
                ]
            });
        }
    }
    getScriptKindName(languageId) {
        switch (languageId) {
            case 'typescript': return 'TS';
            case 'typescriptreact': return 'TSX';
            case 'javascript': return 'JS';
            case 'javascriptreact': return 'JSX';
        }
        return undefined;
    }
    didCloseTextDocument(params) {
        const file = (0, protocol_translation_1.uriToPath)(params.textDocument.uri);
        this.logger.log('onDidCloseTextDocument', params, file);
        if (!file) {
            return;
        }
        this.closeDocument(file);
    }
    closeDocument(file) {
        const document = this.documents.close(file);
        if (!document) {
            return;
        }
        this.tspClient.notify("close" /* Close */, { file });
        // We won't be updating diagnostics anymore for that file, so clear them
        // so we don't leave stale ones.
        this.options.lspClient.publishDiagnostics({
            uri: document.uri,
            diagnostics: []
        });
    }
    didChangeTextDocument(params) {
        const { textDocument } = params;
        const file = (0, protocol_translation_1.uriToPath)(textDocument.uri);
        this.logger.log('onDidChangeTextDocument', params, file);
        if (!file) {
            return;
        }
        const document = this.documents.get(file);
        if (!document) {
            this.logger.error('Received change on non-opened document ' + textDocument.uri);
            throw new Error('Received change on non-opened document ' + textDocument.uri);
        }
        if (textDocument.version === null) {
            throw new Error(`Received document change event for ${textDocument.uri} without valid version identifier`);
        }
        for (const change of params.contentChanges) {
            let line = 0;
            let offset = 0;
            let endLine = 0;
            let endOffset = 0;
            if (lsp.TextDocumentContentChangeEvent.isIncremental(change)) {
                line = change.range.start.line + 1;
                offset = change.range.start.character + 1;
                endLine = change.range.end.line + 1;
                endOffset = change.range.end.character + 1;
            }
            else {
                line = 1;
                offset = 1;
                const endPos = document.positionAt(document.getText().length);
                endLine = endPos.line + 1;
                endOffset = endPos.character + 1;
            }
            this.tspClient.notify("change" /* Change */, {
                file,
                line,
                offset,
                endLine,
                endOffset,
                insertString: change.text
            });
            document.applyEdit(textDocument.version, change);
        }
        this.requestDiagnostics();
    }
    didSaveTextDocument(_params) {
        // do nothing
    }
    definition(params) {
        return __awaiter(this, void 0, void 0, function* () {
            // TODO: implement version checking and if semver.gte(version, 270) use `definitionAndBoundSpan` instead
            return this.getDefinition({
                type: 'definition',
                params
            });
        });
    }
    implementation(params) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.getDefinition({
                type: 'implementation',
                params
            });
        });
    }
    typeDefinition(params) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.getDefinition({
                type: 'typeDefinition',
                params
            });
        });
    }
    getDefinition({ type, params }) {
        return __awaiter(this, void 0, void 0, function* () {
            const file = (0, protocol_translation_1.uriToPath)(params.textDocument.uri);
            this.logger.log(type, params, file);
            if (!file) {
                return [];
            }
            const result = yield this.tspClient.request(type, {
                file,
                line: params.position.line + 1,
                offset: params.position.character + 1
            });
            return result.body ? result.body.map(fileSpan => (0, protocol_translation_1.toLocation)(fileSpan, this.documents)) : [];
        });
    }
    documentSymbol(params) {
        return __awaiter(this, void 0, void 0, function* () {
            const file = (0, protocol_translation_1.uriToPath)(params.textDocument.uri);
            this.logger.log('symbol', params, file);
            if (!file) {
                return [];
            }
            const response = yield this.tspClient.request("navtree" /* NavTree */, {
                file
            });
            const tree = response.body;
            if (!tree || !tree.childItems) {
                return [];
            }
            if (this.supportHierarchicalDocumentSymbol) {
                const symbols = [];
                for (const item of tree.childItems) {
                    (0, document_symbol_1.collectDocumentSymbols)(item, symbols);
                }
                return symbols;
            }
            const symbols = [];
            for (const item of tree.childItems) {
                (0, document_symbol_1.collectSymbolInformation)(params.textDocument.uri, item, symbols);
            }
            return symbols;
        });
    }
    get supportHierarchicalDocumentSymbol() {
        const textDocument = this.initializeParams.capabilities.textDocument;
        const documentSymbol = textDocument && textDocument.documentSymbol;
        return !!documentSymbol && !!documentSymbol.hierarchicalDocumentSymbolSupport;
    }
    /*
     * implemented based on
     * https://github.com/Microsoft/vscode/blob/master/extensions/typescript-language-features/src/features/completions.ts
     */
    completion(params) {
        return __awaiter(this, void 0, void 0, function* () {
            const file = (0, protocol_translation_1.uriToPath)(params.textDocument.uri);
            this.logger.log('completion', params, file);
            if (!file) {
                return lsp.CompletionList.create([]);
            }
            const document = this.documents.get(file);
            if (!document) {
                throw new Error('The document should be opened for completion, file: ' + file);
            }
            try {
                const result = yield this.interuptDiagnostics(() => this.tspClient.request("completionInfo" /* CompletionInfo */, {
                    file,
                    line: params.position.line + 1,
                    offset: params.position.character + 1
                }));
                const { body } = result;
                const completions = (body ? body.entries : [])
                    .filter(entry => entry.kind !== 'warning')
                    .map(entry => (0, completion_1.asCompletionItem)(entry, file, params.position, document));
                return lsp.CompletionList.create(completions, body === null || body === void 0 ? void 0 : body.isIncomplete);
            }
            catch (error) {
                if (error.message === 'No content available.') {
                    this.logger.info('No content was available for completion request');
                    return null;
                }
                else {
                    throw error;
                }
            }
        });
    }
    completionResolve(item) {
        return __awaiter(this, void 0, void 0, function* () {
            this.logger.log('completion/resolve', item);
            yield this.tspClient.request("configure" /* Configure */, {
                formatOptions: this.getWorkspacePreferencesForDocument(item.data.file).format
            });
            const { body } = yield this.interuptDiagnostics(() => this.tspClient.request("completionEntryDetails" /* CompletionDetails */, item.data));
            const details = body && body.length && body[0];
            if (!details) {
                return item;
            }
            return (0, completion_1.asResolvedCompletionItem)(item, details, this.tspClient, this.workspaceConfiguration.completions || {});
        });
    }
    hover(params) {
        return __awaiter(this, void 0, void 0, function* () {
            const file = (0, protocol_translation_1.uriToPath)(params.textDocument.uri);
            this.logger.log('hover', params, file);
            if (!file) {
                return { contents: [] };
            }
            const result = yield this.interuptDiagnostics(() => this.getQuickInfo(file, params.position));
            if (!result || !result.body) {
                return { contents: [] };
            }
            const range = (0, protocol_translation_1.asRange)(result.body);
            const contents = [];
            if (result.body.displayString) {
                contents.push({ language: 'typescript', value: result.body.displayString });
            }
            const tags = (0, protocol_translation_1.asTagsDocumentation)(result.body.tags);
            const documentation = (0, protocol_translation_1.asPlainText)(result.body.documentation);
            contents.push(documentation + (tags ? '\n\n' + tags : ''));
            return {
                contents,
                range
            };
        });
    }
    getQuickInfo(file, position) {
        return __awaiter(this, void 0, void 0, function* () {
            try {
                return yield this.tspClient.request("quickinfo" /* Quickinfo */, {
                    file,
                    line: position.line + 1,
                    offset: position.character + 1
                });
            }
            catch (err) {
                return undefined;
            }
        });
    }
    rename(params) {
        return __awaiter(this, void 0, void 0, function* () {
            const file = (0, protocol_translation_1.uriToPath)(params.textDocument.uri);
            this.logger.log('onRename', params, file);
            if (!file) {
                return undefined;
            }
            const result = yield this.tspClient.request("rename" /* Rename */, {
                file,
                line: params.position.line + 1,
                offset: params.position.character + 1
            });
            if (!result.body || !result.body.info.canRename || result.body.locs.length === 0) {
                return undefined;
            }
            const workspaceEdit = {
                changes: {}
            };
            result.body.locs
                .forEach((spanGroup) => {
                const uri = (0, protocol_translation_1.pathToUri)(spanGroup.file, this.documents), textEdits = workspaceEdit.changes[uri] || (workspaceEdit.changes[uri] = []);
                spanGroup.locs.forEach((textSpan) => {
                    textEdits.push({
                        newText: params.newName,
                        range: {
                            start: (0, protocol_translation_1.toPosition)(textSpan.start),
                            end: (0, protocol_translation_1.toPosition)(textSpan.end)
                        }
                    });
                });
            });
            return workspaceEdit;
        });
    }
    references(params) {
        return __awaiter(this, void 0, void 0, function* () {
            const file = (0, protocol_translation_1.uriToPath)(params.textDocument.uri);
            this.logger.log('onReferences', params, file);
            if (!file) {
                return [];
            }
            const result = yield this.tspClient.request("references" /* References */, {
                file,
                line: params.position.line + 1,
                offset: params.position.character + 1
            });
            if (!result.body) {
                return [];
            }
            return result.body.refs
                .filter(fileSpan => params.context.includeDeclaration || !fileSpan.isDefinition)
                .map(fileSpan => (0, protocol_translation_1.toLocation)(fileSpan, this.documents));
        });
    }
    documentFormatting(params) {
        return __awaiter(this, void 0, void 0, function* () {
            const file = (0, protocol_translation_1.uriToPath)(params.textDocument.uri);
            this.logger.log('documentFormatting', params, file);
            if (!file) {
                return [];
            }
            const formatOptions = this.getFormattingOptions(file, params.options);
            // options are not yet supported in tsserver, but we can send a configure request first
            yield this.tspClient.request("configure" /* Configure */, {
                formatOptions
            });
            const response = yield this.tspClient.request("format" /* Format */, {
                file,
                line: 1,
                offset: 1,
                endLine: Number.MAX_SAFE_INTEGER,
                endOffset: Number.MAX_SAFE_INTEGER,
                options: formatOptions
            });
            if (response.body) {
                return response.body.map(e => (0, protocol_translation_1.toTextEdit)(e));
            }
            return [];
        });
    }
    documentRangeFormatting(params) {
        return __awaiter(this, void 0, void 0, function* () {
            const file = (0, protocol_translation_1.uriToPath)(params.textDocument.uri);
            this.logger.log('documentRangeFormatting', params, file);
            if (!file) {
                return [];
            }
            const formatOptions = this.getFormattingOptions(file, params.options);
            // options are not yet supported in tsserver, but we can send a configure request first
            yield this.tspClient.request("configure" /* Configure */, {
                formatOptions
            });
            const response = yield this.tspClient.request("format" /* Format */, {
                file,
                line: params.range.start.line + 1,
                offset: params.range.start.character + 1,
                endLine: params.range.end.line + 1,
                endOffset: params.range.end.character + 1,
                options: formatOptions
            });
            if (response.body) {
                return response.body.map(e => (0, protocol_translation_1.toTextEdit)(e));
            }
            return [];
        });
    }
    getFormattingOptions(file, requestOptions) {
        const workspacePreference = this.getWorkspacePreferencesForDocument(file);
        let opts = Object.assign(Object.assign({}, (workspacePreference === null || workspacePreference === void 0 ? void 0 : workspacePreference.format) || {}), requestOptions);
        // translate
        if (opts.convertTabsToSpaces === undefined) {
            opts.convertTabsToSpaces = requestOptions.insertSpaces;
        }
        if (opts.indentSize === undefined) {
            opts.indentSize = requestOptions.tabSize;
        }
        if (this.workspaceRoot) {
            try {
                opts = JSON.parse(fs.readFileSync(this.workspaceRoot + '/tsfmt.json', 'utf-8'));
            }
            catch (err) {
                this.logger.log(`No formatting options found ${err}`);
            }
        }
        return opts;
    }
    signatureHelp(params) {
        return __awaiter(this, void 0, void 0, function* () {
            const file = (0, protocol_translation_1.uriToPath)(params.textDocument.uri);
            this.logger.log('signatureHelp', params, file);
            if (!file) {
                return undefined;
            }
            const response = yield this.interuptDiagnostics(() => this.getSignatureHelp(file, params.position));
            if (!response || !response.body) {
                return undefined;
            }
            return (0, hover_1.asSignatureHelp)(response.body);
        });
    }
    getSignatureHelp(file, position) {
        return __awaiter(this, void 0, void 0, function* () {
            try {
                return yield this.tspClient.request("signatureHelp" /* SignatureHelp */, {
                    file,
                    line: position.line + 1,
                    offset: position.character + 1
                });
            }
            catch (err) {
                return undefined;
            }
        });
    }
    codeAction(params) {
        var _a, _b;
        return __awaiter(this, void 0, void 0, function* () {
            const file = (0, protocol_translation_1.uriToPath)(params.textDocument.uri);
            this.logger.log('codeAction', params, file);
            if (!file) {
                return [];
            }
            const args = (0, protocol_translation_1.toFileRangeRequestArgs)(file, params.range);
            const actions = [];
            const kinds = (_a = params.context.only) === null || _a === void 0 ? void 0 : _a.map(kind => new types_1.CodeActionKind(kind));
            if (!kinds || kinds.some(kind => kind.contains(types_1.CodeActionKind.QuickFix))) {
                const errorCodes = params.context.diagnostics.map(diagnostic => Number(diagnostic.code));
                actions.push(...(0, quickfix_1.provideQuickFix)(yield this.getCodeFixes(Object.assign(Object.assign({}, args), { errorCodes })), this.documents));
            }
            if (!kinds || kinds.some(kind => kind.contains(types_1.CodeActionKind.Refactor))) {
                actions.push(...(0, refactor_1.provideRefactors)(yield this.getRefactors(args), args));
            }
            // organize import is provided by tsserver for any line, so we only get it if explicitly requested
            if (kinds === null || kinds === void 0 ? void 0 : kinds.some(kind => kind.contains(types_1.CodeActionKind.SourceOrganizeImportsTs))) {
                // see this issue for more context about how this argument is used
                // https://github.com/microsoft/TypeScript/issues/43051
                const skipDestructiveCodeActions = params.context.diagnostics.some(
                // assume no severity is an error
                d => { var _a; return ((_a = d.severity) !== null && _a !== void 0 ? _a : 0) <= 2; });
                const response = yield this.getOrganizeImports({
                    scope: { type: 'file', args },
                    skipDestructiveCodeActions
                });
                actions.push(...(0, organize_imports_1.provideOrganizeImports)(response, this.documents));
            }
            // TODO: Since we rely on diagnostics pointing at errors in the correct places, we can't proceed if we are not
            // sure that diagnostics are up-to-date. Thus we check `pendingDebouncedRequest` to see if there are *any*
            // pending diagnostic requests (regardless of for which file).
            // In general would be better to replace the whole diagnostics handling logic with the one from
            // bufferSyncSupport.ts in VSCode's typescript language features.
            if (kinds && !this.pendingDebouncedRequest) {
                const diagnostics = ((_b = this.diagnosticQueue) === null || _b === void 0 ? void 0 : _b.getDiagnosticsForFile(file)) || [];
                if (diagnostics.length) {
                    actions.push(...yield this.typeScriptAutoFixProvider.provideCodeActions(kinds, file, diagnostics, this.documents));
                }
            }
            return actions;
        });
    }
    getCodeFixes(args) {
        return __awaiter(this, void 0, void 0, function* () {
            try {
                return yield this.tspClient.request("getCodeFixes" /* GetCodeFixes */, args);
            }
            catch (err) {
                return undefined;
            }
        });
    }
    getRefactors(args) {
        return __awaiter(this, void 0, void 0, function* () {
            try {
                return yield this.tspClient.request("getApplicableRefactors" /* GetApplicableRefactors */, args);
            }
            catch (err) {
                return undefined;
            }
        });
    }
    getOrganizeImports(args) {
        return __awaiter(this, void 0, void 0, function* () {
            try {
                // Pass format options to organize imports
                yield this.tspClient.request("configure" /* Configure */, {
                    formatOptions: this.getWorkspacePreferencesForDocument(args.scope.args.file).format
                });
                return yield this.tspClient.request("organizeImports" /* OrganizeImports */, args);
            }
            catch (err) {
                return undefined;
            }
        });
    }
    executeCommand(arg) {
        return __awaiter(this, void 0, void 0, function* () {
            this.logger.log('executeCommand', arg);
            if (arg.command === commands_1.Commands.APPLY_WORKSPACE_EDIT && arg.arguments) {
                const edit = arg.arguments[0];
                yield this.options.lspClient.applyWorkspaceEdit({
                    edit
                });
            }
            else if (arg.command === commands_1.Commands.APPLY_CODE_ACTION && arg.arguments) {
                const codeAction = arg.arguments[0];
                if (!(yield this.applyFileCodeEdits(codeAction.changes))) {
                    return;
                }
                if (codeAction.commands && codeAction.commands.length) {
                    for (const command of codeAction.commands) {
                        yield this.tspClient.request("applyCodeActionCommand" /* ApplyCodeActionCommand */, { command });
                    }
                }
            }
            else if (arg.command === commands_1.Commands.APPLY_REFACTORING && arg.arguments) {
                const args = arg.arguments[0];
                const { body } = yield this.tspClient.request("getEditsForRefactor" /* GetEditsForRefactor */, args);
                if (!body || !body.edits.length) {
                    return;
                }
                for (const edit of body.edits) {
                    yield fs.ensureFile(edit.fileName);
                }
                if (!(yield this.applyFileCodeEdits(body.edits))) {
                    return;
                }
                const renameLocation = body.renameLocation;
                if (renameLocation) {
                    yield this.options.lspClient.rename({
                        textDocument: {
                            uri: (0, protocol_translation_1.pathToUri)(args.file, this.documents)
                        },
                        position: (0, protocol_translation_1.toPosition)(renameLocation)
                    });
                }
            }
            else if (arg.command === commands_1.Commands.ORGANIZE_IMPORTS && arg.arguments) {
                const file = arg.arguments[0];
                const additionalArguments = arg.arguments[1] || {};
                yield this.tspClient.request("configure" /* Configure */, {
                    formatOptions: this.getWorkspacePreferencesForDocument(file).format
                });
                const { body } = yield this.tspClient.request("organizeImports" /* OrganizeImports */, {
                    scope: {
                        type: 'file',
                        args: { file }
                    },
                    skipDestructiveCodeActions: additionalArguments.skipDestructiveCodeActions
                });
                yield this.applyFileCodeEdits(body);
            }
            else if (arg.command === commands_1.Commands.APPLY_RENAME_FILE && arg.arguments) {
                const { sourceUri, targetUri } = arg.arguments[0];
                this.applyRenameFile(sourceUri, targetUri);
            }
            else if (arg.command === commands_1.Commands.APPLY_COMPLETION_CODE_ACTION && arg.arguments) {
                const [_, codeActions] = arg.arguments;
                for (const codeAction of codeActions) {
                    yield this.applyFileCodeEdits(codeAction.changes);
                    if (codeAction.commands && codeAction.commands.length) {
                        for (const command of codeAction.commands) {
                            yield this.tspClient.request("applyCodeActionCommand" /* ApplyCodeActionCommand */, { command });
                        }
                    }
                    // Execute only the first code action.
                    break;
                }
            }
            else {
                this.logger.error(`Unknown command ${arg.command}.`);
            }
        });
    }
    applyFileCodeEdits(edits) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!edits.length) {
                return false;
            }
            const changes = {};
            for (const edit of edits) {
                changes[(0, protocol_translation_1.pathToUri)(edit.fileName, this.documents)] = edit.textChanges.map(protocol_translation_1.toTextEdit);
            }
            const { applied } = yield this.options.lspClient.applyWorkspaceEdit({
                edit: { changes }
            });
            return applied;
        });
    }
    applyRenameFile(sourceUri, targetUri) {
        return __awaiter(this, void 0, void 0, function* () {
            const edits = yield this.getEditsForFileRename(sourceUri, targetUri);
            this.applyFileCodeEdits(edits);
        });
    }
    getEditsForFileRename(sourceUri, targetUri) {
        return __awaiter(this, void 0, void 0, function* () {
            const newFilePath = (0, protocol_translation_1.uriToPath)(targetUri);
            const oldFilePath = (0, protocol_translation_1.uriToPath)(sourceUri);
            if (!newFilePath || !oldFilePath) {
                return [];
            }
            try {
                const { body } = yield this.tspClient.request("getEditsForFileRename" /* GetEditsForFileRename */, {
                    oldFilePath,
                    newFilePath
                });
                return body;
            }
            catch (err) {
                return [];
            }
        });
    }
    documentHighlight(arg) {
        return __awaiter(this, void 0, void 0, function* () {
            const file = (0, protocol_translation_1.uriToPath)(arg.textDocument.uri);
            this.logger.log('documentHighlight', arg, file);
            if (!file) {
                return [];
            }
            let response;
            try {
                response = yield this.tspClient.request("documentHighlights" /* DocumentHighlights */, {
                    file,
                    line: arg.position.line + 1,
                    offset: arg.position.character + 1,
                    filesToSearch: [file]
                });
            }
            catch (err) {
                return [];
            }
            if (!response.body) {
                return [];
            }
            const result = [];
            for (const item of response.body) {
                // tsp returns item.file with POSIX path delimiters, whereas file is platform specific.
                // Converting to a URI and back to a path ensures consistency.
                if ((0, protocol_translation_1.normalizePath)(item.file) === file) {
                    const highlights = (0, protocol_translation_1.toDocumentHighlight)(item);
                    result.push(...highlights);
                }
            }
            return result;
        });
    }
    lastFileOrDummy() {
        return this.documents.files[0] || this.workspaceRoot;
    }
    workspaceSymbol(params) {
        return __awaiter(this, void 0, void 0, function* () {
            const result = yield this.tspClient.request("navto" /* Navto */, {
                file: this.lastFileOrDummy(),
                searchValue: params.query
            });
            if (!result.body) {
                return [];
            }
            return result.body.map(item => {
                return {
                    location: {
                        uri: (0, protocol_translation_1.pathToUri)(item.file, this.documents),
                        range: {
                            start: (0, protocol_translation_1.toPosition)(item.start),
                            end: (0, protocol_translation_1.toPosition)(item.end)
                        }
                    },
                    kind: (0, protocol_translation_1.toSymbolKind)(item.kind),
                    name: item.name
                };
            });
        });
    }
    /**
     * implemented based on https://github.com/Microsoft/vscode/blob/master/extensions/typescript-language-features/src/features/folding.ts
     */
    foldingRanges(params) {
        return __awaiter(this, void 0, void 0, function* () {
            const file = (0, protocol_translation_1.uriToPath)(params.textDocument.uri);
            this.logger.log('foldingRanges', params, file);
            if (!file) {
                return undefined;
            }
            const document = this.documents.get(file);
            if (!document) {
                throw new Error("The document should be opened for foldingRanges', file: " + file);
            }
            const { body } = yield this.tspClient.request("getOutliningSpans" /* GetOutliningSpans */, { file });
            if (!body) {
                return undefined;
            }
            const foldingRanges = [];
            for (const span of body) {
                const foldingRange = this.asFoldingRange(span, document);
                if (foldingRange) {
                    foldingRanges.push(foldingRange);
                }
            }
            return foldingRanges;
        });
    }
    asFoldingRange(span, document) {
        const range = (0, protocol_translation_1.asRange)(span.textSpan);
        const kind = this.asFoldingRangeKind(span);
        // workaround for https://github.com/Microsoft/vscode/issues/49904
        if (span.kind === 'comment') {
            const line = document.getLine(range.start.line);
            if (line.match(/\/\/\s*#endregion/gi)) {
                return undefined;
            }
        }
        const startLine = range.start.line;
        // workaround for https://github.com/Microsoft/vscode/issues/47240
        const endLine = range.end.character > 0 && document.getText(lsp.Range.create(lsp.Position.create(range.end.line, range.end.character - 1), range.end)) === '}' ? Math.max(range.end.line - 1, range.start.line) : range.end.line;
        return {
            startLine,
            endLine,
            kind
        };
    }
    asFoldingRangeKind(span) {
        switch (span.kind) {
            case 'comment': return lsp.FoldingRangeKind.Comment;
            case 'region': return lsp.FoldingRangeKind.Region;
            case 'imports': return lsp.FoldingRangeKind.Imports;
            case 'code':
            default: return undefined;
        }
    }
    onTsEvent(event) {
        var _a;
        if (event.event === "semanticDiag" /* SementicDiag */ ||
            event.event === "syntaxDiag" /* SyntaxDiag */ ||
            event.event === "suggestionDiag" /* SuggestionDiag */) {
            (_a = this.diagnosticQueue) === null || _a === void 0 ? void 0 : _a.updateDiagnostics(event.event, event);
        }
        else if (event.event === "projectLoadingStart" /* ProjectLoadingStart */) {
            this.loadingIndicator.startedLoadingProject(event.body.projectName);
        }
        else if (event.event === "projectLoadingFinish" /* ProjectLoadingFinish */) {
            this.loadingIndicator.finishedLoadingProject(event.body.projectName);
        }
        else {
            this.logger.log('Ignored event', {
                event: event.event
            });
        }
    }
    calls(params) {
        return __awaiter(this, void 0, void 0, function* () {
            let callsResult = { calls: [] };
            const file = (0, protocol_translation_1.uriToPath)(params.textDocument.uri);
            this.logger.log('calls', params, file);
            if (!file) {
                return callsResult;
            }
            if (params.direction === lspcalls.CallDirection.Outgoing) {
                const documentProvider = (file) => this.documents.get(file);
                callsResult = yield (0, calls_1.computeCallees)(this.tspClient, params, documentProvider);
            }
            else {
                callsResult = yield (0, calls_1.computeCallers)(this.tspClient, params);
            }
            return callsResult;
        });
    }
    inlayHints(params) {
        var _a, _b, _c, _d, _e, _f;
        return __awaiter(this, void 0, void 0, function* () {
            const file = (0, protocol_translation_1.uriToPath)(params.textDocument.uri);
            this.logger.log('inlayHints', params, file);
            if (!file) {
                return { inlayHints: [] };
            }
            yield this.tspClient.request("configure" /* Configure */, {
                preferences: this.getInlayHintsOptions(file)
            });
            const doc = this.documents.get(file);
            if (!doc) {
                return { inlayHints: [] };
            }
            const start = doc.offsetAt((_b = (_a = params.range) === null || _a === void 0 ? void 0 : _a.start) !== null && _b !== void 0 ? _b : {
                line: 0,
                character: 0
            });
            const end = doc.offsetAt((_d = (_c = params.range) === null || _c === void 0 ? void 0 : _c.end) !== null && _d !== void 0 ? _d : {
                line: doc.lineCount + 1,
                character: 0
            });
            try {
                const result = yield this.tspClient.request("provideInlayHints" /* ProvideInlayHints */, {
                    file,
                    start: start,
                    length: end - start
                });
                return {
                    inlayHints: (_f = (_e = result.body) === null || _e === void 0 ? void 0 : _e.map((item) => ({
                        text: item.text,
                        position: (0, protocol_translation_1.toPosition)(item.position),
                        whitespaceAfter: item.whitespaceAfter,
                        whitespaceBefore: item.whitespaceBefore,
                        kind: item.kind
                    }))) !== null && _f !== void 0 ? _f : []
                };
            }
            catch (_g) {
                return {
                    inlayHints: []
                };
            }
        });
    }
    getInlayHintsOptions(file) {
        var _a, _b;
        const workspacePreference = this.getWorkspacePreferencesForDocument(file);
        const userPreferences = ((_a = this.initializeParams.initializationOptions) === null || _a === void 0 ? void 0 : _a.preferences) || {};
        return Object.assign(Object.assign({}, userPreferences), (_b = workspacePreference.inlayHints) !== null && _b !== void 0 ? _b : {});
    }
    semanticTokensFull(params) {
        return __awaiter(this, void 0, void 0, function* () {
            const file = (0, protocol_translation_1.uriToPath)(params.textDocument.uri);
            this.logger.log('semanticTokensFull', params, file);
            if (!file) {
                return { data: [] };
            }
            const doc = this.documents.get(file);
            if (!doc) {
                return { data: [] };
            }
            const start = doc.offsetAt({
                line: 0,
                character: 0
            });
            const end = doc.offsetAt({
                line: doc.lineCount,
                character: 0
            });
            return this.getSemanticTokens(doc, file, start, end);
        });
    }
    semanticTokensRange(params) {
        return __awaiter(this, void 0, void 0, function* () {
            const file = (0, protocol_translation_1.uriToPath)(params.textDocument.uri);
            this.logger.log('semanticTokensRange', params, file);
            if (!file) {
                return { data: [] };
            }
            const doc = this.documents.get(file);
            if (!doc) {
                return { data: [] };
            }
            const start = doc.offsetAt(params.range.start);
            const end = doc.offsetAt(params.range.end);
            return this.getSemanticTokens(doc, file, start, end);
        });
    }
    getSemanticTokens(doc, file, startOffset, endOffset) {
        var _a, _b;
        return __awaiter(this, void 0, void 0, function* () {
            try {
                const result = yield this.tspClient.request("encodedSemanticClassifications-full" /* EncodedSemanticClassificationsFull */, {
                    file,
                    start: startOffset,
                    length: endOffset - startOffset,
                    format: '2020'
                });
                const spans = (_b = (_a = result.body) === null || _a === void 0 ? void 0 : _a.spans) !== null && _b !== void 0 ? _b : [];
                return { data: lspsemanticTokens.transformSpans(doc, spans) };
            }
            catch (_c) {
                return { data: [] };
            }
        });
    }
}
exports.LspServer = LspServer;
//# sourceMappingURL=lsp-server.js.map