diff --git a/extensions/typescript-language-features/src/extension.ts b/extensions/typescript-language-features/src/extension.ts index fbbd1b6fc8c..24406ed1977 100644 --- a/extensions/typescript-language-features/src/extension.ts +++ b/extensions/typescript-language-features/src/extension.ts @@ -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 => { diff --git a/extensions/typescript-language-features/src/tsServer/server.ts b/extensions/typescript-language-features/src/tsServer/server.ts index 2c3c48aca84..482c3f9fa47 100644 --- a/extensions/typescript-language-features/src/tsServer/server.ts +++ b/extensions/typescript-language-features/src/tsServer/server.ts @@ -59,7 +59,7 @@ export const enum TsServerProcessKind { export interface TsServerProcessFactory { fork( - tsServerPath: string, + version: TypeScriptVersion, args: readonly string[], kind: TsServerProcessKind, configuration: TypeScriptServiceConfiguration, diff --git a/extensions/typescript-language-features/src/tsServer/serverProcess.browser.ts b/extensions/typescript-language-features/src/tsServer/serverProcess.browser.ts index aa856c6d0c3..1d00a6dc02f 100644 --- a/extensions/typescript-language-features/src/tsServer/serverProcess.browser.ts +++ b/extensions/typescript-language-features/src/tsServer/serverProcess.browser.ts @@ -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, diff --git a/extensions/typescript-language-features/src/tsServer/serverProcess.electron.ts b/extensions/typescript-language-features/src/tsServer/serverProcess.electron.ts index ad366f5f347..7bfd43e36e0 100644 --- a/extensions/typescript-language-features/src/tsServer/serverProcess.electron.ts +++ b/extensions/typescript-language-features/src/tsServer/serverProcess.electron.ts @@ -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 extends Disposable { } } -export class ChildServerProcess extends Disposable implements TsServerProcess { - private readonly _reader: Reader; +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; + + 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); + } +} diff --git a/extensions/typescript-language-features/src/tsServer/spawner.ts b/extensions/typescript-language-features/src/tsServer/spawner.ts index 9a1e767bdbc..a08eb06858c 100644 --- a/extensions/typescript-language-features/src/tsServer/spawner.ts +++ b/extensions/typescript-language-features/src/tsServer/spawner.ts @@ -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( diff --git a/extensions/typescript-language-features/src/utils/api.ts b/extensions/typescript-language-features/src/utils/api.ts index 61d9801ca71..56e98f18024 100644 --- a/extensions/typescript-language-features/src/utils/api.ts +++ b/extensions/typescript-language-features/src/utils/api.ts @@ -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);