diff --git a/CHANGELOG.md b/CHANGELOG.md index db3a35e4..a68830f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to the "leetcode" extension will be documented in this file. Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. +## [0.3.0] +### Added +- Test current solution file [(#15)](https://github.com/jdneo/vscode-leetcode/issues/15) + ## [0.2.1] ### Fixed - Fix the wrong icon bug in LeetCode Explorer [(#9)](https://github.com/jdneo/vscode-leetcode/issues/9) diff --git a/README.md b/README.md index 6cedbb17..c189bad5 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ Solve LeetCode problems in VS Code. - Switch and create session - Show problems in explorer - Search problems by keywords +- Test solutions by customized test case - Submit solutions to LeetCode ### Sign In and Sign Out @@ -32,6 +33,9 @@ Solve LeetCode problems in VS Code. ### Search Problems by Keywords ![SearchProblem](resources/gif/searchproblem.gif) +### Test solutions by customized test case +![TestSolution](resources/gif/testsolution.gif) + ### Submit Solutions to LeetCode ![SubmitSolution](resources/gif/solveproblem.gif) @@ -43,6 +47,7 @@ This extension provides several commands in the Command Palette (F1 or Ctrl + Sh - **LeetCode: Create new session** - Create a new session - **LeetCode: Refresh** - Refresh the LeetCode Explorer - **LeetCode: Search Problem** - Search for problems by keywords +- **LeetCode: Test Current File** - Test the solution by customized test case - **LeetCode: Submit** - Submit the solution to LeetCode ## Known Issues: @@ -68,6 +73,7 @@ This extension is based on [@skygragon](https://github.com/skygragon)'s [leetcod - 切换及创建 session - 在 Explorer 中展示题目 - 根据关键字搜索题目 +- 用自定义测试用例测试答案 - 向 LeetCode 提交答案 ### 登入及登出 @@ -82,6 +88,9 @@ This extension is based on [@skygragon](https://github.com/skygragon)'s [leetcod ### 根据关键字搜索题目 ![SearchProblem](resources/gif/searchproblem.gif) +### 用自定义测试用例测试答案 +![TestSolution](resources/gif/testsolution.gif) + ### 向 LeetCode 提交答案 ![SubmitSolution](resources/gif/solveproblem.gif) @@ -93,6 +102,7 @@ This extension is based on [@skygragon](https://github.com/skygragon)'s [leetcod - **LeetCode: Create new session** - 创建一个新的答题进度存档 - **LeetCode: Refresh** - 刷新左侧题目列表视图 - **LeetCode: Search Problem** - 根据关键字搜索题目 +- **LeetCode: Test Current File** - 用自定义测试用例测试答案 - **LeetCode: Submit** - 提交答案到 LeetCode ## 已知问题 diff --git a/package.json b/package.json index 53e2d892..822fe441 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "vscode-leetcode", "displayName": "LeetCode", "description": "Solve LeetCode problems in VS Code", - "version": "0.2.1", + "version": "0.3.0", "author": "Sheng Chen", "publisher": "shengchen", "icon": "resources/LeetCode.png", @@ -32,6 +32,7 @@ "onCommand:leetcode.refreshExplorer", "onCommand:leetcode.showProblem", "onCommand:leetcode.searchProblem", + "onCommand:leetcode.testSolution", "onCommand:leetcode.submitSolution", "onView:leetCodeExplorer" ], @@ -78,6 +79,11 @@ "category": "LeetCode", "icon": "resources/search.png" }, + { + "command": "leetcode.testSolution", + "title": "Test Current File", + "category": "LeetCode" + }, { "command": "leetcode.submitSolution", "title": "Submit", diff --git a/resources/gif/testsolution.gif b/resources/gif/testsolution.gif new file mode 100644 index 00000000..df559b2a Binary files /dev/null and b/resources/gif/testsolution.gif differ diff --git a/src/commands/list.ts b/src/commands/list.ts index f8f324c7..5fcd3d53 100644 --- a/src/commands/list.ts +++ b/src/commands/list.ts @@ -37,7 +37,7 @@ export async function listProblems(channel: vscode.OutputChannel): Promise { @@ -12,7 +12,7 @@ export async function getSessionList(channel: vscode.OutputChannel): Promise= 2) { await vscode.window.showTextDocument(vscode.Uri.file(match[1].trim()), { preview: false }); } else { - throw new Error("Failed to fetch the problem information"); + throw new Error("Failed to fetch the problem information."); } if (!defaultLanguage && leetCodeConfig.get("showSetDefaultLanguageHint")) { @@ -72,7 +72,7 @@ async function showProblemInternal(channel: vscode.OutputChannel, id: string): P } } } catch (error) { - await promptForOpenOutputChannel("Failed to fetch the problem information. Please open the output channel for details", DialogType.error, channel); + await promptForOpenOutputChannel("Failed to fetch the problem information. Please open the output channel for details.", DialogType.error, channel); } } diff --git a/src/commands/submit.ts b/src/commands/submit.ts index 05fbc7d5..cd12fd9e 100644 --- a/src/commands/submit.ts +++ b/src/commands/submit.ts @@ -1,13 +1,10 @@ "use strict"; -import * as fse from "fs-extra"; -import * as os from "os"; -import * as path from "path"; import * as vscode from "vscode"; import { leetCodeManager } from "../leetCodeManager"; import { leetCodeBinaryPath } from "../shared"; import { executeCommand } from "../utils/cpUtils"; -import { DialogType, promptForOpenOutputChannel, promptForSignIn } from "../utils/uiUtils"; +import { DialogType, promptForOpenOutputChannel, promptForSignIn, showResultFile } from "../utils/uiUtils"; export async function submitSolution(channel: vscode.OutputChannel): Promise { if (!leetCodeManager.getUser()) { @@ -25,11 +22,8 @@ export async function submitSolution(channel: vscode.OutputChannel): Promise { + try { + if (leetCodeManager.getStatus() === UserStatus.SignedOut) { + return; + } + + const activeText: vscode.TextEditor | undefined = vscode.window.activeTextEditor; + if (!activeText) { + vscode.window.showErrorMessage("Please open a LeetCode solution file first."); + return; + } + + const filePath = activeText.document.uri.fsPath; + const picks: Array> = []; + picks.push( + { + label: "$(three-bars) Default test cases", + description: "", + detail: "Test with the default cases", + value: ":default", + }, + { + label: "$(pencil) Write directly...", + description: "", + detail: "Write test cases in input box", + value: ":direct", + }, + { + label: "$(file-text) Browse...", + description: "", + detail: "Test with the writen cases in file", + value: ":file", + }, + ); + const choice: IQuickItemEx | undefined = await vscode.window.showQuickPick(picks); + if (!choice) { + return; + } + + let result: string | undefined; + switch (choice.value) { + case ":default": + result = await executeCommand(channel, "node", [leetCodeBinaryPath, "test", filePath]); + break; + case ":direct": + const testString: string | undefined = await vscode.window.showInputBox({ + prompt: "Enter the test cases.", + validateInput: (s: string) => s && s.trim() ? undefined : "Test case must not be empty.", + placeHolder: "Example: [1,2,3]\\n4", + ignoreFocusOut: true, + }); + if (testString) { + result = await executeCommand(channel, "node", [leetCodeBinaryPath, "test", filePath, "-t", `"${testString.replace(/"/g, "")}"`]); + } + break; + case ":file": + const testFile: vscode.Uri[] | undefined = await showFileSelectDialog(); + if (testFile && testFile.length) { + const input: string = await fse.readFile(testFile[0].fsPath, "utf-8"); + if (input.trim()) { + result = await executeCommand(channel, "node", [leetCodeBinaryPath, "test", filePath, "-t", `"${input.replace(/"/g, "").replace(/\r?\n/g, "\\n")}"`]); + } else { + vscode.window.showErrorMessage("The selected test file must not be empty."); + } + } + break; + default: + break; + } + if (!result) { + return; + } + await showResultFile(result); + } catch (error) { + await promptForOpenOutputChannel("Failed to test the solution. Please open the output channel for details.", DialogType.error, channel); + } +} diff --git a/src/extension.ts b/src/extension.ts index f89d9835..6a4f6b52 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -4,6 +4,7 @@ import * as vscode from "vscode"; import * as session from "./commands/session"; import * as show from "./commands/show"; import * as submit from "./commands/submit"; +import * as test from "./commands/test"; import { LeetCodeNode, LeetCodeTreeDataProvider } from "./leetCodeExplorer"; import { leetCodeManager } from "./leetCodeManager"; import { leetCodeStatusBarItem } from "./leetCodeStatusBarItem"; @@ -26,6 +27,7 @@ export async function activate(context: vscode.ExtensionContext) { vscode.commands.registerCommand("leetcode.showProblem", (node: LeetCodeNode) => show.showProblem(channel, node)), vscode.commands.registerCommand("leetcode.searchProblem", () => show.searchProblem(channel)), vscode.commands.registerCommand("leetcode.refreshExplorer", () => leetCodeTreeDataProvider.refresh()), + vscode.commands.registerCommand("leetcode.testSolution", () => test.testSolution(channel)), vscode.commands.registerCommand("leetcode.submitSolution", () => submit.submitSolution(channel)), ); diff --git a/src/utils/uiUtils.ts b/src/utils/uiUtils.ts index b1e804e9..2814b5bc 100644 --- a/src/utils/uiUtils.ts +++ b/src/utils/uiUtils.ts @@ -1,6 +1,9 @@ "use strict"; +import * as fse from "fs-extra"; import * as opn from "opn"; +import * as os from "os"; +import * as path from "path"; import * as vscode from "vscode"; export namespace DialogOptions { @@ -51,6 +54,25 @@ export async function promptForSignIn(): Promise { } } +export async function showFileSelectDialog(): Promise { + const defaultUri: vscode.Uri | undefined = vscode.workspace.rootPath ? vscode.Uri.file(vscode.workspace.rootPath) : undefined; + const options: vscode.OpenDialogOptions = { + defaultUri, + canSelectFiles: true, + canSelectFolders: false, + canSelectMany: false, + openLabel: "Select", + }; + return await vscode.window.showOpenDialog(options); +} + +export async function showResultFile(result: string): Promise { + const resultPath: string = path.join(os.homedir(), ".leetcode", "Result"); + await fse.ensureFile(resultPath); + await fse.writeFile(resultPath, result); + await vscode.window.showTextDocument(vscode.Uri.file(resultPath)); +} + export enum DialogType { info = "info", warning = "warning",