From 8c7e53edee5709e2417e9484d0fcf922b2e6b317 Mon Sep 17 00:00:00 2001 From: Vigilans Date: Wed, 6 Mar 2019 00:24:18 +0800 Subject: [PATCH 1/9] Add `allProblems` buffer to LeetCodeTreeDataProvidder --- src/explorer/LeetCodeTreeDataProvider.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/explorer/LeetCodeTreeDataProvider.ts b/src/explorer/LeetCodeTreeDataProvider.ts index da9ca751..8e8d0261 100644 --- a/src/explorer/LeetCodeTreeDataProvider.ts +++ b/src/explorer/LeetCodeTreeDataProvider.ts @@ -13,6 +13,8 @@ import { LeetCodeNode } from "./LeetCodeNode"; export class LeetCodeTreeDataProvider implements vscode.TreeDataProvider { + private allProblems: Map; // store reference of all problems. + private treeData: { Difficulty: Map, Tag: Map, @@ -31,6 +33,13 @@ export class LeetCodeTreeDataProvider implements vscode.TreeDataProvider { + if (this.allProblems.has(node.id)) { + Object.assign(this.allProblems[node.id], node); // problem reference is preserved + this.onDidChangeTreeDataEvent.fire(); + } + } + public getTreeItem(element: LeetCodeNode): vscode.TreeItem | Thenable { if (element.id === "notSignIn") { return { @@ -100,6 +109,7 @@ export class LeetCodeTreeDataProvider implements vscode.TreeDataProvider { // clear cache + this.allProblems = new Map(); this.treeData = { Difficulty: new Map(), Tag: new Map(), @@ -107,6 +117,7 @@ export class LeetCodeTreeDataProvider implements vscode.TreeDataProvider Date: Wed, 6 Mar 2019 09:54:48 +0800 Subject: [PATCH 2/9] Expose LeetCodeNode's inner IProblem data as property --- src/explorer/LeetCodeNode.ts | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/explorer/LeetCodeNode.ts b/src/explorer/LeetCodeNode.ts index ad5211cb..459fb98b 100644 --- a/src/explorer/LeetCodeNode.ts +++ b/src/explorer/LeetCodeNode.ts @@ -6,17 +6,26 @@ import { IProblem, ProblemState } from "../shared"; export class LeetCodeNode { constructor(private data: IProblem, private parentNodeName: string, private isProblemNode: boolean = true) { } + public get nodeData(): IProblem { + return this.data; + } + + public get isProblem(): boolean { + return this.isProblemNode; + } + + public get parentName(): string { + return this.parentNodeName; + } + public get locked(): boolean { return this.data.locked; } + public get name(): string { return this.data.name; } - public get state(): ProblemState { - return this.data.state; - } - public get id(): string { return this.data.id; } @@ -37,15 +46,11 @@ export class LeetCodeNode { return this.data.companies; } - public get isFavorite(): boolean { - return this.data.isFavorite; - } - - public get isProblem(): boolean { - return this.isProblemNode; + public get state(): ProblemState { + return this.data.state; } - public get parentName(): string { - return this.parentNodeName; + public get isFavorite(): boolean { + return this.data.isFavorite; } } From 42b085641653a305cb5b05fe38fe9c7c7a1ff43e Mon Sep 17 00:00:00 2001 From: Vigilans Date: Wed, 6 Mar 2019 09:55:58 +0800 Subject: [PATCH 3/9] Let dataProvider exposes method to update one specific problem's data --- src/explorer/LeetCodeTreeDataProvider.ts | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/explorer/LeetCodeTreeDataProvider.ts b/src/explorer/LeetCodeTreeDataProvider.ts index 8e8d0261..57e79edf 100644 --- a/src/explorer/LeetCodeTreeDataProvider.ts +++ b/src/explorer/LeetCodeTreeDataProvider.ts @@ -33,9 +33,10 @@ export class LeetCodeTreeDataProvider implements vscode.TreeDataProvider { - if (this.allProblems.has(node.id)) { - Object.assign(this.allProblems[node.id], node); // problem reference is preserved + public async updateProblem(problem: IProblem): Promise { + if (this.allProblems.has(problem.id)) { + this.updateTreeDataByProblem(problem); // only modify the content of tree data, problem is not updated. + Object.assign(this.allProblems.get(problem.id), problem); // update problem, where reference is preserved. this.onDidChangeTreeDataEvent.fire(); } } @@ -208,6 +209,18 @@ export class LeetCodeTreeDataProvider implements vscode.TreeDataProvider Number(p.id) >= Number(problem.id)); + if (problem.isFavorite) { + this.treeData.Favorite.splice(problemIndex, 0, origin); // insert original problem's reference as favorite + } else { + this.treeData.Favorite.splice(problemIndex, 1); // delete favorite + } + } + } + private addProblemToTreeData(problem: IProblem): void { this.putProblemToMap(this.treeData.Difficulty, problem.difficulty, problem); for (const tag of problem.tags) { From 95dea19bbf34a2bb611a9e0c91225c89b34c8db8 Mon Sep 17 00:00:00 2001 From: Vigilans Date: Wed, 6 Mar 2019 09:57:29 +0800 Subject: [PATCH 4/9] Add `Star Problem` extension --- package.json | 14 ++++++++++++++ src/commands/star.ts | 19 +++++++++++++++++++ src/explorer/LeetCodeTreeDataProvider.ts | 2 +- src/extension.ts | 2 ++ src/leetCodeExecutor.ts | 10 ++++++++++ 5 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 src/commands/star.ts diff --git a/package.json b/package.json index 8a316d76..2509d3c1 100644 --- a/package.json +++ b/package.json @@ -94,6 +94,11 @@ "title": "Show Problem", "category": "LeetCode" }, + { + "command": "leetcode.starProblem", + "title": "Star/Unstar Problem", + "category": "LeetCode" + }, { "command": "leetcode.searchProblem", "title": "Search Problem", @@ -164,12 +169,21 @@ "command": "leetcode.showProblem", "when": "view == leetCodeExplorer && viewItem == problem", "group": "leetcode@1" + }, + { + "command": "leetcode.starProblem", + "when": "view == leetCodeExplorer && viewItem == problem", + "group": "leetcode@1" } ], "commandPalette": [ { "command": "leetcode.showProblem", "when": "never" + }, + { + "command": "leetcode.starProblem", + "when": "never" } ], "explorer/context": [ diff --git a/src/commands/star.ts b/src/commands/star.ts new file mode 100644 index 00000000..09645545 --- /dev/null +++ b/src/commands/star.ts @@ -0,0 +1,19 @@ +// Copyright (c) jdneo. All rights reserved. +// Licensed under the MIT license. + +import { LeetCodeNode } from "../explorer/LeetCodeNode"; +import { LeetCodeTreeDataProvider } from "../explorer/LeetCodeTreeDataProvider"; +import { leetCodeExecutor } from "../leetCodeExecutor"; +import { IProblem } from "../shared"; +import { DialogType, promptForOpenOutputChannel } from "../utils/uiUtils"; + +export async function starProblem(provider: LeetCodeTreeDataProvider, node: LeetCodeNode): Promise { + try { + const problem: IProblem = Object.assign({}, node.nodeData, { + isFavorite: await leetCodeExecutor.starProblem(node, !node.isFavorite), + }); + provider.updateProblem(problem); + } catch (error) { + await promptForOpenOutputChannel("Failed to star the problem. Please open the output channel for details.", DialogType.error); + } +} diff --git a/src/explorer/LeetCodeTreeDataProvider.ts b/src/explorer/LeetCodeTreeDataProvider.ts index 57e79edf..7e77cb8f 100644 --- a/src/explorer/LeetCodeTreeDataProvider.ts +++ b/src/explorer/LeetCodeTreeDataProvider.ts @@ -56,7 +56,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.starProblem", (node: LeetCodeNode) => star.starProblem(leetCodeTreeDataProvider, 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 373bda11..c498a3f9 100644 --- a/src/leetCodeExecutor.ts +++ b/src/leetCodeExecutor.ts @@ -87,6 +87,16 @@ class LeetCodeExecutor { return filePath; } + public async starProblem(node: IProblem, markStarred: boolean): Promise { + let description: string = ""; + if (markStarred) { + description = await this.executeCommandEx("node", [await this.getLeetCodeBinaryPath(), "star", node.id]); + } else { + description = await this.executeCommandEx("node", [await this.getLeetCodeBinaryPath(), "star", node.id, "-d"]); + } + return description.includes("♥"); + } + public async listSessions(): Promise { return await this.executeCommandEx("node", [await this.getLeetCodeBinaryPath(), "session"]); } From 885cac7e7af903ddbbc66b347498839ebec17f22 Mon Sep 17 00:00:00 2001 From: Vigilans Date: Wed, 6 Mar 2019 10:31:50 +0800 Subject: [PATCH 5/9] Add `All Problems` Category && enhanced tooltips --- src/explorer/LeetCodeTreeDataProvider.ts | 29 ++++++++++++++++++++---- src/shared.ts | 1 + 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/explorer/LeetCodeTreeDataProvider.ts b/src/explorer/LeetCodeTreeDataProvider.ts index 7e77cb8f..0a8242b8 100644 --- a/src/explorer/LeetCodeTreeDataProvider.ts +++ b/src/explorer/LeetCodeTreeDataProvider.ts @@ -76,6 +76,10 @@ export class LeetCodeTreeDataProvider implements vscode.TreeDataProvider new LeetCodeNode(p, Category.All)); case Category.Favorite: const nodes: IProblem[] = this.treeData[Category.Favorite]; return nodes.map((p: IProblem) => new LeetCodeNode(p, Category.Favorite)); @@ -180,12 +187,26 @@ export class LeetCodeTreeDataProvider implements vscode.TreeDataProvider Date: Thu, 7 Mar 2019 19:01:11 +0800 Subject: [PATCH 6/9] Renaming: `starProblem` -> `toggleFavorite` & `parentName` -> `parentId` --- package.json | 6 +++--- src/commands/star.ts | 4 ++-- src/explorer/LeetCodeNode.ts | 9 ++++++--- src/explorer/LeetCodeTreeDataProvider.ts | 14 +++++++------- src/extension.ts | 2 +- src/leetCodeExecutor.ts | 2 +- 6 files changed, 20 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index 2509d3c1..83594f8e 100644 --- a/package.json +++ b/package.json @@ -95,7 +95,7 @@ "category": "LeetCode" }, { - "command": "leetcode.starProblem", + "command": "leetcode.toggleFavorite", "title": "Star/Unstar Problem", "category": "LeetCode" }, @@ -171,7 +171,7 @@ "group": "leetcode@1" }, { - "command": "leetcode.starProblem", + "command": "leetcode.toggleFavorite", "when": "view == leetCodeExplorer && viewItem == problem", "group": "leetcode@1" } @@ -182,7 +182,7 @@ "when": "never" }, { - "command": "leetcode.starProblem", + "command": "leetcode.toggleFavorite", "when": "never" } ], diff --git a/src/commands/star.ts b/src/commands/star.ts index 09645545..e1db10da 100644 --- a/src/commands/star.ts +++ b/src/commands/star.ts @@ -7,10 +7,10 @@ import { leetCodeExecutor } from "../leetCodeExecutor"; import { IProblem } from "../shared"; import { DialogType, promptForOpenOutputChannel } from "../utils/uiUtils"; -export async function starProblem(provider: LeetCodeTreeDataProvider, node: LeetCodeNode): Promise { +export async function toggleFavorite(provider: LeetCodeTreeDataProvider, node: LeetCodeNode): Promise { try { const problem: IProblem = Object.assign({}, node.nodeData, { - isFavorite: await leetCodeExecutor.starProblem(node, !node.isFavorite), + isFavorite: await leetCodeExecutor.toggleFavorite(node, !node.isFavorite), }); provider.updateProblem(problem); } catch (error) { diff --git a/src/explorer/LeetCodeNode.ts b/src/explorer/LeetCodeNode.ts index 459fb98b..3001903a 100644 --- a/src/explorer/LeetCodeNode.ts +++ b/src/explorer/LeetCodeNode.ts @@ -4,7 +4,10 @@ import { IProblem, ProblemState } from "../shared"; export class LeetCodeNode { - constructor(private data: IProblem, private parentNodeName: string, private isProblemNode: boolean = true) { } + constructor( + private data: IProblem, + private parentNodeId: string, + private isProblemNode: boolean = true) { } public get nodeData(): IProblem { return this.data; @@ -14,8 +17,8 @@ export class LeetCodeNode { return this.isProblemNode; } - public get parentName(): string { - return this.parentNodeName; + public get parentId(): string { + return this.parentNodeId; } public get locked(): boolean { diff --git a/src/explorer/LeetCodeTreeDataProvider.ts b/src/explorer/LeetCodeTreeDataProvider.ts index 0a8242b8..63c57d60 100644 --- a/src/explorer/LeetCodeTreeDataProvider.ts +++ b/src/explorer/LeetCodeTreeDataProvider.ts @@ -22,9 +22,9 @@ export class LeetCodeTreeDataProvider implements vscode.TreeDataProvider = new vscode.EventEmitter(); + private onDidChangeTreeDataEvent: vscode.EventEmitter = new vscode.EventEmitter(); // tslint:disable-next-line:member-ordering - public readonly onDidChangeTreeData: vscode.Event = this.onDidChangeTreeDataEvent.event; + public readonly onDidChangeTreeData: vscode.Event = this.onDidChangeTreeDataEvent.event; constructor(private context: vscode.ExtensionContext) { } @@ -37,7 +37,7 @@ export class LeetCodeTreeDataProvider implements vscode.TreeDataProvider | undefined = this.treeData[node.parentName]; + const map: Map | undefined = this.treeData[node.parentId]; if (!map) { - leetCodeChannel.appendLine(`Category: ${node.parentName} is not available.`); + leetCodeChannel.appendLine(`Category: ${node.parentId} is not available.`); return []; } const problems: IProblem[] = map.get(node.name) || []; @@ -204,7 +204,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.starProblem", (node: LeetCodeNode) => star.starProblem(leetCodeTreeDataProvider, node)), + vscode.commands.registerCommand("leetcode.toggleFavorite", (node: LeetCodeNode) => star.toggleFavorite(leetCodeTreeDataProvider, 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 c498a3f9..e9f0ae4c 100644 --- a/src/leetCodeExecutor.ts +++ b/src/leetCodeExecutor.ts @@ -87,7 +87,7 @@ class LeetCodeExecutor { return filePath; } - public async starProblem(node: IProblem, markStarred: boolean): Promise { + public async toggleFavorite(node: IProblem, markStarred: boolean): Promise { let description: string = ""; if (markStarred) { description = await this.executeCommandEx("node", [await this.getLeetCodeBinaryPath(), "star", node.id]); From ed4b40ca4a88ef8f4e1b2a1de0a6271fb2b8a611 Mon Sep 17 00:00:00 2001 From: Vigilans Date: Thu, 7 Mar 2019 19:33:08 +0800 Subject: [PATCH 7/9] Refactor getTreeItem & getChildren --- src/explorer/LeetCodeTreeDataProvider.ts | 69 ++++++++++-------------- 1 file changed, 29 insertions(+), 40 deletions(-) diff --git a/src/explorer/LeetCodeTreeDataProvider.ts b/src/explorer/LeetCodeTreeDataProvider.ts index 63c57d60..a9c9863f 100644 --- a/src/explorer/LeetCodeTreeDataProvider.ts +++ b/src/explorer/LeetCodeTreeDataProvider.ts @@ -42,7 +42,7 @@ export class LeetCodeTreeDataProvider implements vscode.TreeDataProvider { - if (element.id === "notSignIn") { + if (element.id === "NotSignIn") { return { label: element.name, id: element.id, @@ -52,53 +52,42 @@ export class LeetCodeTreeDataProvider implements vscode.TreeDataProvider { if (!leetCodeManager.getUser()) { return [ new LeetCodeNode(Object.assign({}, defaultProblem, { - id: "notSignIn", + id: "NotSignIn", name: "Sign in to LeetCode", - }), "ROOT", false), + }), "Root", false), ]; } if (!element) { // Root view - return [ - new LeetCodeNode(Object.assign({}, defaultProblem, { - id: Category.All, - name: Category.All, - }), "ROOT", false), - new LeetCodeNode(Object.assign({}, defaultProblem, { - id: Category.Difficulty, - name: Category.Difficulty, - }), "ROOT", false), - new LeetCodeNode(Object.assign({}, defaultProblem, { - id: Category.Tag, - name: Category.Tag, - }), "ROOT", false), - new LeetCodeNode(Object.assign({}, defaultProblem, { - id: Category.Company, - name: Category.Company, - }), "ROOT", false), - new LeetCodeNode(Object.assign({}, defaultProblem, { - id: Category.Favorite, - name: Category.Favorite, - }), "ROOT", false), - ]; + return Object.keys(Category).map((c: Category) => new LeetCodeNode( + Object.assign({}, defaultProblem, { id: c, name: c }), "Root", false, + )); } else { - switch (element.name) { // First-level + // First-level + switch (element.name) { case Category.All: const all: IProblem[] = [...this.allProblems.values()]; return all.map((p: IProblem) => new LeetCodeNode(p, Category.All)); @@ -109,9 +98,9 @@ export class LeetCodeTreeDataProvider implements vscode.TreeDataProvider Date: Thu, 7 Mar 2019 20:09:37 +0800 Subject: [PATCH 8/9] Move `All Problems` data into `treeData` & use Category to represent `treeData` keys --- src/explorer/LeetCodeTreeDataProvider.ts | 53 +++++++++++++----------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/src/explorer/LeetCodeTreeDataProvider.ts b/src/explorer/LeetCodeTreeDataProvider.ts index a9c9863f..65be7c1b 100644 --- a/src/explorer/LeetCodeTreeDataProvider.ts +++ b/src/explorer/LeetCodeTreeDataProvider.ts @@ -16,10 +16,11 @@ export class LeetCodeTreeDataProvider implements vscode.TreeDataProvider; // store reference of all problems. private treeData: { - Difficulty: Map, - Tag: Map, - Company: Map, - Favorite: IProblem[], + [Category.All]: IProblem[], + [Category.Difficulty]: Map, + [Category.Tag]: Map, + [Category.Company]: Map, + [Category.Favorite]: IProblem[], }; private onDidChangeTreeDataEvent: vscode.EventEmitter = new vscode.EventEmitter(); @@ -29,7 +30,7 @@ export class LeetCodeTreeDataProvider implements vscode.TreeDataProvider { - await this.getProblemData(); + await this.getFullProblemData(); this.onDidChangeTreeDataEvent.fire(); } @@ -82,18 +83,22 @@ export class LeetCodeTreeDataProvider implements vscode.TreeDataProvider new LeetCodeNode( + return [ + Category.All, + Category.Difficulty, + Category.Tag, + Category.Company, + Category.Favorite, + ].map((c: Category) => new LeetCodeNode( Object.assign({}, defaultProblem, { id: c, name: c }), "Root", false, )); } else { // First-level switch (element.name) { case Category.All: - const all: IProblem[] = [...this.allProblems.values()]; - return all.map((p: IProblem) => new LeetCodeNode(p, Category.All)); case Category.Favorite: - const nodes: IProblem[] = this.treeData[Category.Favorite]; - return nodes.map((p: IProblem) => new LeetCodeNode(p, Category.Favorite)); + const nodes: IProblem[] = this.treeData[element.name]; + return nodes.map((p: IProblem) => new LeetCodeNode(p, element.name)); case Category.Difficulty: case Category.Tag: case Category.Company: @@ -104,16 +109,18 @@ export class LeetCodeTreeDataProvider implements vscode.TreeDataProvider { + private async getFullProblemData(): Promise { // clear cache this.allProblems = new Map(); this.treeData = { - Difficulty: new Map(), - Tag: new Map(), - Company: new Map(), - Favorite: [], + [Category.All]: [], + [Category.Difficulty]: new Map(), + [Category.Tag]: new Map(), + [Category.Company]: new Map(), + [Category.Favorite]: [], }; for (const problem of await list.listProblems()) { + // Add every problem to problem pool this.allProblems.set(problem.id, problem); // Add favorite problem, no matter whether it is solved. if (problem.isFavorite) { @@ -144,8 +151,8 @@ export class LeetCodeTreeDataProvider implements vscode.TreeDataProvider | undefined = this.treeData[category]; @@ -187,14 +194,11 @@ export class LeetCodeTreeDataProvider implements vscode.TreeDataProvider Date: Thu, 7 Mar 2019 20:30:14 +0800 Subject: [PATCH 9/9] Minor modification --- package.json | 2 +- src/explorer/LeetCodeTreeDataProvider.ts | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 83594f8e..eb9d04b8 100644 --- a/package.json +++ b/package.json @@ -96,7 +96,7 @@ }, { "command": "leetcode.toggleFavorite", - "title": "Star/Unstar Problem", + "title": "Toggle Favorite Problem", "category": "LeetCode" }, { diff --git a/src/explorer/LeetCodeTreeDataProvider.ts b/src/explorer/LeetCodeTreeDataProvider.ts index 65be7c1b..db93080d 100644 --- a/src/explorer/LeetCodeTreeDataProvider.ts +++ b/src/explorer/LeetCodeTreeDataProvider.ts @@ -13,7 +13,7 @@ import { LeetCodeNode } from "./LeetCodeNode"; export class LeetCodeTreeDataProvider implements vscode.TreeDataProvider { - private allProblems: Map; // store reference of all problems. + private allProblems: Map; // maintains the ownership of all problems. private treeData: { [Category.All]: IProblem[], @@ -38,7 +38,7 @@ export class LeetCodeTreeDataProvider implements vscode.TreeDataProvider Number(p.id) >= Number(problem.id)); + // Find appropriate index to insert/delete a problem + const problemIndex: number = this.treeData[Category.Favorite].findIndex((p: LeetCodeNode) => Number(p.id) >= Number(problem.id)); if (problem.isFavorite) { - this.treeData.Favorite.splice(problemIndex, 0, origin); // insert original problem's reference as favorite + this.treeData[Category.Favorite].splice(problemIndex, 0, origin); // insert original problem's reference as favorite } else { - this.treeData.Favorite.splice(problemIndex, 1); // delete favorite + this.treeData[Category.Favorite].splice(problemIndex, 1); // delete favorite } } }