From 058c713e6db4a28cee3a920d4cdcb8aff0248496 Mon Sep 17 00:00:00 2001 From: zhang Date: Wed, 13 Feb 2019 18:54:16 +0900 Subject: [PATCH 1/6] add "show problem" function to right-click menu (original "show problem" is changed to "show to solve problem" --- package.json | 174 ++++++++++++++++++++-------------------- src/commands/show.ts | 54 ++++++++++++- src/extension.ts | 1 + src/leetCodeExecutor.ts | 6 +- src/utils/uiUtils.ts | 2 + 5 files changed, 147 insertions(+), 90 deletions(-) diff --git a/package.json b/package.json index 53aa74f2..b99ad753 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "onCommand:leetcode.selectSessions", "onCommand:leetcode.createSession", "onCommand:leetcode.refreshExplorer", + "onCommand:leetcode.showToSolveProblem", "onCommand:leetcode.showProblem", "onCommand:leetcode.searchProblem", "onCommand:leetcode.testSolution", @@ -40,8 +41,7 @@ ], "main": "./out/src/extension", "contributes": { - "commands": [ - { + "commands": [{ "command": "leetcode.deleteCache", "title": "Delete Cache", "category": "LeetCode" @@ -93,6 +93,11 @@ "title": "Show Problem", "category": "LeetCode" }, + { + "command": "leetcode.showToSolveProblem", + "title": "Solve Problem", + "category": "LeetCode" + }, { "command": "leetcode.searchProblem", "title": "Search Problem", @@ -114,25 +119,20 @@ } ], "viewsContainers": { - "activitybar": [ - { - "id": "leetcode", - "title": "LeetCode", - "icon": "resources/LeetCode.svg" - } - ] + "activitybar": [{ + "id": "leetcode", + "title": "LeetCode", + "icon": "resources/LeetCode.svg" + }] }, "views": { - "leetcode": [ - { - "id": "leetCodeExplorer", - "name": "Problems" - } - ] + "leetcode": [{ + "id": "leetCodeExplorer", + "name": "Problems" + }] }, "menus": { - "view/title": [ - { + "view/title": [{ "command": "leetcode.toggleLeetCodeCn", "when": "view == leetCodeExplorer", "group": "navigation@0" @@ -153,21 +153,22 @@ "group": "navigation@3" } ], - "view/item/context": [ - { - "command": "leetcode.showProblem", + "view/item/context": [{ + "command": "leetcode.showToSolveProblem", "when": "view == leetCodeExplorer && viewItem == problem", "group": "leetcode@1" - } - ], - "commandPalette": [ + }, { "command": "leetcode.showProblem", - "when": "never" + "when": "view == leetCodeExplorer && viewItem == problem", + "group": "leetcode@1" } ], - "explorer/context": [ - { + "commandPalette": [{ + "command": "leetcode.showToSolveProblem", + "when": "never" + }], + "explorer/context": [{ "command": "leetcode.testSolution", "when": "explorerResourceIsFolder == false", "group": "leetcode@1" @@ -178,8 +179,7 @@ "group": "leetcode@2" } ], - "editor/context": [ - { + "editor/context": [{ "command": "leetcode.testSolution", "group": "leetcode@1" }, @@ -189,68 +189,66 @@ } ] }, - "configuration": [ - { - "title": "LeetCode", - "properties": { - "leetcode.hideSolved": { - "type": "boolean", - "default": false, - "scope": "application", - "description": "Hide solved problems." - }, - "leetcode.showLocked": { - "type": "boolean", - "default": false, - "scope": "application", - "description": "Show locked problems." - }, - "leetcode.defaultLanguage": { - "type": "string", - "enum": [ - "bash", - "c", - "cpp", - "csharp", - "golang", - "java", - "javascript", - "kotlin", - "mysql", - "python", - "python3", - "ruby", - "scala", - "swift" - ], - "scope": "application", - "description": "Default language for solving the problems." - }, - "leetcode.showSetDefaultLanguageHint": { - "type": "boolean", - "default": true, - "scope": "application", - "description": "Show a hint to set the default language." - }, - "leetcode.useWsl": { - "type": "boolean", - "default": false, - "scope": "application", - "description": "Use Node.js inside the Windows Subsystem for Linux." - }, - "leetcode.endpoint": { - "type": "string", - "default": "leetcode", - "scope": "application", - "enum": [ - "leetcode", - "leetcode-cn" - ], - "description": "Endpoint of the user account." - } + "configuration": [{ + "title": "LeetCode", + "properties": { + "leetcode.hideSolved": { + "type": "boolean", + "default": false, + "scope": "application", + "description": "Hide solved problems." + }, + "leetcode.showLocked": { + "type": "boolean", + "default": false, + "scope": "application", + "description": "Show locked problems." + }, + "leetcode.defaultLanguage": { + "type": "string", + "enum": [ + "bash", + "c", + "cpp", + "csharp", + "golang", + "java", + "javascript", + "kotlin", + "mysql", + "python", + "python3", + "ruby", + "scala", + "swift" + ], + "scope": "application", + "description": "Default language for solving the problems." + }, + "leetcode.showSetDefaultLanguageHint": { + "type": "boolean", + "default": true, + "scope": "application", + "description": "Show a hint to set the default language." + }, + "leetcode.useWsl": { + "type": "boolean", + "default": false, + "scope": "application", + "description": "Use Node.js inside the Windows Subsystem for Linux." + }, + "leetcode.endpoint": { + "type": "string", + "default": "leetcode", + "scope": "application", + "enum": [ + "leetcode", + "leetcode-cn" + ], + "description": "Endpoint of the user account." } } - ] + }] }, "scripts": { "vscode:prepublish": "npm run compile", diff --git a/src/commands/show.ts b/src/commands/show.ts index 780aa45e..6a856a8d 100644 --- a/src/commands/show.ts +++ b/src/commands/show.ts @@ -13,6 +13,13 @@ import * as wsl from "../utils/wslUtils"; import * as list from "./list"; export async function showProblem(node?: LeetCodeNode): Promise { + if (!node) { + return; + } + await showProblemContent(node); +} + +export async function showToSolveProblem(node?: LeetCodeNode): Promise { if (!node) { return; } @@ -37,6 +44,51 @@ export async function searchProblem(): Promise { await showProblemInternal(choice.value); } +async function showProblemContent(node: LeetCodeNode): Promise { + try { + const leetCodeConfig: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration("leetcode"); + let defaultLanguage: string | undefined = leetCodeConfig.get("defaultLanguage"); + if (defaultLanguage && languages.indexOf(defaultLanguage) < 0) { + defaultLanguage = undefined; + } + const language: string | undefined = defaultLanguage || await vscode.window.showQuickPick(languages, { placeHolder: "Select the language you want to use" }); + if (!language) { + return; + } + + const result: string = await leetCodeExecutor.showProblem(node.id, language); + //TODO: format the string in message box, or use other method to show the problem contents + //TODO: only complete the right-click function, how to use left-click or double left-click to show the problem? + console.log(result); + if (result) { + const choice: vscode.MessageItem | undefined = await vscode.window.showInformationMessage( + result, + DialogOptions.solve, + DialogOptions.close + ); + if (choice === DialogOptions.solve) { + showToSolveProblem(node); + } + } + + if (!defaultLanguage && leetCodeConfig.get("showSetDefaultLanguageHint")) { + const choice: vscode.MessageItem | undefined = await vscode.window.showInformationMessage( + `Would you like to set '${language}' as your default language?`, + DialogOptions.yes, + DialogOptions.no, + DialogOptions.never, + ); + if (choice === DialogOptions.yes) { + leetCodeConfig.update("defaultLanguage", language, true /* UserSetting */); + } else if (choice === DialogOptions.never) { + leetCodeConfig.update("showSetDefaultLanguageHint", false, true /* UserSetting */); + } + } + } catch (error) { + await promptForOpenOutputChannel("Failed to fetch the problem information. Please open the output channel for details.", DialogType.error); + } +} + async function showProblemInternal(id: string): Promise { try { const leetCodeConfig: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration("leetcode"); @@ -51,7 +103,7 @@ async function showProblemInternal(id: string): Promise { const outDir: string = await selectWorkspaceFolder(); await fse.ensureDir(outDir); - const result: string = await leetCodeExecutor.showProblem(id, language, outDir); + const result: string = await leetCodeExecutor.showToSolveProblem(id, language, outDir); const reg: RegExp = /\* Source Code:\s*(.*)/; const match: RegExpMatchArray | null = result.match(reg); if (match && match.length >= 2) { diff --git a/src/extension.ts b/src/extension.ts index 9685b3f4..7e01169a 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -43,6 +43,7 @@ export async function activate(context: vscode.ExtensionContext): Promise vscode.commands.registerCommand("leetcode.selectSessions", () => session.selectSession()), vscode.commands.registerCommand("leetcode.createSession", () => session.createSession()), vscode.commands.registerCommand("leetcode.showProblem", (node: LeetCodeNode) => show.showProblem(node)), + vscode.commands.registerCommand("leetcode.showToSolveProblem", (node: LeetCodeNode) => show.showToSolveProblem(node)), vscode.commands.registerCommand("leetcode.searchProblem", () => show.searchProblem()), vscode.commands.registerCommand("leetcode.refreshExplorer", () => leetCodeTreeDataProvider.refresh()), vscode.commands.registerCommand("leetcode.testSolution", (uri?: vscode.Uri) => test.testSolution(uri)), diff --git a/src/leetCodeExecutor.ts b/src/leetCodeExecutor.ts index b43b02bc..a8f08348 100644 --- a/src/leetCodeExecutor.ts +++ b/src/leetCodeExecutor.ts @@ -74,7 +74,11 @@ class LeetCodeExecutor { ); } - public async showProblem(id: string, language: string, outDir: string): Promise { + public async showProblem(id: string, language: string): Promise { + return await this.executeCommandWithProgressEx("Fetching problem data...", "node", [await this.getLeetCodeBinaryPath(), "show", id, "-l", language]); + } + + public async showToSolveProblem(id: string, language: string, outDir: string): Promise { return await this.executeCommandWithProgressEx("Fetching problem data...", "node", [await this.getLeetCodeBinaryPath(), "show", id, "-gx", "-l", language, "-o", `"${outDir}"`]); } diff --git a/src/utils/uiUtils.ts b/src/utils/uiUtils.ts index 3f17c97b..21423fba 100644 --- a/src/utils/uiUtils.ts +++ b/src/utils/uiUtils.ts @@ -11,6 +11,8 @@ export namespace DialogOptions { export const no: vscode.MessageItem = { title: "No", isCloseAffordance: true }; export const never: vscode.MessageItem = { title: "Never" }; export const singUp: vscode.MessageItem = { title: "Sign up" }; + export const solve: vscode.MessageItem = { title: "Solve" }; + export const close: vscode.MessageItem = { title: "Close" }; } export async function promptForOpenOutputChannel(message: string, type: DialogType): Promise { From aacc683acceca4bec37974a2ec113dd1c8611103 Mon Sep 17 00:00:00 2001 From: zhang Date: Fri, 15 Feb 2019 19:35:09 +0900 Subject: [PATCH 2/6] gain question description by sending post request to graphql, and use Webview to show the description in HTML format --- src/commands/show.ts | 69 +++++++++++++++++++----- src/explorer/LeetCodeNode.ts | 8 ++- src/explorer/LeetCodeTreeDataProvider.ts | 18 ++++++- 3 files changed, 78 insertions(+), 17 deletions(-) diff --git a/src/commands/show.ts b/src/commands/show.ts index 6a856a8d..cc65581b 100644 --- a/src/commands/show.ts +++ b/src/commands/show.ts @@ -2,10 +2,12 @@ // Licensed under the MIT license. import * as fse from "fs-extra"; +import * as request from "request"; import * as vscode from "vscode"; import { LeetCodeNode } from "../explorer/LeetCodeNode"; import { leetCodeExecutor } from "../leetCodeExecutor"; import { leetCodeManager } from "../leetCodeManager"; +import { } from "../shared"; import { IProblem, IQuickItemEx, languages, ProblemState } from "../shared"; import { DialogOptions, DialogType, promptForOpenOutputChannel, promptForSignIn } from "../utils/uiUtils"; import { selectWorkspaceFolder } from "../utils/workspaceUtils"; @@ -56,20 +58,45 @@ async function showProblemContent(node: LeetCodeNode): Promise { return; } - const result: string = await leetCodeExecutor.showProblem(node.id, language); - //TODO: format the string in message box, or use other method to show the problem contents - //TODO: only complete the right-click function, how to use left-click or double left-click to show the problem? - console.log(result); - if (result) { - const choice: vscode.MessageItem | undefined = await vscode.window.showInformationMessage( - result, - DialogOptions.solve, - DialogOptions.close + // TODO: only complete the right-click function, how to use left-click or double left-click to show the problem? + // this request referring to the leetcode-cli, for it doesn't provide export for its function. + const opts = {}; + opts.headers = {}; + opts.url = "https://leetcode.com/graphql"; + opts.headers.Origin = "https://leetcode.com"; + + opts.json = true; + opts.body = { + query: [ + "query getQuestionDetail($titleSlug: String!) {", + " question(titleSlug: $titleSlug) {", + " content", + " stats", + " codeDefinition", + " sampleTestCase", + " enableRunCode", + " metaData", + " translatedContent", + " }", + "}", + ].join("\n"), + variables: { titleSlug: node.name.replace(/ /g, "-").toLocaleLowerCase() }, + operationName: "getQuestionDetail", + }; + + request.post(opts, function (e, resp, body) { + // TODO: add error handling + const content = body.data.question.content; + + const panel = vscode.window.createWebviewPanel( + "problemContent", + node.name, + vscode.ViewColumn.One, + {} ); - if (choice === DialogOptions.solve) { - showToSolveProblem(node); - } - } + + panel.webview.html = getWebviewContent(content); + }); if (!defaultLanguage && leetCodeConfig.get("showSetDefaultLanguageHint")) { const choice: vscode.MessageItem | undefined = await vscode.window.showInformationMessage( @@ -89,6 +116,22 @@ async function showProblemContent(node: LeetCodeNode): Promise { } } +function getWebviewContent(inputContent: string) { + return ` + + + + + + Cat Coding + + + + ${inputContent} + + `; +} + async function showProblemInternal(id: string): Promise { try { const leetCodeConfig: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration("leetcode"); diff --git a/src/explorer/LeetCodeNode.ts b/src/explorer/LeetCodeNode.ts index ad5211cb..c74cdada 100644 --- a/src/explorer/LeetCodeNode.ts +++ b/src/explorer/LeetCodeNode.ts @@ -2,9 +2,13 @@ // Licensed under the MIT license. import { IProblem, ProblemState } from "../shared"; +import { inherits } from "util"; +import { TreeItem } from "vscode"; -export class LeetCodeNode { - constructor(private data: IProblem, private parentNodeName: string, private isProblemNode: boolean = true) { } +export class LeetCodeNode extends TreeItem { + constructor(private data: IProblem, private parentNodeName: string, private isProblemNode: boolean = true) { + super(data.name); + } public get locked(): boolean { return this.data.locked; diff --git a/src/explorer/LeetCodeTreeDataProvider.ts b/src/explorer/LeetCodeTreeDataProvider.ts index 83a97534..eb9e08ce 100644 --- a/src/explorer/LeetCodeTreeDataProvider.ts +++ b/src/explorer/LeetCodeTreeDataProvider.ts @@ -88,7 +88,15 @@ export class LeetCodeTreeDataProvider implements vscode.TreeDataProvider new LeetCodeNode(p, Category.Favorite)); + return nodes.map((p: IProblem) => { + const item = new LeetCodeNode(p, Category.Favorite); + item.command = { + command: "leetcode.showProblem", + title: "Select Node", + arguments: [item], + } + return item; + }); case Category.Difficulty: case Category.Tag: case Category.Company: @@ -130,7 +138,13 @@ export class LeetCodeTreeDataProvider implements vscode.TreeDataProvider Date: Fri, 15 Feb 2019 19:38:13 +0900 Subject: [PATCH 3/6] remove unused MessageItem --- src/utils/uiUtils.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/utils/uiUtils.ts b/src/utils/uiUtils.ts index 21423fba..3f17c97b 100644 --- a/src/utils/uiUtils.ts +++ b/src/utils/uiUtils.ts @@ -11,8 +11,6 @@ export namespace DialogOptions { export const no: vscode.MessageItem = { title: "No", isCloseAffordance: true }; export const never: vscode.MessageItem = { title: "Never" }; export const singUp: vscode.MessageItem = { title: "Sign up" }; - export const solve: vscode.MessageItem = { title: "Solve" }; - export const close: vscode.MessageItem = { title: "Close" }; } export async function promptForOpenOutputChannel(message: string, type: DialogType): Promise { From 29b89e2cc9eb6e71adf3c487ec527a07439755c9 Mon Sep 17 00:00:00 2001 From: zhang Date: Fri, 15 Feb 2019 20:02:09 +0900 Subject: [PATCH 4/6] remove unused import --- src/explorer/LeetCodeNode.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/explorer/LeetCodeNode.ts b/src/explorer/LeetCodeNode.ts index c74cdada..8b891362 100644 --- a/src/explorer/LeetCodeNode.ts +++ b/src/explorer/LeetCodeNode.ts @@ -1,9 +1,8 @@ // Copyright (c) jdneo. All rights reserved. // Licensed under the MIT license. -import { IProblem, ProblemState } from "../shared"; -import { inherits } from "util"; import { TreeItem } from "vscode"; +import { IProblem, ProblemState } from "../shared"; export class LeetCodeNode extends TreeItem { constructor(private data: IProblem, private parentNodeName: string, private isProblemNode: boolean = true) { From 4e453f0413ec001672bec65e5cf39ddece8ced16 Mon Sep 17 00:00:00 2001 From: zhang Date: Fri, 15 Feb 2019 20:48:29 +0900 Subject: [PATCH 5/6] modify some typedef and comma following typescript rule --- src/commands/show.ts | 12 ++++++------ src/explorer/LeetCodeTreeDataProvider.ts | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/commands/show.ts b/src/commands/show.ts index cc65581b..54065c11 100644 --- a/src/commands/show.ts +++ b/src/commands/show.ts @@ -60,7 +60,7 @@ async function showProblemContent(node: LeetCodeNode): Promise { // TODO: only complete the right-click function, how to use left-click or double left-click to show the problem? // this request referring to the leetcode-cli, for it doesn't provide export for its function. - const opts = {}; + const opts: any = {}; opts.headers = {}; opts.url = "https://leetcode.com/graphql"; opts.headers.Origin = "https://leetcode.com"; @@ -84,15 +84,15 @@ async function showProblemContent(node: LeetCodeNode): Promise { operationName: "getQuestionDetail", }; - request.post(opts, function (e, resp, body) { + request.post(opts, (e: any, resp: any, body: any) => { // TODO: add error handling - const content = body.data.question.content; + const content: any = body.data.question.content; - const panel = vscode.window.createWebviewPanel( + const panel: any = vscode.window.createWebviewPanel( "problemContent", node.name, vscode.ViewColumn.One, - {} + {}, ); panel.webview.html = getWebviewContent(content); @@ -116,7 +116,7 @@ async function showProblemContent(node: LeetCodeNode): Promise { } } -function getWebviewContent(inputContent: string) { +function getWebviewContent(inputContent: string): string { return ` diff --git a/src/explorer/LeetCodeTreeDataProvider.ts b/src/explorer/LeetCodeTreeDataProvider.ts index eb9e08ce..a4997395 100644 --- a/src/explorer/LeetCodeTreeDataProvider.ts +++ b/src/explorer/LeetCodeTreeDataProvider.ts @@ -89,12 +89,12 @@ export class LeetCodeTreeDataProvider implements vscode.TreeDataProvider { - const item = new LeetCodeNode(p, Category.Favorite); + const item: LeetCodeNode = new LeetCodeNode(p, Category.Favorite); item.command = { command: "leetcode.showProblem", title: "Select Node", arguments: [item], - } + }; return item; }); case Category.Difficulty: @@ -138,12 +138,12 @@ export class LeetCodeTreeDataProvider implements vscode.TreeDataProvider Date: Sun, 17 Feb 2019 19:25:14 +0900 Subject: [PATCH 6/6] solve vsce package error by using original file --- package-lock.json | 10 +- package.json | 180 ++++++++++++----------- src/commands/show.ts | 148 ++++++------------- src/explorer/LeetCodeNode.ts | 7 +- src/explorer/LeetCodeTreeDataProvider.ts | 18 +-- src/extension.ts | 1 - src/leetCodeExecutor.ts | 6 +- 7 files changed, 152 insertions(+), 218 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0b273108..b94f125e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,7 +40,8 @@ "acorn": { "version": "2.7.0", "resolved": "http://registry.npmjs.org/acorn/-/acorn-2.7.0.tgz", - "integrity": "sha1-q259nYhqrKiwhbwzEreaGYQz8Oc=" + "integrity": "sha1-q259nYhqrKiwhbwzEreaGYQz8Oc=", + "optional": true }, "acorn-globals": { "version": "1.0.9", @@ -585,7 +586,8 @@ "cssom": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.4.tgz", - "integrity": "sha512-+7prCSORpXNeR4/fUP3rL+TzqtiFfhMvTd7uEqMdgPvLPt4+uzFUeufx5RHjGTACCargg/DiEt/moMQmvnfkog==" + "integrity": "sha512-+7prCSORpXNeR4/fUP3rL+TzqtiFfhMvTd7uEqMdgPvLPt4+uzFUeufx5RHjGTACCargg/DiEt/moMQmvnfkog==", + "optional": true }, "cssstyle": { "version": "0.2.37", @@ -2827,7 +2829,8 @@ "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "optional": true }, "preserve": { "version": "0.2.0", @@ -3488,6 +3491,7 @@ "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "optional": true, "requires": { "prelude-ls": "~1.1.2" } diff --git a/package.json b/package.json index a463549b..b317b76f 100644 --- a/package.json +++ b/package.json @@ -32,8 +32,8 @@ "onCommand:leetcode.selectSessions", "onCommand:leetcode.createSession", "onCommand:leetcode.refreshExplorer", - "onCommand:leetcode.showToSolveProblem", "onCommand:leetcode.showProblem", + "onCommand:leetcode.showToSolveProblem", "onCommand:leetcode.searchProblem", "onCommand:leetcode.testSolution", "onCommand:leetcode.submitSolution", @@ -41,7 +41,8 @@ ], "main": "./out/src/extension", "contributes": { - "commands": [{ + "commands": [ + { "command": "leetcode.deleteCache", "title": "Delete Cache", "category": "LeetCode" @@ -93,11 +94,6 @@ "title": "Show Problem", "category": "LeetCode" }, - { - "command": "leetcode.showToSolveProblem", - "title": "Solve Problem", - "category": "LeetCode" - }, { "command": "leetcode.searchProblem", "title": "Search Problem", @@ -119,20 +115,25 @@ } ], "viewsContainers": { - "activitybar": [{ - "id": "leetcode", - "title": "LeetCode", - "icon": "resources/LeetCode.svg" - }] + "activitybar": [ + { + "id": "leetcode", + "title": "LeetCode", + "icon": "resources/LeetCode.svg" + } + ] }, "views": { - "leetcode": [{ - "id": "leetCodeExplorer", - "name": "Problems" - }] + "leetcode": [ + { + "id": "leetCodeExplorer", + "name": "Problems" + } + ] }, "menus": { - "view/title": [{ + "view/title": [ + { "command": "leetcode.toggleLeetCodeCn", "when": "view == leetCodeExplorer", "group": "navigation@0" @@ -153,22 +154,21 @@ "group": "navigation@3" } ], - "view/item/context": [{ - "command": "leetcode.showToSolveProblem", - "when": "view == leetCodeExplorer && viewItem == problem", - "group": "leetcode@1" - }, + "view/item/context": [ { "command": "leetcode.showProblem", "when": "view == leetCodeExplorer && viewItem == problem", "group": "leetcode@1" } ], - "commandPalette": [{ - "command": "leetcode.showToSolveProblem", - "when": "never" - }], - "explorer/context": [{ + "commandPalette": [ + { + "command": "leetcode.showProblem", + "when": "never" + } + ], + "explorer/context": [ + { "command": "leetcode.testSolution", "when": "explorerResourceIsFolder == false", "group": "leetcode@1" @@ -179,7 +179,8 @@ "group": "leetcode@2" } ], - "editor/context": [{ + "editor/context": [ + { "command": "leetcode.testSolution", "group": "leetcode@1" }, @@ -189,66 +190,73 @@ } ] }, - "configuration": [{ - "title": "LeetCode", - "properties": { - "leetcode.hideSolved": { - "type": "boolean", - "default": false, - "scope": "application", - "description": "Hide solved problems." - }, - "leetcode.showLocked": { - "type": "boolean", - "default": false, - "scope": "application", - "description": "Show locked problems." - }, - "leetcode.defaultLanguage": { - "type": "string", - "enum": [ - "bash", - "c", - "cpp", - "csharp", - "golang", - "java", - "javascript", - "kotlin", - "mysql", - "python", - "python3", - "ruby", - "scala", - "swift" - ], - "scope": "application", - "description": "Default language for solving the problems." - }, - "leetcode.showSetDefaultLanguageHint": { - "type": "boolean", - "default": true, - "scope": "application", - "description": "Show a hint to set the default language." - }, - "leetcode.useWsl": { - "type": "boolean", - "default": false, - "scope": "application", - "description": "Use Node.js inside the Windows Subsystem for Linux." - }, - "leetcode.endpoint": { - "type": "string", - "default": "leetcode", - "scope": "application", - "enum": [ - "leetcode", - "leetcode-cn" - ], - "description": "Endpoint of the user account." + "configuration": [ + { + "title": "LeetCode", + "properties": { + "leetcode.hideSolved": { + "type": "boolean", + "default": false, + "scope": "application", + "description": "Hide solved problems." + }, + "leetcode.showLocked": { + "type": "boolean", + "default": false, + "scope": "application", + "description": "Show locked problems." + }, + "leetcode.defaultLanguage": { + "type": "string", + "enum": [ + "bash", + "c", + "cpp", + "csharp", + "golang", + "java", + "javascript", + "kotlin", + "mysql", + "python", + "python3", + "ruby", + "scala", + "swift" + ], + "scope": "application", + "description": "Default language for solving the problems." + }, + "leetcode.showSetDefaultLanguageHint": { + "type": "boolean", + "default": true, + "scope": "application", + "description": "Show a hint to set the default language." + }, + "leetcode.useWsl": { + "type": "boolean", + "default": false, + "scope": "application", + "description": "Use Node.js inside the Windows Subsystem for Linux." + }, + "leetcode.endpoint": { + "type": "string", + "default": "leetcode", + "scope": "application", + "enum": [ + "leetcode", + "leetcode-cn" + ], + "description": "Endpoint of the user account." + }, + "leetcode.outputFolder": { + "type": "string", + "scope": "application", + "description": "Specify the relative path to save the problem files." + } } } - }] + ] }, "scripts": { "vscode:prepublish": "npm run compile", diff --git a/src/commands/show.ts b/src/commands/show.ts index 54065c11..f9796621 100644 --- a/src/commands/show.ts +++ b/src/commands/show.ts @@ -2,12 +2,12 @@ // Licensed under the MIT license. import * as fse from "fs-extra"; -import * as request from "request"; +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 { } from "../shared"; import { IProblem, IQuickItemEx, languages, ProblemState } from "../shared"; import { DialogOptions, DialogType, promptForOpenOutputChannel, promptForSignIn } from "../utils/uiUtils"; import { selectWorkspaceFolder } from "../utils/workspaceUtils"; @@ -18,14 +18,7 @@ export async function showProblem(node?: LeetCodeNode): Promise { if (!node) { return; } - await showProblemContent(node); -} - -export async function showToSolveProblem(node?: LeetCodeNode): Promise { - if (!node) { - return; - } - await showProblemInternal(node.id); + await showProblemInternal(node); } export async function searchProblem(): Promise { @@ -33,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, @@ -46,7 +39,7 @@ export async function searchProblem(): Promise { await showProblemInternal(choice.value); } -async function showProblemContent(node: LeetCodeNode): Promise { +async function showProblemInternal(node: IProblem): Promise { try { const leetCodeConfig: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration("leetcode"); let defaultLanguage: string | undefined = leetCodeConfig.get("defaultLanguage"); @@ -58,95 +51,21 @@ async function showProblemContent(node: LeetCodeNode): Promise { return; } - // TODO: only complete the right-click function, how to use left-click or double left-click to show the problem? - // this request referring to the leetcode-cli, for it doesn't provide export for its function. - const opts: any = {}; - opts.headers = {}; - opts.url = "https://leetcode.com/graphql"; - opts.headers.Origin = "https://leetcode.com"; - - opts.json = true; - opts.body = { - query: [ - "query getQuestionDetail($titleSlug: String!) {", - " question(titleSlug: $titleSlug) {", - " content", - " stats", - " codeDefinition", - " sampleTestCase", - " enableRunCode", - " metaData", - " translatedContent", - " }", - "}", - ].join("\n"), - variables: { titleSlug: node.name.replace(/ /g, "-").toLocaleLowerCase() }, - operationName: "getQuestionDetail", - }; - - request.post(opts, (e: any, resp: any, body: any) => { - // TODO: add error handling - const content: any = body.data.question.content; - - const panel: any = vscode.window.createWebviewPanel( - "problemContent", - node.name, - vscode.ViewColumn.One, - {}, - ); - - panel.webview.html = getWebviewContent(content); - }); - - if (!defaultLanguage && leetCodeConfig.get("showSetDefaultLanguageHint")) { - const choice: vscode.MessageItem | undefined = await vscode.window.showInformationMessage( - `Would you like to set '${language}' as your default language?`, - DialogOptions.yes, - DialogOptions.no, - DialogOptions.never, - ); - if (choice === DialogOptions.yes) { - leetCodeConfig.update("defaultLanguage", language, true /* UserSetting */); - } else if (choice === DialogOptions.never) { - leetCodeConfig.update("showSetDefaultLanguageHint", false, true /* UserSetting */); + 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; } - } - } catch (error) { - await promptForOpenOutputChannel("Failed to fetch the problem information. Please open the output channel for details.", DialogType.error); - } -} - -function getWebviewContent(inputContent: string): string { - return ` - - - - - - Cat Coding - - - - ${inputContent} - - `; -} - -async function showProblemInternal(id: string): Promise { - try { - const leetCodeConfig: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration("leetcode"); - let defaultLanguage: string | undefined = leetCodeConfig.get("defaultLanguage"); - if (defaultLanguage && languages.indexOf(defaultLanguage) < 0) { - defaultLanguage = undefined; - } - const language: string | undefined = defaultLanguage || await vscode.window.showQuickPick(languages, { placeHolder: "Select the language you want to use" }); - if (!language) { - return; + relativePath = resolvedPath; } - const outDir: string = await selectWorkspaceFolder(); + outDir = path.join(outDir, relativePath); await fse.ensureDir(outDir); - const result: string = await leetCodeExecutor.showToSolveProblem(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) { @@ -171,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); }); @@ -197,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); + } +} diff --git a/src/explorer/LeetCodeNode.ts b/src/explorer/LeetCodeNode.ts index 8b891362..ad5211cb 100644 --- a/src/explorer/LeetCodeNode.ts +++ b/src/explorer/LeetCodeNode.ts @@ -1,13 +1,10 @@ // Copyright (c) jdneo. All rights reserved. // Licensed under the MIT license. -import { TreeItem } from "vscode"; import { IProblem, ProblemState } from "../shared"; -export class LeetCodeNode extends TreeItem { - constructor(private data: IProblem, private parentNodeName: string, private isProblemNode: boolean = true) { - super(data.name); - } +export class LeetCodeNode { + constructor(private data: IProblem, private parentNodeName: string, private isProblemNode: boolean = true) { } public get locked(): boolean { return this.data.locked; diff --git a/src/explorer/LeetCodeTreeDataProvider.ts b/src/explorer/LeetCodeTreeDataProvider.ts index a4997395..83a97534 100644 --- a/src/explorer/LeetCodeTreeDataProvider.ts +++ b/src/explorer/LeetCodeTreeDataProvider.ts @@ -88,15 +88,7 @@ export class LeetCodeTreeDataProvider implements vscode.TreeDataProvider { - const item: LeetCodeNode = new LeetCodeNode(p, Category.Favorite); - item.command = { - command: "leetcode.showProblem", - title: "Select Node", - arguments: [item], - }; - return item; - }); + return nodes.map((p: IProblem) => new LeetCodeNode(p, Category.Favorite)); case Category.Difficulty: case Category.Tag: case Category.Company: @@ -138,13 +130,7 @@ export class LeetCodeTreeDataProvider implements vscode.TreeDataProvider vscode.commands.registerCommand("leetcode.selectSessions", () => session.selectSession()), vscode.commands.registerCommand("leetcode.createSession", () => session.createSession()), vscode.commands.registerCommand("leetcode.showProblem", (node: LeetCodeNode) => show.showProblem(node)), - vscode.commands.registerCommand("leetcode.showToSolveProblem", (node: LeetCodeNode) => show.showToSolveProblem(node)), vscode.commands.registerCommand("leetcode.searchProblem", () => show.searchProblem()), vscode.commands.registerCommand("leetcode.refreshExplorer", () => leetCodeTreeDataProvider.refresh()), vscode.commands.registerCommand("leetcode.testSolution", (uri?: vscode.Uri) => test.testSolution(uri)), diff --git a/src/leetCodeExecutor.ts b/src/leetCodeExecutor.ts index a8f08348..b43b02bc 100644 --- a/src/leetCodeExecutor.ts +++ b/src/leetCodeExecutor.ts @@ -74,11 +74,7 @@ class LeetCodeExecutor { ); } - public async showProblem(id: string, language: string): Promise { - return await this.executeCommandWithProgressEx("Fetching problem data...", "node", [await this.getLeetCodeBinaryPath(), "show", id, "-l", language]); - } - - public async showToSolveProblem(id: string, language: string, outDir: string): Promise { + public async showProblem(id: string, language: string, outDir: string): Promise { return await this.executeCommandWithProgressEx("Fetching problem data...", "node", [await this.getLeetCodeBinaryPath(), "show", id, "-gx", "-l", language, "-o", `"${outDir}"`]); }