// Copyright (c) jdneo. All rights reserved. // Licensed under the MIT license. 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 { 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 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 "" if (wsl.useWsl()) { if (!this.leetCodeRootPathInWsl) { this.leetCodeRootPathInWsl = `${await wsl.toWslPath(this.leetCodeRootPath)}`; } return `${this.leetCodeRootPathInWsl}`; } return `${this.leetCodeRootPath}`; } public async getLeetCodeBinaryPath(): Promise { // wrapped by "" return `"${path.join(await this.getLeetCodeRootPath(), "bin", "leetcode")}"`; } 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(this.nodeExecutable, ["-v"]); } catch (error) { const choice: MessageItem | undefined = await window.showErrorMessage( "LeetCode extension needs Node.js installed in environment path", DialogOptions.open, ); if (choice === DialogOptions.open) { openUrl("https://nodejs.org"); } return false; } for (const plugin of supportedPlugins) { try { // Check plugin await this.executeCommandEx(this.nodeExecutable, [await this.getLeetCodeBinaryPath(), "plugin", "-e", plugin]); } catch (error) { // Download plugin and activate await this.executeCommandEx(this.nodeExecutable, [await this.getLeetCodeBinaryPath(), "plugin", "-i", plugin]); } } return true; } public async deleteCache(): Promise { return await this.executeCommandEx(this.nodeExecutable, [await this.getLeetCodeBinaryPath(), "cache", "-d"]); } public async getUserInfo(): Promise { return await this.executeCommandEx(this.nodeExecutable, [await this.getLeetCodeBinaryPath(), "user"]); } public async signOut(): Promise { return await await this.executeCommandEx(this.nodeExecutable, [await this.getLeetCodeBinaryPath(), "user", "-L"]); } public async listProblems(showLocked: boolean): Promise { return await this.executeCommandEx(this.nodeExecutable, showLocked ? [await this.getLeetCodeBinaryPath(), "list"] : [await this.getLeetCodeBinaryPath(), "list", "-q", "L"], ); } public async showProblem(problemNode: IProblem, language: string, outDir: string, detailed: boolean = false): Promise { const fileName: string = genFileName(problemNode, language); const filePath: string = path.join(outDir, fileName); const templateType: string = detailed ? "-cx" : "-c"; if (!await fse.pathExists(filePath)) { const codeTemplate: string = await this.executeCommandWithProgressEx("Fetching problem data...", this.nodeExecutable, [await this.getLeetCodeBinaryPath(), "show", problemNode.id, templateType, "-l", language]); await fse.writeFile(filePath, codeTemplate); } return filePath; } public async showSolution(input: string, language: string): Promise { const solution: string = await this.executeCommandWithProgressEx("Fetching top voted solution from discussions...", this.nodeExecutable, [await this.getLeetCodeBinaryPath(), "show", input, "--solution", "-l", language]); return solution; } public async getDescription(problemNodeId: string): Promise { return await this.executeCommandWithProgressEx("Fetching problem description...", this.nodeExecutable, [await this.getLeetCodeBinaryPath(), "show", problemNodeId, "-x"]); } public async listSessions(): Promise { return await this.executeCommandEx(this.nodeExecutable, [await this.getLeetCodeBinaryPath(), "session"]); } public async enableSession(name: string): Promise { return await this.executeCommandEx(this.nodeExecutable, [await this.getLeetCodeBinaryPath(), "session", "-e", name]); } public async createSession(name: string): Promise { 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...", this.nodeExecutable, [await this.getLeetCodeBinaryPath(), "submit", `"${filePath}"`]); } catch (error) { if (error.result) { return error.result; } throw error; } } public async testSolution(filePath: string, testString?: string): Promise { if (testString) { return await this.executeCommandWithProgressEx("Submitting to LeetCode...", this.nodeExecutable, [await this.getLeetCodeBinaryPath(), "test", `"${filePath}"`, "-t", `${testString}`]); } 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(this.nodeExecutable, [await this.getLeetCodeBinaryPath(), "plugin", "-e", "leetcode.cn"]); case Endpoint.LeetCode: default: return await this.executeCommandEx(this.nodeExecutable, [await this.getLeetCodeBinaryPath(), "plugin", "-d", "leetcode.cn"]); } } public async getCompaniesAndTags(): Promise<{ companies: { [key: string]: string[] }, tags: { [key: string]: string[] } }> { // preprocess the plugin source const companiesTagsPath: string = path.join(await leetCodeExecutor.getLeetCodeRootPath(), "lib", "plugins", "company.js"); const companiesTagsSrc: string = (await fse.readFile(companiesTagsPath, "utf8")).replace( "module.exports = plugin", "module.exports = { COMPONIES, TAGS }", ); const { COMPONIES, TAGS } = requireFromString(companiesTagsSrc, companiesTagsPath); 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); } return await executeCommand(command, args, options); } private async executeCommandWithProgressEx(message: string, command: string, args: string[], options: cp.SpawnOptions = { shell: true }): Promise { if (wsl.useWsl()) { return await executeCommandWithProgress(message, "wsl", [command].concat(args), options); } return await executeCommandWithProgress(message, command, args, options); } } export const leetCodeExecutor: LeetCodeExecutor = new LeetCodeExecutor();