mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-22 01:29:04 +01:00
Use node ipc for communication with TS Server (#135341)
* Use node ipc for communicating with TS Server For https://github.com/microsoft/TypeScript/issues/46417 * Don't use ipc for stdio of we're not using ipc * Use node ipc for communicating with TS Server Fixes #85565
This commit is contained in:
@@ -12,7 +12,7 @@ import { LanguageConfigurationManager } from './languageFeatures/languageConfigu
|
||||
import { createLazyClientHost, lazilyActivateClient } from './lazyClientHost';
|
||||
import { nodeRequestCancellerFactory } from './tsServer/cancellation.electron';
|
||||
import { NodeLogDirectoryProvider } from './tsServer/logDirectoryProvider.electron';
|
||||
import { ChildServerProcess } from './tsServer/serverProcess.electron';
|
||||
import { ElectronServiceProcessFactory } from './tsServer/serverProcess.electron';
|
||||
import { DiskTypeScriptVersionProvider } from './tsServer/versionProvider.electron';
|
||||
import { ActiveJsTsEditorTracker } from './utils/activeJsTsEditorTracker';
|
||||
import { ElectronServiceConfigurationProvider } from './utils/configuration.electron';
|
||||
@@ -46,7 +46,7 @@ export function activate(
|
||||
logDirectoryProvider,
|
||||
cancellerFactory: nodeRequestCancellerFactory,
|
||||
versionProvider,
|
||||
processFactory: ChildServerProcess,
|
||||
processFactory: new ElectronServiceProcessFactory(),
|
||||
activeJsTsEditorTracker,
|
||||
serviceConfigurationProvider: new ElectronServiceConfigurationProvider(),
|
||||
}, item => {
|
||||
|
||||
@@ -59,7 +59,7 @@ export const enum TsServerProcessKind {
|
||||
|
||||
export interface TsServerProcessFactory {
|
||||
fork(
|
||||
tsServerPath: string,
|
||||
version: TypeScriptVersion,
|
||||
args: readonly string[],
|
||||
kind: TsServerProcessKind,
|
||||
configuration: TypeScriptServiceConfiguration,
|
||||
|
||||
@@ -9,6 +9,7 @@ import type * as Proto from '../protocol';
|
||||
import { TypeScriptServiceConfiguration } from '../utils/configuration';
|
||||
import { memoize } from '../utils/memoize';
|
||||
import { TsServerProcess, TsServerProcessKind } from './server';
|
||||
import { TypeScriptVersion } from './versionProvider';
|
||||
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
@@ -19,11 +20,12 @@ declare type Worker = any;
|
||||
export class WorkerServerProcess implements TsServerProcess {
|
||||
|
||||
public static fork(
|
||||
tsServerPath: string,
|
||||
version: TypeScriptVersion,
|
||||
args: readonly string[],
|
||||
_kind: TsServerProcessKind,
|
||||
_configuration: TypeScriptServiceConfiguration,
|
||||
) {
|
||||
const tsServerPath = version.tsServerPath;
|
||||
const worker = new Worker(tsServerPath);
|
||||
return new WorkerServerProcess(worker, [
|
||||
...args,
|
||||
|
||||
@@ -10,10 +10,12 @@ import type { Readable } from 'stream';
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import type * as Proto from '../protocol';
|
||||
import API from '../utils/api';
|
||||
import { TypeScriptServiceConfiguration } from '../utils/configuration';
|
||||
import { Disposable } from '../utils/dispose';
|
||||
import { TsServerProcess, TsServerProcessKind } from './server';
|
||||
import { TsServerProcess, TsServerProcessFactory, TsServerProcessKind } from './server';
|
||||
import { TypeScriptVersionManager } from './versionManager';
|
||||
import { TypeScriptVersion } from './versionProvider';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
@@ -134,84 +136,89 @@ class Reader<T> extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
export class ChildServerProcess extends Disposable implements TsServerProcess {
|
||||
private readonly _reader: Reader<Proto.Response>;
|
||||
function generatePatchedEnv(env: any, modulePath: string): any {
|
||||
const newEnv = Object.assign({}, env);
|
||||
|
||||
public static fork(
|
||||
tsServerPath: string,
|
||||
args: readonly string[],
|
||||
kind: TsServerProcessKind,
|
||||
configuration: TypeScriptServiceConfiguration,
|
||||
versionManager: TypeScriptVersionManager,
|
||||
): ChildServerProcess {
|
||||
if (!fs.existsSync(tsServerPath)) {
|
||||
vscode.window.showWarningMessage(localize('noServerFound', 'The path {0} doesn\'t point to a valid tsserver install. Falling back to bundled TypeScript version.', tsServerPath));
|
||||
versionManager.reset();
|
||||
tsServerPath = versionManager.currentVersion.tsServerPath;
|
||||
}
|
||||
newEnv['ELECTRON_RUN_AS_NODE'] = '1';
|
||||
newEnv['NODE_PATH'] = path.join(modulePath, '..', '..', '..');
|
||||
|
||||
const childProcess = child_process.fork(tsServerPath, args, {
|
||||
silent: true,
|
||||
cwd: undefined,
|
||||
env: this.generatePatchedEnv(process.env, tsServerPath),
|
||||
execArgv: this.getExecArgv(kind, configuration),
|
||||
});
|
||||
// Ensure we always have a PATH set
|
||||
newEnv['PATH'] = newEnv['PATH'] || process.env.PATH;
|
||||
|
||||
return new ChildServerProcess(childProcess);
|
||||
return newEnv;
|
||||
}
|
||||
|
||||
function getExecArgv(kind: TsServerProcessKind, configuration: TypeScriptServiceConfiguration): string[] {
|
||||
const args: string[] = [];
|
||||
|
||||
const debugPort = getDebugPort(kind);
|
||||
if (debugPort) {
|
||||
const inspectFlag = getTssDebugBrk() ? '--inspect-brk' : '--inspect';
|
||||
args.push(`${inspectFlag}=${debugPort}`);
|
||||
}
|
||||
|
||||
private static generatePatchedEnv(env: any, modulePath: string): any {
|
||||
const newEnv = Object.assign({}, env);
|
||||
|
||||
newEnv['ELECTRON_RUN_AS_NODE'] = '1';
|
||||
newEnv['NODE_PATH'] = path.join(modulePath, '..', '..', '..');
|
||||
|
||||
// Ensure we always have a PATH set
|
||||
newEnv['PATH'] = newEnv['PATH'] || process.env.PATH;
|
||||
|
||||
return newEnv;
|
||||
if (configuration.maxTsServerMemory) {
|
||||
args.push(`--max-old-space-size=${configuration.maxTsServerMemory}`);
|
||||
}
|
||||
|
||||
private static getExecArgv(kind: TsServerProcessKind, configuration: TypeScriptServiceConfiguration): string[] {
|
||||
const args: string[] = [];
|
||||
return args;
|
||||
}
|
||||
|
||||
const debugPort = this.getDebugPort(kind);
|
||||
if (debugPort) {
|
||||
const inspectFlag = ChildServerProcess.getTssDebugBrk() ? '--inspect-brk' : '--inspect';
|
||||
args.push(`${inspectFlag}=${debugPort}`);
|
||||
}
|
||||
|
||||
if (configuration.maxTsServerMemory) {
|
||||
args.push(`--max-old-space-size=${configuration.maxTsServerMemory}`);
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
private static getDebugPort(kind: TsServerProcessKind): number | undefined {
|
||||
if (kind === TsServerProcessKind.Syntax) {
|
||||
// We typically only want to debug the main semantic server
|
||||
return undefined;
|
||||
}
|
||||
const value = ChildServerProcess.getTssDebugBrk() || ChildServerProcess.getTssDebug();
|
||||
if (value) {
|
||||
const port = parseInt(value);
|
||||
if (!isNaN(port)) {
|
||||
return port;
|
||||
}
|
||||
}
|
||||
function getDebugPort(kind: TsServerProcessKind): number | undefined {
|
||||
if (kind === 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;
|
||||
}
|
||||
|
||||
private static getTssDebug(): string | undefined {
|
||||
return process.env[vscode.env.remoteName ? 'TSS_REMOTE_DEBUG' : 'TSS_DEBUG'];
|
||||
function getTssDebug(): string | undefined {
|
||||
return process.env[vscode.env.remoteName ? 'TSS_REMOTE_DEBUG' : 'TSS_DEBUG'];
|
||||
}
|
||||
|
||||
function getTssDebugBrk(): string | undefined {
|
||||
return process.env[vscode.env.remoteName ? 'TSS_REMOTE_DEBUG_BRK' : 'TSS_DEBUG_BRK'];
|
||||
}
|
||||
|
||||
class IpcChildServerProcess extends Disposable implements TsServerProcess {
|
||||
constructor(
|
||||
private readonly _process: child_process.ChildProcess,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
private static getTssDebugBrk(): string | undefined {
|
||||
return process.env[vscode.env.remoteName ? 'TSS_REMOTE_DEBUG_BRK' : 'TSS_DEBUG_BRK'];
|
||||
write(serverRequest: Proto.Request): void {
|
||||
this._process.send(serverRequest);
|
||||
}
|
||||
|
||||
private constructor(
|
||||
onData(handler: (data: Proto.Response) => void): void {
|
||||
this._process.on('message', handler);
|
||||
}
|
||||
|
||||
onExit(handler: (code: number | null, signal: string | null) => void): void {
|
||||
this._process.on('exit', handler);
|
||||
}
|
||||
|
||||
onError(handler: (err: Error) => void): void {
|
||||
this._process.on('error', handler);
|
||||
}
|
||||
|
||||
kill(): void {
|
||||
this._process.kill();
|
||||
}
|
||||
}
|
||||
|
||||
class StdioChildServerProcess extends Disposable implements TsServerProcess {
|
||||
private readonly _reader: Reader<Proto.Response>;
|
||||
|
||||
constructor(
|
||||
private readonly _process: child_process.ChildProcess,
|
||||
) {
|
||||
super();
|
||||
@@ -240,3 +247,38 @@ export class ChildServerProcess extends Disposable implements TsServerProcess {
|
||||
this._reader.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export class ElectronServiceProcessFactory implements TsServerProcessFactory {
|
||||
fork(
|
||||
version: TypeScriptVersion,
|
||||
args: readonly string[],
|
||||
kind: TsServerProcessKind,
|
||||
configuration: TypeScriptServiceConfiguration,
|
||||
versionManager: TypeScriptVersionManager,
|
||||
): TsServerProcess {
|
||||
let tsServerPath = version.tsServerPath;
|
||||
|
||||
if (!fs.existsSync(tsServerPath)) {
|
||||
vscode.window.showWarningMessage(localize('noServerFound', 'The path {0} doesn\'t point to a valid tsserver install. Falling back to bundled TypeScript version.', tsServerPath));
|
||||
versionManager.reset();
|
||||
tsServerPath = versionManager.currentVersion.tsServerPath;
|
||||
}
|
||||
|
||||
const useIpc = version.apiVersion?.gte(API.v460);
|
||||
|
||||
const runtimeArgs = [...args];
|
||||
if (useIpc) {
|
||||
runtimeArgs.push('--useNodeIpc');
|
||||
}
|
||||
|
||||
const childProcess = child_process.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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ export class TypeScriptServerSpawner {
|
||||
}
|
||||
|
||||
this._logger.info(`<${kind}> Forking...`);
|
||||
const process = this._factory.fork(version.tsServerPath, args, kind, configuration, this._versionManager);
|
||||
const process = this._factory.fork(version, args, kind, configuration, this._versionManager);
|
||||
this._logger.info(`<${kind}> Starting...`);
|
||||
|
||||
return new ProcessBasedTsServer(
|
||||
|
||||
@@ -37,6 +37,7 @@ export default class API {
|
||||
public static readonly v420 = API.fromSimpleString('4.2.0');
|
||||
public static readonly v430 = API.fromSimpleString('4.3.0');
|
||||
public static readonly v440 = API.fromSimpleString('4.4.0');
|
||||
public static readonly v460 = API.fromSimpleString('4.6.0');
|
||||
|
||||
public static fromVersionString(versionString: string): API {
|
||||
let version = semver.valid(versionString);
|
||||
|
||||
Reference in New Issue
Block a user