diff --git a/package.json b/package.json index d6b87f8e..54ff1440 100644 --- a/package.json +++ b/package.json @@ -247,6 +247,11 @@ "leetcode-cn" ], "description": "Endpoint of the user account." + }, + "leetcode.outputFolder": { + "type": "string", + "scope": "application", + "description": "Specify the relative path to save the problem files." } } } diff --git a/src/commands/show.ts b/src/commands/show.ts index 780aa45e..f9796621 100644 --- a/src/commands/show.ts +++ b/src/commands/show.ts @@ -2,8 +2,10 @@ // Licensed under the MIT license. import * as fse from "fs-extra"; +import * as path from "path"; import * as vscode from "vscode"; import { LeetCodeNode } from "../explorer/LeetCodeNode"; +import { leetCodeChannel } from "../leetCodeChannel"; import { leetCodeExecutor } from "../leetCodeExecutor"; import { leetCodeManager } from "../leetCodeManager"; import { IProblem, IQuickItemEx, languages, ProblemState } from "../shared"; @@ -16,7 +18,7 @@ export async function showProblem(node?: LeetCodeNode): Promise { if (!node) { return; } - await showProblemInternal(node.id); + await showProblemInternal(node); } export async function searchProblem(): Promise { @@ -24,7 +26,7 @@ export async function searchProblem(): Promise { promptForSignIn(); return; } - const choice: IQuickItemEx | undefined = await vscode.window.showQuickPick( + const choice: IQuickItemEx | undefined = await vscode.window.showQuickPick( parseProblemsToPicks(list.listProblems()), { matchOnDetail: true, @@ -37,7 +39,7 @@ export async function searchProblem(): Promise { await showProblemInternal(choice.value); } -async function showProblemInternal(id: string): Promise { +async function showProblemInternal(node: IProblem): Promise { try { const leetCodeConfig: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration("leetcode"); let defaultLanguage: string | undefined = leetCodeConfig.get("defaultLanguage"); @@ -49,9 +51,21 @@ async function showProblemInternal(id: string): Promise { return; } - const outDir: string = await selectWorkspaceFolder(); + let outDir: string = await selectWorkspaceFolder(); + let relativePath: string = (leetCodeConfig.get("outputFolder") || "").trim(); + const matchResult: RegExpMatchArray | null = relativePath.match(/\$\{(.*?)\}/); + if (matchResult) { + const resolvedPath: string | undefined = await resolveRelativePath(matchResult[1].toLocaleLowerCase(), node, language); + if (!resolvedPath) { + leetCodeChannel.appendLine("Showing problem canceled by user."); + return; + } + relativePath = resolvedPath; + } + + outDir = path.join(outDir, relativePath); await fse.ensureDir(outDir); - const result: string = await leetCodeExecutor.showProblem(id, language, outDir); + const result: string = await leetCodeExecutor.showProblem(node.id, language, outDir); const reg: RegExp = /\* Source Code:\s*(.*)/; const match: RegExpMatchArray | null = result.match(reg); if (match && match.length >= 2) { @@ -76,17 +90,17 @@ async function showProblemInternal(id: string): Promise { } } } catch (error) { - await promptForOpenOutputChannel("Failed to fetch the problem information. Please open the output channel for details.", DialogType.error); + await promptForOpenOutputChannel("Failed to show the problem. Please open the output channel for details.", DialogType.error); } } -async function parseProblemsToPicks(p: Promise): Promise>> { - return new Promise(async (resolve: (res: Array>) => void): Promise => { - const picks: Array> = (await p).map((problem: IProblem) => Object.assign({}, { +async function parseProblemsToPicks(p: Promise): Promise>> { + return new Promise(async (resolve: (res: Array>) => void): Promise => { + const picks: Array> = (await p).map((problem: IProblem) => Object.assign({}, { label: `${parseProblemDecorator(problem.state, problem.locked)}${problem.id}.${problem.name}`, description: "", detail: `AC rate: ${problem.passRate}, Difficulty: ${problem.difficulty}`, - value: problem.id, + value: problem, })); resolve(picks); }); @@ -102,3 +116,28 @@ function parseProblemDecorator(state: ProblemState, locked: boolean): string { return locked ? "$(lock) " : ""; } } + +async function resolveRelativePath(value: string, node: IProblem, selectedLanguage: string): Promise { + switch (value) { + case "tag": + if (node.tags.length === 1) { + return node.tags[0]; + } + return await vscode.window.showQuickPick( + node.tags, + { + matchOnDetail: true, + placeHolder: "Multiple tags available, please select one", + ignoreFocusOut: true, + }, + ); + case "language": + return selectedLanguage; + case "difficulty": + return node.difficulty; + default: + const errorMsg: string = `The config '${value}' is not supported.`; + leetCodeChannel.appendLine(errorMsg); + throw new Error(errorMsg); + } +}