diff --git a/src/explorer/LeetCodeNode.ts b/src/explorer/LeetCodeNode.ts index 3f685b70..67aad324 100644 --- a/src/explorer/LeetCodeNode.ts +++ b/src/explorer/LeetCodeNode.ts @@ -5,7 +5,8 @@ import { Command } from "vscode"; import { IProblem, ProblemState } from "../shared"; export class LeetCodeNode { - constructor(private data: IProblem, private parentNodeName: string, private isProblemNode: boolean = true) { } + + constructor(private data: IProblem, private isProblemNode: boolean = true) { } public get locked(): boolean { return this.data.locked; @@ -46,10 +47,6 @@ export class LeetCodeNode { return this.isProblemNode; } - public get parentName(): string { - return this.parentNodeName; - } - public get previewCommand(): Command { return { title: "Preview Problem", diff --git a/src/explorer/LeetCodeTreeDataProvider.ts b/src/explorer/LeetCodeTreeDataProvider.ts index 09f47530..c04346d6 100644 --- a/src/explorer/LeetCodeTreeDataProvider.ts +++ b/src/explorer/LeetCodeTreeDataProvider.ts @@ -1,26 +1,16 @@ // Copyright (c) jdneo. All rights reserved. // Licensed under the MIT license. -import * as _ from "lodash"; import * as os from "os"; import * as path from "path"; import * as vscode from "vscode"; -import * as list from "../commands/list"; -import { leetCodeChannel } from "../leetCodeChannel"; import { leetCodeManager } from "../leetCodeManager"; -import { Category, defaultProblem, IProblem, ProblemState } from "../shared"; -import { getWorkspaceConfiguration } from "../utils/workspaceUtils"; +import { Category, defaultProblem, ProblemState } from "../shared"; +import { explorerNodeManager } from "./explorerNodeManager"; import { LeetCodeNode } from "./LeetCodeNode"; export class LeetCodeTreeDataProvider implements vscode.TreeDataProvider { - private treeData: { - Difficulty: Map, - Tag: Map, - Company: Map, - Favorite: IProblem[], - }; - private onDidChangeTreeDataEvent: vscode.EventEmitter = new vscode.EventEmitter(); // tslint:disable-next-line:member-ordering public readonly onDidChangeTreeData: vscode.Event = this.onDidChangeTreeDataEvent.event; @@ -28,7 +18,7 @@ export class LeetCodeTreeDataProvider implements vscode.TreeDataProvider { - await this.getProblemData(); + await explorerNodeManager.refreshCache(); this.onDidChangeTreeDataEvent.fire(); } @@ -49,7 +39,7 @@ export class LeetCodeTreeDataProvider implements vscode.TreeDataProvider new LeetCodeNode(p, Category.Favorite)); + return explorerNodeManager.getFavoriteNodes(); case Category.Difficulty: + return explorerNodeManager.getAllDifficultyNodes(); case Category.Tag: + return explorerNodeManager.getAllTagNodes(); case Category.Company: - return this.composeSubCategoryNodes(element); - default: // Second and lower levels - return element.isProblem ? [] : this.composeProblemNodes(element); - } - } - } - - private async getProblemData(): Promise { - // clear cache - this.treeData = { - Difficulty: new Map(), - Tag: new Map(), - Company: new Map(), - Favorite: [], - }; - for (const problem of await list.listProblems()) { - // Add favorite problem, no matter whether it is solved. - if (problem.isFavorite) { - this.treeData[Category.Favorite].push(problem); - } - // Hide solved problem in other category. - if (problem.state === ProblemState.AC && getWorkspaceConfiguration().get("hideSolved")) { - continue; + return explorerNodeManager.getAllCompanyNodes(); + default: + if (element.isProblem) { + return []; + } + return explorerNodeManager.getChildrenNodesById(element.id); } - - this.addProblemToTreeData(problem); - } - } - - private composeProblemNodes(node: LeetCodeNode): LeetCodeNode[] { - const map: Map | undefined = this.treeData[node.parentName]; - if (!map) { - leetCodeChannel.appendLine(`Category: ${node.parentName} is not available.`); - return []; - } - const problems: IProblem[] = map.get(node.name) || []; - const problemNodes: LeetCodeNode[] = []; - for (const problem of problems) { - problemNodes.push(new LeetCodeNode(problem, node.name)); } - return problemNodes; - } - - private composeSubCategoryNodes(node: LeetCodeNode): LeetCodeNode[] { - const category: Category = node.name as Category; - if (category === Category.Favorite) { - leetCodeChannel.appendLine("No sub-level for Favorite nodes"); - return []; - } - const map: Map | undefined = this.treeData[category]; - if (!map) { - leetCodeChannel.appendLine(`Category: ${category} is not available.`); - return []; - } - return this.getSubCategoryNodes(map, category); } private parseIconPathFromProblemState(element: LeetCodeNode): string { @@ -171,16 +98,16 @@ export class LeetCodeTreeDataProvider implements vscode.TreeDataProvider, key: string, problem: IProblem): void { - const problems: IProblem[] | undefined = map.get(key); - if (problems) { - problems.push(problem); - } else { - map.set(key, [problem]); - } - } - - private getSubCategoryNodes(map: Map, category: Category): LeetCodeNode[] { - const subCategoryNodes: LeetCodeNode[] = Array.from(map.keys()).map((subCategory: string) => { - return new LeetCodeNode(Object.assign({}, defaultProblem, { - id: subCategory, - name: subCategory, - }), category.toString(), false); - }); - this.sortSubCategoryNodes(subCategoryNodes, category); - return subCategoryNodes; - } - - private sortSubCategoryNodes(subCategoryNodes: LeetCodeNode[], category: Category): void { - switch (category) { - case Category.Difficulty: - subCategoryNodes.sort((a: LeetCodeNode, b: LeetCodeNode): number => { - function getValue(input: LeetCodeNode): number { - switch (input.name.toLowerCase()) { - case "easy": - return 1; - case "medium": - return 2; - case "hard": - return 3; - default: - return Number.MAX_SAFE_INTEGER; - } - } - return getValue(a) - getValue(b); - }); - break; - case Category.Tag: - case Category.Company: - subCategoryNodes.sort((a: LeetCodeNode, b: LeetCodeNode): number => { - if (a.name === "Unknown") { - return 1; - } else if (b.name === "Unknown") { - return -1; - } else { - return Number(a.name > b.name) - Number(a.name < b.name); - } - }); - break; - default: - break; - } - } } diff --git a/src/explorer/explorerNodeManager.ts b/src/explorer/explorerNodeManager.ts new file mode 100644 index 00000000..dcf1cc09 --- /dev/null +++ b/src/explorer/explorerNodeManager.ts @@ -0,0 +1,178 @@ +// Copyright (c) jdneo. All rights reserved. +// Licensed under the MIT license. + +import * as _ from "lodash"; +import { Disposable } from "vscode"; +import * as list from "../commands/list"; +import { Category, defaultProblem } from "../shared"; +import { LeetCodeNode } from "./LeetCodeNode"; + +class ExplorerNodeManager implements Disposable { + private explorerNodeMap: Map = new Map(); + private companySet: Set = new Set(); + private tagSet: Set = new Set(); + + public async refreshCache(): Promise { + this.dispose(); + for (const problem of await list.listProblems()) { + this.explorerNodeMap.set(problem.id, new LeetCodeNode(problem)); + for (const company of problem.companies) { + this.companySet.add(company); + } + for (const tag of problem.tags) { + this.tagSet.add(tag); + } + } + } + + public getRootNodes(): LeetCodeNode[] { + return [ + new LeetCodeNode(Object.assign({}, defaultProblem, { + id: Category.Difficulty, + name: Category.Difficulty, + }), false), + new LeetCodeNode(Object.assign({}, defaultProblem, { + id: Category.Tag, + name: Category.Tag, + }), false), + new LeetCodeNode(Object.assign({}, defaultProblem, { + id: Category.Company, + name: Category.Company, + }), false), + new LeetCodeNode(Object.assign({}, defaultProblem, { + id: Category.Favorite, + name: Category.Favorite, + }), false), + ]; + } + + public getAllDifficultyNodes(): LeetCodeNode[] { + const res: LeetCodeNode[] = []; + res.push( + new LeetCodeNode(Object.assign({}, defaultProblem, { + id: `${Category.Difficulty}.Easy`, + name: "Easy", + }), false), + new LeetCodeNode(Object.assign({}, defaultProblem, { + id: `${Category.Difficulty}.Medium`, + name: "Medium", + }), false), + new LeetCodeNode(Object.assign({}, defaultProblem, { + id: `${Category.Difficulty}.Hard`, + name: "Hard", + }), false), + ); + this.sortSubCategoryNodes(res, Category.Difficulty); + return res; + } + + public getAllCompanyNodes(): LeetCodeNode[] { + const res: LeetCodeNode[] = []; + for (const company of this.companySet.values()) { + res.push(new LeetCodeNode(Object.assign({}, defaultProblem, { + id: `${Category.Company}.${company}`, + name: _.startCase(company), + }), false)); + } + this.sortSubCategoryNodes(res, Category.Company); + return res; + } + + public getAllTagNodes(): LeetCodeNode[] { + const res: LeetCodeNode[] = []; + for (const tag of this.tagSet.values()) { + res.push(new LeetCodeNode(Object.assign({}, defaultProblem, { + id: `${Category.Tag}.${tag}`, + name: _.startCase(tag), + }), false)); + } + this.sortSubCategoryNodes(res, Category.Tag); + return res; + } + + public getNodeById(id: string): LeetCodeNode | undefined { + return this.explorerNodeMap.get(id); + } + + public getFavoriteNodes(): LeetCodeNode[] { + const res: LeetCodeNode[] = []; + for (const node of this.explorerNodeMap.values()) { + if (node.isFavorite) { + res.push(node); + } + } + return res; + } + + public getChildrenNodesById(id: string): LeetCodeNode[] { + // The sub-category node's id is named as {Category.SubName} + const metaInfo: string[] = id.split("."); + const res: LeetCodeNode[] = []; + for (const node of this.explorerNodeMap.values()) { + switch (metaInfo[0]) { + case Category.Company: + if (node.companies.indexOf(metaInfo[1]) >= 0) { + res.push(node); + } + break; + case Category.Difficulty: + if (node.difficulty === metaInfo[1]) { + res.push(node); + } + break; + case Category.Tag: + if (node.tags.indexOf(metaInfo[1]) >= 0) { + res.push(node); + } + break; + default: + break; + } + } + return res; + } + + public dispose(): void { + this.explorerNodeMap.clear(); + this.companySet.clear(); + this.tagSet.clear(); + } + + private sortSubCategoryNodes(subCategoryNodes: LeetCodeNode[], category: Category): void { + switch (category) { + case Category.Difficulty: + subCategoryNodes.sort((a: LeetCodeNode, b: LeetCodeNode): number => { + function getValue(input: LeetCodeNode): number { + switch (input.name.toLowerCase()) { + case "easy": + return 1; + case "medium": + return 2; + case "hard": + return 3; + default: + return Number.MAX_SAFE_INTEGER; + } + } + return getValue(a) - getValue(b); + }); + break; + case Category.Tag: + case Category.Company: + subCategoryNodes.sort((a: LeetCodeNode, b: LeetCodeNode): number => { + if (a.name === "Unknown") { + return 1; + } else if (b.name === "Unknown") { + return -1; + } else { + return Number(a.name > b.name) - Number(a.name < b.name); + } + }); + break; + default: + break; + } + } +} + +export const explorerNodeManager: ExplorerNodeManager = new ExplorerNodeManager(); diff --git a/src/extension.ts b/src/extension.ts index 877ff90c..2ebefd9b 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -10,6 +10,7 @@ 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 { explorerNodeManager } from "./explorer/explorerNodeManager"; import { LeetCodeNode } from "./explorer/LeetCodeNode"; import { LeetCodeTreeDataProvider } from "./explorer/LeetCodeTreeDataProvider"; import { leetCodeChannel } from "./leetCodeChannel"; @@ -44,6 +45,7 @@ export async function activate(context: vscode.ExtensionContext): Promise leetCodeExecutor, markdownEngine, codeLensController, + explorerNodeManager, vscode.window.createTreeView("leetCodeExplorer", { treeDataProvider: leetCodeTreeDataProvider, showCollapseAll: true }), vscode.commands.registerCommand("leetcode.deleteCache", () => cache.deleteCache()), vscode.commands.registerCommand("leetcode.toggleLeetCodeCn", () => plugin.switchEndpoint()),