("colorizeProblems", false);
129 | }
130 | }
131 |
132 | export const treeColor: TreeColor = new TreeColor();
133 |
--------------------------------------------------------------------------------
/src/rpc/actionChain/chainNode/cache.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * https://github.com/ccagml/leetcode-extension/src/rpc/actionChain/cache.ts
3 | * Path: https://github.com/ccagml/leetcode-extension
4 | * Created Date: Monday, November 14th 2022, 4:04:31 pm
5 | * Author: ccagml
6 | *
7 | * Copyright (c) 2022 ccagml . All rights reserved.
8 | */
9 |
10 | import { ChainNodeBase } from "../chainNodeBase";
11 |
12 | let underscore = require("underscore");
13 |
14 | import { storageUtils } from "../../utils/storageUtils";
15 | import { commUtils } from "../../utils/commUtils";
16 | import { sessionUtils } from "../../utils/sessionUtils";
17 |
18 | /* It's a plugin that caches the data it gets from the next plugin in the chain */
19 | class CachePlugin extends ChainNodeBase {
20 | id = 50;
21 | name = "cache";
22 | builtin = true;
23 | constructor() {
24 | super();
25 | }
26 |
27 | /* Checking if the translation config has changed. If it has, it clears the cache. */
28 | clearCacheIfTchanged = (needTranslation) => {
29 | const translationConfig = storageUtils.getCache(commUtils.KEYS.translation);
30 | if (!translationConfig || translationConfig["useEndpointTranslation"] != needTranslation) {
31 | storageUtils.deleteAllCache();
32 | storageUtils.setCache(commUtils.KEYS.translation, {
33 | useEndpointTranslation: needTranslation,
34 | });
35 | }
36 | };
37 |
38 | /* A method that is used to get problems from the cache. If the cache is empty, it will get the
39 | problems from the next layer. */
40 | public getProblems = (needTranslation, cb) => {
41 | this.clearCacheIfTchanged(needTranslation);
42 | const problems = storageUtils.getCache(commUtils.KEYS.problems);
43 | if (problems) {
44 | return cb(null, problems);
45 | }
46 | this.next.getProblems(needTranslation, function (e, problems) {
47 | if (e) return cb(e);
48 | storageUtils.setCache(commUtils.KEYS.problems, problems);
49 | return cb(null, problems);
50 | });
51 | };
52 |
53 | /* A method that is used to get problems from the cache. If the cache is empty, it will get the
54 | problems from the next layer. */
55 |
56 | public getRatingOnline = (cb) => {
57 | const cacheRantingData = storageUtils.getCache(commUtils.KEYS.ranting_path);
58 | if (cacheRantingData) {
59 | return cb(null, cacheRantingData);
60 | }
61 | this.next.getRatingOnline(function (e, ratingData) {
62 | if (e) return cb(e);
63 | let ratingObj;
64 | try {
65 | ratingObj = JSON.parse(ratingData);
66 | } catch (error) {
67 | return cb("JSON.parse(ratingData) error");
68 | }
69 | storageUtils.setCache(commUtils.KEYS.ranting_path, ratingObj);
70 | return cb(null, ratingObj);
71 | });
72 | };
73 |
74 | /* A cache layer for the getProblem function. */
75 | public getProblem = (problem, needTranslation, cb) => {
76 | this.clearCacheIfTchanged(needTranslation);
77 | const k = commUtils.KEYS.problem(problem);
78 | const _problem = storageUtils.getCache(k);
79 | let that = this;
80 | if (_problem) {
81 | if (!_problem.desc.includes("")) {
82 | //
83 | } else if (!["likes", "dislikes"].every((p) => p in _problem)) {
84 | //
85 | } else {
86 | underscore.extendOwn(problem, _problem);
87 | return cb(null, problem);
88 | }
89 | }
90 | this.next.getProblem(problem, needTranslation, function (e, _problem) {
91 | if (e) return cb(e);
92 |
93 | that.saveProblem(_problem);
94 | return cb(null, _problem);
95 | });
96 | };
97 |
98 | saveProblem = (problem) => {
99 | const _problem = underscore.omit(problem, ["locked", "state", "starred"]);
100 | return storageUtils.setCache(commUtils.KEYS.problem(problem), _problem);
101 | };
102 |
103 | /* Updating the problem in the cache. */
104 | updateProblem = (problem, kv) => {
105 | const problems = storageUtils.getCache(commUtils.KEYS.problems);
106 | if (!problems) return false;
107 |
108 | const _problem = problems.find((x) => x.id === problem.id);
109 | if (!_problem) return false;
110 |
111 | underscore.extend(_problem, kv);
112 | return storageUtils.setCache(commUtils.KEYS.problems, problems);
113 | };
114 |
115 | /* Logging out the user and then logging in the user. */
116 | login = (user, cb) => {
117 | this.logout(user, false);
118 | this.next.login(user, function (e, user) {
119 | if (e) return cb(e);
120 | sessionUtils.saveUser(user);
121 | return cb(null, user);
122 | });
123 | };
124 |
125 | /* Logging out the user and then logging in the user. */
126 | logout = (user, purge) => {
127 | if (!user) user = sessionUtils.getUser();
128 | if (purge) sessionUtils.deleteUser();
129 | sessionUtils.deleteCodingSession();
130 | return user;
131 | };
132 |
133 | public getHintsOnline = (problem, cb) => {
134 | const hints = storageUtils.getCache(commUtils.KEYS.hints) || {};
135 | if (hints && hints[problem.id]) {
136 | return cb(null, hints[problem.id]);
137 | }
138 | this.next.getHintsOnline(problem, function (e, hints_result) {
139 | if (e) return cb(e);
140 |
141 | const hints = storageUtils.getCache(commUtils.KEYS.hints) || {};
142 | hints[problem.id] = hints_result;
143 | storageUtils.setCache(commUtils.KEYS.hints, hints);
144 |
145 | return cb(null, hints_result);
146 | });
147 | };
148 | }
149 |
150 | export const pluginObj: CachePlugin = new CachePlugin();
151 |
--------------------------------------------------------------------------------
/src/utils/problemUtils.ts:
--------------------------------------------------------------------------------
1 | import * as fse from "fs-extra";
2 | import * as path from "path";
3 | import * as vscode from "vscode";
4 |
5 | import { useWsl, isWindows, usingCmd } from "./SystemUtils";
6 |
7 | const beforeStubReg: RegExp = /@lcpr-before-debug-begin([\s\S]*?)@lcpr-before-debug-end/;
8 | const afterStubReg: RegExp = /@lcpr-after-debug-begin([\s\S]*?)@lcpr-after-debug-end/;
9 |
10 | interface IExtensionState {
11 | context: vscode.ExtensionContext;
12 | cachePath: string;
13 | }
14 |
15 | export interface IDebugConfig {
16 | type: string;
17 | program?: string;
18 | env?: {
19 | [key: string]: any;
20 | };
21 | [x: string]: any;
22 | }
23 |
24 | export interface IProblemType {
25 | funName: string;
26 | paramTypes: string[];
27 | returnType: string;
28 | testCase?: string;
29 | specialFunName?: {
30 | [x: string]: string;
31 | };
32 | }
33 |
34 | export interface IDebugResult {
35 | type: "success" | "error";
36 | message: string;
37 | problemNum: number;
38 | language: string;
39 | filePath: string;
40 | testString: string;
41 | }
42 |
43 | export const extensionState: IExtensionState = {
44 | context: null as any,
45 | cachePath: "",
46 | };
47 |
48 | export const languages: string[] = [
49 | "bash",
50 | "c",
51 | "cpp",
52 | "csharp",
53 | "golang",
54 | "java",
55 | "javascript",
56 | "kotlin",
57 | "mysql",
58 | "php",
59 | "python",
60 | "python3",
61 | "ruby",
62 | "rust",
63 | "scala",
64 | "swift",
65 | ];
66 |
67 | export const langExt: Map = new Map([
68 | ["bash", "sh"],
69 | ["c", "c"],
70 | ["cpp", "cpp"],
71 | ["csharp", "cs"],
72 | ["golang", "go"],
73 | ["java", "java"],
74 | ["javascript", "js"],
75 | ["kotlin", "kt"],
76 | ["mysql", "sql"],
77 | ["php", "php"],
78 | ["python", "py"],
79 | ["python3", "py"],
80 | ["ruby", "rb"],
81 | ["rust", "rs"],
82 | ["scala", "scala"],
83 | ["swift", "swift"],
84 | ]);
85 |
86 | export const supportDebugLanguages: string[] = ["javascript", "python3", "cpp"];
87 |
88 | export interface ProblemMeta {
89 | id: string;
90 | lang: string;
91 | }
92 |
93 | export function genFileExt(language: string): string {
94 | const ext: string | undefined = langExt.get(language);
95 | if (!ext) {
96 | throw new Error(`The language "${language}" is not supported.`);
97 | }
98 | return ext;
99 | }
100 |
101 | export function fileMeta(content: string): ProblemMeta | null {
102 | const result: RegExpExecArray | null = /@lc app=(.*) id=(.*|\w*|\W*|[\\u4e00-\\u9fa5]*) lang=(.*)/.exec(content);
103 | if (result != null) {
104 | return {
105 | id: result[2],
106 | lang: result[3],
107 | };
108 | }
109 | return null;
110 | }
111 |
112 | export async function getUnstubedFile(filePath: string): Promise {
113 | const content: string = (await fse.readFile(filePath)).toString();
114 | const stripped: string = content.replace(beforeStubReg, "").replace(afterStubReg, "");
115 |
116 | if (content.length === stripped.length) {
117 | // no stub, return original filePath
118 | return filePath;
119 | }
120 |
121 | const meta: { id: string; lang: string } | null = fileMeta(content);
122 | if (meta == null) {
123 | vscode.window.showErrorMessage(
124 | "File meta info has been changed, please check the content: '@lc app=leetcode.cn id=xx lang=xx'."
125 | );
126 | throw new Error("");
127 | }
128 |
129 | const newPath: string = path.join(extensionState.cachePath, `${meta.id}-${meta.lang}`);
130 | await fse.writeFile(newPath, stripped);
131 | return newPath;
132 | }
133 |
134 | export async function getProblemSpecialCode(
135 | language: string,
136 | problem: string,
137 | fileExt: string,
138 | extDir: string
139 | ): Promise {
140 | const problemPath: string = path.join(extDir, "resources/debug/entry", language, "problems", `${problem}.${fileExt}`);
141 | const isSpecial: boolean = await fse.pathExists(problemPath);
142 | if (isSpecial) {
143 | const specialContent: Buffer = await fse.readFile(problemPath);
144 | return specialContent.toString();
145 | }
146 | if (language === "cpp") {
147 | return "";
148 | }
149 | const fileContent: Buffer = await fse.readFile(
150 | path.join(extDir, "resources/debug/entry", language, "problems", `common.${fileExt}`)
151 | );
152 | return fileContent.toString();
153 | }
154 |
155 | export async function getEntryFile(language: string, problem: string): Promise {
156 | const extDir: string = vscode.extensions.getExtension("ccagml.vscode-leetcode-problem-rating")!.extensionPath;
157 | const fileExt: string = genFileExt(language);
158 | const specialCode: string = await getProblemSpecialCode(language, problem, fileExt, extDir);
159 | const tmpEntryCode: string = (
160 | await fse.readFile(path.join(extDir, "resources/debug/entry", language, `entry.${fileExt}`))
161 | ).toString();
162 | const entryCode: string = tmpEntryCode.replace(/\/\/ @@stub-for-code@@/, specialCode);
163 | const entryPath: string = path.join(extensionState.cachePath, `${language}problem${problem}.${fileExt}`);
164 | await fse.writeFile(entryPath, entryCode);
165 | return entryPath;
166 | }
167 |
168 | export function parseTestString(test: string): string {
169 | if (useWsl() || !isWindows()) {
170 | return `'${test}'`;
171 | }
172 |
173 | // In windows and not using WSL
174 | if (usingCmd()) {
175 | return `"${test.replace(/"/g, '\\"')}"`;
176 | } else {
177 | // Assume using PowerShell
178 | return `'${test.replace(/"/g, '\\"')}'`;
179 | }
180 | }
181 |
182 | export function randomString(len: number): string {
183 | len = len || 32;
184 | const $chars: string = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678";
185 | const maxPos: number = $chars.length;
186 | let pwd: string = "";
187 | for (let i: number = 0; i < len; i++) {
188 | pwd += $chars.charAt(Math.floor(Math.random() * maxPos));
189 | }
190 | return pwd;
191 | }
192 |
--------------------------------------------------------------------------------
/src/rpc/utils/configUtils.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Filename: https://github.com/ccagml/leetcode-extension/src/rpc/config.ts
3 | * Path: https://github.com/ccagml/leetcode-extension
4 | * Created Date: Thursday, October 27th 2022, 7:43:29 pm
5 | * Author: ccagml
6 | *
7 | * Copyright (c) 2022 ccagml . All rights reserved.
8 | */
9 |
10 | let underscore = require("underscore");
11 |
12 | class Config {
13 | LCPTCTX: any;
14 | app: any;
15 | sys: any;
16 | autologin: any;
17 | code: any;
18 | file: any;
19 | color: any;
20 | icon: any;
21 | network: any;
22 | plugins: any;
23 | constructor() {
24 | this.sys = {
25 | categories: ["algorithms", "LCCI", "LCOF", "LCOF2"],
26 | langs: [
27 | "bash",
28 | "c",
29 | "cpp",
30 | "csharp",
31 | "golang",
32 | "java",
33 | "javascript",
34 | "kotlin",
35 | "mysql",
36 | "php",
37 | "python",
38 | "python3",
39 | "ruby",
40 | "rust",
41 | "scala",
42 | "swift",
43 | "typescript",
44 | ],
45 | my_headers: {
46 | User_Agent: 'Mozilla/5.0 (X11; Linux x86_64; rv:123.0) Gecko/20100101 Firefox/123.0',
47 | Referer: 'https://leetcode.com',
48 | Origin: 'https://leetcode.com/',
49 | Host: 'leetcode.com',
50 | Content_Type: 'application/json',
51 | Accept: 'application/json',
52 | },
53 |
54 | urls: {
55 | // base urls
56 | base: "https://leetcode.com",
57 | graphql: "https://leetcode.com/graphql",
58 | login: "https://leetcode.com/accounts/login/",
59 | // third part login base urls. TODO facebook google
60 | github_login: "https://leetcode.com/accounts/github/login/?next=%2F",
61 | facebook_login: "https://leetcode.com/accounts/facebook/login/?next=%2F",
62 | linkedin_login: "https://leetcode.com/accounts/linkedin_oauth2/login/?next=%2F",
63 | // redirect urls
64 | leetcode_redirect: "https://leetcode.com/",
65 | github_tf_redirect: "https://github.com/sessions/two-factor",
66 | // simulate login urls
67 | github_login_request: "https://github.com/login",
68 | github_session_request: "https://github.com/session",
69 | github_tf_session_request: "https://github.com/sessions/two-factor",
70 | linkedin_login_request: "https://www.linkedin.com/login",
71 | linkedin_session_request: "https://www.linkedin.com/checkpoint/lg/login-submit",
72 | // questions urls
73 | problems: "https://leetcode.com/api/problems/$category/",
74 | problem: "https://leetcode.com/problems/$slug/description/",
75 | test: "https://leetcode.com/problems/$slug/interpret_solution/",
76 | session: "https://leetcode.com/session/",
77 | submit: "https://leetcode.com/problems/$slug/submit/",
78 | submissions: "https://leetcode.com/api/submissions/$slug",
79 | submission: "https://leetcode.com/submissions/detail/$id/",
80 | verify: "https://leetcode.com/submissions/detail/$id/check/",
81 | favorites: "https://leetcode.com/list/api/questions",
82 | favorite_delete: "https://leetcode.com/list/api/questions/$hash/$id",
83 | problem_detail: "",
84 | noj_go: "",
85 | u: "",
86 | },
87 | };
88 |
89 | this.autologin = {
90 | enable: false,
91 | retry: 2,
92 | };
93 | this.code = {
94 | editor: "vim",
95 | lang: "cpp",
96 | };
97 | this.file = {
98 | show: "${fid}.${slug}",
99 | submission: "${fid}.${slug}.${sid}.${ac}",
100 | };
101 | this.color = {
102 | enable: true,
103 | theme: "default",
104 | };
105 | this.icon = {
106 | theme: "",
107 | };
108 | this.network = {
109 | concurrency: 10,
110 | delay: 1,
111 | };
112 | this.plugins = {};
113 | }
114 |
115 | init(ctx) {
116 | this.LCPTCTX = ctx;
117 | }
118 |
119 | getAll(useronly) {
120 | const cfg = underscore.extendOwn({}, this);
121 | if (useronly) delete cfg.sys;
122 | return cfg;
123 | }
124 |
125 | isCN() {
126 | return this.app == "leetcode.cn"
127 | }
128 |
129 | fix_cn() {
130 | this.app = "leetcode.cn";
131 | this.sys.urls.base = "https://leetcode.cn";
132 | this.sys.urls.login = "https://leetcode.cn/accounts/login/";
133 | this.sys.urls.problems = "https://leetcode.cn/api/problems/$category/";
134 | this.sys.urls.problem = "https://leetcode.cn/problems/$slug/description/";
135 | this.sys.urls.graphql = "https://leetcode.cn/graphql";
136 | this.sys.urls.problem_detail = "https://leetcode.cn/graphql";
137 | this.sys.urls.test = "https://leetcode.cn/problems/$slug/interpret_solution/";
138 | this.sys.urls.session = "https://leetcode.cn/session/";
139 | this.sys.urls.submit = "https://leetcode.cn/problems/$slug/submit/";
140 | this.sys.urls.submissions = "https://leetcode.cn/api/submissions/$slug";
141 | this.sys.urls.submission = "https://leetcode.cn/submissions/detail/$id/";
142 | this.sys.urls.verify = "https://leetcode.cn/submissions/detail/$id/check/";
143 | this.sys.urls.favorites = "https://leetcode.cn/list/api/questions";
144 | this.sys.urls.favorite_delete = "https://leetcode.cn/list/api/questions/$hash/$id";
145 | this.sys.urls.noj_go = "https://leetcode.cn/graphql/noj-go/";
146 | this.sys.urls.u = "https://leetcode.cn/u/$username/";
147 | this.sys.urls.github_login = "https://leetcode.cn/accounts/github/login/?next=%2F";
148 | this.sys.urls.linkedin_login = "https://leetcode.cn/accounts/linkedin_oauth2/login/?next=%2F";
149 | this.sys.urls.leetcode_redirect = "https://leetcode.cn/";
150 | this.sys.my_headers = {
151 | User_Agent: 'Mozilla/5.0 (X11; Linux x86_64; rv:123.0) Gecko/20100101 Firefox/123.0',
152 | Referer: 'https://leetcode.cn',
153 | Origin: 'https://leetcode.cn/',
154 | Host: 'leetcode.cn',
155 | Content_Type: 'application/json',
156 | Accept: 'application/json',
157 | }
158 | }
159 | }
160 |
161 | export const configUtils: Config = new Config();
162 |
--------------------------------------------------------------------------------
/src/rpc/factory/api/userApi.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * https://github.com/ccagml/leetcode-extension/src/rpc/factory/api/userApi.ts
3 | * Path: https://github.com/ccagml/leetcode-extension
4 | * Created Date: Thursday, November 17th 2022, 11:44:14 am
5 | * Author: ccagml
6 | *
7 | * Copyright (c) 2022 ccagml . All rights reserved.
8 | */
9 |
10 | let prompt_out = require("prompt");
11 |
12 | import { reply } from "../../utils/ReplyUtils";
13 |
14 | import { sessionUtils } from "../../utils/sessionUtils";
15 | import { ApiBase } from "../apiBase";
16 |
17 | import { chainMgr } from "../../actionChain/chainManager";
18 |
19 | class UserApi extends ApiBase {
20 | constructor() {
21 | super();
22 | }
23 |
24 | callArg(argv) {
25 | let argv_config = this.api_argv()
26 | .option("l", {
27 | alias: "login",
28 | type: "boolean",
29 | default: false,
30 | describe: "Login",
31 | })
32 | .option("c", {
33 | alias: "cookie",
34 | type: "boolean",
35 | default: false,
36 | describe: "cookieLogin",
37 | })
38 | .option("r", {
39 | alias: "curl",
40 | type: "boolean",
41 | default: false,
42 | describe: "curl",
43 | })
44 | .option("g", {
45 | alias: "github",
46 | type: "boolean",
47 | default: false,
48 | describe: "githubLogin",
49 | })
50 | .option("i", {
51 | alias: "linkedin",
52 | type: "boolean",
53 | default: false,
54 | describe: "linkedinLogin",
55 | })
56 | .option("L", {
57 | alias: "logout",
58 | type: "boolean",
59 | default: false,
60 | describe: "Logout",
61 | });
62 |
63 | argv_config.parseArgFromCmd(argv);
64 |
65 | return argv_config.get_result();
66 | }
67 |
68 | call(argv) {
69 | sessionUtils.argv = argv;
70 | let user: any = null;
71 | if (argv.login) {
72 | // login
73 | prompt_out.colors = false;
74 | prompt_out.message = "";
75 | prompt_out.start();
76 | prompt_out.get(
77 | [
78 | { name: "login", required: true },
79 | { name: "pass", required: true, hidden: true },
80 | ],
81 | function (e, user) {
82 | if (e) {
83 | return reply.info(JSON.stringify({ code: -1, msg: e.msg || e }));
84 | }
85 |
86 | chainMgr.getChainHead().login(user, function (e, user) {
87 | if (e) {
88 | return reply.info(JSON.stringify({ code: -2, msg: e.msg || e }));
89 | }
90 | reply.info(JSON.stringify({ code: 100, user_name: user.name || user.login || "username" }));
91 | });
92 | }
93 | );
94 | } else if (argv.logout) {
95 | // logout
96 | user = chainMgr.getChainHead().logout(user, true);
97 | if (user) reply.info(JSON.stringify({ code: 100, user_name: user.name || user.login || "username" }));
98 | else reply.info(JSON.stringify({ code: -3, msg: "You are not login yet?" }));
99 | // third parties
100 | } else if (argv.github || argv.linkedin) {
101 | // add future third parties here
102 | const functionMap = new Map([
103 | ["g", chainMgr.getChainHead().githubLogin],
104 | ["github", chainMgr.getChainHead().githubLogin],
105 | ["i", chainMgr.getChainHead().linkedinLogin],
106 | ["linkedin", chainMgr.getChainHead().linkedinLogin],
107 | ]);
108 | const keyword = Object.entries(argv).filter((i) => i[1] === true)[0][0];
109 | const coreFunction = functionMap.get(keyword);
110 | if (coreFunction) {
111 | prompt_out.colors = false;
112 | prompt_out.message = "";
113 | prompt_out.start();
114 | prompt_out.get(
115 | [
116 | { name: "login", required: true },
117 | { name: "pass", required: true, hidden: true },
118 | ],
119 | function (e, user) {
120 | if (e) return reply.info(JSON.stringify({ code: -4, msg: e.msg || e }));
121 | coreFunction(user, function (e, user) {
122 | if (e) return reply.info(JSON.stringify({ code: -5, msg: e.msg || e }));
123 | reply.info(JSON.stringify({ code: 100, user_name: user.name || user.login || "username" }));
124 | });
125 | }
126 | );
127 | }
128 | } else if (argv.cookie) {
129 | // session
130 | prompt_out.colors = false;
131 | prompt_out.message = "";
132 | prompt_out.start();
133 | prompt_out.get(
134 | [
135 | { name: "login", required: true },
136 | { name: "cookie", required: true },
137 | ],
138 | function (e, user) {
139 | if (e) return reply.info(e);
140 | chainMgr.getChainHead().cookieLogin(user, function (e, user) {
141 | if (e) return reply.info(JSON.stringify({ code: -6, msg: e.msg || e }));
142 | reply.info(JSON.stringify({ code: 100, user_name: user.name || user.login || "username" }));
143 | });
144 | }
145 | );
146 | } else if (argv.curl) {
147 | // session
148 | prompt_out.colors = false;
149 | prompt_out.message = "";
150 | prompt_out.start();
151 | prompt_out.get(
152 | [
153 | { name: "login", required: true },
154 | { name: "curl_data", required: true },
155 | ],
156 | function (e, user) {
157 | if (e) return reply.info(e);
158 | chainMgr.getChainHead().curlcookieLogin(user, function (e, user) {
159 | if (e) return reply.info(JSON.stringify({ code: -6, msg: e.msg || e }));
160 | reply.info(JSON.stringify({ code: 100, user_name: user.name || user.login || "username" }));
161 | });
162 | }
163 | );
164 | } else {
165 | // show current user
166 | user = sessionUtils.getUser();
167 | if (user) {
168 | reply.info(JSON.stringify({ code: 100, user_name: user.name || user.login || "username" }));
169 | } else return reply.info(JSON.stringify({ code: -7, msg: "You are not login yet?" }));
170 | }
171 | }
172 | }
173 |
174 | export const userApi: UserApi = new UserApi();
175 |
--------------------------------------------------------------------------------
/src/rpc/factory/api/queryApi.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * https://github.com/ccagml/leetcode-extension/src/rpc/factory/api/queryApi.ts
3 | * Path: https://github.com/ccagml/leetcode-extension
4 | * Created Date: Thursday, November 17th 2022, 11:44:14 am
5 | * Author: ccagml
6 | *
7 | * Copyright (c) 2022 ccagml . All rights reserved.
8 | */
9 |
10 | import { reply } from "../../utils/ReplyUtils";
11 | import { sessionUtils } from "../../utils/sessionUtils";
12 | import { ApiBase } from "../apiBase";
13 |
14 | import { chainMgr } from "../../actionChain/chainManager";
15 | import { configUtils } from "../../utils/configUtils";
16 |
17 | class QueryApi extends ApiBase {
18 | constructor() {
19 | super();
20 | }
21 | callArg(argv) {
22 | let argv_config = this.api_argv()
23 | .option("T", {
24 | alias: "dontTranslate",
25 | type: "boolean",
26 | default: false,
27 | describe: "Set to true to disable endpoint's translation",
28 | })
29 | .option("a", {
30 | alias: "getTodayQuestion",
31 | type: "boolean",
32 | default: false,
33 | describe: "getTodayQuestion",
34 | })
35 | .option("b", {
36 | alias: "username",
37 | type: "string",
38 | default: "",
39 | describe: "user name",
40 | })
41 | .option("c", {
42 | alias: "getRating",
43 | type: "boolean",
44 | default: false,
45 | describe: "ranking",
46 | })
47 | .option("d", {
48 | alias: "getAllProblems",
49 | type: "boolean",
50 | default: false,
51 | describe: "getAllProblems",
52 | })
53 | .option("e", {
54 | alias: "getHelp",
55 | type: "boolean",
56 | default: false,
57 | describe: "getHelp",
58 | })
59 | .option("f", {
60 | alias: "help_cn",
61 | type: "boolean",
62 | default: false,
63 | describe: "getHelp help_cn",
64 | })
65 | .option("g", {
66 | alias: "lang",
67 | type: "string",
68 | default: configUtils.code.lang,
69 | describe: "getHelp Programming language of the source code",
70 | })
71 | .option("h", {
72 | alias: "hints",
73 | type: "boolean",
74 | default: false,
75 | describe: "get Hints Programming language of the source code",
76 | })
77 | .option("i", {
78 | alias: "getRecentContest",
79 | type: "boolean",
80 | default: false,
81 | describe: "getRecentContestList",
82 | })
83 | .option("j", {
84 | alias: "getContestQuestion",
85 | type: "string",
86 | default: false,
87 | describe: "get Question list of a contest",
88 | })
89 | .option("z", {
90 | alias: "test",
91 | type: "string",
92 | default: "",
93 | describe: "test",
94 | })
95 | .positional("keyword", {
96 | type: "string",
97 | default: "",
98 | describe: "帮助的参数?",
99 | });
100 | argv_config.parseArgFromCmd(argv);
101 | return argv_config.get_result();
102 | }
103 |
104 | call(argv) {
105 | sessionUtils.argv = argv;
106 | if (argv.a) {
107 | chainMgr.getChainHead().getTodayQuestion(function (e, result) {
108 | if (e) return;
109 | reply.info(JSON.stringify(result));
110 | });
111 | } else if (argv.b) {
112 | chainMgr.getChainHead().getUserContest(argv.b, function (e, result) {
113 | if (e) return;
114 | reply.info(JSON.stringify(result));
115 | });
116 | } else if (argv.c) {
117 | chainMgr.getChainHead().getRating(function (e, result) {
118 | if (e) {
119 | let log_data = {
120 | code: 101,
121 | data: {},
122 | };
123 | if (e.code == "ETIMEDOUT") {
124 | log_data.code = 102;
125 | }
126 |
127 | reply.info(JSON.stringify(log_data));
128 | return;
129 | }
130 | let log_data = {
131 | code: 100,
132 | data: result,
133 | };
134 | reply.info(JSON.stringify(log_data));
135 | });
136 | } else if (argv.d) {
137 | chainMgr.getChainHead().filterProblems(argv, function (e, problems) {
138 | if (e) return reply.info(e);
139 | let new_objcet: Array = [];
140 | problems.forEach((element) => {
141 | let temp_ele: any = {};
142 | for (const key in element) {
143 | if (key != "link") {
144 | temp_ele[key] = element[key];
145 | }
146 | }
147 | new_objcet.push(temp_ele);
148 | });
149 | reply.info(JSON.stringify(new_objcet));
150 | });
151 | } else if (argv.e) {
152 | if (argv.keyword.length > 0) {
153 | // show specific one
154 | chainMgr.getChainHead().getProblem(argv.keyword, !argv.dontTranslate, function (e, problem) {
155 | if (e) return reply.info(e);
156 | chainMgr.getChainHead().getHelpOnline(problem, argv.f, argv.g);
157 | });
158 | }
159 | } else if (argv.h) {
160 | if (argv.keyword.length > 0) {
161 | // show specific one
162 | chainMgr.getChainHead().getProblem(argv.keyword, !argv.dontTranslate, function (e, problem) {
163 | if (e) return reply.info(e);
164 | chainMgr.getChainHead().getHintsOnline(problem, function (e, result) {
165 | if (e) return;
166 | reply.info(JSON.stringify({ code: 100, hints: result }));
167 | });
168 | });
169 | }
170 | } if (argv.i) {
171 | chainMgr.getChainHead().getRecentContest(function (e, result) {
172 | if (e) return;
173 | reply.info(JSON.stringify(result));
174 | });
175 | } if (argv.j) {
176 | chainMgr.getChainHead().getContestQuestion(argv.j, function (e, result) {
177 | if (e) return;
178 | reply.info(JSON.stringify(result));
179 | });
180 | } else if (argv.z) {
181 | chainMgr.getChainHead().getQueryZ(argv.z, function (e, result) {
182 | if (e) return;
183 | reply.info(JSON.stringify(result));
184 | });
185 | }
186 | }
187 | }
188 |
189 | export const queryApi: QueryApi = new QueryApi();
190 |
--------------------------------------------------------------------------------
/src/rpc/actionChain/chainNode/core.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * https://github.com/ccagml/leetcode-extension/src/rpc/actionChain/core.ts
3 | * Path: https://github.com/ccagml/leetcode-extension
4 | * Created Date: Monday, November 14th 2022, 4:04:31 pm
5 | * Author: ccagml
6 | *
7 | * Copyright (c) 2022 ccagml . All rights reserved.
8 | */
9 |
10 | let util = require("util");
11 |
12 | let _ = require("underscore");
13 | let cheerio = require("cheerio");
14 |
15 | import { storageUtils } from "../../utils/storageUtils";
16 | import { configUtils } from "../../utils/configUtils";
17 |
18 | import { ChainNodeBase } from "../chainNodeBase";
19 |
20 | // function hasTag(o, tag) {
21 | // return Array.isArray(o) && o.some((x) => x.indexOf(tag.toLowerCase()) >= 0);
22 | // }
23 |
24 | /* It's a class that extends the ChainNodeBase class, and it has a bunch of methods that are called by
25 | the LeetCode CLI */
26 | class CorePlugin extends ChainNodeBase {
27 | id = 99999999;
28 | name = "core";
29 | builtin = true;
30 | constructor() {
31 | super();
32 | }
33 |
34 | /* It's a method that filters the problems. */
35 | filterProblems = (opts, cb) => {
36 | this.getProblems(!opts.dontTranslate, function (e, problems) {
37 | if (e) return cb(e);
38 |
39 | // for (let q of (opts.query || "").split("")) {
40 | // const f = QUERY_HANDLERS[q];
41 | // if (!f) continue;
42 | // problems = problems.filter((x) => f(x, q));
43 | // }
44 |
45 | // for (let t of opts.tag || []) {
46 | // problems = problems.filter(function (x) {
47 | // return x.category === t || hasTag(x.companies, t) || hasTag(x.tags, t);
48 | // });
49 | // }
50 |
51 | return cb(null, problems);
52 | });
53 | };
54 | /* It's a method that gets the problem. */
55 | public getProblem = (keyword, needTranslation, cb) => {
56 | let that = this;
57 | this.getProblems(needTranslation, function (e, problems) {
58 | if (e) return cb(e);
59 | keyword = Number(keyword) || keyword;
60 | const metaFid = storageUtils.exist(keyword) ? storageUtils.meta(keyword).id : NaN;
61 | const problem = problems.find(function (x) {
62 | if (keyword?.fid) {
63 | return x.fid + "" === keyword.fid + "";
64 | } else if (keyword?.qid) {
65 | return x.id + "" === keyword.qid + "";
66 | } else {
67 | return x.id + "" === keyword + "" || x.fid + "" === metaFid + "" || x.name === keyword || x.slug === keyword;
68 | }
69 | });
70 | if (!problem) return cb("Problem not found!");
71 | that.next.getProblem(problem, needTranslation, cb);
72 | });
73 | };
74 |
75 | /* It's a method that stars the problem. */
76 | starProblem = (problem, starred, cb) => {
77 | if (problem.starred === starred) {
78 | return cb(null, starred);
79 | }
80 |
81 | this.next.starProblem(problem, starred, cb);
82 | };
83 |
84 | /* It's a method that exports the problem. */
85 | exportProblem = (problem, opts) => {
86 | const data = _.extend({}, problem);
87 |
88 | // 增加版本信息
89 | data.LCPTCTX = configUtils.LCPTCTX;
90 | data.allCaseList = storageUtils.getAllCase(problem.desc);
91 | // unify format before rendering
92 |
93 | data.app = configUtils.app || "leetcode";
94 | if (!data.fid) data.fid = data.id;
95 | if (!data.lang) data.lang = opts.lang;
96 | data.code = (opts.code || data.code || "").replace(/\r\n/g, "\n");
97 | data.comment = storageUtils.getCommentStyleByLanguage(data.lang);
98 | data.percent = data.percent.toFixed(2);
99 | data.testcase = util.inspect(data.testcase || "");
100 |
101 | if (opts.tpl === "detailed") {
102 | let desc = data.desc;
103 | // Replace with '^' as the power operator
104 | desc = desc.replace(/<\/sup>/gm, "").replace(//gm, "^");
105 | desc = require("he").decode(cheerio.load(desc).root().text());
106 | // NOTE: wordwrap internally uses '\n' as EOL, so here we have to
107 | // remove all '\r' in the raw string.
108 | desc = desc.replace(/\r\n/g, "\n").replace(/^ /gm, "");
109 | const wrap = require("wordwrap")(79 - data.comment.line.length);
110 | data.desc = wrap(desc).split("\n");
111 | }
112 | return storageUtils.render(opts.tpl, data);
113 | };
114 |
115 | getTodayQuestion = (cb) => {
116 | this.getQuestionOfToday(function (e, result) {
117 | if (e) return cb(e);
118 | return cb(null, result);
119 | });
120 | };
121 |
122 | getRecentContest = (cb) => {
123 | this.getRecentContestList(function (e, result) {
124 | if (e) return cb(e);
125 | return cb(null, result);
126 | });
127 | };
128 |
129 | getContestQuestion = (contestName, cb) => {
130 | this.getContestQuestionList(contestName, function (e, result) {
131 | if (e) return cb(e);
132 | return cb(null, result);
133 | });
134 | };
135 | getRating = (cb) => {
136 | this.getRatingOnline(function (e, result) {
137 | if (e) return cb(e);
138 | return cb(null, result);
139 | });
140 | };
141 |
142 | getQueryZ = (username, cb) => {
143 | this.getTestApi(username, function (e, result) {
144 | if (e) return cb(e);
145 | return cb(null, result);
146 | });
147 | };
148 |
149 | getUserContest = (username, cb) => {
150 | this.getUserContestP(username, function (e, result) {
151 | if (e) return cb(e);
152 | return cb(null, result);
153 | });
154 | };
155 | getHelp = (problem, cn_flag, lang) => {
156 | this.getHelpOnline(problem, cn_flag, lang);
157 | };
158 | }
159 |
160 | // const isLevel = (x, q) => x.level[0].toLowerCase() === q.toLowerCase();
161 | // const isACed = (x) => x.state === "ac";
162 | // const isLocked = (x) => x.locked;
163 | // const isStarred = (x) => x.starred;
164 |
165 | /* It's a dictionary that maps the query to the function that filters the problems. */
166 | // const QUERY_HANDLERS = {
167 | // e: isLevel,
168 | // E: _.negate(isLevel),
169 | // m: isLevel,
170 | // M: _.negate(isLevel),
171 | // h: isLevel,
172 | // H: _.negate(isLevel),
173 | // l: isLocked,
174 | // L: _.negate(isLocked),
175 | // d: isACed,
176 | // D: _.negate(isACed),
177 | // s: isStarred,
178 | // S: _.negate(isStarred),
179 | // };
180 |
181 | export const corePlugin: CorePlugin = new CorePlugin();
182 |
--------------------------------------------------------------------------------
/src/rpc/factory/api/testApi.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * https://github.com/ccagml/leetcode-extension/src/rpc/factory/api/testApi.ts
3 | * Path: https://github.com/ccagml/leetcode-extension
4 | * Created Date: Thursday, November 17th 2022, 11:44:14 am
5 | * Author: ccagml
6 | *
7 | * Copyright (c) 2022 ccagml . All rights reserved.
8 | */
9 |
10 | let _ = require("underscore");
11 | let lodash = require("lodash");
12 |
13 | import { storageUtils } from "../../utils/storageUtils";
14 | import { reply } from "../../utils/ReplyUtils";
15 |
16 | import { sessionUtils } from "../../utils/sessionUtils";
17 | import { ApiBase } from "../apiBase";
18 | // import { commUtils } from "../../utils/commUtils";
19 | import { chainMgr } from "../../actionChain/chainManager";
20 |
21 | class TestApi extends ApiBase {
22 | constructor() {
23 | super();
24 | }
25 |
26 | callArg(argv) {
27 | let argv_config = this.api_argv()
28 | .option("i", {
29 | alias: "interactive",
30 | type: "boolean",
31 | default: false,
32 | describe: "Provide test case interactively",
33 | })
34 | .option("t", {
35 | alias: "testcase",
36 | type: "string",
37 | default: "",
38 | describe: "Provide test case",
39 | })
40 | .option("a", {
41 | alias: "allcase",
42 | type: "boolean",
43 | default: false,
44 | describe: "Provide all test case",
45 | })
46 | .positional("filename", {
47 | type: "string",
48 | default: "",
49 | describe: "Code file to test",
50 | });
51 |
52 | argv_config.parseArgFromCmd(argv);
53 |
54 | return argv_config.get_result();
55 | }
56 |
57 | printResult(actual, extra, k, log_obj) {
58 | if (!actual.hasOwnProperty(k)) return;
59 | // HACk: leetcode still return 'Accepted' even the answer is wrong!!
60 | const v = actual[k] || "";
61 | if (k === "state" && v === "Accepted") return;
62 |
63 | // let ok = actual.ok;
64 |
65 | const lines = Array.isArray(v) ? v : [v];
66 | for (let line of lines) {
67 | const extraInfo = extra ? ` (${extra})` : "";
68 | if (k !== "state") {
69 | let new_kk = lodash.startCase(k) + extraInfo;
70 | if (!log_obj.hasOwnProperty(new_kk)) {
71 | log_obj[new_kk] = [line];
72 | } else {
73 | log_obj[new_kk].push(line);
74 | }
75 | } else {
76 | log_obj.messages.push(line);
77 | }
78 | }
79 | }
80 |
81 | runTest(argv) {
82 | let that = this;
83 | if (!storageUtils.exist(argv.filename)) return reply.fatal("File " + argv.filename + " not exist!");
84 |
85 | const meta = storageUtils.meta(argv.filename);
86 |
87 | // [key: string]: string[];
88 | // messages: string[];
89 |
90 | chainMgr.getChainHead().getProblem(meta, true, function (e, problem) {
91 | if (e)
92 | return reply.info(
93 | JSON.stringify({
94 | messages: ["error"],
95 | code: [-1],
96 | error: [e.msg || e],
97 | })
98 | );
99 |
100 | if (!problem.testable)
101 | return reply.info(
102 | JSON.stringify({
103 | messages: ["error"],
104 | code: [-2],
105 | error: ["not testable? please submit directly!"],
106 | })
107 | );
108 |
109 | if (argv.testcase) {
110 | problem.testcase = argv.testcase.replace(/\\n/g, "\n");
111 | }
112 |
113 | if (argv.allcase) {
114 | let temp_test_set: Set = new Set();
115 |
116 | let new_desc = problem.desc;
117 | let calcCaseList = storageUtils.getAllCase(new_desc);
118 | calcCaseList.forEach((x) => {
119 | let xxx = x.join("\n");
120 | temp_test_set.add(xxx);
121 | });
122 | if (meta.writeCase) {
123 | meta.writeCase.forEach((xxx) => {
124 | temp_test_set.add(xxx);
125 | });
126 | }
127 |
128 | let temp_test: Array = [];
129 | temp_test_set.forEach((x) => {
130 | temp_test.push(x);
131 | });
132 |
133 | let all_case = temp_test.join("\n");
134 | problem.testcase = all_case;
135 | }
136 |
137 | if (!problem.testcase)
138 | return reply.info(
139 | JSON.stringify({
140 | messages: ["error"],
141 | code: [-3],
142 | error: ["missing testcase?"],
143 | })
144 | );
145 |
146 | problem.file = argv.filename;
147 | problem.lang = meta.lang;
148 |
149 | chainMgr.getChainHead().testProblem(problem, function (e, results) {
150 | if (e) return reply.info(JSON.stringify(e));
151 |
152 | results = _.sortBy(results, (x) => x.type);
153 |
154 | let log_obj: any = {};
155 | log_obj.messages = [];
156 | log_obj.system_message = {};
157 | log_obj.system_message.fid = problem.fid;
158 | log_obj.system_message.id = problem.id;
159 | log_obj.system_message.qid = problem.id;
160 | log_obj.system_message.sub_type = "test";
161 | log_obj.system_message.accepted = false;
162 |
163 | if (results[0].state === "Accepted") {
164 | results[0].state = "Finished";
165 | log_obj.system_message.compare_result = results[0].compare_result;
166 | }
167 | if (results[0].ok) {
168 | log_obj.system_message.accepted = true;
169 | }
170 | that.printResult(results[0], null, "state", log_obj);
171 | that.printResult(results[0], null, "error", log_obj);
172 |
173 | results[0].your_input = problem.testcase;
174 | results[0].output = results[0].answer;
175 | // LeetCode-CN returns the actual and expected answer into two separate responses
176 | if (results[1]) {
177 | results[0].expected_answer = results[1].answer;
178 | }
179 | results[0].stdout = results[0].stdout.slice(1, -1).replace(/\\n/g, "\n");
180 | that.printResult(results[0], null, "your_input", log_obj);
181 | that.printResult(results[0], results[0].runtime, "output", log_obj);
182 | that.printResult(results[0], null, "expected_answer", log_obj);
183 | that.printResult(results[0], null, "stdout", log_obj);
184 | reply.info(JSON.stringify(log_obj));
185 | });
186 | });
187 | }
188 |
189 | call(argv) {
190 | let that = this;
191 | sessionUtils.argv = argv;
192 | if (!argv.i) return that.runTest(argv);
193 |
194 | // commUtils.readStdin(function (e, data) {
195 | // if (e) return reply.info(e);
196 |
197 | // argv.testcase = data;
198 | // return that.runTest(argv);
199 | // });
200 | }
201 | }
202 |
203 | export const testApi: TestApi = new TestApi();
204 |
--------------------------------------------------------------------------------
/src/service/MarkdownService.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Filename: https://github.com/ccagml/leetcode-extension/src/service/markdownService.ts
3 | * Path: https://github.com/ccagml/leetcode-extension
4 | * Created Date: Thursday, October 27th 2022, 7:43:29 pm
5 | * Author: ccagml
6 | *
7 | * Copyright (c) 2022 ccagml . All rights reserved.
8 | */
9 |
10 | import * as hljs from "highlight.js";
11 | import * as MarkdownIt from "markdown-it";
12 | import * as MarkDownItKatex from "markdown-it-katex";
13 | import * as os from "os";
14 | import * as path from "path";
15 | import * as vscode from "vscode";
16 | import { BABA, BabaStr } from "../BABA";
17 | import { isWindows } from "../utils/SystemUtils";
18 |
19 | class MarkdownService implements vscode.Disposable {
20 | private engine: MarkdownIt;
21 | private config: MarkdownConfiguration;
22 | private listener: vscode.Disposable;
23 |
24 | public constructor() {
25 | this.reload();
26 | this.listener = vscode.workspace.onDidChangeConfiguration((event: vscode.ConfigurationChangeEvent) => {
27 | if (event.affectsConfiguration("markdown")) {
28 | this.reload();
29 | }
30 | }, this);
31 | }
32 |
33 | public get localResourceRoots(): vscode.Uri[] {
34 | return [
35 | vscode.Uri.file(path.join(this.config.extRoot, "media")),
36 | vscode.Uri.file(path.join(__dirname, "..", "..", "..", "resources", "katexcss")),
37 | ];
38 | }
39 |
40 | public dispose(): void {
41 | this.listener.dispose();
42 | }
43 |
44 | public reload(): void {
45 | this.engine = this.initEngine();
46 | this.config = new MarkdownConfiguration();
47 | }
48 |
49 | public render(md: string, env?: any): string {
50 | return this.engine.render(md, env);
51 | }
52 |
53 | public getStyles(panel: vscode.WebviewPanel | undefined): string {
54 | return [this.getBuiltinStyles(panel), this.getDefaultStyle()].join(os.EOL);
55 | }
56 |
57 | private getBuiltinStyles(panel: vscode.WebviewPanel | undefined): string {
58 | let styles: vscode.Uri[] = [];
59 | try {
60 | const stylePaths: string[] = require(path.join(this.config.extRoot, "package.json"))["contributes"][
61 | "markdown.previewStyles"
62 | ];
63 | styles = stylePaths.map((p: string) =>
64 | vscode.Uri.file(path.join(this.config.extRoot, p))
65 | );
66 | } catch (error) {
67 | BABA.getProxy(BabaStr.LogOutputProxy).get_log().appendLine("[Error] Fail to load built-in markdown style file.");
68 | }
69 | let bbb = styles
70 | .map((style: vscode.Uri) => ``)
71 | .join(os.EOL);
72 | return bbb
73 | }
74 |
75 | private getDefaultStyle(): string {
76 | return [
77 | ``,
84 | ].join(os.EOL);
85 | }
86 |
87 | private initEngine(): MarkdownIt {
88 | const md: MarkdownIt = new MarkdownIt({
89 | linkify: true,
90 | typographer: true,
91 | highlight: (code: string, lang?: string): string => {
92 | switch (lang && lang.toLowerCase()) {
93 | case "mysql":
94 | lang = "sql";
95 | break;
96 | case "json5":
97 | lang = "json";
98 | break;
99 | case "python3":
100 | lang = "python";
101 | break;
102 | }
103 | if (lang && hljs.getLanguage(lang)) {
104 | try {
105 | return hljs.highlight(lang, code, true).value;
106 | } catch (error) {
107 | /* do not highlight */
108 | }
109 | }
110 | return ""; // use external default escaping
111 | },
112 | });
113 |
114 | md.use(MarkDownItKatex);
115 | this.addCodeBlockHighlight(md);
116 | this.addImageUrlCompletion(md);
117 | this.addLinkValidator(md);
118 | return md;
119 | }
120 |
121 | private addCodeBlockHighlight(md: MarkdownIt): void {
122 | const codeBlock: MarkdownIt.TokenRender = md.renderer.rules["code_block"];
123 | // tslint:disable-next-line:typedef
124 | md.renderer.rules["code_block"] = (tokens, idx, options, env, self) => {
125 | // if any token uses lang-specified code fence, then do not highlight code block
126 | if (tokens.some((token: any) => token.type === "fence")) {
127 | return codeBlock(tokens, idx, options, env, self);
128 | }
129 | // otherwise, highlight with default lang in env object.
130 | const highlighted: string = options.highlight(tokens[idx].content, env.lang);
131 | return [
132 | ``,
133 | highlighted || md.utils.escapeHtml(tokens[idx].content),
134 | "
",
135 | ].join(os.EOL);
136 | };
137 | }
138 |
139 | private addImageUrlCompletion(md: MarkdownIt): void {
140 | const image: MarkdownIt.TokenRender = md.renderer.rules["image"];
141 | // tslint:disable-next-line:typedef
142 | md.renderer.rules["image"] = (tokens, idx, options, env, self) => {
143 | const imageSrc: string[] | undefined = tokens[idx].attrs.find((value: string[]) => value[0] === "src");
144 | if (env.host && imageSrc && imageSrc[1].startsWith("/")) {
145 | imageSrc[1] = `${env.host}${imageSrc[1]}`;
146 | }
147 | return image(tokens, idx, options, env, self);
148 | };
149 | }
150 |
151 | private addLinkValidator(md: MarkdownIt): void {
152 | const validateLink: (link: string) => boolean = md.validateLink;
153 | md.validateLink = (link: string): boolean => {
154 | // support file:// protocal link
155 | return validateLink(link) || link.startsWith("file:");
156 | };
157 | }
158 | }
159 |
160 | // tslint:disable-next-line: max-classes-per-file
161 | class MarkdownConfiguration {
162 | public readonly extRoot: string; // root path of vscode built-in markdown extension
163 | public readonly lineHeight: number;
164 | public readonly fontSize: number;
165 | public readonly fontFamily: string;
166 |
167 | public constructor() {
168 | const markdownConfig: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration("markdown", null);
169 | this.extRoot = path.join(vscode.env.appRoot, "extensions", "markdown-language-features");
170 | this.lineHeight = Math.max(0.6, +markdownConfig.get("preview.lineHeight", NaN));
171 | this.fontSize = Math.max(8, +markdownConfig.get("preview.fontSize", NaN));
172 | this.fontFamily = this.resolveFontFamily(markdownConfig);
173 | }
174 |
175 | private resolveFontFamily(config: vscode.WorkspaceConfiguration): string {
176 | let fontFamily: string = config.get("preview.fontFamily", "");
177 | if (isWindows() && fontFamily === config.inspect("preview.fontFamily")!.defaultValue) {
178 | fontFamily = `${fontFamily}, 'Microsoft Yahei UI'`;
179 | }
180 | return fontFamily;
181 | }
182 | }
183 |
184 | export const markdownService: MarkdownService = new MarkdownService();
185 |
--------------------------------------------------------------------------------
/src/questionData/QuestionDataModule.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Filename: /home/cc/leetcode-extension/src/questionData/questionDataModule.ts
3 | * Path: /home/cc/leetcode-extension
4 | * Created Date: Monday, October 16th 2023, 10:00:51 am
5 | * Author: ccagml
6 | *
7 | * Copyright (c) 2023 ccagml . All rights reserved
8 | */
9 |
10 | import { BABA, BABAMediator, BABAProxy, BabaStr, BaseCC } from "../BABA";
11 | import { ISubmitEvent, OutPutType, ProblemState, UserStatus } from "../model/ConstDefind";
12 | import { CreateTreeNodeModel, IQuestionData, TreeNodeModel, TreeNodeType } from "../model/TreeNodeModel";
13 |
14 | import { isShowLocked, isUseEndpointTranslation } from "../utils/ConfigUtils";
15 | import { ShowMessage } from "../utils/OutputUtils";
16 |
17 | class QuestionData {
18 | private fidMapQuestionData: Map = new Map();
19 | private fidToQid: Map = new Map();
20 | private qidToFid: Map = new Map();
21 | private companySet: Set = new Set();
22 | private tagSet: Set = new Set();
23 |
24 | public clearCache(): void {
25 | this.fidMapQuestionData.clear();
26 | this.companySet.clear();
27 | this.tagSet.clear();
28 | this.fidToQid.clear();
29 | this.qidToFid.clear();
30 | }
31 |
32 | public async ReBuildQuestionData() {
33 | let all_data = await BABA.getProxy(BabaStr.QuestionDataProxy).getAllQuestionData();
34 | for (const problem of all_data) {
35 | let TreeNodeObj = CreateTreeNodeModel(problem, TreeNodeType.TreeQuestionData);
36 | this.fidMapQuestionData.set(TreeNodeObj.id, TreeNodeObj);
37 | this.fidToQid.set(TreeNodeObj.id, TreeNodeObj.qid.toString());
38 | this.qidToFid.set(TreeNodeObj.qid.toString(), TreeNodeObj.id);
39 |
40 | for (const company of TreeNodeObj.companies) {
41 | this.companySet.add(company);
42 | }
43 | for (const tag of TreeNodeObj.tags) {
44 | this.tagSet.add(tag);
45 | }
46 | }
47 |
48 | BABA.sendNotification(BabaStr.QuestionData_ReBuildQuestionDataFinish);
49 | }
50 | public getfidMapQuestionData(): Map {
51 | return this.fidMapQuestionData;
52 | }
53 |
54 | public getCompanySet() {
55 | return this.companySet;
56 | }
57 | public getTagSet() {
58 | return this.tagSet;
59 | }
60 | public getFidToQid() {
61 | return this.fidToQid;
62 | }
63 | public getQidToFid() {
64 | return this.qidToFid;
65 | }
66 | public checkSubmit(e: ISubmitEvent) {
67 | if (e.sub_type == "submit" && e.accepted) {
68 | if (this.fidMapQuestionData.get(e.fid)?.state != ProblemState.AC) {
69 | BABA.sendNotification(BabaStr.QuestionData_submitNewAccept);
70 | }
71 | }
72 | }
73 | }
74 |
75 | const questionData: QuestionData = new QuestionData();
76 |
77 | export class QuestionDataProxy extends BABAProxy {
78 | static NAME = BabaStr.QuestionDataProxy;
79 | constructor() {
80 | super(QuestionDataProxy.NAME);
81 | }
82 |
83 | public getfidMapQuestionData(): Map {
84 | return questionData.getfidMapQuestionData();
85 | }
86 |
87 | public getCompanySet() {
88 | return questionData.getCompanySet();
89 | }
90 | public getTagSet() {
91 | return questionData.getTagSet();
92 | }
93 |
94 | public getNodeById(id: string): TreeNodeModel | undefined {
95 | let map = this.getfidMapQuestionData() as Map;
96 | if (map.has(id)) {
97 | return map.get(id);
98 | } else if (map.has(parseInt(id))) {
99 | return map.get(parseInt(id));
100 | } else {
101 | return undefined;
102 | }
103 | }
104 |
105 | public getNodeByQid(qid: string): TreeNodeModel | undefined {
106 | let new_qid = qid.toString();
107 | return this.getNodeById(questionData.getQidToFid().get(new_qid) || "");
108 | }
109 |
110 | public getQidByFid(id: string) {
111 | return questionData.getFidToQid().get(id);
112 | }
113 |
114 | public async getAllQuestionData(): Promise {
115 | try {
116 | let sbp = BABA.getProxy(BabaStr.StatusBarProxy);
117 | if (sbp.getStatus() === UserStatus.SignedOut) {
118 | return [];
119 | }
120 |
121 | const showLockedFlag: boolean = isShowLocked();
122 | const useEndpointTranslation: boolean = isUseEndpointTranslation();
123 | const result: string = await BABA.getProxy(BabaStr.ChildCallProxy)
124 | .get_instance()
125 | .getAllProblems(showLockedFlag, useEndpointTranslation);
126 | let all_problem_info = JSON.parse(result);
127 | if (!showLockedFlag) {
128 | all_problem_info = all_problem_info.filter((p) => !p.locked);
129 | }
130 | const problems: IQuestionData[] = [];
131 | // const AllScoreData = BABA.getProxy(BabaStr.TreeDataProxy).getScoreData();
132 | // // 增加直接在线获取分数数据
133 | // const AllScoreDataOnline = await BABA.getProxy(BabaStr.TreeDataProxy).getScoreDataOnline();
134 | for (const p of all_problem_info) {
135 | problems.push({
136 | id: p.fid,
137 | qid: p.id,
138 | isFavorite: p.starred,
139 | locked: p.locked,
140 | state: this.parseProblemState(p.state),
141 | name: p.name,
142 | cn_name: p.cn_name,
143 | en_name: p.en_name,
144 | difficulty: p.level,
145 | passRate: p.percent,
146 | companies: p.companies || [],
147 | });
148 | }
149 | return problems.reverse();
150 | } catch (error) {
151 | await ShowMessage("获取题目失败. 请查看控制台信息~", OutPutType.error);
152 | return [];
153 | }
154 | }
155 | public parseProblemState(stateOutput: string): ProblemState {
156 | if (!stateOutput) {
157 | return ProblemState.Unknown;
158 | }
159 | switch (stateOutput.trim()) {
160 | case "v":
161 | case "✔":
162 | case "√":
163 | case "ac":
164 | return ProblemState.AC;
165 | case "X":
166 | case "✘":
167 | case "×":
168 | case "notac":
169 | return ProblemState.NotAC;
170 | default:
171 | return ProblemState.Unknown;
172 | }
173 | }
174 | }
175 |
176 | export class QuestionDataMediator extends BABAMediator {
177 | static NAME = BabaStr.QuestionDataMediator;
178 | constructor() {
179 | super(QuestionDataMediator.NAME);
180 | }
181 |
182 | listNotificationInterests(): string[] {
183 | return [
184 | BabaStr.VSCODE_DISPOST,
185 | BabaStr.QuestionData_clearCache,
186 | BabaStr.QuestionData_ReBuildQuestionData,
187 | BabaStr.CommitResult_showFinish,
188 | BabaStr.StartReadData,
189 | ];
190 | }
191 | async handleNotification(_notification: BaseCC.BaseCC.INotification) {
192 | switch (_notification.getName()) {
193 | case BabaStr.VSCODE_DISPOST:
194 | break;
195 | case BabaStr.QuestionData_clearCache:
196 | questionData.clearCache();
197 | break;
198 | case BabaStr.QuestionData_ReBuildQuestionData:
199 | await questionData.ReBuildQuestionData();
200 | break;
201 | case BabaStr.CommitResult_showFinish:
202 | questionData.checkSubmit(_notification.getBody());
203 | break;
204 | case BabaStr.StartReadData:
205 | questionData.clearCache();
206 | await questionData.ReBuildQuestionData();
207 | break;
208 | default:
209 | break;
210 | }
211 | }
212 | }
213 |
--------------------------------------------------------------------------------