typescript-language-server
Version:
Language Server Protocol (LSP) implementation for TypeScript using tsserver
238 lines • 8.04 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) 2022 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 ChildProcess from 'node:child_process';
import path from 'node:path';
import API from '../utils/api.js';
export class NodeTsServerProcessFactory {
fork(version, args, kind, configuration) {
const tsServerPath = version.tsServerPath;
const useIpc = version.version?.gte(API.v460);
const runtimeArgs = [...args];
if (useIpc) {
runtimeArgs.push('--useNodeIpc');
}
const childProcess = ChildProcess.fork(tsServerPath, runtimeArgs, {
silent: true,
cwd: undefined,
env: generatePatchedEnv(process.env, tsServerPath),
execArgv: getExecArgv(kind, configuration),
stdio: useIpc ? ['pipe', 'pipe', 'pipe', 'ipc'] : undefined,
});
return useIpc ? new IpcChildServerProcess(childProcess) : new StdioChildServerProcess(childProcess);
}
}
function generatePatchedEnv(env, modulePath) {
const newEnv = Object.assign({}, env);
newEnv.NODE_PATH = path.join(modulePath, '..', '..', '..');
// Ensure we always have a PATH set
newEnv.PATH = newEnv.PATH || process.env.PATH;
return newEnv;
}
function getExecArgv(kind, configuration) {
const args = [];
const debugPort = getDebugPort(kind);
if (debugPort) {
const inspectFlag = getTssDebugBrk() ? '--inspect-brk' : '--inspect';
args.push(`${inspectFlag}=${debugPort}`);
}
if (configuration.maxTsServerMemory) {
args.push(`--max-old-space-size=${configuration.maxTsServerMemory}`);
}
return args;
}
function getDebugPort(kind) {
if (kind === "syntax" /* TsServerProcessKind.Syntax */) {
// We typically only want to debug the main semantic server
return undefined;
}
const value = getTssDebugBrk() || getTssDebug();
if (value) {
const port = parseInt(value);
if (!isNaN(port)) {
return port;
}
}
return undefined;
}
function getTssDebug() {
return process.env.TSS_DEBUG;
}
function getTssDebugBrk() {
return process.env.TSS_DEBUG_BRK;
}
class IpcChildServerProcess {
constructor(_process) {
this._process = _process;
}
write(serverRequest) {
this._process.send(serverRequest);
}
onData(handler) {
this._process.on('message', handler);
}
onExit(handler) {
this._process.on('exit', handler);
}
onError(handler) {
this._process.on('error', handler);
}
kill() {
this._process.kill();
}
}
class StdioChildServerProcess {
constructor(_process) {
this._process = _process;
this._reader = new Reader(this._process.stdout);
}
get reader() {
return this._reader;
}
write(serverRequest) {
this._process.stdin.write(JSON.stringify(serverRequest) + '\r\n', 'utf8');
}
onData(handler) {
this.reader.onData(handler);
}
onExit(handler) {
this._process.on('exit', handler);
}
onError(handler) {
this._process.on('error', handler);
this.reader.onError(handler);
}
kill() {
this._process.kill();
this.reader.dispose();
this._reader = null;
}
}
class Reader {
constructor(readable) {
this.buffer = new ProtocolBuffer();
this.nextMessageLength = -1;
this._onError = (_error) => { };
this._onData = (_data) => { };
this.isDisposed = false;
readable.on('data', data => this.onLengthData(data));
}
dispose() {
this.isDisposed = true;
this._onError = (_error) => { };
this._onData = (_data) => { };
}
onError(handler) {
this._onError = handler;
}
onData(handler) {
this._onData = handler;
}
onLengthData(data) {
if (this.isDisposed) {
return;
}
try {
this.buffer.append(data);
// eslint-disable-next-line no-constant-condition
while (true) {
if (this.nextMessageLength === -1) {
this.nextMessageLength = this.buffer.tryReadContentLength();
if (this.nextMessageLength === -1) {
return;
}
}
const msg = this.buffer.tryReadContent(this.nextMessageLength);
if (msg === null) {
return;
}
this.nextMessageLength = -1;
const json = JSON.parse(msg);
this._onData(json);
}
}
catch (e) {
this._onError(e);
}
}
}
const defaultSize = 8192;
const contentLength = 'Content-Length: ';
const contentLengthSize = Buffer.byteLength(contentLength, 'utf8');
const blank = Buffer.from(' ', 'utf8')[0];
const backslashR = Buffer.from('\r', 'utf8')[0];
const backslashN = Buffer.from('\n', 'utf8')[0];
class ProtocolBuffer {
constructor() {
this.index = 0;
this.buffer = Buffer.allocUnsafe(defaultSize);
}
append(data) {
let toAppend = null;
if (Buffer.isBuffer(data)) {
toAppend = data;
}
else {
toAppend = Buffer.from(data, 'utf8');
}
if (this.buffer.length - this.index >= toAppend.length) {
toAppend.copy(this.buffer, this.index, 0, toAppend.length);
}
else {
const newSize = (Math.ceil((this.index + toAppend.length) / defaultSize) + 1) * defaultSize;
if (this.index === 0) {
this.buffer = Buffer.allocUnsafe(newSize);
toAppend.copy(this.buffer, 0, 0, toAppend.length);
}
else {
this.buffer = Buffer.concat([this.buffer.slice(0, this.index), toAppend], newSize);
}
}
this.index += toAppend.length;
}
tryReadContentLength() {
let result = -1;
let current = 0;
// we are utf8 encoding...
while (current < this.index && (this.buffer[current] === blank || this.buffer[current] === backslashR || this.buffer[current] === backslashN)) {
current++;
}
if (this.index < current + contentLengthSize) {
return result;
}
current += contentLengthSize;
const start = current;
while (current < this.index && this.buffer[current] !== backslashR) {
current++;
}
if (current + 3 >= this.index || this.buffer[current + 1] !== backslashN || this.buffer[current + 2] !== backslashR || this.buffer[current + 3] !== backslashN) {
return result;
}
const data = this.buffer.toString('utf8', start, current);
result = parseInt(data);
this.buffer = this.buffer.slice(current + 4);
this.index = this.index - (current + 4);
return result;
}
tryReadContent(length) {
if (this.index < length) {
return null;
}
const result = this.buffer.toString('utf8', 0, length);
let sourceStart = length;
while (sourceStart < this.index && (this.buffer[sourceStart] === backslashR || this.buffer[sourceStart] === backslashN)) {
sourceStart++;
}
this.buffer.copy(this.buffer, 0, sourceStart);
this.index = this.index - sourceStart;
return result;
}
}
//# sourceMappingURL=serverProcess.js.map