// 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 * as vscode 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"; class LeetCodeExecutor { private leetCodeRootPath: string; private leetCodeRootPathInWsl: string; constructor() { this.leetCodeRootPath = path.join(__dirname, "..", "..", "node_modules", "vsc-leetcode-cli"); this.leetCodeRootPathInWsl = ""; } 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 { try { await this.executeCommandEx("node", ["-v"]); } catch (error) { const choice: vscode.MessageItem | undefined = await vscode.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("node", [await this.getLeetCodeBinaryPath(), "plugin", "-e", plugin]); } catch (error) { // Download plugin and activate await this.executeCommandEx("node", [await this.getLeetCodeBinaryPath(), "plugin", "-i", plugin]); } } return true; } public async deleteCache(): Promise { return await this.executeCommandEx("node", [await this.getLeetCodeBinaryPath(), "cache", "-d"]); } public async getUserInfo(): Promise { return await this.executeCommandEx("node", [await this.getLeetCodeBinaryPath(), "user"]); } public async signOut(): Promise { return await await this.executeCommandEx("node", [await this.getLeetCodeBinaryPath(), "user", "-L"]); } public async listProblems(showLocked: boolean): Promise { return await this.executeCommandEx("node", showLocked ? [await this.getLeetCodeBinaryPath(), "list"] : [await this.getLeetCodeBinaryPath(), "list", "-q", "L"], ); } public async showProblem(problemNode: IProblem, language: string, outDir: string): Promise { const fileName: string = genFileName(problemNode, language); 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]); await fse.writeFile(filePath, codeTemplate); } return filePath; } 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]); return solution; } public async getDescription(problemNode: IProblem): Promise { return await this.executeCommandWithProgressEx("Fetching problem description...", "node", [await this.getLeetCodeBinaryPath(), "show", problemNode.id, "-x"]); } public async listSessions(): Promise { return await this.executeCommandEx("node", [await this.getLeetCodeBinaryPath(), "session"]); } public async enableSession(name: string): Promise { return await this.executeCommandEx("node", [await this.getLeetCodeBinaryPath(), "session", "-e", name]); } public async createSession(name: string): Promise { return await this.executeCommandEx("node", [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}"`]); } 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...", "node", [await this.getLeetCodeBinaryPath(), "test", `"${filePath}"`, "-t", `${testString}`]); } return await this.executeCommandWithProgressEx("Submitting to LeetCode...", "node", [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"]); case Endpoint.LeetCode: default: return await this.executeCommandEx("node", [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 }; } 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();