UNPKG

typescript-language-server

Version:

Language Server Protocol (LSP) implementation for TypeScript using tsserver

238 lines 8.04 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) 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