diff --git a/.vscode/settings.json b/.vscode/settings.json index a2d0a8b..80ecdea 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -9,6 +9,4 @@ "**/node_modules": true, ".vscode-test": true }, - "tslint.autoFixOnSave": true, - "tslint.ignoreDefinitionFiles": true -} \ No newline at end of file +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 0751c9e..0413183 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## version 2.2.1 + +- 优化登录提示 +- 新增工地功能 + +## version 2.1.1 + +- 修复切换账号时竞赛分显示问题 + ## version 2.1.0 - 重构工厂模式、责任链模式 diff --git a/README.md b/README.md index 4723698..5d9566c 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,8 @@ - 增加一键提交全部题目测试用例功能 - 尝试不需要额外安装 node 环境,使用 vscode 自带的 node 版本 - 从[zerotrac.github.io](https://zerotrac.github.io/leetcode_problem_rating/data.json)获取数据进行缓存,数据更新时,可以尝试使用 deleteAllCache,重新获取数据 -- 将所有提供的测试用例单独存放到代码下方,一键提交也会收集这部分的测试用例(简单去重()) +- [新增区块测试用例](#区块测试用例) +- [新增搬砖功能(重复练习?)](#搬砖功能的说明) # 关于本项目 @@ -64,16 +65,50 @@ - 简单的比较这些用例字符串是否相同 + +- (完成) 在文件里面插入一些测试用例? +- (完成) 不是中文站点,直接隐藏账号密码登录的方式 +- 默认的工作目录修改 不再是.leetcode(不改了) +- 想做一个可以根据计算下次回顾本题的功能?(需要在设置的工作目录中存放多个文件?) +- (完成) Test 按钮应该不在需要 case 和 allcase 只要留下输入的功能 +- 备忘录功能(数据直接放设置的工作目录?用 github 同步?) +- 获取提交历史(直接找官方的提交数据) +- 提交答案与期望答案不同的地方? +- 做题目计时 +- 还没出分前周赛题目显示 未评分(需要官网获取最新几期的题目编号) --> + +## 搬砖功能的说明 + +### 功能设想 + +- 重复做 x 天之前的题目(正确提交后 x 天再做本题) +- 重复练习可以提高水平?(待定验证) +- 有什么学习方法可以与我交流,这个方法我也不知道有没有用 + +### 新增在工作目录存放数据 + +- 目录说明 + + > workspace/ 工作目录 + > + > > .lcpr_data/ 存数据 + > > + > > > bricks.json + +### bricks.json 存放格式 + +``` +{ + version: 1, + all_bricks: { + [qid]: { + submit_time: [], // 上次提交的时间 + type: 1, // 类型 + }, + }, + }; + +``` ## 运行条件 diff --git a/package-lock.json b/package-lock.json index 38f039a..e619d11 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "vscode-leetcode-problem-rating", - "version": "2.1.0", + "version": "2.1.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "vscode-leetcode-problem-rating", - "version": "2.1.0", + "version": "2.1.1", "license": "MIT", "dependencies": { "ansi-styles": "3.2.1", diff --git a/package.json b/package.json index b49e4c3..ca3846c 100644 --- a/package.json +++ b/package.json @@ -2,11 +2,11 @@ "name": "vscode-leetcode-problem-rating", "displayName": "LeetCode problem rating", "description": "为LeetCode题目难度进行打分。避免只有简单、中等、困难三种难度", - "version": "2.1.0", + "version": "2.2.1", "author": "ccagml", "publisher": "ccagml", "license": "MIT", - "icon": "resources/LeetCode.png", + "icon": "resources/LCPR.png", "engines": { "vscode": "^1.57.0" }, @@ -17,7 +17,8 @@ "homepage": "https://github.com/ccagml/vscode-leetcode-problem-rating/README.md", "categories": [ "Other", - "Snippets" + "Snippets", + "Education" ], "keywords": [ "leetcode", @@ -26,95 +27,135 @@ ], "preview": true, "activationEvents": [ - "onCommand:leetcode.toggleLeetCodeCn", - "onCommand:leetcode.signin", - "onCommand:leetcode.signout", - "onCommand:leetcode.refreshExplorer", - "onCommand:leetcode.pickOne", - "onCommand:leetcode.deleteAllCache", - "onCommand:leetcode.showProblem", - "onCommand:leetcode.previewProblem", - "onCommand:leetcode.searchProblem", - "onCommand:leetcode.testSolution", - "onCommand:leetcode.submitSolution", - "onCommand:leetcode.switchDefaultLanguage", - "onCommand:leetcode.problems.sort", - "onView:leetCodeExplorer" + "onCommand:lcpr.toggleLeetCodeCn", + "onCommand:lcpr.signin", + "onCommand:lcpr.signout", + "onCommand:lcpr.refreshExplorer", + "onCommand:lcpr.pickOne", + "onCommand:lcpr.deleteAllCache", + "onCommand:lcpr.showProblem", + "onCommand:lcpr.previewProblem", + "onCommand:lcpr.searchProblem", + "onCommand:lcpr.testSolution", + "onCommand:lcpr.submitSolution", + "onCommand:lcpr.setDefaultLanguage", + "onCommand:lcpr.problems.sort", + "onView:QuestionExplorer" ], "main": "./out/src/extension", "contributes": { "commands": [ { - "command": "leetcode.deleteCache", + "command": "lcpr.deleteCache", "title": "Delete Cache", "category": "LeetCode" }, { - "command": "leetcode.toggleLeetCodeCn", + "command": "lcpr.toggleLeetCodeCn", "title": "Switch Endpoint", "category": "LeetCode", "icon": "$(globe)" }, { - "command": "leetcode.signin", + "command": "lcpr.signin", "title": "Sign In", "category": "LeetCode", "icon": "$(sign-in)" }, { - "command": "leetcode.signout", + "command": "lcpr.signout", "title": "Sign Out", "category": "LeetCode" }, { - "command": "leetcode.refreshExplorer", + "command": "lcpr.refreshExplorer", "title": "Refresh", "category": "LeetCode", "icon": "$(refresh)" }, { - "command": "leetcode.pickOne", + "command": "lcpr.pickOne", "title": "Pick One", "category": "LeetCode" }, { - "command": "leetcode.deleteAllCache", + "command": "lcpr.deleteAllCache", "title": "deleteAllCache", "category": "LeetCode" }, { - "command": "leetcode.showProblem", + "command": "lcpr.showProblem", "title": "Show Problem", "category": "LeetCode" }, { - "command": "leetcode.previewProblem", + "command": "lcpr.previewProblem", "title": "Preview Problem", "category": "LeetCode" }, { - "command": "leetcode.searchProblem", + "command": "lcpr.setBricksType0", + "title": "设置不出现在工地", + "category": "LeetCode" + }, + { + "command": "lcpr.setBricksType1", + "title": "最后一次提交在14天前,出现在工地", + "category": "LeetCode" + }, + { + "command": "lcpr.setBricksType2", + "title": "最后一次提交在7天前,出现在工地", + "category": "LeetCode" + }, + { + "command": "lcpr.setBricksType3", + "title": "最后一次提交在5天前,出现在工地", + "category": "LeetCode" + }, + { + "command": "lcpr.setBricksType4", + "title": "最后一次提交在3天前,出现在工地", + "category": "LeetCode" + }, + { + "command": "lcpr.setBricksType5", + "title": "最后一次提交在2天前,出现在工地", + "category": "LeetCode" + }, + { + "command": "lcpr.setBricksType6", + "title": "最后一次提交在1天前,出现在工地", + "category": "LeetCode" + }, + { + "command": "lcpr.setBricksType7", + "title": "最后一次提交在1天前,出现在工地", + "category": "LeetCode" + }, + { + "command": "lcpr.searchProblem", "title": "Search Problem", "category": "LeetCode", "icon": "$(search)" }, { - "command": "leetcode.showSolution", - "title": "Show Top Voted Solution", + "command": "lcpr.showSolution", + "title": "获取题解", "category": "LeetCode" }, { - "command": "leetcode.testSolution", + "command": "lcpr.testSolution", "title": "Test in LeetCode", "category": "LeetCode" }, { - "command": "leetcode.submitSolution", + "command": "lcpr.submitSolution", "title": "Submit to LeetCode", "category": "LeetCode" }, { - "command": "leetcode.addFavorite", + "command": "lcpr.addFavorite", "title": "Add to Favorite List", "category": "LeetCode", "icon": { @@ -123,7 +164,7 @@ } }, { - "command": "leetcode.removeFavorite", + "command": "lcpr.removeFavorite", "title": "Remove from Favorite List", "category": "LeetCode", "icon": { @@ -132,12 +173,12 @@ } }, { - "command": "leetcode.switchDefaultLanguage", + "command": "lcpr.setDefaultLanguage", "title": "Switch Default Language", "category": "LeetCode" }, { - "command": "leetcode.problems.sort", + "command": "lcpr.problems.sort", "title": "Sort Problems", "category": "LeetCode", "icon": "$(sort-precedence)" @@ -146,147 +187,244 @@ "viewsContainers": { "activitybar": [ { - "id": "leetcode", - "title": "LeetCode", - "icon": "resources/LeetCode.svg" + "id": "lcpr_bar", + "title": "LCPR", + "icon": "resources/LCPR.svg" } ] }, "views": { - "leetcode": [ + "lcpr_bar": [ { - "id": "leetCodeExplorer", + "id": "QuestionExplorer", "name": "Problems" + }, + { + "id": "BricksExplorer", + "name": "搬砖工地" } ] }, "menus": { "view/title": [ { - "command": "leetcode.toggleLeetCodeCn", - "when": "view == leetCodeExplorer", + "command": "lcpr.toggleLeetCodeCn", + "when": "view == QuestionExplorer", "group": "navigation@0" }, { - "command": "leetcode.signin", - "when": "view == leetCodeExplorer", + "command": "lcpr.signin", + "when": "view == QuestionExplorer", "group": "navigation@1" }, { - "command": "leetcode.searchProblem", - "when": "view == leetCodeExplorer", + "command": "lcpr.searchProblem", + "when": "view == QuestionExplorer", "group": "navigation@2" }, { - "command": "leetcode.refreshExplorer", - "when": "view == leetCodeExplorer", + "command": "lcpr.refreshExplorer", + "when": "view == QuestionExplorer", "group": "navigation@3" }, { - "command": "leetcode.pickOne", - "when": "view == leetCodeExplorer", + "command": "lcpr.pickOne", + "when": "view == QuestionExplorer", "group": "overflow@0" }, { - "command": "leetcode.problems.sort", - "when": "view == leetCodeExplorer", + "command": "lcpr.problems.sort", + "when": "view == QuestionExplorer", "group": "overflow@1" }, { - "command": "leetcode.deleteAllCache", - "when": "view == leetCodeExplorer", + "command": "lcpr.deleteAllCache", + "when": "view == QuestionExplorer", "group": "overflow@2" } ], "view/item/context": [ { - "command": "leetcode.previewProblem", - "when": "view == leetCodeExplorer && viewItem =~ /problem*/", + "command": "lcpr.previewProblem", + "when": "view == QuestionExplorer && viewItem =~ /problem*/", "group": "leetcode@1" }, { - "command": "leetcode.showProblem", - "when": "view == leetCodeExplorer && viewItem =~ /problem*/", + "command": "lcpr.showProblem", + "when": "view == QuestionExplorer && viewItem =~ /problem*/", "group": "leetcode@2" }, { - "command": "leetcode.showSolution", - "when": "view == leetCodeExplorer && viewItem =~ /problem*/", + "command": "lcpr.showSolution", + "when": "view == QuestionExplorer && viewItem =~ /problem*/", "group": "leetcode@3" }, { - "command": "leetcode.addFavorite", - "when": "view == leetCodeExplorer && viewItem == problem", + "submenu": "lcpr.setBricksType_sub", + "when": "view == QuestionExplorer && viewItem =~ /problem*/", + "group": "leetcode@4" + }, + { + "command": "lcpr.addFavorite", + "when": "view == QuestionExplorer && viewItem == problem", "group": "inline" }, { - "command": "leetcode.removeFavorite", - "when": "view == leetCodeExplorer && viewItem == problem-favorite", + "command": "lcpr.removeFavorite", + "when": "view == QuestionExplorer && viewItem == problem-favorite", "group": "inline" + }, + { + "command": "lcpr.previewProblem", + "when": "view == BricksExplorer && viewItem == bricks", + "group": "leetcode@1" + }, + { + "command": "lcpr.showProblem", + "when": "view == BricksExplorer && viewItem == bricks", + "group": "leetcode@2" + }, + { + "command": "lcpr.showSolution", + "when": "view == BricksExplorer && viewItem == bricks", + "group": "leetcode@3" + }, + { + "submenu": "lcpr.setBricksType_sub1", + "when": "view == BricksExplorer && viewItem == bricks", + "group": "leetcode@4" } ], "commandPalette": [ { - "command": "leetcode.showProblem", + "command": "lcpr.showProblem", "when": "never" }, { - "command": "leetcode.showSolution", + "command": "lcpr.showSolution", "when": "never" }, { - "command": "leetcode.previewProblem", + "command": "lcpr.previewProblem", "when": "never" }, { - "command": "leetcode.addFavorite", + "command": "lcpr.addFavorite", "when": "never" }, { - "command": "leetcode.removeFavorite", + "command": "lcpr.removeFavorite", "when": "never" } ], "explorer/context": [ { - "command": "leetcode.testSolution", + "command": "lcpr.testSolution", "when": "explorerResourceIsFolder == false", "group": "leetcode@1" }, { - "command": "leetcode.submitSolution", + "command": "lcpr.submitSolution", "when": "explorerResourceIsFolder == false", "group": "leetcode@2" } ], "editor/context": [ { - "submenu": "leetcode.editorAction" + "submenu": "lcpr.editorAction" } ], - "leetcode.editorAction": [ + "lcpr.editorAction": [ { - "command": "leetcode.testSolution", + "command": "lcpr.testSolution", "group": "leetcode@1" }, { - "command": "leetcode.submitSolution", + "command": "lcpr.submitSolution", "group": "leetcode@2" }, { - "command": "leetcode.showSolution", + "command": "lcpr.showSolution", "group": "leetcode@3" }, { - "command": "leetcode.previewProblem", + "command": "lcpr.previewProblem", "group": "leetcode@4" } + ], + "lcpr.setBricksType_sub": [ + { + "command": "lcpr.setBricksType0", + "group": "leetcode@1" + }, + { + "command": "lcpr.setBricksType6", + "group": "leetcode@2" + }, + { + "command": "lcpr.setBricksType5", + "group": "leetcode@3" + }, + { + "command": "lcpr.setBricksType4", + "group": "leetcode@4" + }, + { + "command": "lcpr.setBricksType3", + "group": "leetcode@5" + }, + { + "command": "lcpr.setBricksType2", + "group": "leetcode@6" + }, + { + "command": "lcpr.setBricksType1", + "group": "leetcode@7" + } + ], + "lcpr.setBricksType_sub1": [ + { + "command": "lcpr.setBricksType0", + "group": "leetcode@1" + }, + { + "command": "lcpr.setBricksType6", + "group": "leetcode@2" + }, + { + "command": "lcpr.setBricksType5", + "group": "leetcode@3" + }, + { + "command": "lcpr.setBricksType4", + "group": "leetcode@4" + }, + { + "command": "lcpr.setBricksType3", + "group": "leetcode@5" + }, + { + "command": "lcpr.setBricksType2", + "group": "leetcode@6" + }, + { + "command": "lcpr.setBricksType1", + "group": "leetcode@7" + } ] }, "submenus": [ { - "id": "leetcode.editorAction", - "label": "LeetCode" + "id": "lcpr.editorAction", + "label": "LCPR菜单" + }, + { + "id": "lcpr.setBricksType_sub", + "label": "设置砖头类型" + }, + { + "id": "lcpr.setBricksType_sub1", + "label": "设置砖头类型" } ], "configuration": [ diff --git a/resources/LeetCode.png b/resources/LCPR.png similarity index 100% rename from resources/LeetCode.png rename to resources/LCPR.png diff --git a/resources/LeetCode.svg b/resources/LCPR.svg similarity index 100% rename from resources/LeetCode.svg rename to resources/LCPR.svg diff --git a/src/controller/BricksViewController.ts b/src/controller/BricksViewController.ts new file mode 100644 index 0000000..3b126f2 --- /dev/null +++ b/src/controller/BricksViewController.ts @@ -0,0 +1,67 @@ +/* + * Filename: /home/cc/vscode-leetcode-problem-rating/src/controller/BricksViewController.ts + * Path: /home/cc/vscode-leetcode-problem-rating + * Created Date: Tuesday, November 22nd 2022, 11:04:59 am + * Author: ccagml + * + * Copyright (c) 2022 ccagml . All rights reserved. + */ + +import { Disposable, TreeItemCollapsibleState } from "vscode"; + +import { bricksDao } from "../dao/bricksDao"; +import { BricksNormalId, defaultProblem } from "../model/Model"; +import { BricksNode } from "../model/NodeModel"; +import { bricksDataService } from "../service/BricksDataService"; +import { treeViewController } from "./TreeViewController"; + +// 视图控制器 +class BricksViewController implements Disposable { + public async initialize() { + await bricksDataService.initialize(); + } + + // 需要的 + public async getHaveNodes() { + let all_qid: string[] = await bricksDao.getTodayBricks(); + const baseNode: BricksNode[] = []; + all_qid.forEach((qid) => { + let node = treeViewController.getNodeByQid(qid); + if (node) { + baseNode.push(node); + } + }); + return baseNode; + } + // 今天搬的 + public async getTodayNodes() { + const baseNode: BricksNode[] = []; + return baseNode; + } + + public async getRootNodes(): Promise { + let all_qid: string[] = await bricksDao.getTodayBricks(); + let has_qid = all_qid.length > 0; + const baseNode: BricksNode[] = []; + + baseNode.push( + new BricksNode( + Object.assign({}, defaultProblem, { + id: has_qid ? BricksNormalId.Have : BricksNormalId.No, + name: has_qid ? BricksNormalId.HaveDesc : BricksNormalId.NoDesc, + }), + false, + 0, + has_qid ? TreeItemCollapsibleState.Collapsed : TreeItemCollapsibleState.None + ) + ); + return baseNode; + } + + public async setBricksType(node: BricksNode, type) { + await bricksDataService.setBricksType(node, type); + } + public dispose(): void {} +} + +export const bricksViewController: BricksViewController = new BricksViewController(); diff --git a/src/controller/EventController.ts b/src/controller/EventController.ts index 9cbb350..658cfc1 100644 --- a/src/controller/EventController.ts +++ b/src/controller/EventController.ts @@ -9,12 +9,18 @@ import { eventService } from "../service/EventService"; // 事件的控制器 +/* The EventController class has a method called addEvent that calls the addEvent method on the +eventService class */ class EventContorller { /** * 监听事件 */ - public add_event() { - eventService.add_event(); + /** + * The function `addEvent()` is a public function that calls the `addEvent()` function in the + * `eventService` service + */ + public addEvent() { + eventService.addEvent(); } } diff --git a/src/controller/FileButtonController.ts b/src/controller/FileButtonController.ts index a40764b..c5bf44d 100644 --- a/src/controller/FileButtonController.ts +++ b/src/controller/FileButtonController.ts @@ -10,6 +10,7 @@ import { ConfigurationChangeEvent, Disposable, languages, workspace } from "vscode"; import { fileButtonService } from "../service/FileButtonService"; // 文件按钮的控制器 +/* It listens to configuration changes and refreshes the file button service */ class FileButtonController implements Disposable { private registeredProvider: Disposable | undefined; private configurationChangeListener: Disposable; diff --git a/src/controller/LoginController.ts b/src/controller/LoginController.ts index 09b5184..29cadbb 100644 --- a/src/controller/LoginController.ts +++ b/src/controller/LoginController.ts @@ -10,84 +10,46 @@ import * as cp from "child_process"; import * as systemUtils from "../utils/SystemUtils"; import { executeService } from "../service/ExecuteService"; -import { DialogType, Endpoint, IQuickItemEx, loginArgsMapping, UserStatus } from "../model/Model"; +import { OutPutType, Endpoint, IQuickItemEx, loginArgsMapping, UserStatus } from "../model/Model"; import { createEnvOption } from "../utils/CliUtils"; import { logOutput, promptForOpenOutputChannel } from "../utils/OutputUtils"; import { eventService } from "../service/EventService"; -import { window, QuickPickOptions } from "vscode"; +import { window, QuickPickOptions, ProgressLocation, Progress } from "vscode"; import { statusBarService } from "../service/StatusBarService"; import { treeDataService } from "../service/TreeDataService"; import { getLeetCodeEndpoint } from "../utils/ConfigUtils"; +import { bricksDataService } from "../service/BricksDataService"; // 登录控制器 class LoginContorller { constructor() {} + commandArg: string | undefined; + loginMethod: string; - // 登录操作 - public async signIn(): Promise { - const picks: Array> = []; - let qpOpiton: QuickPickOptions = { - title: "正在登录leetcode.com", - matchOnDescription: false, - matchOnDetail: false, - placeHolder: "请选择登录方式 正在登录leetcode.com", - }; - if (getLeetCodeEndpoint() == Endpoint.LeetCodeCN) { - picks.push({ - label: "LeetCode Account", - detail: "只能登录leetcode.cn", - value: "LeetCode", - }); - qpOpiton.title = "正在登录中文版leetcode.cn"; - qpOpiton.placeHolder = "请选择登录方式 正在登录中文版leetcode.cn"; - } - picks.push( - { - label: "Third-Party: GitHub", - detail: "Use GitHub account to login", - value: "GitHub", - }, - { - label: "Third-Party: LinkedIn", - detail: "Use LinkedIn account to login", - value: "LinkedIn", - }, - { - label: "LeetCode Cookie", - detail: "Use LeetCode cookie copied from browser to login", - value: "Cookie", - } - ); - const choice: IQuickItemEx | undefined = await window.showQuickPick(picks, qpOpiton); - if (!choice) { - return; - } - const loginMethod: string = choice.value; - const commandArg: string | undefined = loginArgsMapping.get(loginMethod); - if (!commandArg) { - throw new Error(`不支持 "${loginMethod}" 方式登录`); - } - const isByCookie: boolean = loginMethod === "Cookie"; - const inMessage: string = isByCookie ? " 通过cookie登录" : "登录"; - try { - const userName: string | undefined = await new Promise( + public async getUserName(): Promise { + let result: string = ""; + + await window.withProgress({ location: ProgressLocation.Notification }, async (p: Progress<{}>) => { + return new Promise( async (resolve: (res: string | undefined) => void, reject: (e: Error) => void): Promise => { + if (this.commandArg == undefined) { + reject(new Error("not commandArg")); + return; + } const leetCodeBinaryPath: string = await executeService.getLeetCodeBinaryPath(); - let childProc: cp.ChildProcess; - if (systemUtils.useVscodeNode()) { - childProc = cp.fork(await executeService.getLeetCodeBinaryPath(), ["user", commandArg], { + childProc = cp.fork(await executeService.getLeetCodeBinaryPath(), ["user", this.commandArg], { silent: true, env: createEnvOption(), }); } else { if (systemUtils.useWsl()) { - childProc = cp.spawn("wsl", [executeService.node, leetCodeBinaryPath, "user", commandArg], { + childProc = cp.spawn("wsl", [executeService.node, leetCodeBinaryPath, "user", this.commandArg], { shell: true, }); } else { - childProc = cp.spawn(executeService.node, [leetCodeBinaryPath, "user", commandArg], { + childProc = cp.spawn(executeService.node, [leetCodeBinaryPath, "user", this.commandArg], { shell: true, env: createEnvOption(), }); @@ -112,7 +74,7 @@ class LoginContorller { childProc.stdin?.write(`${twoFactor}\n`); } - let successMatch; + let successMatch: any; try { successMatch = JSON.parse(data); } catch (e) { @@ -120,6 +82,7 @@ class LoginContorller { } if (successMatch.code == 100) { childProc.stdin?.end(); + result = successMatch.user_name; return resolve(successMatch.user_name); } else if (successMatch.code < 0) { childProc.stdin?.end(); @@ -128,8 +91,8 @@ class LoginContorller { }); childProc.stderr?.on("data", (data: string | Buffer) => logOutput.append(data.toString())); - childProc.on("error", reject); + const name: string | undefined = await window.showInputBox({ prompt: "Enter username or E-mail.", ignoreFocusOut: true, @@ -141,6 +104,7 @@ class LoginContorller { return resolve(undefined); } childProc.stdin?.write(`${name}\n`); + const isByCookie: boolean = this.loginMethod === "Cookie"; const pwd: string | undefined = await window.showInputBox({ prompt: isByCookie ? "Enter cookie" : "Enter password.", password: true, @@ -153,39 +117,105 @@ class LoginContorller { return resolve(undefined); } childProc.stdin?.write(`${pwd}\n`); + p.report({ message: "正在登录中~~~~" }); } ); + }); + + return result; + } + + /* A login function. */ + // 登录操作 + public async signIn(): Promise { + const picks: Array> = []; + let qpOpiton: QuickPickOptions = { + title: "正在登录leetcode.com", + matchOnDescription: false, + matchOnDetail: false, + placeHolder: "请选择登录方式 正在登录leetcode.com", + }; + if (getLeetCodeEndpoint() == Endpoint.LeetCodeCN) { + picks.push({ + label: "LeetCode Account", + detail: "只能登录leetcode.cn", + value: "LeetCode", + }); + qpOpiton.title = "正在登录中文版leetcode.cn"; + qpOpiton.placeHolder = "请选择登录方式 正在登录中文版leetcode.cn"; + } + picks.push( + { + label: "Third-Party: GitHub", + detail: "Use GitHub account to login", + value: "GitHub", + }, + { + label: "Third-Party: LinkedIn", + detail: "Use LinkedIn account to login", + value: "LinkedIn", + }, + { + label: "LeetCode Cookie", + detail: "Use LeetCode cookie copied from browser to login", + value: "Cookie", + } + ); + const choice: IQuickItemEx | undefined = await window.showQuickPick(picks, qpOpiton); + if (!choice) { + return; + } + this.loginMethod = choice.value; + this.commandArg = loginArgsMapping.get(this.loginMethod); + if (!this.commandArg) { + throw new Error(`不支持 "${this.loginMethod}" 方式登录`); + } + const isByCookie: boolean = this.loginMethod === "Cookie"; + const inMessage: string = isByCookie ? " 通过cookie登录" : "登录"; + try { + const userName: string | undefined = await this.getUserName(); if (userName) { eventService.emit("statusChanged", UserStatus.SignedIn, userName); window.showInformationMessage(`${inMessage} 成功`); } } catch (error) { - promptForOpenOutputChannel(`${inMessage}失败. 请看看控制台输出信息`, DialogType.error); + promptForOpenOutputChannel(`${inMessage}失败. 请看看控制台输出信息`, OutPutType.error); } } // 登出 + /** + * It signs out the user + */ public async signOut(): Promise { try { await executeService.signOut(); window.showInformationMessage("成功登出"); eventService.emit("statusChanged", UserStatus.SignedOut, undefined); } catch (error) { - // promptForOpenOutputChannel(`Failed to signOut. Please open the output channel for details`, DialogType.error); + // promptForOpenOutputChannel(`Failed to signOut. Please open the output channel for details`, OutPutType.error); } } // 获取登录状态 + /** + * It returns the login status of the user. + * @returns The login status of the user. + */ public async getLoginStatus() { return await statusBarService.getLoginStatus(); } // 删除所有缓存 + /** + * It signs out, removes old cache, switches to the default endpoint, and refreshes the tree data + */ public async deleteAllCache(): Promise { await this.signOut(); await executeService.removeOldCache(); await executeService.switchEndpoint(getLeetCodeEndpoint()); await treeDataService.refresh(); + await bricksDataService.refresh(); } } diff --git a/src/controller/MainController.ts b/src/controller/MainController.ts index d8a0ddf..0b945bb 100644 --- a/src/controller/MainController.ts +++ b/src/controller/MainController.ts @@ -20,6 +20,10 @@ class MainContorller { /** * 检查运行环境 */ + /** + * It checks if the environment meets the requirements + * @param {ExtensionContext} context - ExtensionContext + */ public async checkNodeEnv(context: ExtensionContext) { if (!systemUtils.useVscodeNode()) { if (!(await executeService.checkNodeEnv(context))) { @@ -28,6 +32,10 @@ class MainContorller { } } + /** + * It takes the version number from the package.json file and converts it to a number + * @param {ExtensionContext} context - ExtensionContext + */ public setGlobal(context: ExtensionContext) { let cur_version: string = context.extension.packageJSON.version || "1.0.0"; let cur_version_arr: Array = cur_version.split("."); @@ -40,12 +48,20 @@ class MainContorller { } // 初始化上下文 + /** + * This function sets the global variable and then calls the initialize function of the + * treeDataService. + * @param {ExtensionContext} context - ExtensionContext + */ public initialize(context: ExtensionContext) { this.setGlobal(context); treeDataService.initialize(context); } // 删除缓存 +/** + * It deletes the cache. + */ public async deleteCache() { await executeService.deleteCache(); } diff --git a/src/controller/TreeViewController.ts b/src/controller/TreeViewController.ts index 2122112..648b4cf 100644 --- a/src/controller/TreeViewController.ts +++ b/src/controller/TreeViewController.ts @@ -12,7 +12,7 @@ import * as path from "path"; import * as unescapeJS from "unescape-js"; import * as vscode from "vscode"; import { toNumber } from "lodash"; -import { Disposable, Uri, window, QuickPickItem, workspace, WorkspaceConfiguration } from "vscode"; +import { Disposable, Uri, window } from "vscode"; import { SearchNode, userContestRankingObj, @@ -20,7 +20,6 @@ import { UserStatus, IProblem, IQuickItemEx, - languages, Category, defaultProblem, ProblemState, @@ -31,9 +30,7 @@ import { ISubmitEvent, SORT_ORDER, Endpoint, - OpenOption, - DialogType, - DialogOptions, + OutPutType, } from "../model/Model"; import { isHideSolvedProblem, @@ -48,6 +45,10 @@ import { getSortingStrategy, getLeetCodeEndpoint, openSettingsEditor, + fetchProblemLanguage, + getBelongingWorkspaceFolderUri, + selectWorkspaceFolder, + setDefaultLanguage, } from "../utils/ConfigUtils"; import { NodeModel } from "../model/NodeModel"; import { ISearchSet } from "../model/Model"; @@ -66,13 +67,13 @@ import { fileButtonService } from "../service/FileButtonService"; import * as fse from "fs-extra"; import { submissionService } from "../service/SubmissionService"; - -import * as os from "os"; -import { getVsCodeConfig, getWorkspaceFolder } from "../utils/ConfigUtils"; +import { bricksDataService } from "../service/BricksDataService"; // 视图控制器 class TreeViewController implements Disposable { private explorerNodeMap: Map = new Map(); + private fidToQid: Map = new Map(); + private qidToFid: Map = new Map(); private companySet: Set = new Set(); private tagSet: Set = new Set(); private searchSet: Map = new Map(); @@ -80,6 +81,11 @@ class TreeViewController implements Disposable { private waitUserContest: boolean; // 获取当前文件的路径 + /** + * It returns the path of the currently active file, or undefined if there is no active file + * @param [uri] - The file path to open. + * @returns A promise that resolves to a string or undefined. + */ public async getActiveFilePath(uri?: vscode.Uri): Promise { let textEditor: vscode.TextEditor | undefined; if (uri) { @@ -103,6 +109,13 @@ class TreeViewController implements Disposable { } // 提交问题 + /** + * It gets the active file path, then submits the solution to the server, and finally refreshes the + * tree view + * @param [uri] - The URI of the file to be submitted. If not provided, the currently active file will + * be submitted. + * @returns A promise that resolves to a string. + */ public async submitSolution(uri?: vscode.Uri): Promise { if (!statusBarService.getUser()) { promptForSignIn(); @@ -119,14 +132,20 @@ class TreeViewController implements Disposable { submissionService.show(result); eventService.emit("submit", submissionService.getSubmitEvent()); } catch (error) { - await promptForOpenOutputChannel("提交出错了. 请查看控制台信息~", DialogType.error); + await promptForOpenOutputChannel("提交出错了. 请查看控制台信息~", OutPutType.error); return; } - treeDataService.refresh(); + await treeDataService.refresh(); + await bricksDataService.refresh(); } // 提交测试用例 + /** + * It takes the current file, and sends it to the server to be tested + * @param [uri] - The file path of the file to be submitted. If it is not passed, the currently active + * file is submitted. + */ public async testSolution(uri?: vscode.Uri): Promise { try { if (statusBarService.getStatus() === UserStatus.SignedOut) { @@ -214,11 +233,18 @@ class TreeViewController implements Disposable { submissionService.show(result); eventService.emit("submit", submissionService.getSubmitEvent()); } catch (error) { - await promptForOpenOutputChannel("提交测试出错了. 请查看控制台信息~", DialogType.error); + await promptForOpenOutputChannel("提交测试出错了. 请查看控制台信息~", OutPutType.error); } } + /** + * "Show a file selection dialog, and return the selected file's URI." + * + * The function is async, so it returns a promise + * @param {string} [fsPath] - The path of the file that is currently open in the editor. + * @returns An array of file URIs or undefined. + */ public async showFileSelectDialog(fsPath?: string): Promise { - const defaultUri: vscode.Uri | undefined = this.getBelongingWorkspaceFolderUri(fsPath); + const defaultUri: vscode.Uri | undefined = getBelongingWorkspaceFolderUri(fsPath); const options: vscode.OpenDialogOptions = { defaultUri, canSelectFiles: true, @@ -229,7 +255,15 @@ class TreeViewController implements Disposable { return await vscode.window.showOpenDialog(options); } - public async testSolutionDefault(uri?: vscode.Uri, allCase?: boolean): Promise { + /** + * It gets the active file path, and then calls the executeService.testSolution function to test the + * solution + * @param [uri] - The path of the file to be submitted. If it is not passed, the currently active file + * is submitted. + * @param {boolean} [allCase] - Whether to submit all cases. + * @returns a promise that resolves to void. + */ + public async testCaseDef(uri?: vscode.Uri, allCase?: boolean): Promise { try { if (statusBarService.getStatus() === UserStatus.SignedOut) { return; @@ -247,11 +281,20 @@ class TreeViewController implements Disposable { submissionService.show(result); eventService.emit("submit", submissionService.getSubmitEvent()); } catch (error) { - await promptForOpenOutputChannel("提交测试出错了. 请查看控制台信息~", DialogType.error); + await promptForOpenOutputChannel("提交测试出错了. 请查看控制台信息~", OutPutType.error); } } - public async testSolutionArea(uri?: vscode.Uri, testcase?: string): Promise { + /** + * It gets the active file path, then calls the executeService.testSolution function to test the + * solution + * @param [uri] - The file path of the file to be submitted. If it is not passed in, the currently + * active file is submitted. + * @param {string} [testcase] - The test case to be tested. If it is not specified, the test case will + * be randomly selected. + * @returns a promise that resolves to void. + */ + public async tesCaseArea(uri?: vscode.Uri, testcase?: string): Promise { try { if (statusBarService.getStatus() === UserStatus.SignedOut) { return; @@ -269,10 +312,17 @@ class TreeViewController implements Disposable { submissionService.show(result); eventService.emit("submit", submissionService.getSubmitEvent()); } catch (error) { - await promptForOpenOutputChannel("提交测试出错了. 请查看控制台信息~", DialogType.error); + await promptForOpenOutputChannel("提交测试出错了. 请查看控制台信息~", OutPutType.error); } } + /** + * "If the ComSpec environment variable is not set, or if it is set to cmd.exe, then return true." + * + * The ComSpec environment variable is set to the path of the command processor. On Windows, this is + * usually cmd.exe. On Linux, it is usually bash + * @returns A boolean value. + */ public usingCmd(): boolean { const comSpec: string | undefined = process.env.ComSpec; // 'cmd.exe' is used as a fallback if process.env.ComSpec is unavailable. @@ -286,6 +336,12 @@ class TreeViewController implements Disposable { return false; } + /** + * If you're on Windows, and you're using cmd.exe, then you need to escape double quotes with + * backslashes. Otherwise, you don't + * @param {string} test - The test string to be parsed. + * @returns a string. + */ public parseTestString(test: string): string { if (systemUtils.useWsl() || !systemUtils.isWindows()) { if (systemUtils.useVscodeNode()) { @@ -310,6 +366,10 @@ class TreeViewController implements Disposable { } } + /** + * It switches the endpoint of LeetCode, and then signs out and signs in again + * @returns a promise that resolves to a void. + */ public async switchEndpoint(): Promise { const isCnEnabled: boolean = getLeetCodeEndpoint() === Endpoint.LeetCodeCN; const picks: Array> = []; @@ -338,18 +398,23 @@ class TreeViewController implements Disposable { await leetCodeConfig.update("endpoint", endpoint, true /* UserSetting */); vscode.window.showInformationMessage(`Switched the endpoint to ${endpoint}`); } catch (error) { - await promptForOpenOutputChannel("切换站点出错. 请查看控制台信息~", DialogType.error); + await promptForOpenOutputChannel("切换站点出错. 请查看控制台信息~", OutPutType.error); } try { - await vscode.commands.executeCommand("leetcode.signout"); + await vscode.commands.executeCommand("lcpr.signout"); await executeService.deleteCache(); await promptForSignIn(); } catch (error) { - await promptForOpenOutputChannel("登录失败. 请查看控制台信息~", DialogType.error); + await promptForOpenOutputChannel("登录失败. 请查看控制台信息~", OutPutType.error); } } + /** + * It shows a quick pick menu with the available sorting strategies, and if the user selects one, it + * updates the sorting strategy and refreshes the tree view + * @returns A promise that resolves to a void. + */ public async switchSortingStrategy(): Promise { const currentStrategy: SortingStrategy = getSortingStrategy(); const picks: Array> = []; @@ -369,32 +434,47 @@ class TreeViewController implements Disposable { await updateSortingStrategy(choice.value, true); await treeDataService.refresh(); + await bricksDataService.refresh(); } + /** + * It adds a node to the user's favorites + * @param {NodeModel} node - NodeModel + */ public async addFavorite(node: NodeModel): Promise { try { await executeService.toggleFavorite(node, true); await treeDataService.refresh(); + await bricksDataService.refresh(); if (isStarShortcut()) { fileButtonService.refresh(); } } catch (error) { - await promptForOpenOutputChannel("添加喜欢题目失败. 请查看控制台信息~", DialogType.error); + await promptForOpenOutputChannel("添加喜欢题目失败. 请查看控制台信息~", OutPutType.error); } } + /** + * It removes a node from the user's favorites + * @param {NodeModel} node - The node that is currently selected in the tree. + */ public async removeFavorite(node: NodeModel): Promise { try { await executeService.toggleFavorite(node, false); await treeDataService.refresh(); + await bricksDataService.refresh(); if (isStarShortcut()) { fileButtonService.refresh(); } } catch (error) { - await promptForOpenOutputChannel("移除喜欢题目失败. 请查看控制台信息~", DialogType.error); + await promptForOpenOutputChannel("移除喜欢题目失败. 请查看控制台信息~", OutPutType.error); } } + /** + * It returns a list of problems + * @returns An array of problems. + */ public async listProblems(): Promise { try { if (statusBarService.getStatus() === UserStatus.SignedOut) { @@ -430,7 +510,7 @@ class TreeViewController implements Disposable { } return problems.reverse(); } catch (error) { - await promptForOpenOutputChannel("获取题目失败. 请查看控制台信息~", DialogType.error); + await promptForOpenOutputChannel("获取题目失败. 请查看控制台信息~", OutPutType.error); return []; } } @@ -455,105 +535,8 @@ class TreeViewController implements Disposable { } } - public async switchDefaultLanguage(): Promise { - const leetCodeConfig: WorkspaceConfiguration = workspace.getConfiguration("leetcode-problem-rating"); - const defaultLanguage: string | undefined = leetCodeConfig.get("defaultLanguage"); - const languageItems: QuickPickItem[] = []; - for (const language of languages) { - languageItems.push({ - label: language, - description: defaultLanguage === language ? "Currently used" : undefined, - }); - } - // Put the default language at the top of the list - languageItems.sort((a: QuickPickItem, b: QuickPickItem) => { - if (a.description) { - return Number.MIN_SAFE_INTEGER; - } else if (b.description) { - return Number.MAX_SAFE_INTEGER; - } - return a.label.localeCompare(b.label); - }); - - const selectedItem: QuickPickItem | undefined = await window.showQuickPick(languageItems, { - placeHolder: "请设置默认语言", - ignoreFocusOut: true, - }); - - if (!selectedItem) { - return; - } - - leetCodeConfig.update("defaultLanguage", selectedItem.label, true /* Global */); - window.showInformationMessage(`设置默认语言 ${selectedItem.label} 成功`); - } - - public isSubFolder(from: string, to: string): boolean { - const relative: string = path.relative(from, to); - if (relative === "") { - return true; - } - return !relative.startsWith("..") && !path.isAbsolute(relative); - } - - public async determineLeetCodeFolder(): Promise { - let result: string; - const picks: Array> = []; - picks.push( - { - label: `Default location`, - detail: `${path.join(os.homedir(), ".leetcode")}`, - value: `${path.join(os.homedir(), ".leetcode")}`, - }, - { - label: "$(file-directory) Browse...", - value: ":browse", - } - ); - const choice: IQuickItemEx | undefined = await vscode.window.showQuickPick(picks, { - placeHolder: "Select where you would like to save your LeetCode files", - }); - if (!choice) { - result = ""; - } else if (choice.value === ":browse") { - const directory: vscode.Uri[] | undefined = await this.showDirectorySelectDialog(); - if (!directory || directory.length < 1) { - result = ""; - } else { - result = directory[0].fsPath; - } - } else { - result = choice.value; - } - - getVsCodeConfig().update("workspaceFolder", result, vscode.ConfigurationTarget.Global); - - return result; - } - - public async showDirectorySelectDialog(fsPath?: string): Promise { - const defaultUri: vscode.Uri | undefined = this.getBelongingWorkspaceFolderUri(fsPath); - const options: vscode.OpenDialogOptions = { - defaultUri, - canSelectFiles: false, - canSelectFolders: true, - canSelectMany: false, - openLabel: "Select", - }; - return await vscode.window.showOpenDialog(options); - } - - public getBelongingWorkspaceFolderUri(fsPath: string | undefined): vscode.Uri | undefined { - let defaultUri: vscode.Uri | undefined; - if (fsPath) { - const workspaceFolder: vscode.WorkspaceFolder | undefined = vscode.workspace.getWorkspaceFolder( - vscode.Uri.file(fsPath) - ); - if (workspaceFolder) { - defaultUri = workspaceFolder.uri; - } - } - return defaultUri; + public async setDefaultLanguage(): Promise { + await setDefaultLanguage(); } public async searchProblem(): Promise { @@ -642,7 +625,7 @@ class TreeViewController implements Disposable { return; } - const language: string | undefined = await this.fetchProblemLanguage(); + const language: string | undefined = await fetchProblemLanguage(); if (!language) { return; } @@ -652,7 +635,7 @@ class TreeViewController implements Disposable { solutionService.show(unescapeJS(solution)); } catch (error) { logOutput.appendLine(error.toString()); - await promptForOpenOutputChannel("Failed to fetch the top voted solution. 请查看控制台信息~", DialogType.error); + await promptForOpenOutputChannel("Failed to fetch the top voted solution. 请查看控制台信息~", OutPutType.error); } } @@ -674,7 +657,7 @@ class TreeViewController implements Disposable { console.log(query_result); } catch (error) { logOutput.appendLine(error.toString()); - await promptForOpenOutputChannel("Failed to fetch today question. 请查看控制台信息~", DialogType.error); + await promptForOpenOutputChannel("Failed to fetch today question. 请查看控制台信息~", OutPutType.error); } } @@ -731,100 +714,16 @@ class TreeViewController implements Disposable { } } - public async fetchProblemLanguage(): Promise { - const leetCodeConfig: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration("leetcode-problem-rating"); - 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", - ignoreFocusOut: true, - })); - // fire-and-forget default language query - (async (): Promise => { - if (language && !defaultLanguage && leetCodeConfig.get("hint.setDefaultLanguage")) { - 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("hint.setDefaultLanguage", false, true /* UserSetting */); - } - } - })(); - return language; - } - - public async selectWorkspaceFolder(): Promise { - let workspaceFolderSetting: string = getWorkspaceFolder(); - if (workspaceFolderSetting.trim() === "") { - workspaceFolderSetting = await this.determineLeetCodeFolder(); - if (workspaceFolderSetting === "") { - // User cancelled - return workspaceFolderSetting; - } - } - let needAsk: boolean = true; - await fse.ensureDir(workspaceFolderSetting); - for (const folder of vscode.workspace.workspaceFolders || []) { - if (this.isSubFolder(folder.uri.fsPath, workspaceFolderSetting)) { - needAsk = false; - } - } - - if (needAsk) { - const choice: string | undefined = await vscode.window.showQuickPick( - [ - OpenOption.justOpenFile, - OpenOption.openInCurrentWindow, - OpenOption.openInNewWindow, - OpenOption.addToWorkspace, - ], - { - placeHolder: "The LeetCode workspace folder is not opened in VS Code, would you like to open it?", - } - ); - - // Todo: generate file first - switch (choice) { - case OpenOption.justOpenFile: - return workspaceFolderSetting; - case OpenOption.openInCurrentWindow: - await vscode.commands.executeCommand("vscode.openFolder", vscode.Uri.file(workspaceFolderSetting), false); - return ""; - case OpenOption.openInNewWindow: - await vscode.commands.executeCommand("vscode.openFolder", vscode.Uri.file(workspaceFolderSetting), true); - return ""; - case OpenOption.addToWorkspace: - vscode.workspace.updateWorkspaceFolders(vscode.workspace.workspaceFolders?.length ?? 0, 0, { - uri: vscode.Uri.file(workspaceFolderSetting), - }); - break; - default: - return ""; - } - } - - return systemUtils.useWsl() ? systemUtils.toWslPath(workspaceFolderSetting) : workspaceFolderSetting; - } - public async showProblemInternal(node: IProblem): Promise { try { - const language: string | undefined = await this.fetchProblemLanguage(); + const language: string | undefined = await fetchProblemLanguage(); if (!language) { return; } const leetCodeConfig: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration("leetcode-problem-rating"); - const workspaceFolder: string = await this.selectWorkspaceFolder(); + const workspaceFolder: string = await selectWorkspaceFolder(); if (!workspaceFolder) { return; } @@ -873,7 +772,7 @@ class TreeViewController implements Disposable { await Promise.all(promises); } catch (error) { - await promptForOpenOutputChannel(`${error} 请查看控制台信息~`, DialogType.error); + await promptForOpenOutputChannel(`${error} 请查看控制台信息~`, OutPutType.error); } } @@ -921,6 +820,7 @@ class TreeViewController implements Disposable { }); treeViewController.insertSearchSet(tt); await treeDataService.refresh(); + await bricksDataService.refresh(); } public async searchContest(): Promise { @@ -938,6 +838,7 @@ class TreeViewController implements Disposable { }); treeViewController.insertSearchSet(tt); await treeDataService.refresh(); + await bricksDataService.refresh(); } public async searchUserContest(): Promise { @@ -953,7 +854,7 @@ class TreeViewController implements Disposable { eventService.emit("searchUserContest", tt); } catch (error) { logOutput.appendLine(error.toString()); - await promptForOpenOutputChannel("Failed to fetch today question. 请查看控制台信息~", DialogType.error); + await promptForOpenOutputChannel("Failed to fetch today question. 请查看控制台信息~", OutPutType.error); } } public async searchToday(): Promise { @@ -977,10 +878,11 @@ class TreeViewController implements Disposable { }); treeViewController.insertSearchSet(tt); await treeDataService.refresh(); + await bricksDataService.refresh(); } } catch (error) { logOutput.appendLine(error.toString()); - await promptForOpenOutputChannel("Failed to fetch today question. 请查看控制台信息~", DialogType.error); + await promptForOpenOutputChannel("Failed to fetch today question. 请查看控制台信息~", OutPutType.error); } } @@ -1094,8 +996,8 @@ class TreeViewController implements Disposable { public checkSubmit(e: ISubmitEvent) { if (e.sub_type == "submit" && e.accepted) { - const day_start = new Date(new Date().setHours(0, 0, 0, 0)).getTime() / 1000; //获取当天零点的时间 - const day_end = new Date(new Date().setHours(0, 0, 0, 0) + 24 * 60 * 60 * 1000 - 1).getTime() / 1000; //获取当天23:59:59的时间 + const day_start = systemUtils.getDayStart(); //获取当天零点的时间 + const day_end = systemUtils.getDayEnd(); //获取当天23:59:59的时间 let need_get_today: boolean = false; this.searchSet.forEach((element) => { if (element.type == SearchSetType.Day) { @@ -1116,8 +1018,8 @@ class TreeViewController implements Disposable { if (!statusBarService.getUser()) { return; } - const day_start = new Date(new Date().setHours(0, 0, 0, 0)).getTime() / 1000; //获取当天零点的时间 - const day_end = new Date(new Date().setHours(0, 0, 0, 0) + 24 * 60 * 60 * 1000 - 1).getTime() / 1000; //获取当天23:59:59的时间 + const day_start = systemUtils.getDayStart(); //获取当天零点的时间 + const day_end = systemUtils.getDayEnd(); //获取当天23:59:59的时间 let need_get_today: boolean = true; this.searchSet.forEach((element) => { if (element.type == SearchSetType.Day) { @@ -1147,6 +1049,9 @@ class TreeViewController implements Disposable { let user_score = statusBarService.getUserContestScore(); for (const problem of await this.listProblems()) { this.explorerNodeMap.set(problem.id, new NodeModel(problem, true, user_score)); + this.fidToQid.set(problem.id, problem.qid.toString()); + this.qidToFid.set(problem.qid.toString(), problem.id); + for (const company of problem.companies) { this.companySet.add(company); } @@ -1157,6 +1062,7 @@ class TreeViewController implements Disposable { this.searchSet = temp_searchSet; this.waitTodayQuestion = temp_waitTodayQuestion; this.waitUserContest = temp_waitUserContest; + eventService.emit("explorerNodeMapSet"); } public getRootNodes(): NodeModel[] { @@ -1475,6 +1381,10 @@ class TreeViewController implements Disposable { return this.explorerNodeMap.get(id); } + public getNodeByQid(qid: string): NodeModel | undefined { + return this.getNodeById(this.qidToFid.get(qid) || ""); + } + public getFavoriteNodes(): NodeModel[] { const res: NodeModel[] = []; for (const node of this.explorerNodeMap.values()) { @@ -1548,6 +1458,8 @@ class TreeViewController implements Disposable { this.explorerNodeMap.clear(); this.companySet.clear(); this.tagSet.clear(); + this.fidToQid.clear(); + this.qidToFid.clear(); } private sortSubCategoryNodes(subCategoryNodes: NodeModel[], category: Category): void { diff --git a/src/dao/bricksDao.ts b/src/dao/bricksDao.ts new file mode 100644 index 0000000..4ed8503 --- /dev/null +++ b/src/dao/bricksDao.ts @@ -0,0 +1,160 @@ +// > workspace/ 工作目录 +// > +// > > .lcpr_data/ 存数据 +// > > +// > > > remake/ 备注 +// > > > +// > > > > 题目内部编号.json 根据 qid 备注的信息 +// > > + +import { fetchProblemLanguage, selectWorkspaceFolder } from "../utils/ConfigUtils"; +import { useWsl, toWinPath, getDayStart, getDayNow } from "../utils/SystemUtils"; +import * as path from "path"; +import * as fse from "fs-extra"; +import { BricksType } from "../model/Model"; + +// let bricks_json = { +// version: 1, +// all_bricks: { +// [qid]: { +// fid: "xxx", // 页面显示的编号可能有空格之类的 +// submit_time: [], // 上次提交的时间 +// type: 1, // 类型 +// }, +// }, +// }; + +class BricksDao { + version = 1; + public async bricks_data_path() { + const language: string | undefined = await fetchProblemLanguage(); + if (!language) { + return; + } + const workspaceFolder: string = await selectWorkspaceFolder(false); + if (!workspaceFolder) { + return; + } + let lcpr_data_path: string = path.join(workspaceFolder, ".lcpr_data"); + await fse.ensureDir(lcpr_data_path); + + let finalPath = path.join(lcpr_data_path, "bricks.json"); + finalPath = useWsl() ? await toWinPath(finalPath) : finalPath; + return finalPath; + } + public async init() { + let lcpr_data_path = await this.bricks_data_path(); + if (!lcpr_data_path) { + return; + } + if (!(await fse.pathExists(lcpr_data_path))) { + await fse.createFile(lcpr_data_path); + await fse.writeFile(lcpr_data_path, JSON.stringify({ version: this.version })); + } + } + + private async _write_data(data: object) { + let lcpr_data_path = await this.bricks_data_path(); + if (!lcpr_data_path) { + return; + } + return await fse.writeFile(lcpr_data_path, JSON.stringify(data)); + } + + private async _read_data() { + let lcpr_data_path = await this.bricks_data_path(); + if (!lcpr_data_path) { + return; + } + let temp_data = await fse.readFile(lcpr_data_path, "utf8"); + return JSON.parse(temp_data) || {}; + } + + public async getAllBricks() { + let allData = await this._read_data(); + return allData.all_bricks || {}; + } + + private getTimeByType(type: number, today_time: number) { + let need_day_ago = 7; + switch (type) { + case BricksType.TYPE_0: + return today_time - today_time; + break; + case BricksType.TYPE_1: + // 1:(14天搬砖simple) + need_day_ago = 14; + break; + case BricksType.TYPE_2: + // 2:(7天后搬砖simple_error) + need_day_ago = 7; + break; + case BricksType.TYPE_3: + // 3:(5天后搬砖simple_time) + need_day_ago = 5; + break; + case BricksType.TYPE_4: + // 4:(3天后搬砖(time_limit)) + need_day_ago = 3; + break; + case BricksType.TYPE_5: + // 5:(2天后搬砖(medium)) + need_day_ago = 2; + break; + case BricksType.TYPE_6: + // 6: (1天后搬砖(hard)) + need_day_ago = 1; + break; + default: + break; + } + return today_time - need_day_ago * 86400; + } + + public async getTodayBricks(): Promise { + let today_time = getDayStart(); + let all_bricks = await this.getAllBricks(); + let all_qid: Array = []; + for (const qid in all_bricks) { + const value = all_bricks[qid]; + const submit_time = value.submit_time || []; + const submit_size = submit_time.length; + if (value.type > BricksType.TYPE_0) { + if (submit_size < 1 || submit_time[submit_size - 1] < this.getTimeByType(value.type, today_time)) { + all_qid.push(qid); + } + } + } + return all_qid; + } + + public async getInfoByQid(qid: string) { + let all_bricks = await this.getAllBricks(); + return all_bricks[qid] || {}; + } + public async setInfoByQid(qid: string, info) { + let all_data = await this._read_data(); + let temp = all_data.all_bricks || {}; + temp[qid] = info; + all_data.all_bricks = temp; + await this._write_data(all_data); + } + + public async addSubmitTimeByQid(qid: string) { + let temp_data = await this.getInfoByQid(qid); + let submit_time = temp_data.submit_time || []; + submit_time.push(getDayNow()); + temp_data.submit_time = submit_time; + if (!temp_data.type) { + temp_data.type = BricksType.TYPE_2; + } + await this.setInfoByQid(qid, temp_data); + } + public async setTypeByQid(qid: string, type) { + let temp_data = await this.getInfoByQid(qid); + temp_data.type = type; + await this.setInfoByQid(qid, temp_data); + } +} + +export const bricksDao: BricksDao = new BricksDao(); diff --git a/src/extension.ts b/src/extension.ts index 7b11ec2..5de553b 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -24,9 +24,15 @@ import { markdownService } from "./service/MarkdownService"; import { mainContorller } from "./controller/MainController"; import { loginContorller } from "./controller/LoginController"; import { getLeetCodeEndpoint } from "./utils/ConfigUtils"; -import { DialogType } from "./model/Model"; +import { BricksType, OutPutType } from "./model/Model"; +import { bricksDataService } from "./service/BricksDataService"; +import { bricksViewController } from "./controller/BricksViewController"; // 激活插件 +/** + * The main function of the extension. It is called when the extension is activated. + * @param {ExtensionContext} context - ExtensionContext + */ export async function activate(context: ExtensionContext): Promise { try { // 初始化控制器 @@ -34,7 +40,7 @@ export async function activate(context: ExtensionContext): Promise { // 检查node环境 await mainContorller.checkNodeEnv(context); // 事件监听 - eventController.add_event(); + eventController.addEvent(); // 资源管理 context.subscriptions.push( @@ -48,47 +54,61 @@ export async function activate(context: ExtensionContext): Promise { fileButtonController, treeViewController, window.registerFileDecorationProvider(treeItemDecorationService), - window.createTreeView("leetCodeExplorer", { - treeDataProvider: treeDataService, - showCollapseAll: true, - }), - commands.registerCommand("leetcode.deleteCache", () => mainContorller.deleteCache()), - commands.registerCommand("leetcode.toggleLeetCodeCn", () => treeViewController.switchEndpoint()), - commands.registerCommand("leetcode.signin", () => loginContorller.signIn()), - commands.registerCommand("leetcode.signout", () => loginContorller.signOut()), - commands.registerCommand("leetcode.previewProblem", (node: NodeModel) => treeViewController.previewProblem(node)), - commands.registerCommand("leetcode.showProblem", (node: NodeModel) => treeViewController.showProblem(node)), - commands.registerCommand("leetcode.pickOne", () => treeViewController.pickOne()), - commands.registerCommand("leetcode.deleteAllCache", () => loginContorller.deleteAllCache()), + window.createTreeView("QuestionExplorer", { treeDataProvider: treeDataService, showCollapseAll: true }), + window.createTreeView("BricksExplorer", { treeDataProvider: bricksDataService, showCollapseAll: true }), + commands.registerCommand("lcpr.deleteCache", () => mainContorller.deleteCache()), + commands.registerCommand("lcpr.toggleLeetCodeCn", () => treeViewController.switchEndpoint()), + commands.registerCommand("lcpr.signin", () => loginContorller.signIn()), + commands.registerCommand("lcpr.signout", () => loginContorller.signOut()), + commands.registerCommand("lcpr.previewProblem", (node: NodeModel) => treeViewController.previewProblem(node)), + commands.registerCommand("lcpr.showProblem", (node: NodeModel) => treeViewController.showProblem(node)), + commands.registerCommand("lcpr.pickOne", () => treeViewController.pickOne()), + commands.registerCommand("lcpr.deleteAllCache", () => loginContorller.deleteAllCache()), commands.registerCommand("leetcode.searchScoreRange", () => treeViewController.searchScoreRange()), - commands.registerCommand("leetcode.searchProblem", () => treeViewController.searchProblem()), - commands.registerCommand("leetcode.showSolution", (input: NodeModel | Uri) => - treeViewController.showSolution(input) + commands.registerCommand("lcpr.searchProblem", () => treeViewController.searchProblem()), + commands.registerCommand("lcpr.showSolution", (input: NodeModel | Uri) => treeViewController.showSolution(input)), + commands.registerCommand("lcpr.refreshExplorer", () => treeDataService.refresh()), + commands.registerCommand("lcpr.testSolution", (uri?: Uri) => treeViewController.testSolution(uri)), + commands.registerCommand("lcpr.testCaseDef", (uri?, allCase?) => treeViewController.testCaseDef(uri, allCase)), + commands.registerCommand("lcpr.tesCaseArea", (uri, testCase?) => treeViewController.tesCaseArea(uri, testCase)), + commands.registerCommand("lcpr.submitSolution", (uri?: Uri) => treeViewController.submitSolution(uri)), + commands.registerCommand("lcpr.setDefaultLanguage", () => treeViewController.setDefaultLanguage()), + commands.registerCommand("lcpr.addFavorite", (node: NodeModel) => treeViewController.addFavorite(node)), + commands.registerCommand("lcpr.removeFavorite", (node: NodeModel) => treeViewController.removeFavorite(node)), + commands.registerCommand("lcpr.problems.sort", () => treeViewController.switchSortingStrategy()), + commands.registerCommand("lcpr.setBricksType0", (node: NodeModel) => + bricksViewController.setBricksType(node, BricksType.TYPE_0) + ), + commands.registerCommand("lcpr.setBricksType1", (node: NodeModel) => + bricksViewController.setBricksType(node, BricksType.TYPE_1) + ), + commands.registerCommand("lcpr.setBricksType2", (node: NodeModel) => + bricksViewController.setBricksType(node, BricksType.TYPE_2) + ), + commands.registerCommand("lcpr.setBricksType3", (node: NodeModel) => + bricksViewController.setBricksType(node, BricksType.TYPE_3) ), - commands.registerCommand("leetcode.refreshExplorer", () => treeDataService.refresh()), - commands.registerCommand("leetcode.testSolution", (uri?: Uri) => treeViewController.testSolution(uri)), - commands.registerCommand("leetcode.testSolutionDefault", (uri?: Uri, allCase?: boolean) => - treeViewController.testSolutionDefault(uri, allCase) + commands.registerCommand("lcpr.setBricksType4", (node: NodeModel) => + bricksViewController.setBricksType(node, BricksType.TYPE_4) ), - commands.registerCommand("leetcode.testSolutionArea", (uri?: Uri, testCase?: string) => - treeViewController.testSolutionArea(uri, testCase) + commands.registerCommand("lcpr.setBricksType5", (node: NodeModel) => + bricksViewController.setBricksType(node, BricksType.TYPE_5) ), - commands.registerCommand("leetcode.submitSolution", (uri?: Uri) => treeViewController.submitSolution(uri)), - commands.registerCommand("leetcode.switchDefaultLanguage", () => treeViewController.switchDefaultLanguage()), - commands.registerCommand("leetcode.addFavorite", (node: NodeModel) => treeViewController.addFavorite(node)), - commands.registerCommand("leetcode.removeFavorite", (node: NodeModel) => treeViewController.removeFavorite(node)), - commands.registerCommand("leetcode.problems.sort", () => treeViewController.switchSortingStrategy()) + commands.registerCommand("lcpr.setBricksType6", (node: NodeModel) => + bricksViewController.setBricksType(node, BricksType.TYPE_6) + ) ); // 设置站点 await executeService.switchEndpoint(getLeetCodeEndpoint()); // 获取登录状态 await loginContorller.getLoginStatus(); + await bricksViewController.initialize(); } catch (error) { logOutput.appendLine(error.toString()); promptForOpenOutputChannel( "Extension initialization failed. Please open output channel for details.", - DialogType.error + OutPutType.error ); } } diff --git a/src/model/Model.ts b/src/model/Model.ts index 6e775df..f549fb3 100644 --- a/src/model/Model.ts +++ b/src/model/Model.ts @@ -25,7 +25,7 @@ export const loginArgsMapping: Map = new Map([ ["LinkedIn", "-i"], ]); -export const languages: string[] = [ +export const AllProgramLanguage: string[] = [ "bash", "c", "cpp", @@ -140,6 +140,26 @@ export const defaultProblem: IProblem = { todayData: undefined, }; +export enum BricksNormalId { + Have = "bricksHave", // 有活 + HaveDesc = "别吹牛了,工头让我叫你快去搬砖了", + No = "bricksNo", // 没活 + NoDesc = "工头让你去上面那个工地,过几天再回来", + Today = "bricksToday", + TodayDesc = "今天搬好的砖", +} + +export enum BricksType { + TYPE_0 = 0, + TYPE_1 = 1, + TYPE_2 = 2, + TYPE_3 = 3, + TYPE_4 = 4, + TYPE_5 = 5, + TYPE_6 = 6, + TYPE_7 = 7, +} + export enum Category { All = "All", Difficulty = "Difficulty", @@ -252,7 +272,7 @@ export enum OpenOption { addToWorkspace = "添加到工作空间", } -export enum DialogType { +export enum OutPutType { info = "info", warning = "warning", error = "error", diff --git a/src/model/NodeModel.ts b/src/model/NodeModel.ts index b1b2448..8f122fc 100644 --- a/src/model/NodeModel.ts +++ b/src/model/NodeModel.ts @@ -12,7 +12,7 @@ import { IProblem, IScoreData, ITodayData, ProblemState, RootNodeSort } from "./ export class NodeModel { private _u_score; - constructor(private data: IProblem, private isProblemNode: boolean = true, userscore: number = 0) { + constructor(public data: IProblem, public isProblemNode: boolean = true, userscore: number = 0) { this._u_score = userscore; } @@ -71,7 +71,7 @@ export class NodeModel { public get previewCommand(): Command { return { title: "Preview Problem", - command: "leetcode.previewProblem", + command: "lcpr.previewProblem", arguments: [this], }; } @@ -139,3 +139,12 @@ export class NodeModel { return this.data.qid || ""; } } + +export class BricksNode extends NodeModel { + public collapsibleState?; + constructor(data: IProblem, ipn: boolean = true, userscore: number = 0, collapsibleState = 0) { + super(data, ipn, userscore); + this.isProblemNode = ipn; + this.collapsibleState = collapsibleState; + } +} diff --git a/src/rpc/actionChain/chainManager.ts b/src/rpc/actionChain/chainManager.ts index 2c0c833..53f2366 100644 --- a/src/rpc/actionChain/chainManager.ts +++ b/src/rpc/actionChain/chainManager.ts @@ -13,6 +13,7 @@ import { storageUtils } from "../utils/storageUtils"; import { commUtils } from "../utils/commUtils"; import { ChainNodeBase } from "./chainNodeBase"; +/* It's a class that manages a chain of plugins */ export class ChainManager { id; name; @@ -25,10 +26,19 @@ export class ChainManager { constructor() {} + /** + * Return the head of the chain. + * @returns The head of the chain. + */ public getChainHead(): ChainNodeBase { return this.head; } + /** + * It loads all the plugins in the directory and initializes them. + * @param {ChainNodeBase | undefined} head - The first node in the chain of responsibility. + * @returns The return value is a boolean. + */ public init(head: ChainNodeBase | undefined): Object | undefined { if (head) { this.head = head; @@ -72,6 +82,9 @@ export class ChainManager { return true; } + /** + * For each plugin in the plugins array, call the save function. + */ public save_all(): void { for (let p of this.plugins) { p.save(); diff --git a/src/rpc/actionChain/chainNode/cache.ts b/src/rpc/actionChain/chainNode/cache.ts index 05c40e0..30c0aba 100644 --- a/src/rpc/actionChain/chainNode/cache.ts +++ b/src/rpc/actionChain/chainNode/cache.ts @@ -15,6 +15,7 @@ import { storageUtils } from "../../utils/storageUtils"; import { commUtils } from "../../utils/commUtils"; import { sessionUtils } from "../../utils/sessionUtils"; +/* It's a plugin that caches the data it gets from the next plugin in the chain */ class CachePlugin extends ChainNodeBase { id = 50; name = "cache"; @@ -23,6 +24,7 @@ class CachePlugin extends ChainNodeBase { super(); } + /* Checking if the translation config has changed. If it has, it clears the cache. */ clearCacheIfTchanged = (needTranslation) => { const translationConfig = storageUtils.getCache(commUtils.KEYS.translation); if (!translationConfig || translationConfig["useEndpointTranslation"] != needTranslation) { @@ -33,6 +35,8 @@ class CachePlugin extends ChainNodeBase { } }; + /* A method that is used to get problems from the cache. If the cache is empty, it will get the +problems from the next layer. */ public getProblems = (needTranslation, cb) => { this.clearCacheIfTchanged(needTranslation); const problems = storageUtils.getCache(commUtils.KEYS.problems); @@ -46,9 +50,9 @@ class CachePlugin extends ChainNodeBase { }); }; - /** - * getRatingOnline - */ + /* A method that is used to get problems from the cache. If the cache is empty, it will get the +problems from the next layer. */ + public getRatingOnline = (cb) => { const cacheRantingData = storageUtils.getCache(commUtils.KEYS.ranting_path); if (cacheRantingData) { @@ -67,6 +71,7 @@ class CachePlugin extends ChainNodeBase { }); }; + /* A cache layer for the getProblem function. */ public getProblem = (problem, needTranslation, cb) => { this.clearCacheIfTchanged(needTranslation); const k = commUtils.KEYS.problem(problem); @@ -95,6 +100,7 @@ class CachePlugin extends ChainNodeBase { return storageUtils.setCache(commUtils.KEYS.problem(problem), _problem); }; + /* Updating the problem in the cache. */ updateProblem = (problem, kv) => { const problems = storageUtils.getCache(commUtils.KEYS.problems); if (!problems) return false; @@ -106,6 +112,7 @@ class CachePlugin extends ChainNodeBase { return storageUtils.setCache(commUtils.KEYS.problems, problems); }; + /* Logging out the user and then logging in the user. */ login = (user, cb) => { this.logout(user, false); this.next.login(user, function (e, user) { @@ -115,10 +122,10 @@ class CachePlugin extends ChainNodeBase { }); }; + /* Logging out the user and then logging in the user. */ logout = (user, purge) => { if (!user) user = sessionUtils.getUser(); if (purge) sessionUtils.deleteUser(); - // NOTE: need invalidate any user related cache sessionUtils.deleteCodingSession(); return user; }; diff --git a/src/rpc/actionChain/chainNode/core.ts b/src/rpc/actionChain/chainNode/core.ts index 1767685..60d7ae3 100644 --- a/src/rpc/actionChain/chainNode/core.ts +++ b/src/rpc/actionChain/chainNode/core.ts @@ -21,6 +21,8 @@ function hasTag(o, tag) { return Array.isArray(o) && o.some((x) => x.indexOf(tag.toLowerCase()) >= 0); } +/* It's a class that extends the ChainNodeBase class, and it has a bunch of methods that are called by +the LeetCode CLI */ class CorePlugin extends ChainNodeBase { id = 99999999; name = "core"; @@ -29,6 +31,7 @@ class CorePlugin extends ChainNodeBase { super(); } + /* It's a method that filters the problems. */ filterProblems = (opts, cb) => { this.getProblems(!opts.dontTranslate, function (e, problems) { if (e) return cb(e); @@ -48,6 +51,7 @@ class CorePlugin extends ChainNodeBase { return cb(null, problems); }); }; + /* It's a method that gets the problem. */ public getProblem = (keyword, needTranslation, cb) => { let that = this; this.getProblems(needTranslation, function (e, problems) { @@ -68,6 +72,7 @@ class CorePlugin extends ChainNodeBase { }); }; + /* It's a method that stars the problem. */ starProblem = (problem, starred, cb) => { if (problem.starred === starred) { return cb(null, starred); @@ -76,6 +81,7 @@ class CorePlugin extends ChainNodeBase { this.next.starProblem(problem, starred, cb); }; + /* It's a method that exports the problem. */ exportProblem = (problem, opts) => { const data = _.extend({}, problem); @@ -140,6 +146,7 @@ const isACed = (x) => x.state === "ac"; const isLocked = (x) => x.locked; const isStarred = (x) => x.starred; +/* It's a dictionary that maps the query to the function that filters the problems. */ const QUERY_HANDLERS = { e: isLevel, E: _.negate(isLevel), diff --git a/src/rpc/actionChain/chainNode/leetcode.cn.ts b/src/rpc/actionChain/chainNode/leetcode.cn.ts index 958cd0e..bb4df30 100644 --- a/src/rpc/actionChain/chainNode/leetcode.cn.ts +++ b/src/rpc/actionChain/chainNode/leetcode.cn.ts @@ -49,6 +49,7 @@ class LeetCodeCn extends ChainNodeBase { }); }; + /* Getting the title of the problems. */ getProblemsTitle = (cb) => { const opts = makeOpts(configUtils.sys.urls.graphql); opts.headers.Origin = configUtils.sys.urls.base; @@ -82,6 +83,7 @@ class LeetCodeCn extends ChainNodeBase { }); }; + /* A function that gets the question of the day from leetcode. */ getQuestionOfToday = (cb) => { const opts = makeOpts(configUtils.sys.urls.graphql); opts.headers.Origin = configUtils.sys.urls.base; @@ -129,6 +131,7 @@ class LeetCodeCn extends ChainNodeBase { return cb(null, result); }); }; + /* A function that is used to get the user contest ranking information. */ getUserContestP = (username, cb) => { const opts = makeOpts(configUtils.sys.urls.noj_go); opts.headers.Origin = configUtils.sys.urls.base; @@ -176,6 +179,7 @@ class LeetCodeCn extends ChainNodeBase { }); }; + /* A function that is used to get the rating of the problems. */ getRatingOnline = (cb) => { const _request = request.defaults({ jar: true }); _request("https://zerotrac.github.io/leetcode_problem_rating/data.json", function (error: any, _, body: any) { @@ -185,6 +189,7 @@ class LeetCodeCn extends ChainNodeBase { }); }; + /* A function that is used to test the api. */ // eslint-disable-next-line @typescript-eslint/no-unused-vars getTestApi = (value: any, _) => { const _request = request.defaults({ jar: true }); diff --git a/src/rpc/actionChain/chainNode/leetcode.ts b/src/rpc/actionChain/chainNode/leetcode.ts index c5e4084..8eb4508 100644 --- a/src/rpc/actionChain/chainNode/leetcode.ts +++ b/src/rpc/actionChain/chainNode/leetcode.ts @@ -81,6 +81,7 @@ class LeetCode extends ChainNodeBase { }); }; + /* Getting the problems from the category. */ getCategoryProblems = (category, cb) => { const opts = this.makeOpts(configUtils.sys.urls.problems.replace("$category", category)); @@ -117,6 +118,8 @@ class LeetCode extends ChainNodeBase { }); }; + /* A function that takes in a problem and a callback function. It then makes a request to the leetcode +server to get the problem's description, test cases, and other information. */ getProblem = (problem, needTranslation, cb) => { const user = sessionUtils.getUser(); if (problem.locked && !user.paid) return cb("failed to load locked problem!"); @@ -171,6 +174,7 @@ class LeetCode extends ChainNodeBase { return cb(null, problem); }); }; + /* A function that is used to run the code on the server. */ runCode = (opts, problem, cb) => { opts.method = "POST"; opts.headers.Origin = configUtils.sys.urls.base; @@ -207,6 +211,7 @@ class LeetCode extends ChainNodeBase { }); }; + /* A function that is used to verify the result of a task. */ verifyResult = (task, queue, cb) => { const opts = queue.ctx.opts; opts.method = "GET"; @@ -229,6 +234,7 @@ class LeetCode extends ChainNodeBase { }); }; + /* Formatting the result of the submission. */ formatResult = (result) => { const x: any = { ok: result.run_success, @@ -274,6 +280,7 @@ class LeetCode extends ChainNodeBase { return x; }; + /* Testing the code. */ testProblem = (problem, cb) => { const opts = this.makeOpts(configUtils.sys.urls.test.replace("$slug", problem.slug)); opts.body = { data_input: problem.testcase }; @@ -294,6 +301,7 @@ class LeetCode extends ChainNodeBase { }); }; + /* Submitting a problem to the server. */ submitProblem = (problem, cb) => { const opts = this.makeOpts(configUtils.sys.urls.submit.replace("$slug", problem.slug)); opts.body = { judge_type: "large" }; @@ -309,6 +317,7 @@ class LeetCode extends ChainNodeBase { }); }; + /* Getting the submissions for a problem. */ getSubmissions = (problem, cb) => { const opts = this.makeOpts(configUtils.sys.urls.submissions.replace("$slug", problem.slug)); opts.headers.Referer = configUtils.sys.urls.problem.replace("$slug", problem.slug); @@ -326,6 +335,7 @@ class LeetCode extends ChainNodeBase { }); }; + /* Getting the submission code and the runtime distribution chart. */ getSubmission = (submission, cb) => { const opts = this.makeOpts(configUtils.sys.urls.submission.replace("$id", submission.id)); let that = this; @@ -342,6 +352,7 @@ class LeetCode extends ChainNodeBase { }); }; + /* A function that is used to star a problem. */ starProblem = (problem, starred, cb) => { const user = sessionUtils.getUser(); const operationName = starred ? "addQuestionToFavorite" : "removeQuestionFromFavorite"; @@ -365,6 +376,7 @@ class LeetCode extends ChainNodeBase { }); }; + /* Making a request to the server to get the favorites. */ getFavorites = (cb: any) => { const opts = this.makeOpts(configUtils.sys.urls.favorites); @@ -378,6 +390,7 @@ class LeetCode extends ChainNodeBase { }); }; + /* Making a POST request to the GraphQL API. */ getUserInfo = (cb: any) => { let that = this; const opts = this.makeOpts(configUtils.sys.urls.graphql); @@ -398,6 +411,7 @@ class LeetCode extends ChainNodeBase { }); }; + /* Making a request to the server and returning the response. */ runSession = (method: any, data: any, cb: any) => { const opts = this.makeOpts(configUtils.sys.urls.session); opts.json = true; @@ -432,6 +446,10 @@ class LeetCode extends ChainNodeBase { this.runSession("DELETE", data, cb); }; + /* A function that takes in a user object and a callback function. It then makes a request to the login +page and gets the csrf token. It then makes a post request to the login page with the csrf token and +the user's login and password. If the response status code is 302, it saves the user's session id +and csrf token to the user object and saves the user object to the session. */ signin = (user: any, cb: any) => { let that = this; // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -465,6 +483,7 @@ class LeetCode extends ChainNodeBase { }); }; + /* Retrieving the user's favorites and user info. */ getUser = (user, cb) => { let that = this; this.getFavorites(function (e, favorites) { @@ -499,6 +518,7 @@ class LeetCode extends ChainNodeBase { }); }; + /* Parsing the cookie to get the sessionId and sessionCSRF. */ parseCookie = (cookie, cb) => { const SessionPattern = /LEETCODE_SESSION=(.+?)(;|$)/; const csrfPattern = /csrftoken=(.+?)(;|$)/; @@ -512,6 +532,7 @@ class LeetCode extends ChainNodeBase { sessionCSRF: reCsrfResult[1], }; }; + /* A function that is used to login to leetcode. */ requestLeetcodeAndSave = (request, leetcodeUrl, user, cb) => { let that = this; @@ -537,6 +558,7 @@ class LeetCode extends ChainNodeBase { this.getUser(user, cb); }; + /* A function that is used to login to GitHub. */ githubLogin = (user, cb) => { const urls = configUtils.sys.urls; const leetcodeUrl = urls.github_login; @@ -626,6 +648,7 @@ class LeetCode extends ChainNodeBase { }); }; + /* A function that logs into LinkedIn and then logs into LeetCode. */ linkedinLogin = (user, cb) => { const urls = configUtils.sys.urls; const leetcodeUrl = urls.linkedin_login; diff --git a/src/rpc/actionChain/chainNode/retry.ts b/src/rpc/actionChain/chainNode/retry.ts index 26ae28c..226746c 100644 --- a/src/rpc/actionChain/chainNode/retry.ts +++ b/src/rpc/actionChain/chainNode/retry.ts @@ -24,6 +24,7 @@ class RetryPlugin extends ChainNodeBase { (this.count[name] || 0) < configUtils.autologin.retry ); }; + /* A wrapper for the API. */ init = () => { const names = [ "activateSession", @@ -67,6 +68,8 @@ class RetryPlugin extends ChainNodeBase { } }; + /* The above code is checking if the user is logged in. If the user is logged in, it will call the next +login function. */ // leetcode.com is limiting one session alive in the same time, // which means once you login on web, your cli session will get // expired immediately. In that case we will try to re-login in diff --git a/src/rpc/actionChain/chainNode/solution.discuss.ts b/src/rpc/actionChain/chainNode/solution.discuss.ts index 35afd37..9745a48 100644 --- a/src/rpc/actionChain/chainNode/solution.discuss.ts +++ b/src/rpc/actionChain/chainNode/solution.discuss.ts @@ -53,6 +53,14 @@ class SolutionDiscuss extends ChainNodeBase { let URL_DISCUSSES = "https://leetcode.com/graphql"; let URL_DISCUSS = "https://leetcode.com/problems/$slug/discuss/$id"; +/** + * It takes a problem object, a language, and a callback. It then makes a request to the LeetCode + * Discuss API to get the top voted solution for that problem in that language + * @param problem - the problem object + * @param lang - The language of the solution. + * @param cb - callback function + * @returns A solution to the problem. + */ function getSolution(problem, lang, cb) { if (!problem) return cb(); diff --git a/src/rpc/actionChain/chainNodeBase.ts b/src/rpc/actionChain/chainNodeBase.ts index 3147fdb..30e8640 100644 --- a/src/rpc/actionChain/chainNodeBase.ts +++ b/src/rpc/actionChain/chainNodeBase.ts @@ -1,6 +1,7 @@ import { commUtils } from "../utils/commUtils"; import { storageUtils } from "../utils/storageUtils"; +/* It's a chain of responsibility pattern */ export class ChainNodeBase { next: ChainNodeBase; // 下一个点 enabled: boolean; diff --git a/src/service/BricksDataService.ts b/src/service/BricksDataService.ts new file mode 100644 index 0000000..0524089 --- /dev/null +++ b/src/service/BricksDataService.ts @@ -0,0 +1,129 @@ +/* + * Filename: /home/cc/vscode-leetcode-problem-rating/src/service/BricksDataService.ts + * Path: /home/cc/vscode-leetcode-problem-rating + * Created Date: Tuesday, November 22nd 2022, 10:42:49 am + * Author: ccagml + * + * Copyright (c) 2022 ccagml . All rights reserved. + */ + +import { TreeDataProvider, EventEmitter, Event, TreeItem, TreeItemCollapsibleState } from "vscode"; +import { BricksNormalId, defaultProblem, ISubmitEvent } from "../model/Model"; +import { bricksViewController } from "../controller/BricksViewController"; +import { BricksNode } from "../model/NodeModel"; +import { statusBarService } from "./StatusBarService"; +import { bricksDao } from "../dao/bricksDao"; + +export class BricksDataService implements TreeDataProvider { + private onDidChangeTreeDataEvent: EventEmitter = new EventEmitter< + BricksNode | undefined | null + >(); + // tslint:disable-next-line:member-ordering + public readonly onDidChangeTreeData: Event = this.onDidChangeTreeDataEvent.event; + + public async refresh(): Promise { + this.onDidChangeTreeDataEvent.fire(null); + } + + public async initialize() { + await bricksDao.init(); + } + + // 节点的内容 + public getTreeItem(element: BricksNode): TreeItem | Thenable { + if (element.id === "notSignIn") { + return { + label: element.name, + collapsibleState: element.collapsibleState, // 没有子节点 + command: { + command: "lcpr.signin", + title: "工头说你不是我们工地的人", + }, + }; + } + let contextValue: string; + if (element.isProblem) { + contextValue = "bricks"; + } else { + contextValue = element.id.toLowerCase(); + } + + const result: TreeItem | Thenable = { + label: element.isProblem + ? (element.score > "0" ? "[score:" + element.score + "]" : "") + `ID:${element.id}.${element.name} ` + : element.name, + tooltip: this.getSubCategoryTooltip(element), + collapsibleState: element.collapsibleState || TreeItemCollapsibleState.None, + // ? vscode.TreeItemCollapsibleState.None // 问题没有子节点 + // : vscode.TreeItemCollapsibleState.Collapsed, // 折叠 + iconPath: this.parseIconPathFromProblemState(element), + command: element.isProblem ? element.previewCommand : undefined, + resourceUri: element.uri, + contextValue, + }; + return result; + } + + // 获取子节点信息 + public async getChildren(element?: BricksNode | undefined): Promise { + if (!statusBarService.getUser()) { + return [ + new BricksNode( + Object.assign({}, defaultProblem, { + id: "notSignIn", + name: "工头说你不是我们工地的人", + }), + false, + 0, + TreeItemCollapsibleState.None + ), + ]; + } + if (!element) { + return await bricksViewController.getRootNodes(); + } else { + switch (element.id) { + case BricksNormalId.Today: + return await bricksViewController.getTodayNodes(); + break; + case BricksNormalId.Have: + return await bricksViewController.getHaveNodes(); + break; + default: + return []; + break; + } + } + } + + public async checkSubmit(e: ISubmitEvent) { + if (e.sub_type == "submit" && e.accepted) { + let qid: string = e.qid.toString(); + bricksDao.addSubmitTimeByQid(qid); + await bricksDataService.refresh(); + } + } + + public async setBricksType(node: BricksNode, type) { + let qid: string = node.qid.toString(); + bricksDao.setTypeByQid(qid, type); + await bricksDataService.refresh(); + } + + private parseIconPathFromProblemState(element: BricksNode): string { + switch (element.state) { + default: + return ""; + } + } + + private getSubCategoryTooltip(element: BricksNode): string { + // return '' unless it is a sub-category node + if (element.id === "ROOT") { + return ""; + } + return ""; + } +} + +export const bricksDataService: BricksDataService = new BricksDataService(); diff --git a/src/service/EventService.ts b/src/service/EventService.ts index 03f096e..7ced495 100644 --- a/src/service/EventService.ts +++ b/src/service/EventService.ts @@ -13,6 +13,7 @@ import { UserStatus } from "../model/Model"; import { ISubmitEvent } from "../model/Model"; import { statusBarService } from "../service/StatusBarService"; import { treeDataService } from "../service/TreeDataService"; +import { bricksDataService } from "./BricksDataService"; class EventService extends EventEmitter { constructor() { @@ -22,21 +23,28 @@ class EventService extends EventEmitter { /** * 监听事件 */ - public add_event() { + public addEvent() { this.on("statusChanged", (userStatus: UserStatus, userName?: string) => { statusBarService.update_status(userStatus, userName); statusBarService.update(); treeDataService.cleanUserScore(); treeDataService.refresh(); + bricksDataService.refresh(); }); this.on("submit", (e: ISubmitEvent) => { treeDataService.checkSubmit(e); + bricksDataService.checkSubmit(e); }); this.on("searchUserContest", (tt) => { statusBarService.update_UserContestInfo(tt); statusBarService.update(); treeDataService.refresh(); + bricksDataService.refresh(); + }); + + this.on("explorerNodeMapSet", () => { + bricksDataService.refresh(); }); } } diff --git a/src/service/ExecuteService.ts b/src/service/ExecuteService.ts index 5becaba..be0ae26 100644 --- a/src/service/ExecuteService.ts +++ b/src/service/ExecuteService.ts @@ -13,8 +13,8 @@ import * as os from "os"; import * as path from "path"; import { ExtensionContext } from "vscode"; import { ConfigurationChangeEvent, Disposable, MessageItem, window, workspace } from "vscode"; -import { DialogOptions, DialogType, Endpoint, IProblem, leetcodeHasInited } from "../model/Model"; -import { executeCommand, executeCommandWithProgress } from "../utils/CliUtils"; +import { DialogOptions, OutPutType, Endpoint, IProblem, leetcodeHasInited } from "../model/Model"; +import { executeCommandWithProgress } from "../utils/CliUtils"; import { getNodePath } from "../utils/ConfigUtils"; import { openUrl, promptForOpenOutputChannel } from "../utils/OutputUtils"; import * as systemUtils from "../utils/SystemUtils"; @@ -67,7 +67,7 @@ class ExecuteService implements Disposable { } } try { - await this.executeCommandEx(this.nodeExecutable, ["-v"]); + await this.executeCommandWithProgressEx("正在检查Node环境~", this.nodeExecutable, ["-v"]); } catch (error) { const choice: MessageItem | undefined = await window.showErrorMessage( "LeetCode extension needs Node.js installed in environment path", @@ -84,18 +84,29 @@ class ExecuteService implements Disposable { public async deleteCache() { try { - await this.executeCommandEx(this.nodeExecutable, [await this.getLeetCodeBinaryPath(), "cache", "-d"]); + await this.executeCommandWithProgressEx("正在清除缓存~", this.nodeExecutable, [ + await this.getLeetCodeBinaryPath(), + "cache", + "-d", + ]); } catch (error) { - await promptForOpenOutputChannel("Failed to delete cache. 请查看控制台信息~", DialogType.error); + await promptForOpenOutputChannel("Failed to delete cache. 请查看控制台信息~", OutPutType.error); } } public async getUserInfo(): Promise { - return await this.executeCommandEx(this.nodeExecutable, [await this.getLeetCodeBinaryPath(), "user"]); + return await this.executeCommandWithProgressEx("正在获取角色信息~", this.nodeExecutable, [ + await this.getLeetCodeBinaryPath(), + "user", + ]); } public async signOut(): Promise { - return await this.executeCommandEx(this.nodeExecutable, [await this.getLeetCodeBinaryPath(), "user", "-L"]); + return await this.executeCommandWithProgressEx("正在登出~", this.nodeExecutable, [ + await this.getLeetCodeBinaryPath(), + "user", + "-L", + ]); } public async listProblems(showLocked: boolean, needTranslation: boolean): Promise { @@ -107,7 +118,7 @@ class ExecuteService implements Disposable { cmd.push("-q"); cmd.push("L"); } - return await this.executeCommandEx(this.nodeExecutable, cmd); + return await this.executeCommandWithProgressEx("正在获取题目数据~", this.nodeExecutable, cmd); } public async showProblem( @@ -127,7 +138,7 @@ class ExecuteService implements Disposable { if (!(await fse.pathExists(filePath))) { await fse.createFile(filePath); const codeTemplate: string = await this.executeCommandWithProgressEx( - "Fetching problem data...", + "正在获取题目数据~", this.nodeExecutable, cmd ); @@ -141,11 +152,7 @@ class ExecuteService implements Disposable { if (!needTranslation) { cmd.push("-T"); } - const solution: string = await this.executeCommandWithProgressEx( - "Fetching top voted solution from discussions...", - this.nodeExecutable, - cmd - ); + const solution: string = await this.executeCommandWithProgressEx("正在获取题解~~~", this.nodeExecutable, cmd); return solution; } @@ -155,22 +162,14 @@ class ExecuteService implements Disposable { if (!needTranslation) { cmd.push("-T"); } - const solution: string = await this.executeCommandWithProgressEx( - "Fetching UserContest...", - this.nodeExecutable, - cmd - ); + const solution: string = await this.executeCommandWithProgressEx("正在获取竞赛分信息~", this.nodeExecutable, cmd); return solution; } public async getScoreDataOnline(): Promise { // solution don't support translation const cmd: string[] = [await this.getLeetCodeBinaryPath(), "query", "-c"]; - const solution: string = await this.executeCommandWithProgressEx( - "get data from https://zerotrac.github.io/leetcode_problem_rating/data.json", - this.nodeExecutable, - cmd - ); + const solution: string = await this.executeCommandWithProgressEx("正在获取分数数据~", this.nodeExecutable, cmd); return solution; } @@ -187,11 +186,7 @@ class ExecuteService implements Disposable { if (!needTranslation) { cmd.push("-T"); } - const solution: string = await this.executeCommandWithProgressEx( - "Fetching today question...", - this.nodeExecutable, - cmd - ); + const solution: string = await this.executeCommandWithProgressEx("正在获取每日一题~", this.nodeExecutable, cmd); return solution; } @@ -200,19 +195,19 @@ class ExecuteService implements Disposable { if (!needTranslation) { cmd.push("-T"); } - return await this.executeCommandWithProgressEx("Fetching problem description...", this.nodeExecutable, cmd); + return await this.executeCommandWithProgressEx("正在获取题目详情~", this.nodeExecutable, cmd); } public async submitSolution(filePath: string): Promise { try { if (systemUtils.useVscodeNode()) { - return await this.executeCommandWithProgressEx("Submitting to LeetCode...", this.nodeExecutable, [ + return await this.executeCommandWithProgressEx("正在提交代码~", this.nodeExecutable, [ await this.getLeetCodeBinaryPath(), "submit", `${filePath}`, ]); } - return await this.executeCommandWithProgressEx("Submitting to LeetCode...", this.nodeExecutable, [ + return await this.executeCommandWithProgressEx("正在提交代码~", this.nodeExecutable, [ await this.getLeetCodeBinaryPath(), "submit", `"${filePath}"`, @@ -228,7 +223,7 @@ class ExecuteService implements Disposable { public async testSolution(filePath: string, testString?: string, allCase?: boolean): Promise { if (testString) { if (systemUtils.useVscodeNode()) { - return await this.executeCommandWithProgressEx("Submitting to LeetCode...", this.nodeExecutable, [ + return await this.executeCommandWithProgressEx("正在提交代码~", this.nodeExecutable, [ await this.getLeetCodeBinaryPath(), "test", `${filePath}`, @@ -236,7 +231,7 @@ class ExecuteService implements Disposable { `${testString}`, ]); } - return await this.executeCommandWithProgressEx("Submitting to LeetCode...", this.nodeExecutable, [ + return await this.executeCommandWithProgressEx("正在提交代码~", this.nodeExecutable, [ await this.getLeetCodeBinaryPath(), "test", `"${filePath}"`, @@ -246,14 +241,14 @@ class ExecuteService implements Disposable { } if (allCase) { if (systemUtils.useVscodeNode()) { - return await this.executeCommandWithProgressEx("Submitting to LeetCode...", this.nodeExecutable, [ + return await this.executeCommandWithProgressEx("正在提交代码~", this.nodeExecutable, [ await this.getLeetCodeBinaryPath(), "test", `${filePath}`, "-a", ]); } - return await this.executeCommandWithProgressEx("Submitting to LeetCode...", this.nodeExecutable, [ + return await this.executeCommandWithProgressEx("正在提交代码~", this.nodeExecutable, [ await this.getLeetCodeBinaryPath(), "test", `"${filePath}"`, @@ -261,13 +256,13 @@ class ExecuteService implements Disposable { ]); } if (systemUtils.useVscodeNode()) { - return await this.executeCommandWithProgressEx("Submitting to LeetCode...", this.nodeExecutable, [ + return await this.executeCommandWithProgressEx("正在提交代码~", this.nodeExecutable, [ await this.getLeetCodeBinaryPath(), "test", `${filePath}`, ]); } - return await this.executeCommandWithProgressEx("Submitting to LeetCode...", this.nodeExecutable, [ + return await this.executeCommandWithProgressEx("正在提交代码~", this.nodeExecutable, [ await this.getLeetCodeBinaryPath(), "test", `"${filePath}"`, @@ -277,7 +272,7 @@ class ExecuteService implements Disposable { public async switchEndpoint(endpoint: string): Promise { switch (endpoint) { case Endpoint.LeetCodeCN: - return await this.executeCommandEx(this.nodeExecutable, [ + return await this.executeCommandWithProgressEx("正在切换登录点~", this.nodeExecutable, [ await this.getLeetCodeBinaryPath(), "plugin", "-e", @@ -285,7 +280,7 @@ class ExecuteService implements Disposable { ]); case Endpoint.LeetCode: default: - return await this.executeCommandEx(this.nodeExecutable, [ + return await this.executeCommandWithProgressEx("正在切换登录点~", this.nodeExecutable, [ await this.getLeetCodeBinaryPath(), "plugin", "-d", @@ -299,7 +294,7 @@ class ExecuteService implements Disposable { if (!addToFavorite) { commandParams.push("-d"); } - await this.executeCommandWithProgressEx("Updating the favorite list...", "node", commandParams); + await this.executeCommandWithProgressEx("正在更新收藏列表~", "node", commandParams); } public get node(): string { @@ -317,16 +312,16 @@ class ExecuteService implements Disposable { return getNodePath(); } - private async executeCommandEx( - command: string, - args: string[], - options: cp.SpawnOptions = { shell: true } - ): Promise { - if (systemUtils.useWsl()) { - return await executeCommand("wsl", [command].concat(args), options); - } - return await executeCommand(command, args, options); - } + // private async executeCommandEx( + // command: string, + // args: string[], + // options: cp.SpawnOptions = { shell: true } + // ): Promise { + // if (systemUtils.useWsl()) { + // return await executeCommand("wsl", [command].concat(args), options); + // } + // return await executeCommand(command, args, options); + // } private async executeCommandWithProgressEx( message: string, diff --git a/src/service/FileButtonService.ts b/src/service/FileButtonService.ts index 875d308..4a67c26 100644 --- a/src/service/FileButtonService.ts +++ b/src/service/FileButtonService.ts @@ -37,7 +37,7 @@ export class FileButtonService implements vscode.CodeLensProvider { temp_result.push( new vscode.CodeLens(range, { title: "Submit", - command: "leetcode.submitSolution", + command: "lcpr.submitSolution", arguments: [document.uri], }) ); @@ -47,7 +47,7 @@ export class FileButtonService implements vscode.CodeLensProvider { temp_result.push( new vscode.CodeLens(range, { title: "case", - command: "leetcode.testSolutionDefault", + command: "lcpr.testCaseDef", arguments: [document.uri, false], }) ); @@ -56,7 +56,7 @@ export class FileButtonService implements vscode.CodeLensProvider { temp_result.push( new vscode.CodeLens(range, { title: "allcase", - command: "leetcode.testSolutionDefault", + command: "lcpr.testCaseDef", arguments: [document.uri, true], }) ); @@ -66,7 +66,7 @@ export class FileButtonService implements vscode.CodeLensProvider { temp_result.push( new vscode.CodeLens(range, { title: "Test", - command: "leetcode.testSolution", + command: "lcpr.testSolution", arguments: [document.uri], }) ); @@ -76,7 +76,7 @@ export class FileButtonService implements vscode.CodeLensProvider { temp_result.push( new vscode.CodeLens(range, { title: node.isFavorite ? "Unstar" : "Star", - command: node.isFavorite ? "leetcode.removeFavorite" : "leetcode.addFavorite", + command: node.isFavorite ? "lcpr.removeFavorite" : "lcpr.addFavorite", arguments: [node], }) ); @@ -86,7 +86,7 @@ export class FileButtonService implements vscode.CodeLensProvider { temp_result.push( new vscode.CodeLens(range, { title: "Solution", - command: "leetcode.showSolution", + command: "lcpr.showSolution", arguments: [document.uri], }) ); @@ -96,7 +96,7 @@ export class FileButtonService implements vscode.CodeLensProvider { temp_result.push( new vscode.CodeLens(range, { title: "Description", - command: "leetcode.previewProblem", + command: "lcpr.previewProblem", arguments: [document.uri], }) ); @@ -112,7 +112,7 @@ export class FileButtonService implements vscode.CodeLensProvider { return new vscode.CodeLens(range, { title: "case", - command: "leetcode.testSolutionArea", + command: "lcpr.tesCaseArea", arguments: [document.uri, testCase], }); } diff --git a/src/service/PreviewService.ts b/src/service/PreviewService.ts index dd872bd..434ab69 100644 --- a/src/service/PreviewService.ts +++ b/src/service/PreviewService.ts @@ -139,7 +139,7 @@ class PreviewService extends BaseWebViewService { protected async onDidReceiveMessage(message: IWebViewMessage): Promise { switch (message.command) { case "ShowProblem": { - await commands.executeCommand("leetcode.showProblem", this.node); + await commands.executeCommand("lcpr.showProblem", this.node); break; } } diff --git a/src/service/StatusBarService.ts b/src/service/StatusBarService.ts index 5d379b8..7d5f321 100644 --- a/src/service/StatusBarService.ts +++ b/src/service/StatusBarService.ts @@ -103,14 +103,21 @@ class StatusBarService implements Disposable { } break; case UserStatus.SignedOut: - default: + this.currentUserContestInfo = undefined; this.instance.text = ""; break; + default: + break; } } public update_status(status: UserStatus, user?: string) { this.userStatus = status; + + // 如果用户名不一样,清掉this.currentUserContestInfo = undefined; + if (user != this.currentUser) { + this.update_UserContestInfo(undefined); + } this.currentUser = user; } public update_UserContestInfo(UserContestInfo?: userContestRanKingBase | undefined) { diff --git a/src/service/TreeDataService.ts b/src/service/TreeDataService.ts index 9eab305..642bfab 100644 --- a/src/service/TreeDataService.ts +++ b/src/service/TreeDataService.ts @@ -17,7 +17,7 @@ import { ProblemState, SearchSetType, ISubmitEvent, - DialogType, + OutPutType, } from "../model/Model"; import { treeViewController } from "../controller/TreeViewController"; import { NodeModel } from "../model/NodeModel"; @@ -40,8 +40,8 @@ export class TreeDataService implements vscode.TreeDataProvider { this.context = context; } - public checkSubmit(e: ISubmitEvent) { - treeViewController.checkSubmit(e); + public async checkSubmit(e: ISubmitEvent) { + await treeViewController.checkSubmit(e); } public cleanUserScore() { @@ -60,7 +60,7 @@ export class TreeDataService implements vscode.TreeDataProvider { label: element.name, collapsibleState: vscode.TreeItemCollapsibleState.None, command: { - command: "leetcode.signin", + command: "lcpr.signin", title: "未登录", }, }; @@ -171,7 +171,7 @@ export class TreeDataService implements vscode.TreeDataProvider { if (objData.code == 101) { promptForOpenOutputChannel( "从 https://zerotrac.github.io/leetcode_problem_rating/data.json 获取数据出错", - DialogType.info + OutPutType.info ); objData = {}; } diff --git a/src/utils/ConfigUtils.ts b/src/utils/ConfigUtils.ts index ab38915..b787ee7 100644 --- a/src/utils/ConfigUtils.ts +++ b/src/utils/ConfigUtils.ts @@ -7,8 +7,33 @@ * Copyright (c) 2022 ccagml . All rights reserved. */ -import { workspace, WorkspaceConfiguration, commands } from "vscode"; -import { DescriptionConfiguration, Endpoint, IProblem, SortingStrategy } from "../model/Model"; +import { + workspace, + WorkspaceConfiguration, + commands, + MessageItem, + window, + Uri, + ConfigurationTarget, + OpenDialogOptions, + WorkspaceFolder, + QuickPickItem, +} from "vscode"; +import { + DescriptionConfiguration, + Endpoint, + IProblem, + SortingStrategy, + AllProgramLanguage, + DialogOptions, + OpenOption, + IQuickItemEx, +} from "../model/Model"; + +import { useWsl, toWslPath } from "../utils/SystemUtils"; +import * as path from "path"; +import * as fse from "fs-extra"; +import * as os from "os"; // vscode的配置 export function getVsCodeConfig(): WorkspaceConfiguration { @@ -164,3 +189,180 @@ export function getLeetCodeEndpoint(): string { export async function openSettingsEditor(query?: string): Promise { await commands.executeCommand("workbench.action.openSettings", query); } + +// 设置默认语言 +export async function fetchProblemLanguage(): Promise { + const leetCodeConfig: WorkspaceConfiguration = workspace.getConfiguration("leetcode-problem-rating"); + let defaultLanguage: string | undefined = leetCodeConfig.get("defaultLanguage"); + if (defaultLanguage && AllProgramLanguage.indexOf(defaultLanguage) < 0) { + defaultLanguage = undefined; + } + const language: string | undefined = + defaultLanguage || + (await window.showQuickPick(AllProgramLanguage, { + placeHolder: "Select the language you want to use", + ignoreFocusOut: true, + })); + + (async (): Promise => { + if (language && !defaultLanguage && leetCodeConfig.get("hint.setDefaultLanguage")) { + const choice: MessageItem | undefined = await 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); + } else if (choice === DialogOptions.never) { + leetCodeConfig.update("hint.setDefaultLanguage", false, true); + } + } + })(); + return language; +} + +export async function determineLeetCodeFolder(): Promise { + let result: string; + const picks: Array> = []; + picks.push( + { + label: `Default location`, + detail: `${path.join(os.homedir(), ".leetcode")}`, + value: `${path.join(os.homedir(), ".leetcode")}`, + }, + { + label: "$(file-directory) Browse...", + value: ":browse", + } + ); + const choice: IQuickItemEx | undefined = await window.showQuickPick(picks, { + placeHolder: "Select where you would like to save your LeetCode files", + }); + if (!choice) { + result = ""; + } else if (choice.value === ":browse") { + const directory: Uri[] | undefined = await showDirectorySelectDialog(); + if (!directory || directory.length < 1) { + result = ""; + } else { + result = directory[0].fsPath; + } + } else { + result = choice.value; + } + + getVsCodeConfig().update("workspaceFolder", result, ConfigurationTarget.Global); + + return result; +} + +export function getBelongingWorkspaceFolderUri(fsPath: string | undefined): Uri | undefined { + let defaultUri: Uri | undefined; + if (fsPath) { + const workspaceFolder: WorkspaceFolder | undefined = workspace.getWorkspaceFolder(Uri.file(fsPath)); + if (workspaceFolder) { + defaultUri = workspaceFolder.uri; + } + } + return defaultUri; +} + +export async function showDirectorySelectDialog(fsPath?: string): Promise { + const defaultUri: Uri | undefined = getBelongingWorkspaceFolderUri(fsPath); + const options: OpenDialogOptions = { + defaultUri, + canSelectFiles: false, + canSelectFolders: true, + canSelectMany: false, + openLabel: "Select", + }; + return await window.showOpenDialog(options); +} + +export function isSubFolder(from: string, to: string): boolean { + const relative: string = path.relative(from, to); + if (relative === "") { + return true; + } + return !relative.startsWith("..") && !path.isAbsolute(relative); +} + +export async function selectWorkspaceFolder(isAsk: boolean = true): Promise { + let workspaceFolderSetting: string = getWorkspaceFolder(); + if (workspaceFolderSetting.trim() === "") { + workspaceFolderSetting = await determineLeetCodeFolder(); + if (workspaceFolderSetting === "") { + // User cancelled + return workspaceFolderSetting; + } + } + let needAsk: boolean = true; + await fse.ensureDir(workspaceFolderSetting); + for (const folder of workspace.workspaceFolders || []) { + if (isSubFolder(folder.uri.fsPath, workspaceFolderSetting)) { + needAsk = false; + } + } + + if (needAsk && isAsk) { + const choice: string | undefined = await window.showQuickPick( + [OpenOption.justOpenFile, OpenOption.openInCurrentWindow, OpenOption.openInNewWindow, OpenOption.addToWorkspace], + { + placeHolder: "The LeetCode workspace folder is not opened in VS Code, would you like to open it?", + } + ); + switch (choice) { + case OpenOption.justOpenFile: + return workspaceFolderSetting; + case OpenOption.openInCurrentWindow: + await commands.executeCommand("vscode.openFolder", Uri.file(workspaceFolderSetting), false); + return ""; + case OpenOption.openInNewWindow: + await commands.executeCommand("vscode.openFolder", Uri.file(workspaceFolderSetting), true); + return ""; + case OpenOption.addToWorkspace: + workspace.updateWorkspaceFolders(workspace.workspaceFolders?.length ?? 0, 0, { + uri: Uri.file(workspaceFolderSetting), + }); + break; + default: + return ""; + } + } + + return useWsl() ? toWslPath(workspaceFolderSetting) : workspaceFolderSetting; +} + +export async function setDefaultLanguage(): Promise { + const leetCodeConfig: WorkspaceConfiguration = workspace.getConfiguration("leetcode-problem-rating"); + const defaultLanguage: string | undefined = leetCodeConfig.get("defaultLanguage"); + const languageItems: QuickPickItem[] = []; + for (const language of AllProgramLanguage) { + languageItems.push({ + label: language, + description: defaultLanguage === language ? "Currently used" : undefined, + }); + } + // Put the default language at the top of the list + languageItems.sort((a: QuickPickItem, b: QuickPickItem) => { + if (a.description) { + return Number.MIN_SAFE_INTEGER; + } else if (b.description) { + return Number.MAX_SAFE_INTEGER; + } + return a.label.localeCompare(b.label); + }); + + const selectedItem: QuickPickItem | undefined = await window.showQuickPick(languageItems, { + placeHolder: "请设置默认语言", + ignoreFocusOut: true, + }); + + if (!selectedItem) { + return; + } + + leetCodeConfig.update("defaultLanguage", selectedItem.label, true /* Global */); + window.showInformationMessage(`设置默认语言 ${selectedItem.label} 成功`); +} diff --git a/src/utils/OutputUtils.ts b/src/utils/OutputUtils.ts index f957381..7009b73 100644 --- a/src/utils/OutputUtils.ts +++ b/src/utils/OutputUtils.ts @@ -8,7 +8,7 @@ */ import * as vscode from "vscode"; -import { DialogOptions, DialogType } from "../model/Model"; +import { DialogOptions, OutPutType } from "../model/Model"; import { getLeetCodeEndpoint, getVsCodeConfig } from "./ConfigUtils"; export async function openUrl(url: string): Promise { @@ -45,7 +45,7 @@ export async function promptForSignIn(): Promise { ); switch (choice) { case DialogOptions.yes: - await vscode.commands.executeCommand("leetcode.signin"); + await vscode.commands.executeCommand("lcpr.signin"); break; case DialogOptions.singUp: if (getLeetCodeEndpoint()) { @@ -59,16 +59,16 @@ export async function promptForSignIn(): Promise { } } -export async function promptForOpenOutputChannel(message: string, type: DialogType): Promise { +export async function promptForOpenOutputChannel(message: string, type: OutPutType): Promise { let result: vscode.MessageItem | undefined; switch (type) { - case DialogType.info: + case OutPutType.info: result = await vscode.window.showInformationMessage(message, DialogOptions.open, DialogOptions.no); break; - case DialogType.warning: + case OutPutType.warning: result = await vscode.window.showWarningMessage(message, DialogOptions.open, DialogOptions.no); break; - case DialogType.error: + case OutPutType.error: result = await vscode.window.showErrorMessage(message, DialogOptions.open, DialogOptions.no); break; default: diff --git a/src/utils/SystemUtils.ts b/src/utils/SystemUtils.ts index 962088d..5458ed9 100644 --- a/src/utils/SystemUtils.ts +++ b/src/utils/SystemUtils.ts @@ -70,3 +70,17 @@ export async function getNodeIdFromFile(fsPath: string): Promise { return id; } + +//获取当天零点的时间 +export function getDayStart(): number { + return new Date(new Date().setHours(0, 0, 0, 0)).getTime() / 1000; +} + +//获取当天23:59:59的时间 +export function getDayEnd(): number { + return getDayStart() + 86399; +} + +export function getDayNow(): number { + return Math.round(new Date().getTime() / 1000); +}