UNPKG

typescript-language-server

Version:

Language Server Protocol (LSP) implementation for TypeScript using tsserver

224 lines 9.62 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ /* * 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 */ import { PrefixingLogger } from './logger.js'; import API from './utils/api.js'; import { ServerResponse } from './tsServer/requests.js'; import { TypeScriptServerError } from './tsServer/serverError.js'; import { TypeScriptServerSpawner } from './tsServer/spawner.js'; class ServerInitializingIndicator { constructor(lspClient) { this.lspClient = lspClient; } reset() { if (this._loadingProjectName) { this._loadingProjectName = undefined; if (this._progressReporter) { this._progressReporter.done(); this._progressReporter = undefined; } } } async 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 = await 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.done(); this._progressReporter = undefined; } } } } export class TspClient { constructor(options) { this.options = options; this.primaryTsServer = null; this.apiVersion = options.typescriptVersion.version || API.defaultVersion; this.logger = new PrefixingLogger(options.logger, '[tsclient]'); this.tsserverLogger = new PrefixingLogger(options.logger, '[tsserver]'); this.loadingIndicator = new ServerInitializingIndicator(options.lspClient); } start() { const tsServerSpawner = new TypeScriptServerSpawner(this.apiVersion, this.logger); const tsServer = tsServerSpawner.spawn(this.options.typescriptVersion, this.options); tsServer.onExit((data) => { this.shutdown(); this.tsserverLogger.error(`Exited. Code: ${data.code}. Signal: ${data.signal}`); if (this.options.onExit) { this.options.onExit(data.code, data.signal); } }); tsServer.onError((err) => { if (err) { this.tsserverLogger.error('Exited with error. Error message is: {0}', err.message || err.name); } this.serviceExited(); }); tsServer.onEvent(event => this.dispatchEvent(event)); this.primaryTsServer = tsServer; return true; } serviceExited() { this.primaryTsServer = null; this.loadingIndicator.reset(); } // private static isLoggingEnabled(configuration: TspClientOptions) { // return configuration.tsServerLogLevel !== TsServerLogLevel.Off; // } dispatchEvent(event) { switch (event.event) { case "syntaxDiag" /* EventTypes.SyntaxDiag */: case "semanticDiag" /* EventTypes.SementicDiag */: case "suggestionDiag" /* EventTypes.SuggestionDiag */: { // This event also roughly signals that projects have been loaded successfully (since the TS server is synchronous) this.loadingIndicator.reset(); this.options.onEvent?.(event); break; } // case EventTypes.ConfigFileDiag: // this._onConfigDiagnosticsReceived.fire(event as tsp.ConfigFileDiagnosticEvent); // break; // case EventTypes.projectLanguageServiceState: { // const body = (event as tsp.ProjectLanguageServiceStateEvent).body!; // if (this.serverState.type === ServerState.Type.Running) { // this.serverState.updateLanguageServiceEnabled(body.languageServiceEnabled); // } // this._onProjectLanguageServiceStateChanged.fire(body); // break; // } // case EventTypes.projectsUpdatedInBackground: { // this.loadingIndicator.reset(); // const body = (event as tsp.ProjectsUpdatedInBackgroundEvent).body; // const resources = body.openFiles.map(file => this.toResource(file)); // this.bufferSyncSupport.getErr(resources); // break; // } // case EventTypes.beginInstallTypes: // this._onDidBeginInstallTypings.fire((event as tsp.BeginInstallTypesEvent).body); // break; // case EventTypes.endInstallTypes: // this._onDidEndInstallTypings.fire((event as tsp.EndInstallTypesEvent).body); // break; // case EventTypes.typesInstallerInitializationFailed: // this._onTypesInstallerInitializationFailed.fire((event as tsp.TypesInstallerInitializationFailedEvent).body); // break; case "projectLoadingStart" /* EventTypes.ProjectLoadingStart */: this.loadingIndicator.startedLoadingProject(event.body.projectName); break; case "projectLoadingFinish" /* EventTypes.ProjectLoadingFinish */: this.loadingIndicator.finishedLoadingProject(event.body.projectName); break; } } shutdown() { if (this.loadingIndicator) { this.loadingIndicator.reset(); } if (this.primaryTsServer) { this.primaryTsServer.kill(); } } notify(command, args) { this.executeWithoutWaitingForResponse(command, args); } requestGeterr(args, token) { return this.executeAsync("geterr" /* CommandTypes.Geterr */, args, token); } request(command, args, token, config) { return this.execute(command, args, token, config); } // Low-level API. execute(command, args, token, config) { let executions; // if (config?.cancelOnResourceChange) { // if (this.primaryTsServer) { // const source = new CancellationTokenSource(); // token.onCancellationRequested(() => source.cancel()); // const inFlight: ToCancelOnResourceChanged = { // resource: config.cancelOnResourceChange, // cancel: () => source.cancel(), // }; // runningServerState.toCancelOnResourceChange.add(inFlight); // executions = this.executeImpl(command, args, { // isAsync: false, // token: source.token, // expectsResult: true, // ...config, // }); // executions[0]!.finally(() => { // runningServerState.toCancelOnResourceChange.delete(inFlight); // source.dispose(); // }); // } // } if (!executions) { executions = this.executeImpl(command, args, { isAsync: false, token, expectsResult: true, ...config, }); } if (config?.nonRecoverable) { executions[0].catch(err => this.fatalError(command, err)); } if (command === "updateOpen" /* CommandTypes.UpdateOpen */) { // If update open has completed, consider that the project has loaded Promise.all(executions).then(() => { this.loadingIndicator.reset(); }); } return executions[0]; } executeWithoutWaitingForResponse(command, args) { this.executeImpl(command, args, { isAsync: false, token: undefined, expectsResult: false, }); } executeAsync(command, args, token) { return this.executeImpl(command, args, { isAsync: true, token, expectsResult: true, })[0]; } executeImpl(command, args, executeInfo) { if (this.primaryTsServer) { return this.primaryTsServer.executeImpl(command, args, executeInfo); } else { return [Promise.resolve(ServerResponse.NoServer)]; } } fatalError(command, error) { this.tsserverLogger.error(`A non-recoverable error occurred while executing command: ${command}`); if (error instanceof TypeScriptServerError && error.serverErrorText) { this.tsserverLogger.error(error.serverErrorText); } if (this.primaryTsServer) { this.logger.info('Killing TS Server'); this.primaryTsServer.kill(); if (error instanceof TypeScriptServerError) { this.primaryTsServer = null; } } } } //# sourceMappingURL=tsp-client.js.map