diff --git a/package.json b/package.json index d570c25d..f57b6fef 100644 --- a/package.json +++ b/package.json @@ -271,7 +271,7 @@ "type": "boolean", "default": false, "scope": "application", - "description": "Use Node.js inside the Windows Subsystem for Linux." + "description": "Use the Windows Subsystem for Linux." }, "leetcode.endpoint": { "type": "string", @@ -286,13 +286,19 @@ "leetcode.outputFolder": { "type": "string", "scope": "application", - "description": "Specify the relative path to save the problem files." + "description": "The relative path to save the problem files." }, "leetcode.enableStatusBar": { "type": "boolean", "default": true, "scope": "application", - "description": "Specify whether the LeetCode status bar will be shown or not." + "description": "Show the LeetCode status bar or not." + }, + "leetcode.nodePath": { + "type": "string", + "default": "node", + "scope": "application", + "description": "The Node.js executable path." } } } diff --git a/src/extension.ts b/src/extension.ts index 7a389142..0a2fe6a2 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -41,6 +41,7 @@ export async function activate(context: vscode.ExtensionContext): Promise leetCodePreviewProvider, leetCodeResultProvider, leetCodeSolutionProvider, + leetCodeExecutor, vscode.window.createTreeView("leetCodeExplorer", { treeDataProvider: leetCodeTreeDataProvider, showCollapseAll: true }), vscode.languages.registerCodeLensProvider({ scheme: "file" }, codeLensProvider), vscode.commands.registerCommand("leetcode.deleteCache", () => cache.deleteCache()), diff --git a/src/leetCodeExecutor.ts b/src/leetCodeExecutor.ts index 241b6251..a142fbf2 100644 --- a/src/leetCodeExecutor.ts +++ b/src/leetCodeExecutor.ts @@ -5,20 +5,29 @@ import * as cp from "child_process"; import * as fse from "fs-extra"; import * as path from "path"; import * as requireFromString from "require-from-string"; -import * as vscode from "vscode"; +import { ConfigurationChangeEvent, Disposable, MessageItem, window, workspace, WorkspaceConfiguration } from "vscode"; import { Endpoint, IProblem, supportedPlugins } from "./shared"; import { executeCommand, executeCommandWithProgress } from "./utils/cpUtils"; import { genFileName } from "./utils/problemUtils"; import { DialogOptions, openUrl } from "./utils/uiUtils"; import * as wsl from "./utils/wslUtils"; +import { toWslPath, useWsl } from "./utils/wslUtils"; -class LeetCodeExecutor { +class LeetCodeExecutor implements Disposable { private leetCodeRootPath: string; private leetCodeRootPathInWsl: string; + private nodeExecutable: string; + private configurationChangeListener: Disposable; constructor() { this.leetCodeRootPath = path.join(__dirname, "..", "..", "node_modules", "vsc-leetcode-cli"); this.leetCodeRootPathInWsl = ""; + this.nodeExecutable = this.getNodePath(); + this.configurationChangeListener = workspace.onDidChangeConfiguration((event: ConfigurationChangeEvent) => { + if (event.affectsConfiguration("leetcode.nodePath")) { + this.nodeExecutable = this.getNodePath(); + } + }, this); } public async getLeetCodeRootPath(): Promise { // not wrapped by "" @@ -36,10 +45,18 @@ class LeetCodeExecutor { } public async meetRequirements(): Promise { + if (this.nodeExecutable !== "node") { + if (!await fse.pathExists(this.nodeExecutable)) { + throw new Error(`The Node.js executable does not exist on path ${this.nodeExecutable}`); + } + if (useWsl()) { + this.nodeExecutable = await toWslPath(this.nodeExecutable); + } + } try { - await this.executeCommandEx("node", ["-v"]); + await this.executeCommandEx(this.nodeExecutable, ["-v"]); } catch (error) { - const choice: vscode.MessageItem | undefined = await vscode.window.showErrorMessage( + const choice: MessageItem | undefined = await window.showErrorMessage( "LeetCode extension needs Node.js installed in environment path", DialogOptions.open, ); @@ -50,28 +67,28 @@ class LeetCodeExecutor { } for (const plugin of supportedPlugins) { try { // Check plugin - await this.executeCommandEx("node", [await this.getLeetCodeBinaryPath(), "plugin", "-e", plugin]); + await this.executeCommandEx(this.nodeExecutable, [await this.getLeetCodeBinaryPath(), "plugin", "-e", plugin]); } catch (error) { // Download plugin and activate - await this.executeCommandEx("node", [await this.getLeetCodeBinaryPath(), "plugin", "-i", plugin]); + await this.executeCommandEx(this.nodeExecutable, [await this.getLeetCodeBinaryPath(), "plugin", "-i", plugin]); } } return true; } public async deleteCache(): Promise { - return await this.executeCommandEx("node", [await this.getLeetCodeBinaryPath(), "cache", "-d"]); + return await this.executeCommandEx(this.nodeExecutable, [await this.getLeetCodeBinaryPath(), "cache", "-d"]); } public async getUserInfo(): Promise { - return await this.executeCommandEx("node", [await this.getLeetCodeBinaryPath(), "user"]); + return await this.executeCommandEx(this.nodeExecutable, [await this.getLeetCodeBinaryPath(), "user"]); } public async signOut(): Promise { - return await await this.executeCommandEx("node", [await this.getLeetCodeBinaryPath(), "user", "-L"]); + return await await this.executeCommandEx(this.nodeExecutable, [await this.getLeetCodeBinaryPath(), "user", "-L"]); } public async listProblems(showLocked: boolean): Promise { - return await this.executeCommandEx("node", showLocked ? + return await this.executeCommandEx(this.nodeExecutable, showLocked ? [await this.getLeetCodeBinaryPath(), "list"] : [await this.getLeetCodeBinaryPath(), "list", "-q", "L"], ); @@ -82,7 +99,7 @@ class LeetCodeExecutor { const filePath: string = path.join(outDir, fileName); if (!await fse.pathExists(filePath)) { - const codeTemplate: string = await this.executeCommandWithProgressEx("Fetching problem data...", "node", [await this.getLeetCodeBinaryPath(), "show", problemNode.id, "-cx", "-l", language]); + const codeTemplate: string = await this.executeCommandWithProgressEx("Fetching problem data...", this.nodeExecutable, [await this.getLeetCodeBinaryPath(), "show", problemNode.id, "-cx", "-l", language]); await fse.writeFile(filePath, codeTemplate); } @@ -90,29 +107,29 @@ class LeetCodeExecutor { } public async showSolution(problemNode: IProblem, language: string): Promise { - const solution: string = await this.executeCommandWithProgressEx("Fetching top voted solution from discussions...", "node", [await this.getLeetCodeBinaryPath(), "show", problemNode.id, "--solution", "-l", language]); + const solution: string = await this.executeCommandWithProgressEx("Fetching top voted solution from discussions...", this.nodeExecutable, [await this.getLeetCodeBinaryPath(), "show", problemNode.id, "--solution", "-l", language]); return solution; } public async getDescription(problemNode: IProblem): Promise { - return await this.executeCommandWithProgressEx("Fetching problem description...", "node", [await this.getLeetCodeBinaryPath(), "show", problemNode.id, "-x"]); + return await this.executeCommandWithProgressEx("Fetching problem description...", this.nodeExecutable, [await this.getLeetCodeBinaryPath(), "show", problemNode.id, "-x"]); } public async listSessions(): Promise { - return await this.executeCommandEx("node", [await this.getLeetCodeBinaryPath(), "session"]); + return await this.executeCommandEx(this.nodeExecutable, [await this.getLeetCodeBinaryPath(), "session"]); } public async enableSession(name: string): Promise { - return await this.executeCommandEx("node", [await this.getLeetCodeBinaryPath(), "session", "-e", name]); + return await this.executeCommandEx(this.nodeExecutable, [await this.getLeetCodeBinaryPath(), "session", "-e", name]); } public async createSession(name: string): Promise { - return await this.executeCommandEx("node", [await this.getLeetCodeBinaryPath(), "session", "-c", name]); + return await this.executeCommandEx(this.nodeExecutable, [await this.getLeetCodeBinaryPath(), "session", "-c", name]); } public async submitSolution(filePath: string): Promise { try { - return await this.executeCommandWithProgressEx("Submitting to LeetCode...", "node", [await this.getLeetCodeBinaryPath(), "submit", `"${filePath}"`]); + return await this.executeCommandWithProgressEx("Submitting to LeetCode...", this.nodeExecutable, [await this.getLeetCodeBinaryPath(), "submit", `"${filePath}"`]); } catch (error) { if (error.result) { return error.result; @@ -123,18 +140,18 @@ class LeetCodeExecutor { public async testSolution(filePath: string, testString?: string): Promise { if (testString) { - return await this.executeCommandWithProgressEx("Submitting to LeetCode...", "node", [await this.getLeetCodeBinaryPath(), "test", `"${filePath}"`, "-t", `${testString}`]); + return await this.executeCommandWithProgressEx("Submitting to LeetCode...", this.nodeExecutable, [await this.getLeetCodeBinaryPath(), "test", `"${filePath}"`, "-t", `${testString}`]); } - return await this.executeCommandWithProgressEx("Submitting to LeetCode...", "node", [await this.getLeetCodeBinaryPath(), "test", `"${filePath}"`]); + return await this.executeCommandWithProgressEx("Submitting to LeetCode...", this.nodeExecutable, [await this.getLeetCodeBinaryPath(), "test", `"${filePath}"`]); } public async switchEndpoint(endpoint: string): Promise { switch (endpoint) { case Endpoint.LeetCodeCN: - return await this.executeCommandEx("node", [await this.getLeetCodeBinaryPath(), "plugin", "-e", "leetcode.cn"]); + return await this.executeCommandEx(this.nodeExecutable, [await this.getLeetCodeBinaryPath(), "plugin", "-e", "leetcode.cn"]); case Endpoint.LeetCode: default: - return await this.executeCommandEx("node", [await this.getLeetCodeBinaryPath(), "plugin", "-d", "leetcode.cn"]); + return await this.executeCommandEx(this.nodeExecutable, [await this.getLeetCodeBinaryPath(), "plugin", "-d", "leetcode.cn"]); } } @@ -149,6 +166,19 @@ class LeetCodeExecutor { return { companies: COMPONIES, tags: TAGS }; } + public get node(): string { + return this.nodeExecutable; + } + + public dispose(): void { + this.configurationChangeListener.dispose(); + } + + private getNodePath(): string { + const extensionConfig: WorkspaceConfiguration = workspace.getConfiguration("leetcode", null); + return extensionConfig.get("nodePath", "node" /* default value */); + } + private async executeCommandEx(command: string, args: string[], options: cp.SpawnOptions = { shell: true }): Promise { if (wsl.useWsl()) { return await executeCommand("wsl", [command].concat(args), options); diff --git a/src/leetCodeManager.ts b/src/leetCodeManager.ts index 7356bf05..a5f1cb3e 100644 --- a/src/leetCodeManager.ts +++ b/src/leetCodeManager.ts @@ -42,8 +42,8 @@ class LeetCodeManager extends EventEmitter { const leetCodeBinaryPath: string = await leetCodeExecutor.getLeetCodeBinaryPath(); const childProc: cp.ChildProcess = wsl.useWsl() - ? cp.spawn("wsl", ["node", leetCodeBinaryPath, "user", "-l"], { shell: true }) - : cp.spawn("node", [leetCodeBinaryPath, "user", "-l"], { + ? cp.spawn("wsl", [leetCodeExecutor.node, leetCodeBinaryPath, "user", "-l"], { shell: true }) + : cp.spawn(leetCodeExecutor.node, [leetCodeBinaryPath, "user", "-l"], { shell: true, env: createEnvOption(), }); diff --git a/src/utils/wslUtils.ts b/src/utils/wslUtils.ts index 856844b1..d496b038 100644 --- a/src/utils/wslUtils.ts +++ b/src/utils/wslUtils.ts @@ -3,10 +3,11 @@ import * as vscode from "vscode"; import { executeCommand } from "./cpUtils"; +import { isWindows } from "./osUtils"; export function useWsl(): boolean { const leetCodeConfig: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration("leetcode"); - return process.platform === "win32" && leetCodeConfig.get("useWsl") === true; + return isWindows() && leetCodeConfig.get("useWsl") === true; } export async function toWslPath(path: string): Promise {