From 7de74a6f3f9854ca69ffdb7230f134e0e82e76dc Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Fri, 11 Aug 2023 16:03:04 -0700 Subject: [PATCH 1/9] Add config for running tsserver on custom node --- .../typescript-language-features/package.json | 8 +++++- .../package.nls.json | 3 ++- .../configuration/configuration.browser.ts | 5 ++++ .../configuration/configuration.electron.ts | 16 ++++++++++++ .../src/configuration/configuration.ts | 3 +++ .../src/tsServer/serverProcess.electron.ts | 25 ++++++++++++++++--- 6 files changed, 54 insertions(+), 6 deletions(-) diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index d855af70e68df..d336df76086ec 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -21,7 +21,8 @@ "restrictedConfigurations": [ "typescript.tsdk", "typescript.tsserver.pluginPaths", - "typescript.npm" + "typescript.npm", + "typescript.tsserver.nodePath" ] } }, @@ -1250,6 +1251,11 @@ "description": "%configuration.tsserver.web.projectWideIntellisense.suppressSemanticErrors%", "scope": "window" }, + "typescript.tsserver.nodePath": { + "type": "string", + "description": "%configuration.tsserver.nodePath%", + "scope": "window" + }, "typescript.preferGoToSourceDefinition": { "type": "boolean", "default": false, diff --git a/extensions/typescript-language-features/package.nls.json b/extensions/typescript-language-features/package.nls.json index 81260c405e103..b36b115cbcb34 100644 --- a/extensions/typescript-language-features/package.nls.json +++ b/extensions/typescript-language-features/package.nls.json @@ -212,5 +212,6 @@ "configuration.suggest.classMemberSnippets.enabled": "Enable/disable snippet completions for class members.", "configuration.suggest.objectLiteralMethodSnippets.enabled": "Enable/disable snippet completions for methods in object literals. Requires using TypeScript 4.7+ in the workspace.", "configuration.tsserver.web.projectWideIntellisense.enabled": "Enable/disable project-wide IntelliSense on web. Requires that VS Code is running in a trusted context.", - "configuration.tsserver.web.projectWideIntellisense.suppressSemanticErrors": "Suppresses semantic errors. This is needed when using external packages as these can't be included analyzed on web." + "configuration.tsserver.web.projectWideIntellisense.suppressSemanticErrors": "Suppresses semantic errors. This is needed when using external packages as these can't be included analyzed on web.", + "configuration.tsserver.nodePath": "Run TSServer on a custom Node install." } diff --git a/extensions/typescript-language-features/src/configuration/configuration.browser.ts b/extensions/typescript-language-features/src/configuration/configuration.browser.ts index cfe7ed8b74d89..88c1173791b42 100644 --- a/extensions/typescript-language-features/src/configuration/configuration.browser.ts +++ b/extensions/typescript-language-features/src/configuration/configuration.browser.ts @@ -16,4 +16,9 @@ export class BrowserServiceConfigurationProvider extends BaseServiceConfiguratio protected readLocalTsdk(_configuration: vscode.WorkspaceConfiguration): string | null { return null; } + + // On browsers, we don't run TSServer on Node + protected readNodePath(_configuration: vscode.WorkspaceConfiguration): string | null { + return null; + } } diff --git a/extensions/typescript-language-features/src/configuration/configuration.electron.ts b/extensions/typescript-language-features/src/configuration/configuration.electron.ts index db84603c31428..52d9592aa64b4 100644 --- a/extensions/typescript-language-features/src/configuration/configuration.electron.ts +++ b/extensions/typescript-language-features/src/configuration/configuration.electron.ts @@ -35,4 +35,20 @@ export class ElectronServiceConfigurationProvider extends BaseServiceConfigurati } return null; } + + protected readNodePath(configuration: vscode.WorkspaceConfiguration): string | null { + const configNodePath = configuration.get('typescript.tsserver.nodePath'); + if (configNodePath) { + let fixedPath = this.fixPathPrefixes(configNodePath); + if (!path.isAbsolute(fixedPath)) { + const first = vscode.workspace.workspaceFolders?.[0]; + if (first) { + fixedPath = vscode.Uri.joinPath(first.uri, fixedPath).fsPath; + } else { + return null; + } + } + } + return null; + } } diff --git a/extensions/typescript-language-features/src/configuration/configuration.ts b/extensions/typescript-language-features/src/configuration/configuration.ts index cab1cf4c81904..a368c6e6d44e0 100644 --- a/extensions/typescript-language-features/src/configuration/configuration.ts +++ b/extensions/typescript-language-features/src/configuration/configuration.ts @@ -118,6 +118,7 @@ export interface TypeScriptServiceConfiguration { readonly watchOptions: Proto.WatchOptions | undefined; readonly includePackageJsonAutoImports: 'auto' | 'on' | 'off' | undefined; readonly enableTsServerTracing: boolean; + readonly nodePath: string | null; } export function areServiceConfigurationsEqual(a: TypeScriptServiceConfiguration, b: TypeScriptServiceConfiguration): boolean { @@ -150,11 +151,13 @@ export abstract class BaseServiceConfigurationProvider implements ServiceConfigu watchOptions: this.readWatchOptions(configuration), includePackageJsonAutoImports: this.readIncludePackageJsonAutoImports(configuration), enableTsServerTracing: this.readEnableTsServerTracing(configuration), + nodePath: this.readNodePath(configuration), }; } protected abstract readGlobalTsdk(configuration: vscode.WorkspaceConfiguration): string | null; protected abstract readLocalTsdk(configuration: vscode.WorkspaceConfiguration): string | null; + protected abstract readNodePath(configuration: vscode.WorkspaceConfiguration): string | null; protected readTsServerLogLevel(configuration: vscode.WorkspaceConfiguration): TsServerLogLevel { const setting = configuration.get('typescript.tsserver.log', 'off'); diff --git a/extensions/typescript-language-features/src/tsServer/serverProcess.electron.ts b/extensions/typescript-language-features/src/tsServer/serverProcess.electron.ts index b5848d5eb9ff1..6d71f3b79b0ff 100644 --- a/extensions/typescript-language-features/src/tsServer/serverProcess.electron.ts +++ b/extensions/typescript-language-features/src/tsServer/serverProcess.electron.ts @@ -134,10 +134,12 @@ class Reader extends Disposable { } } -function generatePatchedEnv(env: any, modulePath: string): any { +function generatePatchedEnv(env: any, modulePath: string, hasExecPath: boolean): any { const newEnv = Object.assign({}, env); - newEnv['ELECTRON_RUN_AS_NODE'] = '1'; + if (!hasExecPath) { + newEnv['ELECTRON_RUN_AS_NODE'] = '1'; + } newEnv['NODE_PATH'] = path.join(modulePath, '..', '..', '..'); // Ensure we always have a PATH set @@ -263,7 +265,17 @@ export class ElectronServiceProcessFactory implements TsServerProcessFactory { tsServerPath = versionManager.currentVersion.tsServerPath; } - const useIpc = version.apiVersion?.gte(API.v460); + const execPath = getExecPath(configuration); + let useExecPath = !!execPath; + if (useExecPath && !fs.existsSync(execPath!)) { + vscode.window.showWarningMessage(vscode.l10n.t("The path {0} doesn\'t point to a valid Node install. Falling back to bundled Node version.", execPath!)); + useExecPath = false; + } + const useIpc = !useExecPath && version.apiVersion?.gte(API.v460); + + if (useExecPath) { + console.log(`Using custom node exec path '${execPath}', useIpc is '${useIpc}'.`); // >> TODO: do real logging + } const runtimeArgs = [...args]; if (useIpc) { @@ -273,11 +285,16 @@ export class ElectronServiceProcessFactory implements TsServerProcessFactory { const childProcess = child_process.fork(tsServerPath, runtimeArgs, { silent: true, cwd: undefined, - env: generatePatchedEnv(process.env, tsServerPath), + env: generatePatchedEnv(process.env, tsServerPath, useExecPath), execArgv: getExecArgv(kind, configuration), + execPath: useExecPath ? execPath! : undefined, stdio: useIpc ? ['pipe', 'pipe', 'pipe', 'ipc'] : undefined, }); return useIpc ? new IpcChildServerProcess(childProcess) : new StdioChildServerProcess(childProcess); } } + +function getExecPath(configuration: TypeScriptServiceConfiguration): string | null { + return configuration.nodePath; +} From 5f288d77bfb655cde4097dd5db432dc8c181e829 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Wed, 16 Aug 2023 13:05:15 -0700 Subject: [PATCH 2/9] log when custom node install is used --- .../src/typescriptServiceClient.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/extensions/typescript-language-features/src/typescriptServiceClient.ts b/extensions/typescript-language-features/src/typescriptServiceClient.ts index 86c6bb8d9f1f0..8e7a828e71608 100644 --- a/extensions/typescript-language-features/src/typescriptServiceClient.ts +++ b/extensions/typescript-language-features/src/typescriptServiceClient.ts @@ -388,6 +388,10 @@ export default class TypeScriptServiceClient extends Disposable implements IType } this.info(`Using tsserver from: ${version.path}`); + const nodePath = this.configuration.nodePath; + if (nodePath) { + this.info(`Using Node install from ${nodePath} to run TS Server`); + } const apiVersion = version.apiVersion || API.defaultVersion; const mytoken = ++this.token; From 080e16a124b181fcb6e4ed6396eb9271aa386652 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Mon, 21 Aug 2023 16:39:37 -0700 Subject: [PATCH 3/9] create node version manager --- .../configuration/configuration.browser.ts | 6 +- .../configuration/configuration.electron.ts | 29 ++-- .../src/configuration/configuration.ts | 9 +- .../src/tsServer/nodeManager.ts | 131 ++++++++++++++++++ .../src/tsServer/serverProcess.electron.ts | 2 +- .../src/typescriptServiceClient.ts | 10 +- 6 files changed, 171 insertions(+), 16 deletions(-) create mode 100644 extensions/typescript-language-features/src/tsServer/nodeManager.ts diff --git a/extensions/typescript-language-features/src/configuration/configuration.browser.ts b/extensions/typescript-language-features/src/configuration/configuration.browser.ts index 88c1173791b42..15d4705de0ec9 100644 --- a/extensions/typescript-language-features/src/configuration/configuration.browser.ts +++ b/extensions/typescript-language-features/src/configuration/configuration.browser.ts @@ -18,7 +18,11 @@ export class BrowserServiceConfigurationProvider extends BaseServiceConfiguratio } // On browsers, we don't run TSServer on Node - protected readNodePath(_configuration: vscode.WorkspaceConfiguration): string | null { + protected readLocalNodePath(_configuration: vscode.WorkspaceConfiguration): string | null { + return null; + } + + protected override readGlobalNodePath(_configuration: vscode.WorkspaceConfiguration): string | null { return null; } } diff --git a/extensions/typescript-language-features/src/configuration/configuration.electron.ts b/extensions/typescript-language-features/src/configuration/configuration.electron.ts index 52d9592aa64b4..f43a3096b1b39 100644 --- a/extensions/typescript-language-features/src/configuration/configuration.electron.ts +++ b/extensions/typescript-language-features/src/configuration/configuration.electron.ts @@ -7,6 +7,7 @@ import * as os from 'os'; import * as path from 'path'; import * as vscode from 'vscode'; import { BaseServiceConfigurationProvider } from './configuration'; +import { RelativeWorkspacePathResolver } from '../utils/relativePathResolver'; export class ElectronServiceConfigurationProvider extends BaseServiceConfigurationProvider { @@ -36,17 +37,25 @@ export class ElectronServiceConfigurationProvider extends BaseServiceConfigurati return null; } - protected readNodePath(configuration: vscode.WorkspaceConfiguration): string | null { - const configNodePath = configuration.get('typescript.tsserver.nodePath'); - if (configNodePath) { - let fixedPath = this.fixPathPrefixes(configNodePath); + protected readLocalNodePath(configuration: vscode.WorkspaceConfiguration): string | null { + const inspect = configuration.inspect('typescript.tsserver.nodePath'); + if (inspect && typeof inspect.workspaceValue === 'string') { + const fixedPath = this.fixPathPrefixes(inspect.workspaceValue); if (!path.isAbsolute(fixedPath)) { - const first = vscode.workspace.workspaceFolders?.[0]; - if (first) { - fixedPath = vscode.Uri.joinPath(first.uri, fixedPath).fsPath; - } else { - return null; - } + const workspacePath = RelativeWorkspacePathResolver.asAbsoluteWorkspacePath(fixedPath); + return workspacePath || null; + } + return fixedPath; + } + return null; + } + + protected readGlobalNodePath(configuration: vscode.WorkspaceConfiguration): string | null { + const inspect = configuration.inspect('typescript.tsserver.nodePath'); + if (inspect && typeof inspect.globalValue === 'string') { + const fixedPath = this.fixPathPrefixes(inspect.globalValue); + if (path.isAbsolute(fixedPath)) { + return fixedPath; } } return null; diff --git a/extensions/typescript-language-features/src/configuration/configuration.ts b/extensions/typescript-language-features/src/configuration/configuration.ts index a368c6e6d44e0..20a672b7d19e6 100644 --- a/extensions/typescript-language-features/src/configuration/configuration.ts +++ b/extensions/typescript-language-features/src/configuration/configuration.ts @@ -118,7 +118,8 @@ export interface TypeScriptServiceConfiguration { readonly watchOptions: Proto.WatchOptions | undefined; readonly includePackageJsonAutoImports: 'auto' | 'on' | 'off' | undefined; readonly enableTsServerTracing: boolean; - readonly nodePath: string | null; + readonly localNodePath: string | null; + readonly globalNodePath: string | null; } export function areServiceConfigurationsEqual(a: TypeScriptServiceConfiguration, b: TypeScriptServiceConfiguration): boolean { @@ -151,13 +152,15 @@ export abstract class BaseServiceConfigurationProvider implements ServiceConfigu watchOptions: this.readWatchOptions(configuration), includePackageJsonAutoImports: this.readIncludePackageJsonAutoImports(configuration), enableTsServerTracing: this.readEnableTsServerTracing(configuration), - nodePath: this.readNodePath(configuration), + localNodePath: this.readLocalNodePath(configuration), + globalNodePath: this.readGlobalNodePath(configuration), }; } protected abstract readGlobalTsdk(configuration: vscode.WorkspaceConfiguration): string | null; protected abstract readLocalTsdk(configuration: vscode.WorkspaceConfiguration): string | null; - protected abstract readNodePath(configuration: vscode.WorkspaceConfiguration): string | null; + protected abstract readLocalNodePath(configuration: vscode.WorkspaceConfiguration): string | null; + protected abstract readGlobalNodePath(configuration: vscode.WorkspaceConfiguration): string | null; protected readTsServerLogLevel(configuration: vscode.WorkspaceConfiguration): TsServerLogLevel { const setting = configuration.get('typescript.tsserver.log', 'off'); diff --git a/extensions/typescript-language-features/src/tsServer/nodeManager.ts b/extensions/typescript-language-features/src/tsServer/nodeManager.ts new file mode 100644 index 0000000000000..80c354e9310e9 --- /dev/null +++ b/extensions/typescript-language-features/src/tsServer/nodeManager.ts @@ -0,0 +1,131 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import { TypeScriptServiceConfiguration } from '../configuration/configuration'; +import { setImmediate } from '../utils/async'; +import { Disposable } from '../utils/dispose'; + + +const useWorkspaceNodeStorageKey = 'typescript.useWorkspaceNode'; + +export class NodeVersionManager extends Disposable { + private _currentVersion: string | undefined; + + public constructor( + private configuration: TypeScriptServiceConfiguration, + private readonly workspaceState: vscode.Memento + ) { + super(); + + this._currentVersion = this.configuration.globalNodePath || undefined; + if (vscode.workspace.isTrusted) { + if (this.configuration.localNodePath) { + if (this.useWorkspaceNodeSetting === undefined) { + setImmediate(() => { + this.promptAndSetWorkspaceNode(); + }); + } + else if (this.useWorkspaceNodeSetting) { + this._currentVersion = this.configuration.localNodePath; + } + } + } + else { + this._disposables.push(vscode.workspace.onDidGrantWorkspaceTrust(() => { + if (this.configuration.localNodePath) { + if (this.useWorkspaceNodeSetting === undefined) { + setImmediate(() => { + this.promptAndSetWorkspaceNode(); + }); + } + else if (this.useWorkspaceNodeSetting) { + this.updateActiveVersion(this.configuration.localNodePath); + } + } + })); + } + } + + private readonly _onDidPickNewVersion = this._register(new vscode.EventEmitter()); + public readonly onDidPickNewVersion = this._onDidPickNewVersion.event; + + public async updateConfiguration(nextConfiguration: TypeScriptServiceConfiguration) { + const oldConfiguration = this.configuration; + this.configuration = nextConfiguration; + if (oldConfiguration.globalNodePath !== nextConfiguration.globalNodePath + || oldConfiguration.localNodePath !== nextConfiguration.localNodePath) { + await this.computeNewVersion(); + } + } + + private async computeNewVersion() { + let version = this.configuration.globalNodePath || undefined; + if (vscode.workspace.isTrusted && this.configuration.localNodePath) { + if (this.useWorkspaceNodeSetting === undefined) { + version = await this.promptUseWorkspaceNode(); + } + else if (this.useWorkspaceNodeSetting) { + version = this.configuration.localNodePath; + } + } + this.updateActiveVersion(version); + } + + public get currentVersion(): string | undefined { + return this._currentVersion; + } + + private async promptUseWorkspaceNode(): Promise { + const workspaceVersion = this.configuration.localNodePath; + + if (workspaceVersion === null) { + throw new Error('Could not prompt to use workspace Node version because no workspace Node install is specified'); + } + + const allowIt = vscode.l10n.t("Allow"); + const dismissPrompt = vscode.l10n.t("Dismiss"); + const alwaysAllow = vscode.l10n.t("Always allow"); + const neverAllow = vscode.l10n.t("Never allow"); + + const result = await vscode.window.showInformationMessage(vscode.l10n.t("This workspace contains a Node install to run TS Server. Would you like to use the workspace Node install to run TS Server?"), + allowIt, + dismissPrompt, + alwaysAllow, + neverAllow + ); + + let version = undefined; + switch (result) { + case alwaysAllow: + await this.workspaceState.update(useWorkspaceNodeStorageKey, true); + case allowIt: + version = workspaceVersion; + break; + case neverAllow: + await this.workspaceState.update(useWorkspaceNodeStorageKey, false); + } + return version; + } + + private async promptAndSetWorkspaceNode(): Promise { + const version = await this.promptUseWorkspaceNode(); + if (version !== undefined) { + this.updateActiveVersion(version); + } + } + + private updateActiveVersion(pickedVersion: string | undefined): void { + const oldVersion = this.currentVersion; + this._currentVersion = pickedVersion; + if (oldVersion !== pickedVersion) { + this._onDidPickNewVersion.fire(); + } + } + + private get useWorkspaceNodeSetting(): boolean | undefined { + return this.workspaceState.get(useWorkspaceNodeStorageKey); + } +} diff --git a/extensions/typescript-language-features/src/tsServer/serverProcess.electron.ts b/extensions/typescript-language-features/src/tsServer/serverProcess.electron.ts index 6d71f3b79b0ff..4114af2564891 100644 --- a/extensions/typescript-language-features/src/tsServer/serverProcess.electron.ts +++ b/extensions/typescript-language-features/src/tsServer/serverProcess.electron.ts @@ -296,5 +296,5 @@ export class ElectronServiceProcessFactory implements TsServerProcessFactory { } function getExecPath(configuration: TypeScriptServiceConfiguration): string | null { - return configuration.nodePath; + return configuration.localNodePath; } diff --git a/extensions/typescript-language-features/src/typescriptServiceClient.ts b/extensions/typescript-language-features/src/typescriptServiceClient.ts index 8e7a828e71608..a3861c5deed39 100644 --- a/extensions/typescript-language-features/src/typescriptServiceClient.ts +++ b/extensions/typescript-language-features/src/typescriptServiceClient.ts @@ -30,6 +30,7 @@ import { TelemetryProperties, TelemetryReporter, VSCodeTelemetryReporter } from import Tracer from './logging/tracer'; import { ProjectType, inferredProjectCompilerOptions } from './tsconfig'; import { Schemes } from './configuration/schemes'; +import { NodeVersionManager } from './tsServer/nodeManager'; export interface TsDiagnostics { @@ -103,6 +104,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType private _configuration: TypeScriptServiceConfiguration; private readonly pluginPathsProvider: TypeScriptPluginPathsProvider; private readonly _versionManager: TypeScriptVersionManager; + private readonly _nodeVersionManager: NodeVersionManager; private readonly logger: Logger; private readonly tracer: Tracer; @@ -173,6 +175,11 @@ export default class TypeScriptServiceClient extends Disposable implements IType this.restartTsServer(); })); + this._nodeVersionManager = this._register(new NodeVersionManager(this._configuration, context.workspaceState)); + this._register(this._nodeVersionManager.onDidPickNewVersion(() => { + this.restartTsServer(); + })); + this.bufferSyncSupport = new BufferSyncSupport(this, allModeIds, onCaseInsenitiveFileSystem); this.onReady(() => { this.bufferSyncSupport.listen(); }); @@ -193,6 +200,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType this.versionProvider.updateConfiguration(this._configuration); this._versionManager.updateConfiguration(this._configuration); this.pluginPathsProvider.updateConfiguration(this._configuration); + this._nodeVersionManager.updateConfiguration(this._configuration); if (this.serverState.type === ServerState.Type.Running) { if (!this._configuration.implicitProjectConfiguration.isEqualTo(oldConfiguration.implicitProjectConfiguration)) { @@ -388,7 +396,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType } this.info(`Using tsserver from: ${version.path}`); - const nodePath = this.configuration.nodePath; + const nodePath = this.configuration.localNodePath; if (nodePath) { this.info(`Using Node install from ${nodePath} to run TS Server`); } From d32c60ecb4fc74a87269eded007e3a8778fe7a4f Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Mon, 21 Aug 2023 17:21:06 -0700 Subject: [PATCH 4/9] get node path from node version manager everywhere --- .../src/tsServer/nodeManager.ts | 12 ++++++---- .../src/tsServer/server.ts | 2 ++ .../src/tsServer/serverProcess.browser.ts | 2 ++ .../src/tsServer/serverProcess.electron.ts | 24 +++++++------------ .../src/tsServer/spawner.ts | 4 +++- .../src/typescriptServiceClient.ts | 4 ++-- 6 files changed, 26 insertions(+), 22 deletions(-) diff --git a/extensions/typescript-language-features/src/tsServer/nodeManager.ts b/extensions/typescript-language-features/src/tsServer/nodeManager.ts index 80c354e9310e9..718b26e542f89 100644 --- a/extensions/typescript-language-features/src/tsServer/nodeManager.ts +++ b/extensions/typescript-language-features/src/tsServer/nodeManager.ts @@ -52,6 +52,14 @@ export class NodeVersionManager extends Disposable { private readonly _onDidPickNewVersion = this._register(new vscode.EventEmitter()); public readonly onDidPickNewVersion = this._onDidPickNewVersion.event; + public get currentVersion(): string | undefined { + return this._currentVersion; + } + + public reset(): void { + this._currentVersion = undefined; + } + public async updateConfiguration(nextConfiguration: TypeScriptServiceConfiguration) { const oldConfiguration = this.configuration; this.configuration = nextConfiguration; @@ -74,10 +82,6 @@ export class NodeVersionManager extends Disposable { this.updateActiveVersion(version); } - public get currentVersion(): string | undefined { - return this._currentVersion; - } - private async promptUseWorkspaceNode(): Promise { const workspaceVersion = this.configuration.localNodePath; diff --git a/extensions/typescript-language-features/src/tsServer/server.ts b/extensions/typescript-language-features/src/tsServer/server.ts index 421c5f5d8e9a6..883aa6830bdaa 100644 --- a/extensions/typescript-language-features/src/tsServer/server.ts +++ b/extensions/typescript-language-features/src/tsServer/server.ts @@ -19,6 +19,7 @@ import type * as Proto from './protocol/protocol'; import { EventName } from './protocol/protocol.const'; import { TypeScriptVersionManager } from './versionManager'; import { TypeScriptVersion } from './versionProvider'; +import { NodeVersionManager } from './nodeManager'; export enum ExecutionTarget { Semantic, @@ -70,6 +71,7 @@ export interface TsServerProcessFactory { kind: TsServerProcessKind, configuration: TypeScriptServiceConfiguration, versionManager: TypeScriptVersionManager, + nodeVersionManager: NodeVersionManager, tsServerLog: TsServerLog | undefined, ): TsServerProcess; } diff --git a/extensions/typescript-language-features/src/tsServer/serverProcess.browser.ts b/extensions/typescript-language-features/src/tsServer/serverProcess.browser.ts index ab916a1f0e933..01e292b5ab8b6 100644 --- a/extensions/typescript-language-features/src/tsServer/serverProcess.browser.ts +++ b/extensions/typescript-language-features/src/tsServer/serverProcess.browser.ts @@ -13,6 +13,7 @@ import type * as Proto from './protocol/protocol'; import { TsServerLog, TsServerProcess, TsServerProcessFactory, TsServerProcessKind } from './server'; import { TypeScriptVersionManager } from './versionManager'; import { TypeScriptVersion } from './versionProvider'; +import { NodeVersionManager } from './nodeManager'; type BrowserWatchEvent = { type: 'watchDirectory' | 'watchFile'; @@ -40,6 +41,7 @@ export class WorkerServerProcessFactory implements TsServerProcessFactory { kind: TsServerProcessKind, _configuration: TypeScriptServiceConfiguration, _versionManager: TypeScriptVersionManager, + _nodeVersionManager: NodeVersionManager, tsServerLog: TsServerLog | undefined, ) { const tsServerPath = version.tsServerPath; diff --git a/extensions/typescript-language-features/src/tsServer/serverProcess.electron.ts b/extensions/typescript-language-features/src/tsServer/serverProcess.electron.ts index 4114af2564891..046f8d9edb051 100644 --- a/extensions/typescript-language-features/src/tsServer/serverProcess.electron.ts +++ b/extensions/typescript-language-features/src/tsServer/serverProcess.electron.ts @@ -15,6 +15,7 @@ import type * as Proto from './protocol/protocol'; import { TsServerLog, TsServerProcess, TsServerProcessFactory, TsServerProcessKind } from './server'; import { TypeScriptVersionManager } from './versionManager'; import { TypeScriptVersion } from './versionProvider'; +import { NodeVersionManager } from './nodeManager'; const defaultSize: number = 8192; @@ -255,6 +256,7 @@ export class ElectronServiceProcessFactory implements TsServerProcessFactory { kind: TsServerProcessKind, configuration: TypeScriptServiceConfiguration, versionManager: TypeScriptVersionManager, + nodeVersionManager: NodeVersionManager, _tsserverLog: TsServerLog | undefined, ): TsServerProcess { let tsServerPath = version.tsServerPath; @@ -265,17 +267,13 @@ export class ElectronServiceProcessFactory implements TsServerProcessFactory { tsServerPath = versionManager.currentVersion.tsServerPath; } - const execPath = getExecPath(configuration); - let useExecPath = !!execPath; - if (useExecPath && !fs.existsSync(execPath!)) { + let execPath = nodeVersionManager.currentVersion; + if (execPath && !fs.existsSync(execPath)) { vscode.window.showWarningMessage(vscode.l10n.t("The path {0} doesn\'t point to a valid Node install. Falling back to bundled Node version.", execPath!)); - useExecPath = false; - } - const useIpc = !useExecPath && version.apiVersion?.gte(API.v460); - - if (useExecPath) { - console.log(`Using custom node exec path '${execPath}', useIpc is '${useIpc}'.`); // >> TODO: do real logging + nodeVersionManager.reset(); + execPath = nodeVersionManager.currentVersion; } + const useIpc = execPath && version.apiVersion?.gte(API.v460); const runtimeArgs = [...args]; if (useIpc) { @@ -285,16 +283,12 @@ export class ElectronServiceProcessFactory implements TsServerProcessFactory { const childProcess = child_process.fork(tsServerPath, runtimeArgs, { silent: true, cwd: undefined, - env: generatePatchedEnv(process.env, tsServerPath, useExecPath), + env: generatePatchedEnv(process.env, tsServerPath, !!execPath), execArgv: getExecArgv(kind, configuration), - execPath: useExecPath ? execPath! : undefined, + execPath, stdio: useIpc ? ['pipe', 'pipe', 'pipe', 'ipc'] : undefined, }); return useIpc ? new IpcChildServerProcess(childProcess) : new StdioChildServerProcess(childProcess); } } - -function getExecPath(configuration: TypeScriptServiceConfiguration): string | null { - return configuration.localNodePath; -} diff --git a/extensions/typescript-language-features/src/tsServer/spawner.ts b/extensions/typescript-language-features/src/tsServer/spawner.ts index 0fa9bedf4a6ab..52dcf5baa1939 100644 --- a/extensions/typescript-language-features/src/tsServer/spawner.ts +++ b/extensions/typescript-language-features/src/tsServer/spawner.ts @@ -19,6 +19,7 @@ import { PluginManager } from './plugins'; import { GetErrRoutingTsServer, ITypeScriptServer, SingleTsServer, SyntaxRoutingTsServer, TsServerDelegate, TsServerLog, TsServerProcessFactory, TsServerProcessKind } from './server'; import { TypeScriptVersionManager } from './versionManager'; import { ITypeScriptVersionProvider, TypeScriptVersion } from './versionProvider'; +import { NodeVersionManager } from './nodeManager'; const enum CompositeServerType { /** Run a single server that handles all commands */ @@ -44,6 +45,7 @@ export class TypeScriptServerSpawner { public constructor( private readonly _versionProvider: ITypeScriptVersionProvider, private readonly _versionManager: TypeScriptVersionManager, + private readonly _nodeVersionManager: NodeVersionManager, private readonly _logDirectoryProvider: ILogDirectoryProvider, private readonly _pluginPathsProvider: TypeScriptPluginPathsProvider, private readonly _logger: Logger, @@ -160,7 +162,7 @@ export class TypeScriptServerSpawner { } this._logger.info(`<${kind}> Forking...`); - const process = this._factory.fork(version, args, kind, configuration, this._versionManager, tsServerLog); + const process = this._factory.fork(version, args, kind, configuration, this._versionManager, this._nodeVersionManager, tsServerLog); this._logger.info(`<${kind}> Starting...`); return new SingleTsServer( diff --git a/extensions/typescript-language-features/src/typescriptServiceClient.ts b/extensions/typescript-language-features/src/typescriptServiceClient.ts index a3861c5deed39..b78ba32067750 100644 --- a/extensions/typescript-language-features/src/typescriptServiceClient.ts +++ b/extensions/typescript-language-features/src/typescriptServiceClient.ts @@ -222,7 +222,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType return this.apiVersion.fullVersionString; }); - this.typescriptServerSpawner = new TypeScriptServerSpawner(this.versionProvider, this._versionManager, this.logDirectoryProvider, this.pluginPathsProvider, this.logger, this.telemetryReporter, this.tracer, this.processFactory); + this.typescriptServerSpawner = new TypeScriptServerSpawner(this.versionProvider, this._versionManager, this._nodeVersionManager, this.logDirectoryProvider, this.pluginPathsProvider, this.logger, this.telemetryReporter, this.tracer, this.processFactory); this._register(this.pluginManager.onDidUpdateConfig(update => { this.configurePlugin(update.pluginId, update.config); @@ -396,7 +396,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType } this.info(`Using tsserver from: ${version.path}`); - const nodePath = this.configuration.localNodePath; + const nodePath = this._nodeVersionManager.currentVersion; if (nodePath) { this.info(`Using Node install from ${nodePath} to run TS Server`); } From 17a3c234cb7ba4c30cb9cab695f29de5f467c04a Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Tue, 22 Aug 2023 12:51:59 -0700 Subject: [PATCH 5/9] modify prompt --- .../package.nls.json | 2 +- .../src/tsServer/nodeManager.ts | 29 ++++++++----------- .../src/typescriptServiceClient.ts | 2 +- 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/extensions/typescript-language-features/package.nls.json b/extensions/typescript-language-features/package.nls.json index b36b115cbcb34..d6db5eb1b4ebf 100644 --- a/extensions/typescript-language-features/package.nls.json +++ b/extensions/typescript-language-features/package.nls.json @@ -213,5 +213,5 @@ "configuration.suggest.objectLiteralMethodSnippets.enabled": "Enable/disable snippet completions for methods in object literals. Requires using TypeScript 4.7+ in the workspace.", "configuration.tsserver.web.projectWideIntellisense.enabled": "Enable/disable project-wide IntelliSense on web. Requires that VS Code is running in a trusted context.", "configuration.tsserver.web.projectWideIntellisense.suppressSemanticErrors": "Suppresses semantic errors. This is needed when using external packages as these can't be included analyzed on web.", - "configuration.tsserver.nodePath": "Run TSServer on a custom Node install." + "configuration.tsserver.nodePath": "Run TS Server on a custom Node installation." } diff --git a/extensions/typescript-language-features/src/tsServer/nodeManager.ts b/extensions/typescript-language-features/src/tsServer/nodeManager.ts index 718b26e542f89..e8fb2312fd3d6 100644 --- a/extensions/typescript-language-features/src/tsServer/nodeManager.ts +++ b/extensions/typescript-language-features/src/tsServer/nodeManager.ts @@ -86,30 +86,25 @@ export class NodeVersionManager extends Disposable { const workspaceVersion = this.configuration.localNodePath; if (workspaceVersion === null) { - throw new Error('Could not prompt to use workspace Node version because no workspace Node install is specified'); + throw new Error('Could not prompt to use workspace Node installation because no workspace Node installation is specified'); } - const allowIt = vscode.l10n.t("Allow"); - const dismissPrompt = vscode.l10n.t("Dismiss"); - const alwaysAllow = vscode.l10n.t("Always allow"); - const neverAllow = vscode.l10n.t("Never allow"); + const allow = vscode.l10n.t("Allow"); + const dismiss = vscode.l10n.t("Dismiss"); + const neverAllow = vscode.l10n.t("Never in this workspace"); - const result = await vscode.window.showInformationMessage(vscode.l10n.t("This workspace contains a Node install to run TS Server. Would you like to use the workspace Node install to run TS Server?"), - allowIt, - dismissPrompt, - alwaysAllow, + const result = await vscode.window.showInformationMessage(vscode.l10n.t("This workspace specifies a custom Node installation to run TS Server. Would you like to use this workspace's custom Node installation to run TS Server?"), + allow, + dismiss, neverAllow ); let version = undefined; - switch (result) { - case alwaysAllow: - await this.workspaceState.update(useWorkspaceNodeStorageKey, true); - case allowIt: - version = workspaceVersion; - break; - case neverAllow: - await this.workspaceState.update(useWorkspaceNodeStorageKey, false); + if (result === allow) { + await this.workspaceState.update(useWorkspaceNodeStorageKey, true); + version = workspaceVersion; + } else if (result === neverAllow) { + await this.workspaceState.update(useWorkspaceNodeStorageKey, false); } return version; } diff --git a/extensions/typescript-language-features/src/typescriptServiceClient.ts b/extensions/typescript-language-features/src/typescriptServiceClient.ts index b78ba32067750..86f139c99040a 100644 --- a/extensions/typescript-language-features/src/typescriptServiceClient.ts +++ b/extensions/typescript-language-features/src/typescriptServiceClient.ts @@ -398,7 +398,7 @@ export default class TypeScriptServiceClient extends Disposable implements IType this.info(`Using tsserver from: ${version.path}`); const nodePath = this._nodeVersionManager.currentVersion; if (nodePath) { - this.info(`Using Node install from ${nodePath} to run TS Server`); + this.info(`Using Node installation from ${nodePath} to run TS Server`); } const apiVersion = version.apiVersion || API.defaultVersion; From f8fcc172d196bf35f274ad3f4059afb931287aa9 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Tue, 22 Aug 2023 13:11:05 -0700 Subject: [PATCH 6/9] fix useIpc --- .../src/tsServer/serverProcess.electron.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/typescript-language-features/src/tsServer/serverProcess.electron.ts b/extensions/typescript-language-features/src/tsServer/serverProcess.electron.ts index 046f8d9edb051..0809cc40b2eeb 100644 --- a/extensions/typescript-language-features/src/tsServer/serverProcess.electron.ts +++ b/extensions/typescript-language-features/src/tsServer/serverProcess.electron.ts @@ -269,11 +269,11 @@ export class ElectronServiceProcessFactory implements TsServerProcessFactory { let execPath = nodeVersionManager.currentVersion; if (execPath && !fs.existsSync(execPath)) { - vscode.window.showWarningMessage(vscode.l10n.t("The path {0} doesn\'t point to a valid Node install. Falling back to bundled Node version.", execPath!)); + vscode.window.showWarningMessage(vscode.l10n.t("The path {0} doesn\'t point to a valid Node installation. Falling back to bundled Node.", execPath!)); nodeVersionManager.reset(); execPath = nodeVersionManager.currentVersion; } - const useIpc = execPath && version.apiVersion?.gte(API.v460); + const useIpc = !execPath && version.apiVersion?.gte(API.v460); const runtimeArgs = [...args]; if (useIpc) { From 8210f67cb84a3b4f392fd3740875ef26ce1df619 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Wed, 23 Aug 2023 12:25:17 -0700 Subject: [PATCH 7/9] use spawn for custom node and set windowsHide --- .../src/tsServer/serverProcess.electron.ts | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/extensions/typescript-language-features/src/tsServer/serverProcess.electron.ts b/extensions/typescript-language-features/src/tsServer/serverProcess.electron.ts index 0809cc40b2eeb..daefff96d30ef 100644 --- a/extensions/typescript-language-features/src/tsServer/serverProcess.electron.ts +++ b/extensions/typescript-language-features/src/tsServer/serverProcess.electron.ts @@ -273,21 +273,29 @@ export class ElectronServiceProcessFactory implements TsServerProcessFactory { nodeVersionManager.reset(); execPath = nodeVersionManager.currentVersion; } - const useIpc = !execPath && version.apiVersion?.gte(API.v460); + const env = generatePatchedEnv(process.env, tsServerPath, !!execPath); const runtimeArgs = [...args]; + const execArgv = getExecArgv(kind, configuration); + const useIpc = !execPath && version.apiVersion?.gte(API.v460); if (useIpc) { runtimeArgs.push('--useNodeIpc'); } - const childProcess = child_process.fork(tsServerPath, runtimeArgs, { - silent: true, - cwd: undefined, - env: generatePatchedEnv(process.env, tsServerPath, !!execPath), - execArgv: getExecArgv(kind, configuration), - execPath, - stdio: useIpc ? ['pipe', 'pipe', 'pipe', 'ipc'] : undefined, - }); + const childProcess = execPath ? + child_process.spawn(execPath, [...execArgv, tsServerPath, ...runtimeArgs], { + shell: true, + windowsHide: true, + cwd: undefined, + env, + }) : + child_process.fork(tsServerPath, runtimeArgs, { + silent: true, + cwd: undefined, + env, + execArgv, + stdio: useIpc ? ['pipe', 'pipe', 'pipe', 'ipc'] : undefined, + }); return useIpc ? new IpcChildServerProcess(childProcess) : new StdioChildServerProcess(childProcess); } From ac967411601e9f1bc6bfecf6bb9770a40f10b4d4 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Wed, 23 Aug 2023 17:41:26 -0700 Subject: [PATCH 8/9] detect node --- .../package.nls.json | 2 +- .../configuration/configuration.electron.ts | 43 +++++++++- .../src/tsServer/nodeManager.ts | 79 ++++++++++++------- .../src/tsServer/serverProcess.electron.ts | 7 +- 4 files changed, 92 insertions(+), 39 deletions(-) diff --git a/extensions/typescript-language-features/package.nls.json b/extensions/typescript-language-features/package.nls.json index d6db5eb1b4ebf..779a9c7bf8d3e 100644 --- a/extensions/typescript-language-features/package.nls.json +++ b/extensions/typescript-language-features/package.nls.json @@ -213,5 +213,5 @@ "configuration.suggest.objectLiteralMethodSnippets.enabled": "Enable/disable snippet completions for methods in object literals. Requires using TypeScript 4.7+ in the workspace.", "configuration.tsserver.web.projectWideIntellisense.enabled": "Enable/disable project-wide IntelliSense on web. Requires that VS Code is running in a trusted context.", "configuration.tsserver.web.projectWideIntellisense.suppressSemanticErrors": "Suppresses semantic errors. This is needed when using external packages as these can't be included analyzed on web.", - "configuration.tsserver.nodePath": "Run TS Server on a custom Node installation." + "configuration.tsserver.nodePath": "Run TS Server on a custom Node installation. This can be a path to a Node executable, or 'node' if you want VS Code to detect a Node installation." } diff --git a/extensions/typescript-language-features/src/configuration/configuration.electron.ts b/extensions/typescript-language-features/src/configuration/configuration.electron.ts index f43a3096b1b39..0c2a7ab12f780 100644 --- a/extensions/typescript-language-features/src/configuration/configuration.electron.ts +++ b/extensions/typescript-language-features/src/configuration/configuration.electron.ts @@ -6,6 +6,8 @@ import * as os from 'os'; import * as path from 'path'; import * as vscode from 'vscode'; +import * as child_process from 'child_process'; +import * as fs from 'fs'; import { BaseServiceConfigurationProvider } from './configuration'; import { RelativeWorkspacePathResolver } from '../utils/relativePathResolver'; @@ -38,8 +40,15 @@ export class ElectronServiceConfigurationProvider extends BaseServiceConfigurati } protected readLocalNodePath(configuration: vscode.WorkspaceConfiguration): string | null { + return this.validatePath(this.readLocalNodePathWorker(configuration)); + } + + private readLocalNodePathWorker(configuration: vscode.WorkspaceConfiguration): string | null { const inspect = configuration.inspect('typescript.tsserver.nodePath'); - if (inspect && typeof inspect.workspaceValue === 'string') { + if (inspect?.workspaceValue && typeof inspect.workspaceValue === 'string') { + if (inspect.workspaceValue === 'node') { + return this.findNodePath(); + } const fixedPath = this.fixPathPrefixes(inspect.workspaceValue); if (!path.isAbsolute(fixedPath)) { const workspacePath = RelativeWorkspacePathResolver.asAbsoluteWorkspacePath(fixedPath); @@ -51,8 +60,15 @@ export class ElectronServiceConfigurationProvider extends BaseServiceConfigurati } protected readGlobalNodePath(configuration: vscode.WorkspaceConfiguration): string | null { + return this.validatePath(this.readGlobalNodePathWorker(configuration)); + } + + private readGlobalNodePathWorker(configuration: vscode.WorkspaceConfiguration): string | null { const inspect = configuration.inspect('typescript.tsserver.nodePath'); - if (inspect && typeof inspect.globalValue === 'string') { + if (inspect?.globalValue && typeof inspect.globalValue === 'string') { + if (inspect.globalValue === 'node') { + return this.findNodePath(); + } const fixedPath = this.fixPathPrefixes(inspect.globalValue); if (path.isAbsolute(fixedPath)) { return fixedPath; @@ -60,4 +76,27 @@ export class ElectronServiceConfigurationProvider extends BaseServiceConfigurati } return null; } + + private findNodePath(): string | null { + try { + const out = child_process.execFileSync('node', ['-e', 'console.log(process.execPath)'], { + windowsHide: true, + timeout: 2000, + cwd: vscode.workspace.workspaceFolders?.[0].uri.fsPath, + encoding: 'utf-8', + }); + return out.trim(); + } catch (error) { + vscode.window.showWarningMessage(vscode.l10n.t("Could not detect a Node installation to run TS Server.")); + return null; + } + } + + private validatePath(nodePath: string | null): string | null { + if (nodePath && (!fs.existsSync(nodePath) || fs.lstatSync(nodePath).isDirectory())) { + vscode.window.showWarningMessage(vscode.l10n.t("The path {0} doesn\'t point to a valid Node installation to run TS Server. Falling back to bundled Node.", nodePath)); + return null; + } + return nodePath; + } } diff --git a/extensions/typescript-language-features/src/tsServer/nodeManager.ts b/extensions/typescript-language-features/src/tsServer/nodeManager.ts index e8fb2312fd3d6..037fc1898e864 100644 --- a/extensions/typescript-language-features/src/tsServer/nodeManager.ts +++ b/extensions/typescript-language-features/src/tsServer/nodeManager.ts @@ -10,6 +10,9 @@ import { Disposable } from '../utils/dispose'; const useWorkspaceNodeStorageKey = 'typescript.useWorkspaceNode'; +const lastKnownWorkspaceNodeStorageKey = 'typescript.lastKnownWorkspaceNode'; +type UseWorkspaceNodeState = undefined | boolean; +type LastKnownWorkspaceNodeState = undefined | string; export class NodeVersionManager extends Disposable { private _currentVersion: string | undefined; @@ -22,27 +25,31 @@ export class NodeVersionManager extends Disposable { this._currentVersion = this.configuration.globalNodePath || undefined; if (vscode.workspace.isTrusted) { - if (this.configuration.localNodePath) { - if (this.useWorkspaceNodeSetting === undefined) { + const workspaceVersion = this.configuration.localNodePath; + if (workspaceVersion) { + const useWorkspaceNode = this.canUseWorkspaceNode(workspaceVersion); + if (useWorkspaceNode === undefined) { setImmediate(() => { this.promptAndSetWorkspaceNode(); }); } - else if (this.useWorkspaceNodeSetting) { - this._currentVersion = this.configuration.localNodePath; + else if (useWorkspaceNode) { + this._currentVersion = workspaceVersion; } } } else { this._disposables.push(vscode.workspace.onDidGrantWorkspaceTrust(() => { - if (this.configuration.localNodePath) { - if (this.useWorkspaceNodeSetting === undefined) { + const workspaceVersion = this.configuration.localNodePath; + if (workspaceVersion) { + const useWorkspaceNode = this.canUseWorkspaceNode(workspaceVersion); + if (useWorkspaceNode === undefined) { setImmediate(() => { this.promptAndSetWorkspaceNode(); }); } - else if (this.useWorkspaceNodeSetting) { - this.updateActiveVersion(this.configuration.localNodePath); + else if (useWorkspaceNode) { + this.updateActiveVersion(workspaceVersion); } } })); @@ -56,10 +63,6 @@ export class NodeVersionManager extends Disposable { return this._currentVersion; } - public reset(): void { - this._currentVersion = undefined; - } - public async updateConfiguration(nextConfiguration: TypeScriptServiceConfiguration) { const oldConfiguration = this.configuration; this.configuration = nextConfiguration; @@ -71,12 +74,14 @@ export class NodeVersionManager extends Disposable { private async computeNewVersion() { let version = this.configuration.globalNodePath || undefined; - if (vscode.workspace.isTrusted && this.configuration.localNodePath) { - if (this.useWorkspaceNodeSetting === undefined) { - version = await this.promptUseWorkspaceNode(); + const workspaceVersion = this.configuration.localNodePath; + if (vscode.workspace.isTrusted && workspaceVersion) { + const useWorkspaceNode = this.canUseWorkspaceNode(workspaceVersion); + if (useWorkspaceNode === undefined) { + version = await this.promptUseWorkspaceNode() || version; } - else if (this.useWorkspaceNodeSetting) { - version = this.configuration.localNodePath; + else if (useWorkspaceNode) { + version = workspaceVersion; } } this.updateActiveVersion(version); @@ -84,27 +89,32 @@ export class NodeVersionManager extends Disposable { private async promptUseWorkspaceNode(): Promise { const workspaceVersion = this.configuration.localNodePath; - if (workspaceVersion === null) { throw new Error('Could not prompt to use workspace Node installation because no workspace Node installation is specified'); } - const allow = vscode.l10n.t("Allow"); - const dismiss = vscode.l10n.t("Dismiss"); - const neverAllow = vscode.l10n.t("Never in this workspace"); + const allow = vscode.l10n.t("Yes"); + const disallow = vscode.l10n.t("No"); + const dismiss = vscode.l10n.t("Not now"); - const result = await vscode.window.showInformationMessage(vscode.l10n.t("This workspace specifies a custom Node installation to run TS Server. Would you like to use this workspace's custom Node installation to run TS Server?"), + const result = await vscode.window.showInformationMessage(vscode.l10n.t("This workspace wants to use the Node installation at '{0}' to run TS Server. Would you like to use it?", workspaceVersion), allow, + disallow, dismiss, - neverAllow ); let version = undefined; - if (result === allow) { - await this.workspaceState.update(useWorkspaceNodeStorageKey, true); - version = workspaceVersion; - } else if (result === neverAllow) { - await this.workspaceState.update(useWorkspaceNodeStorageKey, false); + switch (result) { + case allow: + await this.setUseWorkspaceNodeState(true, workspaceVersion); + version = workspaceVersion; + break; + case disallow: + await this.setUseWorkspaceNodeState(false, workspaceVersion); + break; + case dismiss: + await this.setUseWorkspaceNodeState(undefined, workspaceVersion); + break; } return version; } @@ -124,7 +134,16 @@ export class NodeVersionManager extends Disposable { } } - private get useWorkspaceNodeSetting(): boolean | undefined { - return this.workspaceState.get(useWorkspaceNodeStorageKey); + private canUseWorkspaceNode(nodeVersion: string): boolean | undefined { + const lastKnownWorkspaceNode = this.workspaceState.get(lastKnownWorkspaceNodeStorageKey); + if (lastKnownWorkspaceNode === nodeVersion) { + return this.workspaceState.get(useWorkspaceNodeStorageKey); + } + return undefined; + } + + private async setUseWorkspaceNodeState(allow: boolean | undefined, nodeVersion: string) { + await this.workspaceState.update(lastKnownWorkspaceNodeStorageKey, nodeVersion); + await this.workspaceState.update(useWorkspaceNodeStorageKey, allow); } } diff --git a/extensions/typescript-language-features/src/tsServer/serverProcess.electron.ts b/extensions/typescript-language-features/src/tsServer/serverProcess.electron.ts index daefff96d30ef..8b0ec2fb7b7a9 100644 --- a/extensions/typescript-language-features/src/tsServer/serverProcess.electron.ts +++ b/extensions/typescript-language-features/src/tsServer/serverProcess.electron.ts @@ -267,12 +267,7 @@ export class ElectronServiceProcessFactory implements TsServerProcessFactory { tsServerPath = versionManager.currentVersion.tsServerPath; } - let execPath = nodeVersionManager.currentVersion; - if (execPath && !fs.existsSync(execPath)) { - vscode.window.showWarningMessage(vscode.l10n.t("The path {0} doesn\'t point to a valid Node installation. Falling back to bundled Node.", execPath!)); - nodeVersionManager.reset(); - execPath = nodeVersionManager.currentVersion; - } + const execPath = nodeVersionManager.currentVersion; const env = generatePatchedEnv(process.env, tsServerPath, !!execPath); const runtimeArgs = [...args]; From 2c2e2d7cfbec831829e5ff455defd011aa0f2621 Mon Sep 17 00:00:00 2001 From: Gabriela Araujo Britto Date: Wed, 23 Aug 2023 18:27:47 -0700 Subject: [PATCH 9/9] link memory setting to node setting --- extensions/typescript-language-features/package.json | 2 +- extensions/typescript-language-features/package.nls.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index d336df76086ec..f6ccb579de0ab 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -1132,7 +1132,7 @@ "typescript.tsserver.maxTsServerMemory": { "type": "number", "default": 3072, - "description": "%configuration.tsserver.maxTsServerMemory%", + "markdownDescription": "%configuration.tsserver.maxTsServerMemory%", "scope": "window" }, "typescript.tsserver.experimental.enableProjectDiagnostics": { diff --git a/extensions/typescript-language-features/package.nls.json b/extensions/typescript-language-features/package.nls.json index 779a9c7bf8d3e..ff05f8223ed81 100644 --- a/extensions/typescript-language-features/package.nls.json +++ b/extensions/typescript-language-features/package.nls.json @@ -70,7 +70,7 @@ "configuration.tsserver.useSyntaxServer.always": "Use a lighter weight syntax server to handle all IntelliSense operations. This syntax server can only provide IntelliSense for opened files.", "configuration.tsserver.useSyntaxServer.never": "Don't use a dedicated syntax server. Use a single server to handle all IntelliSense operations.", "configuration.tsserver.useSyntaxServer.auto": "Spawn both a full server and a lighter weight server dedicated to syntax operations. The syntax server is used to speed up syntax operations and provide IntelliSense while projects are loading.", - "configuration.tsserver.maxTsServerMemory": "The maximum amount of memory (in MB) to allocate to the TypeScript server process.", + "configuration.tsserver.maxTsServerMemory": "The maximum amount of memory (in MB) to allocate to the TypeScript server process. To use a memory limit greater than 4 GB, use `#typescript.tsserver.nodePath#` to run TS Server with a custom Node installation.", "configuration.tsserver.experimental.enableProjectDiagnostics": "(Experimental) Enables project wide error reporting.", "typescript.locale": "Sets the locale used to report JavaScript and TypeScript errors. Defaults to use VS Code's locale.", "configuration.implicitProjectConfig.module": "Sets the module system for the program. See more: https://www.typescriptlang.org/tsconfig#module.",