-
Notifications
You must be signed in to change notification settings - Fork 29k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add custom Node option to run TS Server #191019
Changes from all commits
7de74a6
5f288d7
080e16a
d32c60e
17a3c23
f8fcc17
b3830ba
8210f67
ac96741
5d9c2bf
2c2e2d7
5944aae
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
/*--------------------------------------------------------------------------------------------- | ||
* 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'; | ||
const lastKnownWorkspaceNodeStorageKey = 'typescript.lastKnownWorkspaceNode'; | ||
type UseWorkspaceNodeState = undefined | boolean; | ||
type LastKnownWorkspaceNodeState = undefined | string; | ||
|
||
export class NodeVersionManager extends Disposable { | ||
private _currentVersion: string | undefined; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I assume the "version" moniker is left over from copying the tsdk code? Was confused as to whether or not this was checking versions or something until I realized that this was acutally managing the node path. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, do you think simply There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was imagining |
||
|
||
public constructor( | ||
private configuration: TypeScriptServiceConfiguration, | ||
private readonly workspaceState: vscode.Memento | ||
) { | ||
super(); | ||
|
||
this._currentVersion = this.configuration.globalNodePath || undefined; | ||
if (vscode.workspace.isTrusted) { | ||
const workspaceVersion = this.configuration.localNodePath; | ||
if (workspaceVersion) { | ||
const useWorkspaceNode = this.canUseWorkspaceNode(workspaceVersion); | ||
if (useWorkspaceNode === undefined) { | ||
setImmediate(() => { | ||
this.promptAndSetWorkspaceNode(); | ||
}); | ||
} | ||
else if (useWorkspaceNode) { | ||
this._currentVersion = workspaceVersion; | ||
} | ||
} | ||
} | ||
else { | ||
this._disposables.push(vscode.workspace.onDidGrantWorkspaceTrust(() => { | ||
const workspaceVersion = this.configuration.localNodePath; | ||
if (workspaceVersion) { | ||
const useWorkspaceNode = this.canUseWorkspaceNode(workspaceVersion); | ||
if (useWorkspaceNode === undefined) { | ||
setImmediate(() => { | ||
this.promptAndSetWorkspaceNode(); | ||
}); | ||
} | ||
else if (useWorkspaceNode) { | ||
this.updateActiveVersion(workspaceVersion); | ||
} | ||
} | ||
})); | ||
} | ||
} | ||
|
||
private readonly _onDidPickNewVersion = this._register(new vscode.EventEmitter<void>()); | ||
public readonly onDidPickNewVersion = this._onDidPickNewVersion.event; | ||
|
||
public get currentVersion(): string | undefined { | ||
return this._currentVersion; | ||
} | ||
|
||
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; | ||
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 (useWorkspaceNode) { | ||
version = workspaceVersion; | ||
} | ||
} | ||
this.updateActiveVersion(version); | ||
} | ||
|
||
private async promptUseWorkspaceNode(): Promise<string | undefined> { | ||
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("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 wants to use the Node installation at '{0}' to run TS Server. Would you like to use it?", workspaceVersion), | ||
allow, | ||
disallow, | ||
dismiss, | ||
); | ||
|
||
let version = undefined; | ||
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; | ||
} | ||
|
||
private async promptAndSetWorkspaceNode(): Promise<void> { | ||
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 canUseWorkspaceNode(nodeVersion: string): boolean | undefined { | ||
const lastKnownWorkspaceNode = this.workspaceState.get<LastKnownWorkspaceNodeState>(lastKnownWorkspaceNodeStorageKey); | ||
if (lastKnownWorkspaceNode === nodeVersion) { | ||
return this.workspaceState.get<UseWorkspaceNodeState>(useWorkspaceNodeStorageKey); | ||
} | ||
return undefined; | ||
} | ||
|
||
private async setUseWorkspaceNodeState(allow: boolean | undefined, nodeVersion: string) { | ||
await this.workspaceState.update(lastKnownWorkspaceNodeStorageKey, nodeVersion); | ||
await this.workspaceState.update(useWorkspaceNodeStorageKey, allow); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Context: I tried doing the node detection using
which
, like other language extensions do, but it does not work if you have a node version manager like volta, because if you do, your path points to volta'snode.exe
wrapper, and if you use that to run TS Server, it crashes.This alternative approach was suggested by @jakebailey.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason to not do this for all paths?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you mean for all paths?