typescript-language-server
Version:
Language Server Protocol (LSP) implementation for TypeScript using tsserver
224 lines • 9.62 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* 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